Android-GradleAndroid-gradle

管理我们的 gradle 依赖

2018-12-16  本文已影响65人  前行的乌龟

现在 android 开发中对于 gradle 也是很多技巧的,简单的有统一管理依赖及其版本号,复杂一些的涉及到 gradle task,比如 apk 重命名,渠道包配置,buildTypes 编译模式中配置不同的参数,如 debug 和 release 配置不同的baseUrl,最难的是自定义 gradle plugin 脚本,去执行诸如统一图片压缩,打包等

我对 gradle 的了解也有限,但是还是要总结一下

这里我们对 gradle 进行以下几个方面的操作:

本文项目地址:BW_Libs


1. 统一管理远程依赖

我们在根目录创建一个 config.gradle 文件,.gradle 文件,我们可以看成 gradle 脚本。然后我们在这个 config.gradle 写项目中需要的远程依赖,然后在根项目的 build.gradle 中引用我们写的 config.gradle 这个脚本就成了,大家注意写法:

1.1 创建 config.gradle 文件
1.2 在 config.gradle 文件中书写依赖
//第三方依赖库和版本号管理
def versions = [:]

versions.support = "26.1.0"

versions.rxjava = "2.1.10"
versions.rx_android = "2.0.2"
versions.retrofit = "2.4.0"
versions.okhttp3 = "3.4.1"
versions.gson = "2.4.0"


ext {

    android = [
            compileSdkVersion: 26,
            buildToolsVersion: '26.0.2',
            minSdkVersion    : 19,
            targetSdkVersion : 26,
            applicationId    : 'com.bloodcrown.bw',
            versionCode      : 1,
            versionName      : '1.0',
            //            multiDexEnabled  : true
    ]

    signConfigs = [
            'storeFile'    : 'sign.jks',
            'storePassword': '123456',
            'keyAlias'     : 'sign',
            'keyPassword'  : '123456'
    ]

    java = [
            'javaVersion': JavaVersion.VERSION_1_8
    ]

    dependence = [

            // 官方
            'appcompatv7'            : "com.android.support:appcompat-v7:$versions.support",
            'supportv4'              : "com.android.support:support-v4:$versions.support",
            'design'                 : "com.android.support:design:$versions.support",
            'recyclerview'           : "com.android.support:recyclerview-v7:$versions.support",

            // rxjava2
            'rxjava'                 : "io.reactivex.rxjava2:rxjava:$versions.rxjava",
            'rxandroid'              : "io.reactivex.rxjava2:rxandroid:$versions.rx_android",

            // retrofit2
            'retrofit'               : "com.squareup.retrofit2:retrofit:$versions.retrofit",
            'gson'                   : "com.squareup.retrofit2:converter-gson:$versions.gson",
            'retrofitAdapter'        : "com.squareup.retrofit2:adapter-rxjava2:$versions.retrofit",

            // okhttp3
            'okhttp3'              : "com.squareup.okhttp3:okhttp:$versions.okhttp3",
            'http3LoggingInterceptor': "com.squareup.okhttp3:logging-interceptor:$versions.okhttp3",

    ]
}
1.3 根项目 build.gradle 引用 config.gradle 脚本
// Top-level build file where you can add configuration options common to all sub-projects/modules.

// 引入 config.gradle  脚本
apply from: this.file( "config.gradle" )


