android-同一套代码打多个APP

同一套代码打多个APP

  • 前言
  • 实现思路
  • 具体实现
    • 目录结构
    • 将icon和label设置为可配置
    • build.gradle配置

前言

在开发中我们可能遇到各种需求,有时候公司为了渠道的推广或者制作马甲包,一套代码要打出多个App来。对于多个App的定义,首先applicationId不一样,可能App的名字也不一样,图标不一样,可能一些配置文件如服务地址,友盟的key等都不一样,但又确实是一套代码。为了不分出多的项目,避免后期更新麻烦,那么就需要我们类似多渠道配置进行配置。

实现思路

假设:当前代码需要打出两个APP,APP的不同点个前言说的一样
  1. 将两个APP的信息使用数组存入到一个json文件中,分别设置好标识,
  2. 创建文件夹A,并在文件中存放两个APP的图标
  3. 创建文件夹B,并存放两个APP所对应的信息,如APP名字,applicationId等。将json文件和A、B文件夹同放在一个文件夹channel_config下,并将这个文件夹存放在与app目录同级的目录下
  4. 在AndroidManifest.xml中将icon和名称设置为可配置
  5. 在app下build.gradle文件中defaultConfig下配置manifestPlaceholders,设置默认名称和icon
  6. 在app下build.gradle文件中创建 productFlavors,进行多渠道配置。主要是重新设置icon、app 名称,applicationId等
  7. 在app下build.gradle文件中创建sourceSets,进行重新指定图片路径。

具体实现

目录结构

首先看一下channel_config的目录情况,以及json的信息,下面可以看到jngi目录和yzxy目录下就只是放了icon,这是后面需要指定路径的,这里要注意,jngi这个目录要和channels文件中的名字对应,要和groupAll.josn 中的channel_name对应。还有,jngi和yzxy目录下的文件,res-style-blue和res-style-yellow下的目录要和app下面的目录文件位置和名字要保持一致

android-同一套代码打多个APP_第1张图片
android-同一套代码打多个APP_第2张图片

接下来我们看看groupAll.json的类容,这里定义了一个数组,channel_name是一个标识,用于在icons和channels目录下寻找资源。label代表这个是哪个App的配置,COLOR_STYLE表示这个App的主题颜色是什么

	[
  {
    "channel_name": "yzxy",
    "label": "反清",
    "COLOR_STYLE": "blue"
  },
  {
    "channel_name": "jngj",
    "label": "复明",
    "COLOR_STYLE": "yellow"
  }
]

将icon和label设置为可配置

我们直接看AndroidManifest.xml文件,其中 tools:replace="android:icon,android:label,android:theme"这个很关键,意思是:将低优先级清单中的指定属性替换为 此清单中的属性。 换言之,始终保持 高优先级清单的值,下面developer.android中讲的很清楚
合并多个清单文件

	<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.cs.demo">

    <application
        android:allowBackup="true"
        android:hardwareAccelerated="true"
        android:icon="${icon}"
        android:label="${label}"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:replace="android:icon,android:label,android:theme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

build.gradle配置

在 gradle.properties中添加 GROUP=All,当App打包很多的时候,打批包要很长时间,我们可以多创建几个groupAll.josn,中间放不容的App配置,那么就可以进行分批打包,比如groupType1,GROUP=Type1,进行指定

	import groovy.json.JsonSlurper

apply plugin: 'com.android.application'


android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.cs.demo"
        minSdkVersion 16
        targetSdkVersion 28
        versionCode 1
        versionName "1.0.0"
        resConfigs "zh"

        // 设置MultiDex可用
        multiDexEnabled true

        flavorDimensions "versionCode"
        javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }
        //配置默认的app图标和名字
        manifestPlaceholders = [
                label        : "反清",
                icon         : "@drawable/yzxy"

        ]
    }


    buildTypes {
        release {
            //混淆
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            //混淆
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            
            buildConfigField "String", "BASE_URL", "\"https://www.baidu.com\"" //配置debug的服务器地址

        }

    }

    productFlavors {
//在 gradle.properties中添加 GROUP=All
//读取channel_config下json文件
        def json = file("${rootProject.projectDir.path}/channel_config/group${GROUP}.json").getText("UTF-8")
//读取app目录下的channel.json文件内容

        def flavors = new JsonSlurper().parseText(json) //转换成Jsons数组对象
        flavors.each { flavor ->
//循环flavors数组
            def fileName = flavor.channel_name
            def channelJson = file("${rootProject.projectDir.path}/channel_config/channels/${fileName}.json").getText("UTF-8")
            def qudao = new JsonSlurper().parseText(channelJson) //转换成Jsons数组对象
            def INDEPENDENT_URL = flavor.INDEPENDENT_URL
            "${flavor.channel_name}" {//渠道名称,对应文件中的channel_name
                //单独服务地址
                if ("Y".equals(INDEPENDENT_URL)) {
                    buildConfigField "String", "BASE_URL", "\"${qudao.BASE_URL}\"" //服务器地址
                }

                buildConfigField "String", "COLOR_STYLE", "\"${qudao.COLOR_STYLE}\"" //主题颜色
                applicationId qudao.applicationId
                buildConfigField "String", "APP_NAME", "\"${qudao.label}\"" //应用名称

                manifestPlaceholders = [
                        icon        : qudao.icon,//APP要显示的ICON图标,对应文件中的icon
                        label       : qudao.label,
                        channel_name: flavor.channel_name
                ]
            }
        }
    }

    sourceSets {
        def json = file("${rootProject.projectDir.path}/channel_config/group${GROUP}.json").getText("UTF-8")
        //读取app目录下的channel.json文件内容
        def flavors = new JsonSlurper().parseText(json) //转换成Jsons数组对象
        flavors.each { flavor ->
            def colorstyle = flavor.COLOR_STYLE
            "${flavor.channel_name}" { //渠道资源配置
                if (colorstyle.equals("blue")) {
                //单独指定两个资源文件,一个替换res-style-blue下目录文件,一个替换icons下文件
                    res.srcDirs = ["src/res-style-blue", "${rootProject.projectDir.path}/channel_config/icons/${flavor.channel_name}"]
                   
                }
                if (colorstyle.equals("yellow")) {
                    res.srcDirs = ["src/res-style-yellow", "${rootProject.projectDir.path}/channel_config/icons/${flavor.channel_name}"]
                    //指定资源目录
                }
            }
        }
    }

    //修改apk名
    applicationVariants.all { variant ->
        variant.outputs.all { output ->
            def outputFile = output.outputFile
            if (outputFile != null && outputFile.name.endsWith('release.apk')) {
                // 输出apk名称为xxx_v1.1.1_20171005-21:23:30.apk
                def fileName = "${variant.flavorName}_v${variant.versionName}_${releaseTime()}_release.apk"
                outputFileName = new File(fileName)
            }
            if (outputFile != null && outputFile.name.endsWith('debug.apk')) {
                // 输出apk名称为xxx_v1.1.1_20171005-21:23:30.apk
                def fileName = "${variant.flavorName}_v${variant.versionName}_${releaseTime()}_debug.apk"
                outputFileName = new File(fileName)
            }
        }
    }


}

def releaseTime() {
    return new Date().format("yyyyMMdd_HH_mm_ss", TimeZone.getTimeZone("GMT+8:00"))
}


dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

}

基本完成了上面的需要,Demo,不懂请留言

你可能感兴趣的:(Android,应用)