Android gradle 多版本适配

配置构建变体

官网链接

    https://developer.android.com/studio/build/build-variants.html#build-types   

配置构建变体

此页面以配置构建概览为基础,向您介绍如何配置构建变体,以便从同一个项目中创建应用的不同版本,以及如何正确地管理依赖项并签署配置。

每个构建变体都代表您可以构建的不同应用版本。例如,您可能希望构建应用的免费版本(只提供有限的内容)和付费版本(提供更多内容)。您还可以针对不同的设备、根据 API 级别或其他设备变体构建应用的不同版本。然而,如果您希望根据设备 ABI 或界面密度构建不同的版本,则请改为构建多个 APK

构建变体是 Gradle 按照特定规则集合并在构建类型和产品风格中配置的设置、代码和资源所生成的结果。您并不直接配置构建变体,而是配置组成变体的构建类型和产品风格。

例如,一个“演示”产品风格可以指定不同的功能和设备要求,例如自定义源代码、资源和最低 API 级别,而“调试”构建类型则应用不同的构建和打包设置,例如调试选项和签署密钥。最终生成的构建变体是应用的“演示调试”版本,其既包含“演示”产品风格中包含的各种配置和资源,又包含“调试”构建类型和 main/ 源集。

配置构建类型 

您可以在模块级 build.gradle 文件的 android 代码块内部创建和配置构建类型。当您创建新模块时,Android Studio 会自动为您创建调试和发布这两种构建类型。尽管调试构建类型不会出现在构建配置文件中,Android Studio 会为其配置 debuggable true。这样,您可以在安全的 Android 设备上调试应用并使用通用调试密钥库配置 APK 签署。

如果您希望添加或更改特定设置,您可以将调试构建类型添加到您的配置中。以下示例为调试构建类型指定了 applicationIdSuffix,并配置了一个使用调试构建类型中的设置进行初始化的“staging”构建类型。

<code>

android {

    defaultConfig {

        manifestPlaceholders = [hostName:"www.example.com"]


    }

    buildTypes {

        release {

            minifyEnabled true

            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

        }

        debug {

            applicationIdSuffix ".debug"

            debuggable true

        }

        /**

        * The `initWith` property allows you to copy configurations from other build types,

        * then configure only the settings you want to change. This one copies the debug build

        * type, and then changes the manifest placeholder and application ID.

        */

        staging {

            initWith debug

            manifestPlaceholders = [hostName:"internal.example.com"]

            applicationIdSuffix ".debugStaging"

        }

    }

}

</code>

注:当您对构建配置文件进行更改时,Android Studio 会要求您为项目同步新配置。要同步项目,您可以在您做出更改后立即点击通知栏中出现的 Sync Now,也可以点击工具栏中的 Sync Project 

。如果 Android Studio 通知配置出现错误,系统会显示 Messages 窗口,以说明该问题。

如需了解有关可以使用构建类型配置的所有属性的详细信息,请阅读构建类型 DSL 参考

配置产品风格 

创建产品风格与创建构建类型相似:只需将其添加到构建配置中的 productFlavors 代码块并加入所需的设置即可。产品风格支持与 defaultConfig 相同的属性,这是因为 defaultConfig 实际上属于 ProductFlavor 类。这意味着,您可以在 defaultConfig 代码块中提供所有风格的基本配置,每种风格均可更改任何这些默认值,例如 applicationId。要详细了解应用 ID,请阅读设置应用 ID

注:您仍需在 main/ 清单文件中使用 package 属性指定程序包名称。您还必须在源代码中使用此程序包名称引用 R 类,或者解析任何相关的 Activity 或服务注册。这样,您可以使用 applicationId 为每个产品风格分配一个唯一的 ID,以用于打包和分发,而不必更改您的源代码。

所有风格必须属于指定的风格维度,即产品风格组。即使打算只使用一个维度,也必须将风格分配到特定风格维度,否则您会收到以下构建错误:

  Error:All flavors must now belong to a named flavor dimension.

  The flavor 'flavor_name' is not assigned to a flavor dimension.

提示:插件会尝试匹配应用与本地库依赖项的变体。由于 variant-aware 依赖项匹配取决于您命名风格维度的方式,因此请谨慎命名您的风格维度。这样可让您加强对本地依赖项中与各版本应用匹配的代码和资源的控制。

