Gradle 翻译 tips and recipes 使用技巧 MD

Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 [email protected]

目录

Gradle 提示与诀窍

Gradle tips and recipes

GradleAndroid Plugin for Gradle 提供了一种灵活[flexible]的方式来编译、构建和打包您的 Android 应用或库。本页面汇总了一些有用提示和配置,旨在帮助您充分利用每一个构建[get the most out of each build]。

管理项目和源代码

[Manage projects and sources]

下面是一些可用于管理您的项目的模块及其源代码的配置。要详细了解如何创建和管理项目与模块,请阅读 项目概览

更改默认源集配置:sourceSets

[Change default source set configurations]

您可以使用模块级 build.gradle 文件中的 sourceSets 代码块更改 Gradle 希望为源集 的每个组件收集文件的位置。

简洁版:

android {
  sourceSets {
    main {
      java.srcDirs = ['other/java'] //默认为【src/main/java】
      res.srcDirs = ['other/res1', 'other/res2'] //默认为【src/main/res】
      manifest.srcFile 'other/AndroidManifest.xml' //默认为【src/main/AndroidManifest.xml】
    }
    androidTest {
      setRoot 'src/tests' //默认有三个根目录【src/androidTest】【src/test】【src/main】
    }
  }
}

完整版:

android {
  sourceSets {
    main { // Encapsulates将包封 configurations for the main source set.
      java.srcDirs = ['other/java'] // Changes the directory for Java sources.
/** 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. */
      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'
    }

    androidTest {  // Create additional blocks to configure other source sets.

/** 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'
    }
  }
}

配置项目范围的属性:ext

[configure project-wide properties]

对于包含多个模块的项目,在项目级别定义属性,然后在所有模块间共享这些属性可能会非常有用。为此,您可以将 extra properties 添加到 the top-level build.gradle 文件的 ext 代码块中。

简洁版

ext {
    compileSdkVersion = 26
    supportLibVersion = "27.1.1"
}

完整版

buildscript {...}

allprojects {...}

// This block encapsulates封装 custom properties and makes them available to all modules in the project.
ext { // The following are only a few examples of the types of properties you can define.
    compileSdkVersion = 26
    // You can also create properties to specify versions for dependencies指定依赖项的版本.
    // Having consistent一致的 versions between modules can avoid conflicts with behavior.
    supportLibVersion = "27.1.1"
    //...
}

要从相同项目中的模块访问这些属性,请在 module-level build.gradle 文件中使用以下语法。

android {
  compileSdkVersion rootProject.ext.compileSdkVersion //语法【rootProject.ext.property_name】
}

dependencies {
    //语法【$rootProject.ext.property_name】或【${rootProject.ext.property_name}】
    compile "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
}

管理库和依赖项

[Manage libraries and dependencies]

Gradle 提供了一种稳健的机制来管理依赖项,不管它们是远程库还是本地库模块。

将依赖项配置针对特定构建:configurations

[Target specific builds with dependency configurations]

如果您希望某个依赖项仅用于特定的构建变体源集或者测试源集,则必须大写依赖项配置名称并在其前面加上构建变体或测试源集的名称作为前缀。

If you want a dependency for only a specific build variant source set or testing source set, capitalize the dependency configuration name and prefix it with the name of the build variant or testing source set.

简洁版

configurations {
    freeDebugRuntimeOnly{} //初始化配置名称,用于指定具体是哪个构建(此名称代表 free Debug 的构建)
    feeDebugImplementation{}
}

dependencies {
    freeDebugRuntimeOnly fileTree(dir: 'libs', include: ['*.jar']) //仅为指定构建添加此依赖
    feeDebugImplementation 'com.google.firebase:firebase-ads:9.8.0'
}

完整版

android {...}

// Creates Gradle dependency configurations to use in the dependencies block.
configurations {
  // For variants that combine a product flavor and build type, you need to intitialize a placeholder for its dependency configuration.
    freeDebugRuntimeOnly{}
  //...
}

dependencies {
    freeImplementation 'com.google.firebase:firebase-ads:9.8.0'  // Adds an implementation dependency only to the "free" product flavor.
    freeDebugRuntimeOnly fileTree(dir: 'libs', include: ['*.jar']) // Adds a runtimeOnly dependency only to the "freeDebug" build variant.
    testImplementation 'junit:junit:4.12' // Adds a remote binary dependency only for local tests.
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' // Adds a remote binary dependency only for the instrumented test APK.
}

创建不同版本的应用

[Create different versions of your app]

Gradle 和 Android 插件允许您通过配置构建变体的方式从一个模块创建不同版本的应用。

配置多 APK 支持:splits、density、abi

[Configure multiple APK support]

利用 Android 插件,您可以构建多个 APK,让每一个都针对不同的 ABI 或屏幕密度,并充分利用 Google Play 的多 APK 支持。

按屏幕密度配置单独的 APK
[Configure separate APKs per screen density]

要为不同的屏幕密度创建单独的 APK,请将 android.splits.density 代码块添加到您的模块的 build.gradle 文件中。

android {
  splits {
    density { // Configures multiple APKs based on screen density.
      enable true  // The default value is false
      exclude "ldpi", "xxhdpi", "xxxhdpi" //排除。Specifies a list of screen densities Gradle should not create multiple APKs for.
      compatibleScreens 'small', 'normal', 'large', 'xlarge'  //兼容的 Specifies a list of compatible screen size settings for the manifest.
    }
  }
}

按 ABI 配置单独的 APK
[Configure separate APKs per ABI]

要为每个 ABI 创建单独的 APK,请将 android.splits.abi 代码块添加到您的模块的 build.gradle 文件中。

android {
  splits {
    abi { // Configures multiple APKs based on ABI.
      enable true  // The default value is false
      // By default all ABIs are included, so use reset() and include to specify that we only want APKs for x86 and x86_64.
      reset() // Resets the list of ABIs that Gradle should create APKs for to none.
      include "x86", "x86_64"  // Specifies a list of ABIs that Gradle should create APKs for.
      universalApk false  // Specifies that we do not want to also generate a universal APK that includes all ABIs.
    }
  }
}

配置动态版本代码:android.applicationVariants.all

[Configure dynamic version codes]

默认情况下,在 Gradle 为您的项目生成 APK 时,每个 APK 都有相同的版本信息,此信息在模块级 build.gradle 文件中指定。由于 Google Play 商店不允许同一个应用的多个 APK 全都具有相同的版本信息,在上传到 Play 商店之前,您需要确保每个 APK 都有自己唯一的 versionCode

为此,您可以使用自定义构建逻辑在构建时向每个 APK 分配不同的版本代码。例如,在为每个 ABI 创建单独的 APK 时,自动 APK 版本控制将如下所示:

简洁版

ext.abiCodes = ['armeabi-v7a':1, x86:2, x86_64:3]

import com.android.build.OutputFile
android.applicationVariants.all { variant -> 
  variant.outputs.each { output ->
    def baseAbiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))
    if (baseAbiVersionCode != null) {
       output.versionCodeOverride =  baseAbiVersionCode * 1000 + variant.versionCode
    }
  }
}

完整版

ext.abiCodes = ['armeabi-v7a':1, x86:2, x86_64:3]

import com.android.build.OutputFile
android.applicationVariants.all { variant -> 
  variant.outputs.each { output -> // Assigns a different version code for each output APK other than the universal APK.
    def baseAbiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))// Determines the ABI for this variant and returns the mapped value.

/** Because abiCodes.get() returns null for ABIs that are not mapped by ext.abiCodes, the following code does not override the version code for universal APKs. However, because we want universal APKs to have the lowest version code, this outcome is desirable令人满意的. */
    if (baseAbiVersionCode != null) {
/** Assigns the new version code to versionCodeOverride, which changes the version code for only the output APK, not for the variant itself. Skipping this step simply causes只会导致 Gradle to use the value of variant.versionCode for the APK. */
      output.versionCodeOverride =  baseAbiVersionCode * 1000 + variant.versionCode
    }
  }
}

组合多个产品风味:flavorDimensions

[Combine multiple product flavors]

某些情况下,您可能希望组合多个产品风味中的配置。为此,您可以通过 Android Plugin for Gradle 创建产品风味组,称为风味维度。

下面的代码示例使用 flavorDimensions 属性创建一个“模式”风味维度以组织“完整”和“演示”产品风味,以及一个“api”风味维度以基于 API 级别组织产品风味配置。随后,Gradle 会将“模式”维度的产品风味与“api”维度的产品风味组合。

简洁版

android {
  buildTypes {
    debug {...}
    release {...}
  }

  flavorDimensions "api", "mode"

  productFlavors {
    demo {
      dimension "mode"  //风味维度:mode
    }
    full {
      dimension "mode"  //风味维度:mode
    }

    minApi24 {
      dimension "api"  //风味维度:api
      minSdkVersion '24'
      versionCode 30000 + android.defaultConfig.versionCode
      versionNameSuffix "-minApi24"
    }
    minApi23 {...}  //风味维度:api
    minApi21 {...}  //风味维度: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 {
      dimension "mode"  // Assigns this product flavor to the "mode" flavor dimension.
    }

    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[要确保目标设备接收具有最高兼容API级别的应用程序版本], assign分配 version codes in increasing value with API level. */
      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"
    }
  }
}