//引入文件
//apply from: this.file('common.gradle')
buildscript {
    ext.kotlin_version = '1.2.71'
    ext.recyclerview_version = '26.1.0'
    ext.gradle_version = '3.0.1'

    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'me.tatarka:gradle-retrolambda:3.2.5'
        classpath "com.android.tools.build:gradle:$gradle_version"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        maven { url 'http://repo1.maven.org/maven2' }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
1.4 使用 config.gradle 脚本中的依赖


apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

//apply from: rootProject.file( "config.gradle" )

android {

    compileSdkVersion rootProject.ext.android.compileSdkVersion
    buildToolsVersion rootProject.ext.android.buildToolsVersion

    defaultConfig {

        applicationId rootProject.ext.android.applicationId
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName

        vectorDrawables.useSupportLibrary = true
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    dataBinding {
        enabled = true
    }

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

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'

    implementation rootProject.ext.dependence.design
    implementation rootProject.ext.dependence.appcompatv7
    implementation rootProject.ext.dependence.recyclerview

    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
    kapt "com.android.databinding:compiler:$rootProject.ext.gradle_version"
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    implementation project(':basecomponents')

    // retrofit 依赖
    implementation rootProject.ext.dependence.rxandroid
    implementation rootProject.ext.dependence.rxjava
    implementation rootProject.ext.dependence.retrofit
    implementation rootProject.ext.dependence.gson
    implementation rootProject.ext.dependence.retrofitAdapter
    implementation rootProject.ext.dependence.http3LoggingInterceptor
    implementation rootProject.ext.dependence.okhttp3
}

2. 提取所有 module gradle 公共配置项 common.gradle

上面我们使用 config.gradle 记录统一从 module 抽取出来的远程依赖,做成公共的一份再由 module 各自选取其中的依赖,然后我们在 config.gradle 中改远程依赖的版本号就可以在所有的 module 都生效了,这就是 java 中抽象封装的思想,难度是 gradle 语言上手不友好,自动提示出来的不是我们要的, 很难写

那么现在我们继续深入发挥这个 抽象封装 的思路,每个 module 中都有相同的配置项,我们把这个抽取出来,还是统一写到 common.gradle 中,然后每个 module 都依赖这个 common.gradle 即可,和 config.gradle 区别是,config.gradle 根目录 .gradle 引入就行了, common.gradle 需要所有的 module 都引用一份

common.gradle 中的依赖和配置也是要引用 config.gradle 的

2.1 创建 config.gradle 文件
2.2 书写 common.gradle
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

//kapt {
//    arguments {
//        arg("moduleName", project.getName())
//    }
//}

android {

    compileSdkVersion rootProject.ext.android.compileSdkVersion
    buildToolsVersion rootProject.ext.android.buildToolsVersion

    defaultConfig {

        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        vectorDrawables.useSupportLibrary = true

//        multiDexEnabled true

        ndk {
            abiFilters 'armeabi', 'x86'
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    dataBinding {
        enabled = true
    }

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

dependencies {

    implementation fileTree(dir: 'libs', include: ['*.jar'])

    testImplementation rootProject.ext.dependence.junit
    androidTestImplementation rootProject.ext.dependence.runner
    androidTestImplementation rootProject.ext.dependence.espresso
    implementation rootProject.ext.dependence.multidex

    implementation rootProject.ext.dependence.kotlinStdlibJdk8
    implementation rootProject.ext.dependence.kotlinReflect
    kapt rootProject.ext.dependence.databinding

}
repositories {
    mavenCentral()
}
2.3 module 引入 common.gradle 依赖

app module:

apply plugin: 'com.android.application'

apply from:rootProject.file('common.gradle')

android {

    defaultConfig {

        applicationId rootProject.ext.android.applicationId
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName
    }

    signingConfigs {

        //release版的签名配置信息
        release {
            storeFile file(rootProject.ext.signConfigs.storeFile)
            storePassword rootProject.ext.signConfigs.storePassword
            keyAlias rootProject.ext.signConfigs.keyAlias
            keyPassword rootProject.ext.signConfigs.keyPassword
        }
    }

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

dependencies {

    implementation project(':basecomponents')
    implementation project(':baselib')
    implementation project(':common_repositroy')

    implementation rootProject.ext.dependence.design
    implementation rootProject.ext.dependence.appcompatv7
    implementation rootProject.ext.dependence.recyclerview
    implementation rootProject.ext.dependence.constraintLayout

    // retrofit 依赖
    implementation rootProject.ext.dependence.rxandroid
    implementation rootProject.ext.dependence.rxjava
    implementation rootProject.ext.dependence.retrofit
    implementation rootProject.ext.dependence.gson
    implementation rootProject.ext.dependence.retrofitAdapter
    implementation rootProject.ext.dependence.http3LoggingInterceptor
    implementation rootProject.ext.dependence.okhttp3
}

其他 module:

apply plugin: 'com.android.library'
apply from:rootProject.file('common.gradle')

android {

    defaultConfig {
    }

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

}

dependencies {

    implementation project(':baselib')

    implementation rootProject.ext.dependence.appcompatv7

    // retrofit 依赖
    implementation rootProject.ext.dependence.rxandroid
    implementation rootProject.ext.dependence.rxjava
    implementation rootProject.ext.dependence.retrofit
    implementation rootProject.ext.dependence.gson
    implementation rootProject.ext.dependence.retrofitAdapter
    implementation rootProject.ext.dependence.http3LoggingInterceptor
    implementation rootProject.ext.dependence.okhttp3
}

这样每个 module 只关心自己的需要的特殊依赖,配置就行,app moule 中还是有很多配置需要写的,这些配置涉及 app 打包等,所以不适合写进公共配置


gradle 补充

其实我们的 app 若不是很复杂的话或是大家嫌上面麻烦的话,还有一种更简单的写法,当然不是那么正式了

app 构建时在根目录提供了一个 gradle.properties 文件,这个 gradle.properties 就是提供书写 gradle 参数的地方,使用起来也很简单,直接调名字即可

apply plugin: 'com.android.library'

android {
    lintOptions {
        abortOnError false
    }

    compileSdkVersion Integer.parseInt(ANDROID_COMPILE_SDK_VERSION)
    buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION

    defaultConfig {
        minSdkVersion Integer.parseInt(ANDROID_MIN_SDK_VERSION)
        targetSdkVersion Integer.parseInt(ANDROID_BUILD_TARGET_SDK_VERSION)
        versionCode Integer.parseInt(APP_VERSION_CODE)
        versionName project.APP_VERSION_NAME
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }
}

ext {
    supportLibraryVersion = project.ANDROID_SUPPORT_LIBRARY_VERSION
}

dependencies {
    testImplementation 'junit:junit:4.12'
    implementation "com.android.support:appcompat-v7:${supportLibraryVersion}"
    implementation "com.android.support:design:${supportLibraryVersion}"
}

// Place it at the end of the file
apply from: 'install_lib.gradle'
apply from: 'bintray_lib.gradle'

3. 修改资源文件目录

这个需求在多渠道打包或是插件化时常用,比如不同渠道包 icon 图标不同,切换子 module 类型

看下面的例子

3.1 切换 module 类型
android {
    sourceSets {
        main {
            if(isDebug.toBoolean()) {
                manifest.srcFile 'src/debug/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/release/AndroidManifest.xml'
            }
        }
    }
}
3.2 修改 java 文件路径
android {
    // ...
    sourceSets {
        debug {
            java.srcDirs = ['src/main/java', 'src/debug/java']
        }
        release {
            java.srcDirs = ['src/main/java', 'src/release/java']
        }
    }
}
3.3 修改 module 依赖

和 3.2 一样,我们在不筒的 buildType 编译版本时,可以修改 module 依赖

dependencies {
    // ...
    debugImplementation project(':basecomponents')
    releaseImplementation project(':basecomponents')
}

修改资源路径时请慎重,因为非常容易出问题,而且很难搞~


4. 给 module 添加资源前缀 resourcePrefix

app 不管有多少 module ,最后都是整合到一起打成一个 dex 的,除非 dex 太大,报 65535了,才会拆分多个 dex 。所以 module 中的资源文件都会何必在一起的,这样难免会出现资源重名的问题,这可是会报错的,怎么办?

google 提供了 resourcePrefix 资源前缀

andorid{
    ...

    buildTypes{
        ...
    }

    resourcePrefix "moudle_prefix"
}

但是这样也不是就高枕无忧了,resourcePrefix 可以给 res 文件夹下的文件加前缀,但是图片资源就不行了,图片我们还是需要在命名时自己添加前缀


5. 去除重复依赖

这里我们要解决的是,我们引入的远程依赖中依赖的组件和我们本地远程依赖存在版本号的差异,这样 as 在编译的时候很多时候报错

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile("com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion") {
        exlude module: 'support-v4'//根据组件名排除
        exlude group: 'android.support.v4'//根据包名排除
    }
}

6. 修改 module 路径

我们在开发时总是愿意把相同类型的类放在一起,这点放到 module 身上也是一样的,比哦如我希望业务组件类型的 module 都在一个文件夹下,这样好看好找不是

module 的配置是在 根项目 的 setting.gradle 中,那么我们去 setting.gradle 里面改好了

通常的 module 路径配置我们这么写

include ':app', ':basecomponents', ':baselib', ':common_repositroy'

但是我们可以自己在 根项目 里新建一个文件夹,然后把 module 放进去,那么我们这样写就行

project(':basecomponents').projectDir = new File( 'components/basecomponents' )
project(':common_repositroy').projectDir = new File( 'components/common_repositroy' )

7. gradle 修改 manifest 配置文件参数

在 manifest 配置文件我们可以定义 <meta-data> 参数,这个 <meta-data> 参数在 gradle 文件中可以修改的,这也是多渠道打包的基础

<meta-data>

  <meta-data
            android:name="UMENG_CHANNEL"
            android:value="${UMENG_CHANNEL_VALUE}"/>

gradle 修改值

manifestPlaceholders = [UMENG_CHANNEL_VALUE: "yingyongbao"]

大家在集成友盟统计时,就是这么干的


8. gradle 中创建参数

我们在 gradle 声明一个参数,然后根据不同的环境,修改为不同数据,这个最典型的应用就是 baseUrl 的处理了,debug 和 release 环境下 baseUrl 是不同的

gradle 声明参数有2种写法:

// 注意字符串单引号里面要套双引号
buildConfigField 'String', 'app_name', '"HHHHHH"'

// 在 values 中声明参数
resValue "color", "flav_color", "#00ffff"

java 代码获取参数

Log.d("test", BuildConfig.app_name);

tx_info.setTextColor(getResources().getColor(R.color.flav_color));

这里我们来看看 debug 和 release 环境下修改 baseUrl

    buildTypes {

        debug{
            buildConfigField( 'String','baseUrl','"baseUrl_debug"' )
        }
        release {
            buildConfigField( 'String','baseUrl','"baseUrl_release"' )
        }
    }
        btn_toast.setOnClickListener({
            ToastComponent.instance.show("baseUrl = " + BuildConfig.baseUrl);
        })

经过测试的确是可以改变 baseUrl 参数的


9. NDK 配置

1. 配置 SO 支持库
android {

    defaultConfig {
        ndk {

            // APP的build.gradle设置支持的SO库架构
            abiFilters 'armeabi', 'armeabi-v7a', 'x86'
        }
    }
}

这个 so 库配置声明了我们支持哪些 CPU 架构,一般现在我们除了 ARM CPU 架构外都不考虑了,这个配置我们写在 module 公共配置项 common.gradle 里面


10. buildType 不同编译类型种我们一般都配置什么

基本上都是配 混淆,签名,各种参数,AndroidManifest 的 mate_data,其他的看字个特殊需求了

// 签名配置
signingConfigs {
    debug {
        storeFile file("../debug.jks")
        storePassword "123456"
        keyAlias "debug"
        keyPassword "123456"
    }
    release {
        storeFile file("../release.jks")
        storePassword "123456"
        keyAlias "release"
        keyPassword "123456"
    }
}

// 编译类型中我们通畅的配置项
buildTypes {
    debug {

        // 混淆配置
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

        // 签名配置
        signingConfig signingConfigs.debu

        // 各种参数配置,比如 URL
        buildConfigField "String", "URL_HOST","\"https://XXXX.XXXX.XXXX/""
        buildConfigField "String", "QQ_APPID", "\"11111111111\""
        buildConfigField "String", "QQ_APPSECRET", "\"XXXXXXXXXXXXXXXX\""
        buildConfigField "Boolean", "DEBUG_TAG", "true"

        // AndroidManifest 的各种 mate_data
        manifestPlaceholders = [BAIDU_KEY_VAULE        : "fffffffffffffffffffff"]
        manifestPlaceholders = [UMENG_CHANNEL_VALUE    : "xxxxxxxxx"]
    }

    release {

        minifyEnabled true //开启混淆编译
        shrinkResources true //移除无用资源
        zipAlignEnabled true //zipalign优化
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

        signingConfig signingConfigs.release

        buildConfigField "String", "URL_HOST","\"https://XXXX.XXXX.XXXX/""
        buildConfigField "String", "QQ_APPID", "\"11111111111\""
        buildConfigField "String", "QQ_APPSECRET", "\"XXXXXXXXXXXXXXXX\""
        buildConfigField "Boolean", "DEBUG_TAG", "false"

        manifestPlaceholders = [BAIDU_KEY_VAULE        : "fffffffffffffffffffff"]
        manifestPlaceholders = [UMENG_CHANNEL_VALUE    : "xxxxxxxxx"]
    }
}

11. 自定义 apk 名字

android {
    // 自定义输出apk名字
    applicationVariants.all {
        variant ->
            variant.outputs.all { output ->
                outputFileName = new File("app_" +
                        buildType.name +
                        "_v" + defaultConfig.versionName + "_" +
                        new Date().format("yyyy-MM-dd")+ ".apk")
            }
    }
}

跟着上面写就行,grooy 语言兼容 java 语法,上面的写法基本算是固定的



12. 多渠道打包

多渠道打包 gradle 代码不难,和 buildType 一个思路,根据不同的渠道改相应的参数或是资源

比如友盟统计:

/*配置渠道*/
productFlavors {
    wandoujia{
        manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]
    }
    yingyongbao{
        manifestPlaceholders = [UMENG_CHANNEL_VALUE: "yingyongbao"]
    }
   huawei{
        manifestPlaceholders = [UMENG_CHANNEL_VALUE: "huawei"]
    }
  ........
}

    //自动替换清单文件中的渠道号
    productFlavors.all {
        flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
    }

这样打渠道包的缺点:

使用360加固时,支持多渠道打包,这样使用比较方便


注意:友盟统计的渠道号不能全是数字

还有一种是使用三方开源的打包工具,比如美团开源的打包工具:walle


13. 自动维护版本发布文档 gradle 脚本

这个脚本,我是看这篇文章找到的,很赞

gradle 脚本:releaseinfo.gradle

import groovy.xml.MarkupBuilder

/**
 * 描述:版本发布文档自动维护脚本
 * 流程描述:
 *           1、将版本相关信息解析出来
 *           2、将解析出的数据生成xml格式数据
 *           3、写入到已有的文档数据中
 **/
ext {
    versionName = rootProject.ext.android.versionName
    versionCode = rootProject.ext.android.versionCode
    versionInfo = 'App的第1个版本,上线了一些最基础核心的功能,主要是要通过审核'
    destFile = file('releasesInfo.xml')//指定输出文件
    if (destFile != null && !destFile.exists()) {
        destFile.createNewFile()
    }
}
//挂在到应用构建的过程中
this.project.afterEvaluate { project ->
    def buildTask = project.tasks.getByName('assembleRelease')
    if (buildTask != null) {
        buildTask.doLast {//assembleRelease 之后执行
            releaseInfoTask.execute()
        }
    }
}
//创建一个Task,并指定输入输出
task releaseInfoTask {
    inputs.property('versionCode', this.versionCode)
    inputs.property('versionName', this.versionName)
    inputs.property('versionInfo', this.versionInfo)
    outputs.file this.destFile
    doLast {
        //将输入的内容写入到输出文件中去
        def data = inputs.getProperties()
        File file = outputs.getFiles().getSingleFile()
        def versionMsg = new VersionMsg(data)
        //将实体对象写入到xml文件中
        def sw = new StringWriter()
        def xmlBuilder = new MarkupBuilder(sw)
        if (file.text != null && file.text.size() <= 0) {
            //没有内容
            xmlBuilder.releases {
                release {
                    versionCode(versionMsg.versionCode)
                    versionName(versionMsg.versionName)
                    versionInfo(versionMsg.versionInfo)
                }
            }
            //直接写入
            file.withWriter { writer ->
                writer.append("<?xml version=\"1.0\" encoding=\"GBK\"?>" + '\r\n')
                writer.append(sw.toString())
            }
        } else {//有内容判断版本是否改变
            def releases = new XmlParser().parse(file)
            def codeName = releases.release[-1].versionName.text()
            if (codeName != versionMsg.versionName) {//新的版本信息
                xmlBuilder.release {
                    versionCode(versionMsg.versionCode)
                    versionName(versionMsg.versionName)
                    versionInfo(versionMsg.versionInfo)
                }
                //插入到最后一行前面
                def lines = file.readLines()
                def lengths = lines.size() - 1
                file.withWriter { writer ->
                    lines.eachWithIndex { line, index ->
                        if (index != lengths) {
                            writer.append(line + '\r\n')
                        } else if (index == lengths) {
                            writer.append('\r\n' + sw.toString() + '\r\n')
                            writer.append(lines.get(lengths))
                        }
                    }
                }
            }
        }
    }
}
//信息实体类
class VersionMsg {
    String versionCode
    String versionName
    String versionInfo
}

app module 壳工程依赖这个脚本就行啦

apply from: rootProject.file('releaseinfo.gradle')

然后我们在打 release 包之后就在 app 项目下自动生成 releaseInfo.xml文件


<?xml version="1.0" encoding="GBK"?>
<releases>
  <release>
    <versionCode>1</versionCode>
    <versionName>1.0</versionName>
    <versionInfo>App的第1个版本,上线了一些最基础核心的功能,。。。。。。</versionInfo>
  </release>
</releases>
上一篇下一篇

猜你喜欢

热点阅读