以下代码示例将创建名为“version”的风格维度,并添加“demo”和“full”产品风格。这些风格可自行提供其 applicationIdSuffix 和 versionNameSuffix

android {...

defaultConfig

{...}

buildTypes

{

debug

{...}

release

{...}}// Specifies one flavor dimension.flavorDimensions "version"

productFlavors

{

demo

{// Assigns this product flavor to the "version" flavor dimension.// This property is optional if you are using only one dimension.

dimension

"version"

applicationIdSuffix

".demo"

versionNameSuffix

"-demo"}

full

{

dimension

"version"

applicationIdSuffix

".full"

versionNameSuffix

"-full"}}}

注:要利用 Google Play 中的多 APK 支持分发您的应用,请为所有变体分配相同的 applicationId 值并为每个变体分配一个不同的 versionCode。要在 Google Play 中以独立应用的形式分发应用的不同变体,您需要为每个变体分配一个不同的 applicationId。

在创建和配置您的产品风格之后,在通知栏中点击 Sync Now。在同步完成后,Gradle 会根据您的构建类型和产品风格自动创建构建变体,并按照 <product-flavor><Build-Type> 的格式命名这些变体。例如,如果您创建了“演示”和“完整”这两种产品特点,并保留默认的“调试”和“发布”构建类型,Gradle 将创建以下构建不同类型:

demoDebug

演示发布

fullDebug

fullRelease

您可以将构建变体更改为您要构建并运行的任何变体,只需转到 Build > Select Build Variant,然后从下拉菜单中选择一个变体即可。然而,要开始自定义每个构建变体及其功能和资源,您需要了解如何创建和管理源集。创建和管理源集

组合多个产品风格与风格维度 

某些情况下,您可能希望组合多个产品风格中的配置。例如,您可能希望基于 API 级别为“完整”和“演示”产品风格创建不同的配置。为此,您可以通过 Android Plugin for Gradle 创建多组产品风格,即风格维度。构建应用时,Gradle 会将您定义的每个风格维度中的产品风格配置与构建类型配置组合,以创建最终构建变体。Gradle 不会组合属于相同风格维度的产品风格。

提示:要根据 ABI 和界面密度创建不同版本的应用,您应构建多个 APK,而不是使用产品特点。

下面的代码示例使用 flavorDimensions 属性创建一个“模式”风格维度以组织“完整”和“演示”产品风格,以及一个“api”风格维度以基于 API 级别组织产品风格配置:

android {...

buildTypes

{

debug

{...}

release

{...}}

// Specifies the flavor dimensions you want to use. The order in which you// list each dimension determines its priority, from highest to lowest,// when Gradle merges variant sources and configurations. You must assign// each product flavor you configure to one of the flavor dimensions.

flavorDimensions

"api", "mode"

productFlavors

{

demo

{// Assigns this product flavor to the "mode" flavor dimension.

dimension

"mode"...}

full

{

dimension

"mode"...}

// Configurations in the "api" product flavors override those in "mode"// flavors and the defaultConfig block. Gradle determines the priority// between flavor dimensions based on the order in which they appear next// to the flavorDimensions property above--the first dimension has a higher// priority than the second, and so on.

minApi24

{

dimension

"api"

minSdkVersion

24// To ensure the target device receives the version of the app with// the highest compatible API level, assign version codes in increasing// value with API level. To learn more about assigning version codes to// support app updates and uploading to Google Play, read Multiple APK Support

versionCode

30000 + android.defaultConfig.versionCode

versionNameSuffix

"-minApi24"...}

minApi23

{

dimension

"api"

minSdkVersion

23

versionCode

20000  + android.defaultConfig.versionCode

versionNameSuffix

"-minApi23"...}

minApi21

{

dimension

"api"

minSdkVersion

21

versionCode

10000  + android.defaultConfig.versionCode

versionNameSuffix

"-minApi21"...}}}...

Gradle 创建的构建变体数量等于每个风格维度中的风格数量与您配置的构建类型数量的乘积。在 Gradle 为每个构建变体或对应 APK 命名时,属于较高优先级风格维度的产品风格首先显示,之后是较低优先级维度的产品风格,再之后是构建类型。以上面的构建配置为例,Gradle 可以使用以下命名方案创建总共 12 个构建变体:

构建变体:[minApi24, minApi23, minApi21][Demo, Full][Debug, Release]

对应 APK:app-[minApi24, minApi23, minApi21]-[demo, full]-[debug, release].apk

例如,

构建变体:minApi24DemoDebug

对应 APK:app-minApi24-demo-debug.apk

除了可以为各个产品风格和构建变体创建源集目录外,您也可以为每个产品风格组合创建源集目录。例如,您可以创建 Java 源并将其添加到 src/demoMinApi24/java/ 目录中,Gradle 仅会在构建组合了这两种产品风格的变体时使用这些源。与属于各个产品风格的源集相比,您为产品风格组合创建的源集拥有更高的优先级。要详细了解源集和 Gradle 如何合并源,请阅读有关如何创建源集的部分。

过滤变体 

Gradle 会为您配置的产品风格与构建类型的每个可能的组合创建构建变体。不过,某些特定的构建变体在您的项目环境中并不必要,也可能没有意义。您可以在模块级 build.gradle 文件中创建一个变体过滤器,以移除某些构建变体配置。

以上一部分中的构建配置为例,假设您仅计划为演示版本的应用提供 API 级别 23 和更高级别支持。您可使用 variantFilter 代码块过滤出组合“minApi21”和“演示”产品风格的所有构建变体配置:

android {...

buildTypes

{...}

flavorDimensions

"api", "mode"

productFlavors

{

demo

{...}

full

{...}

minApi24

{...}

minApi23

{...}

minApi21

{...}}

variantFilter

{ variant ->def names = variant.flavors*.name

// To check for a certain build type, use variant.buildType.name == "<buildType>"if (names.contains("minApi21") && names.contains("demo")) {// Gradle ignores any variants that satisfy the conditions above.

setIgnore

(true)}}}...

在您向构建配置添加不同类型过滤器并点击通知栏中的 Sync Now 后,Gradle 将忽略满足您指定的条件的任何不同类型构建,在您点击菜单栏中的 Build > Select Build Variant(或工具窗口栏中的 Build Variants 

)时,这些构建不同类型将不会再出现。

创建源集 

默认情况下,Android Studio 会创建 main/ 源集和目录,用于存储您要在所有构建变体之间共享的一切资源。然而,您可以创建新的源集来控制 Gradle 要为特定的构建类型、产品风格(以及使用风格维度时的产品风格组合)和构建变体编译和打包的确切文件。例如,您可以在 main/ 源集中定义基本的功能,使用产品风格源集针对不同的客户更改应用的品牌,或者仅针对使用调试构建类型的构建变体包含特殊的权限和日志记录功能。

Gradle 要求您按照与 main/ 源集类似的特定方式组织源集文件和目录。例如,Gradle 要求您的“调试”构建类型所特定的 Java 类文件位于 src/debug/java/ 目录中。

Android Plugin for Gradle 提供一项有用的 Gradle 任务,可向您展示如何针对每种构建类型、产品风格和构建变体组织您的文件。例如,以下任务输出示例说明 Gradle 希望在何处找到“调试”构建类型的特定文件:

------------------------------------------------------------

Project :app

------------------------------------------------------------

...

debug

----

Compile configuration: compile

build.gradle name: android.sourceSets.debug

Java sources: [app/src/debug/java]

Manifest file: app/src/debug/AndroidManifest.xml

Android resources: [app/src/debug/res]

Assets: [app/src/debug/assets]

AIDL sources: [app/src/debug/aidl]

RenderScript sources: [app/src/debug/rs]

JNI sources: [app/src/debug/jni]

JNI libraries: [app/src/debug/jniLibs]

Java-style resources: [app/src/debug/resources]

要查看此输出,请继续按以下步骤操作:

点击 IDE 窗口右侧的 Gradle 

导航至 MyApplication > Tasks > Android 并双击 sourceSets。Gradle 执行任务后,Run 窗口应随即打开以显示输出。

如果显示信息不是处于如上所示的文本模式,请点击 Run 窗口左侧的 Toggle view 

注:任务输出还向您展示了如何为您希望用来运行应用测试的文件组织源集,例如 test/ 和 androidTest/ 测试源集

当您创建新的构建变体时,Android Studio 不会为您创建源集目录,但会为您提供几个选项,帮助您创建目录。例如,要为“调试”构建类型只创建 java/ 目录,请执行以下操作:

打开 Project 窗格,并从窗格顶部的下拉菜单中选择 Project 视图。

导航至 MyProject/app/src/。

右键点击 src 目录并选择 New > Folder > Java Folder

从 Target Source Set 旁边的下拉菜单中,选择 debug

点击 Finish

Android Studio 将会为您的调试构建类型创建源集目录,然后在该目录内部创建 java/ 目录。或者,在针对特定的构建变体向您的项目中添加新文件时,您也可以让 Android Studio 为您创建目录。例如,要为“调试”构建类型创建 XML 值文件:

在相同的 Project 窗格中,右键点击 src 目录并选择 New > XML > Values XML File

为 XML 文件输入名称或保留默认名称。

从 Target Source Set 旁边的下拉菜单中,选择 debug

点击 Finish

由于“调试”构建类型被指定为目标源集,Android Studio 会在创建 XML 文件时自动创建必要的目录。最终的目录结构看上去应该类似于图 2。

图 2. 调试构建类型的新源集目录。

按照同样的方法,您还可以为产品风格创建源集目录(例如 src/demo/),为构建变体创建源集目录(例如 src/demoDebug/)。此外,您还可以创建针对特定构建变体的测试源集,例如 src/androidTestDemoDebug/。如需了解更多信息,请转至测试源集

更改默认源集配置 

如果您的源未组织到 Gradle 期望的默认源集文件结构中(如上面的创建源集部分中所述),您可以使用 sourceSets 代码块更改 Gradle 希望为源集的每个组件收集文件的位置。您不需要重新定位文件;只需要为 Gradle 提供相对于模块级 build.gradle 文件的路径,Gradle 应当可以在此路径下为每个源集组件找到文件。要了解您可以配置哪些组件,以及是否可以将其映射到多个文件或目录,请阅读 Android Plugin for Gradle DSL 参考

下面的代码示例可以将 app/other/ 目录中的源映射到 main 源集的某些组件,并更改 androidTest 源集的根目录。

android {...

sourceSets

{// Encapsulates configurations for the main source set.

main

{// Changes the directory for Java sources. The default directory is// 'src/main/java'.

java

.srcDirs = ['other/java']

// If you list multiple directories, Gradle uses all of them to collect// sources. Because Gradle gives these directories equal priority, if// you define the same resource in more than one directory, you get an// error when merging resources. The default directory is 'src/main/res'.

res

.srcDirs = ['other/res1', 'other/res2']

// Note: You should avoid specifying a directory which is a parent to one// or more other directories you specify. For example, avoid the following:// res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings']// You should specify either only the root 'other/res1' directory, or only the// nested 'other/res1/layouts' and 'other/res1/strings' directories.

// For each source set, you can specify only one Android manifest.// By default, Android Studio creates a manifest for your main source// set in the src/main/ directory.

manifest

.srcFile 'other/AndroidManifest.xml'...}

// Create additional blocks to configure other source sets.

androidTest

{

// If all the files for a source set are located under a single root// directory, you can specify that directory using the setRoot property.// When gathering sources for the source set, Gradle looks only in locations// relative to the root directory you specify. For example, after applying the// configuration below for the androidTest source set, Gradle looks for Java// sources only in the src/tests/java/ directory.

setRoot

'src/tests'...}}}...

使用源集构建 

您可以使用源集目录包含您希望仅针对某些配置打包的代码和资源。例如,如果您要构建“演示调试”构建变体(它是“演示”产品风格和“调试”构建类型的合体),Gradle 会查看这些目录并赋予以下优先级顺序:

src/demoDebug/(构建变体源集)

src/debug/(构建类型源集)

src/demo/(产品风格源集)

src/main/(主源集)

注:如果您组合多个产品风格,产品风格之间的优先级将由它们所属的风格维度决定。在列示具有 android.flavorDimensions 属性的风格维度时,所列示的第一个风格维度中的产品风格比第二个维度中的产品风格拥有更高的优先级,以此类推。此外,与属于各个产品风格的源集相比,您为产品风格组合创建的源集拥有更高的优先级。