过滤变体:variantFilter

[Filter variants]

您可以使用模块的 build.gradle 文件中的 variantFilter 代码块过滤构建变体,将您不想要的变体过滤掉。以下示例代码将指示 Gradle 不构建任何可以将 minApi21 与 demo 产品风味组合的变体

简洁版

android {
  variantFilter { variant ->
      def names = variant.flavors*.name //精准匹配时使用【variant.buildType.name == "<buildType>"】
      if (names.contains("minApi21") && names.contains("demo")) {
          //具体的逻辑
      }
  }
}

完整版

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")) {
          setIgnore(true)  // Gradle ignores any variants that satisfy满足 the conditions above.
      }
  }
}

测试应用(不常用)

[Test your app]

要详细了解如何运行本地和集成单元测试,请阅读测试应用。

配置 lint 选项:lintOptions

[Configure lint options]

您可以使用模块级 build.gradle 文件中的 lintOptions 代码块配置特定的 lint 选项。要详细了解如何为您的 Android 项目使用 lint,请阅读使用 Lint 改进您的代码。

简洁版

android {
  lintOptions {
    disable 'TypographyFractions','TypographyQuotes'
    enable 'RtlHardcoded', 'RtlCompat', 'RtlEnabled'
    check 'NewApi', 'InlinedApi'
    quiet true
    abortOnError false
    ignoreWarnings true
  }
}

完整版

android {
  lintOptions {
    disable 'TypographyFractions','TypographyQuotes'  // Turns off checks for the issue IDs you specify.
    enable 'RtlHardcoded', 'RtlCompat', 'RtlEnabled' // These checks are in addition to the default lint checks.
/** To enable checks for only a subset of issue IDs and ignore all others, list the issue IDs with the 'check' property instead. This property overrides any issue IDs you enable or disable using the properties above. */
    check 'NewApi', 'InlinedApi'
    quiet true  // If set to true, turns off analysis progress reporting by lint.
    abortOnError false // if set to true (default), stops the build if errors are found.
    ignoreWarnings true // if true, only report errors.
  }
}

配置仪器 manifest 设置:test**

[Configure instrumentation manifest settings]

在 Gradle 构建您的测试 APK 时,它会自动生成 AndroidManifest.xml 文件并为其配置 <instrumentation> 节点。您可以在 测试源集 中创建另一个 manifest 文件或者配置您的模块级 build.gradle 文件,通过这两种方式更改此节点的一些设置,如以下代码示例中所示。

简洁版

android {
  defaultConfig {
    testApplicationId "com.test.foo"
    testInstrumentationRunner "android.test.InstrumentationTestRunner"
    testFunctionalTest true
  }
}

完整版

android {
  defaultConfig {
    testApplicationId "com.test.foo"  // Specifies the application ID for the test APK.
    testInstrumentationRunner "android.test.InstrumentationTestRunner" // fully-qualified class name
    // If set to 'true', enables the instrumentation class to start and stop profiling.
    // If set to false (default), profiling occurs the entire time the instrumentation class is running.
    testHandleProfiling true
    // If set to 'true', indicates that the Android system should run the instrumentation class as a functional test. 
    testFunctionalTest true //The default value is 'false'
  }
}

更改测试构建类型:testBuildType

[Change the test build type]

默认情况下,所有测试均针对调试构建类型运行。您可以利用模块级 build.gradle 文件中的 testBuildType 属性将其更改为其他构建类型。例如,如果您想针对“staging”构建类型运行测试,请按下面这段代码中所示对该文件进行编辑。

android {
    testBuildType "staging"
}

配置 Gradle 测试选项:testOptions

[Configure Gradle test options]

要指定可以更改 Gradle 运行所有测试方式的选项,请配置模块级 build.gradle 中的 testOptions 代码块。