上面列出的顺序决定了在 Gradle 合并代码和资源时哪个源集具有较高的优先级。由于 demoDebug/ 源集目录很可能包含特定于该构建变体的文件,如果 demoDebug/ 包含在 debug/ 中也有定义的文件,Gradle 将使用 demoDebug/ 源集中的文件。同样,Gradle 会为构建类型和产品风格源集中的文件赋予比 main/ 中相同文件更高的优先级。Gradle 在应用以下构建规则时会考虑此优先级顺序:

一起编译 java/ 目录中的所有源代码以生成单一的输出。

注:对于给定的构建变体,如果找到两个或两个以上定义同一 Java 类的源集目录,Gradle 就会引发一个构建错误。例如,在构建调试 APK 时,您不能同时定义 src/debug/Utility.java 和 src/main/Utility.java。这是因为 Gradle 会在构建过程中检查这两个目录并引发“duplicate class”错误。如果希望不同的构建类型具有不同版本的 Utility.java,您可以让每个构建类型定义自己的文件版本,而不将其包含在 main/ 源集中。

所有清单合并为单个清单。将按照上述列表中的相同顺序指定优先级。也就是说,某个构建类型的清单设置会替换某个产品风格的清单设置,依此类推。如需了解详情,请阅读合并清单

同样,values/ 目录中的文件也会合并在一起。如果两个文件同名,例如存在两个 strings.xml 文件,将按照上述列表中的相同顺序指定优先级。也就是说,在构建类型源集中的文件中定义的值将会替换产品风格中同一文件中定义的值,依此类推。

res/ 和 asset/ 目录中的资源将打包到一起。如果两个或两个以上的源集中定义有同名资源,将按照上述列表中的相同顺序指定优先级。

最后,在构建 APK 时,Gradle 会为随库模块依赖项包含的资源和清单分配最低的优先级。

声明依赖项 

您可以通过使用构建变体或测试源集的名称作为 Implementation 关键字的前缀,为特定的构建变体或测试源集配置依赖项,如下例所示。

dependencies {// Adds the local "mylibrary" module as a dependency to the "free" flavor.

freeImplementation project

(":mylibrary")

// Adds a remote binary dependency only for local tests.

testImplementation

'junit:junit:4.12'

// Adds a remote binary dependency only for the instrumented test APK.

androidTestImplementation

'com.android.support.test.espresso:espresso-core:3.0.2'}

如需了解详细信息,请参阅添加构建依赖项

配置签署设置 

除非您为发布构建显式定义签署配置,否则,Gradle 不会签署发布构建的 APK。您可以轻松创建发布密钥并使用 Android Studio 签署发布构建类型

要使用 Gradle 构建配置为您的发布构建类型手动配置签署配置:

创建密钥库。密钥库是一个二进制文件,它包含一组私钥。您必须将密钥库存放在安全可靠的地方。

创建私钥。私钥代表将通过应用识别的实体,如某个人或某家公司。

将签署配置添加到模块级 build.gradle 文件中:

...

android

{...

defaultConfig

{...}

signingConfigs

{

release

{

storeFile file

("myreleasekey.keystore")

storePassword

"password"

keyAlias

"MyReleaseKey"

keyPassword

"password"}}

buildTypes

{

release

{...

signingConfig signingConfigs

.release

}}}

要生成签署的 APK,请从菜单栏中选择 Build > Generate Signed APK。现在,app/build/apk/app-release.apk 中的软件包已使用您的发布密钥进行签署。

注:将发布密钥和密钥库的密码放在构建文件中并不安全。作为替代方案,您可以将此构建文件配置为通过环境变量获取这些密码,或让构建流程提示您输入这些密码。

要通过环境变量获取这些密码:

storePassword System.getenv("KSTOREPWD")

keyPassword System.getenv("KEYPWD")

要让构建流程在您要从命令行调用此构建时提示您输入这些密码:

storePassword System.console().readLine("\nKeystore password: ")

keyPassword System.console().readLine("\nKey password: ")

java文件替换方式之一

<code>

full{

java{

            srcDirs = ['src/main/java']

                exclude'com/ubtechinc/cruzr/gradledemo/ui/MainActivity.java'

                srcDirs += ['src/full/java/com/ubtechinc/cruzr/gradledemo']

//                include 'src/full/java/com/ubtechinc/cruzr/gradledemo/ui/MainActivity.java'

            }

}

demo{

java{

srcDirs = ['src/main/java','src/demo/java']

}

java.sourceFiles.each { println"demo File in source set: " + it }

}

</code>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342