android {
  testOptions {
    // '$rootDir' sets the path relative to the root directory of the current project.
    reportDir "$rootDir/test-reports" //默认在 project/module_name/build/outputs/reports/
    resultsDir "$rootDir/test-results" //默认在 project/module_name/build/outputs/test-results/
  }
}

要仅为本地单元测试指定选项,请配置 testOptions.unitTests 代码块。

android {
  testOptions {
    unitTests {
/** By default, local unit tests throw an exception any time the code you are testing tries to access Android platform APIs (unless you mock嘲笑、藐视、不尊重 Android dependencies yourself or with a testing framework like Mockito). However, you can enable the following property so that the test returns either null or zero when accessing platform APIs, rather than throwing an exception. */
      returnDefaultValues true

      all { // controlling how Gradle executes local unit tests.
        jvmArgs '-XX:MaxPermSize=256m' // Sets JVM argument(s) for the test JVM(s).
        if (it.name == 'testDebugUnitTest') {  // You can also check the task name to apply options to only the tests you specify.
          systemProperty 'debug', 'true'
        }
      }
    }
  }
}

优化您的构建

[Optimize your build]

本部分将介绍一些有助于加快您的完整构建和增量构建速度的配置。

压缩代码:ProGuard

[Shrink your code]

Android Studio 使用 ProGuard 来压缩代码。对于新项目,Android Studio 将使用 Android_SDK/tools/proguard/ 下的默认设置文件 proguard-android.txt。要想进一步压缩代码,请尝试使用位于同一位置的 proguard-android-optimize.txt 文件。

android {
  buildTypes {
    release {
      minifyEnabled true //默认为false
      proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' //默认配置
    }
  }
}

要添加特定于每个构建变体的 ProGuard 规则,请为每个风味配置其他 proguardFiles 属性。例如,以下示例会将 flavor2-rules.pro 添加到“flavor2”中。现在,发布版本“flavor2”使用所有三个 ProGuard 规则,因为还应用了来自 release 代码块的规则。

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
        }
    }
    productFlavors {
        flavor1 {
        }
        flavor2 {
            proguardFile 'flavor2-rules.pro'
        }
    }
}

通过 Instant Run 启用代码压缩:useProguard

[Enable code shrinking with Instant Run]

要通过 Instant Run 启用代码压缩,只需将 useProguard 设为 false(并保持 minifyEnabled 设为 true)。这将使用实验性代码压缩器,它不会对您的代码进行混淆处理或优化(因此,您应当仅为 debug 构建类型启用此压缩器)。

android {
    buildTypes {
        debug {
            minifyEnabled true
            useProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

配置 DEX 选项:dexOptions

[Configure dex Options]

在 Gradle 将您的代码编译到 DEX 文件中时,使用以下属性可以缩短构建时间。

android {
  dexOptions {
    maxProcessCount 8 // Sets the maximum number of DEX processes that can be started concurrently.
    javaMaxHeapSize "2g" // Sets the maximum memory allocation pool size for the dex operation.
    preDexLibraries true // Enables Gradle to pre-dex library dependencies.
  }
}

发布您的应用

[Publish your app]

要详细了解如何将使用的应用发布到 Google Play,请阅读发布您的应用。

签署您的应用:signingConfigs

[Sign your app]

尽管 Android Studio 提供了一种从界面为发布构建配置签署的简单方式,您仍然可以手动配置模块的 build.gradle 文件中的 signingConfigs 代码块:

android {
    defaultConfig {...}
    signingConfigs {
        release {
            storeFile file("myreleasekey.keystore")
            storePassword "password"
            keyAlias "MyReleaseKey"
            keyPassword "password"
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

从您的项目中移除私密签署信息:.properties配置文件

[Remove private signing information from your project]

默认情况下,签署配置将以纯文本形式记录到模块的 build.gradle 文件中。如果您正在与某个团队合作或者参与一个开放源代码项目,可以执行以下步骤,将此敏感信息移出构建文件。

简洁版

def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(rootProject.file("keystore.properties"))) //加载配置文件

android {
  signingConfigs {
    config {
      keyAlias keystoreProperties['keyAlias']
      keyPassword keystoreProperties['keyPassword']
      storeFile file(keystoreProperties['storeFile'])
      storePassword keystoreProperties['storePassword']
    }
  }
}

1、在项目的根目录下创建一个名为 keystore.properties 的文件,并包含以下信息:

storePassword=myStorePassword
keyPassword=myKeyPassword
keyAlias=myKeyAlias
storeFile=myStoreFileLocation

2、在您的 build.gradle 文件中,按以下步骤操作来加载 keystore.properties 文件(必须在 android 代码块之前):

def keystorePropertiesFile = rootProject.file("keystore.properties") // Creates and initializes a variable to the keystore.properties file.
def keystoreProperties = new Properties() // Initializes a new Properties() object
keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) // Loads the keystore.properties file into the Properties object.

android { ... }

3、输入存储在 keystoreProperties 对象中的签署信息:

android {
  signingConfigs {
    config {
      keyAlias keystoreProperties['keyAlias']
      keyPassword keystoreProperties['keyPassword']
      storeFile file(keystoreProperties['storeFile'])
      storePassword keystoreProperties['storePassword']
    }
  }
}

要详细了解应用签署,请阅读 签署您的应用。

简化应用开发

[Simplify app development]

下面的提示有助于简化您的 Android 应用开发。

与代码共享自定义字段和资源值:buildConfigField 和 resValue

[Share custom fields and resource values with your app's code]

在构建时,Gradle 将生成 BuildConfig 类,以便您的应用代码可以检查与当前构建有关的信息。您也可以使用 buildConfigField() 函数,将自定义字段添加到 Gradle 构建配置文件的 BuildConfig 类中,然后在应用的运行时代码中访问这些值。同样,您也可以使用 resValue() 添加应用资源值。

简洁版

android {
  buildTypes {
    release {
      buildConfigField("String", "BUILD_TIME", "\"${minutesSinceEpoch}\"")
      resValue("string", "build_time", "${minutesSinceEpoch}")
    }
  }
}

完整版

android {
  buildTypes {
    release {
      // These values are defined only for the release build, which is typically典型的 used for 【full builds】 and 【continuous builds】.
      buildConfigField("String", "BUILD_TIME", "\"${minutesSinceEpoch}\"")
      resValue("string", "build_time", "${minutesSinceEpoch}")
    }
    debug {
      // Use static values for incremental builds to ensure that resource files and BuildConfig aren't rebuilt重建 with each run.
      // If they were dynamic, they would prevent阻止 certain某一、某些 benefits利益、福利、优势 of Instant Run as well as Gradle UP-TO-DATE checks.
      buildConfigField("String", "BUILD_TIME", "\"0\"")
      resValue("string", "build_time", "0")
    }
  }
}

在您的应用代码中,您可以访问以下属性:

Log.i(TAG, BuildConfig.BUILD_TIME);
Log.i(TAG, getString(R.string.build_time));

与 manifest 共享属性:manifestPlaceholders

[Share properties with the manifest]

某些情况下,您可能需要同时在 manifest 和代码中声明相同属性(例如,在为FileProvider声明机构时)。如以下示例中所示,请在模块的 build.gradle 文件中定义一个属性并使其对 manifest 和代码均可用,而不必在多个位置更新相同的属性以反映更改。要了解详情,请阅读 将构建变量注入 Manifest。

简洁版

android {
  defaultConfig {
      manifestPlaceholders = [
          filesAuthority: "${applicationId}.files",
          app_logo         : "@drawable/icon",
          app_channel_value: "小米应用市场",
      ]
  }
}
android:authorities="${filesAuthority}"

完整版

android {
  defaultConfig {
      def filesAuthorityValue = applicationId + ".files" // Creates a property for the FileProvider authority.

      // Creates a placeholder property to use in the manifest.
      manifestPlaceholders = [filesAuthority: filesAuthorityValue,
                                    app_logo         : "@drawable/icon",
                                    app_channel_value: "小米应用市场",
      ]

      // Adds a new field for the authority to the BuildConfig class.
      buildConfigField("String", "FILES_AUTHORITY", "\"${filesAuthorityValue}\"")
      buildConfigField "String", "BASE_URL", '"http://120.com/"'
      buildConfigField "int", "countryCode", "20094"
  }
}

在您的 manifest 中,访问以下占位符:

<manifest>
  <application>
    <provider
      android:name="android.support.v4.content.FileProvider"
      android:authorities="${filesAuthority}"
      android:exported="false"
      android:grantUriPermissions="true"/>
  </application>
</manifest>

以上内容上次更新日期:五月 8, 2018

2019-5-12

猜你喜欢

转载自www.cnblogs.com/baiqiantao/p/10852853.html