【Android】变种:一份源码根据需求打包不同APK

变种的使用环境

在开发过程中我们可能会遇到这样一种情况:
在现有的APP的基础上更换部分UI或是增删某个功能模块,而大体上两个apk有很多共通的地方,面对这种情况的解决方法无非以下几种:

1.将现有代码复制粘贴一份,在另一个工程中进行修改

2.使用代码托管工具的分支功能

3.使用全局静态变量控制某个功能开关

以上方法在新打包的apk较少的情况下使用是没什么问题的,但当需要打包的apk过多时,以上方法在后续开发时就会变得异常麻烦,所以面对这种情况,变种便于管理的优势就体现得淋漓尽致

正式使用

gradle配置

首先需要在app所在的module的gradle中配置flavorDimensions(风味维度),使变种的flavors保持在同一维度中

flavorDimensions的值可以自由定义


配置flavorDimensions

然后需要在android下建立productFlavors,然后创建一个自定义名称的子项
同时,在src下建立与main同级的文件夹,文件夹名称需与在productFlavors中的子项名称保持一致


变种配置

配置完成后点击sync更新配置,使用变种的第一步就算完成了

此时我们可以点击Android Studio的左下角的Build Variants标签页展开变种目录,选择对应的变种,运行时就会编译对应的变种中的文件

变种选择

当然,仅仅是这样处理的话,我们打包出来的apk会互相覆盖掉,这是因为编译出来的apk包名一致导致的。

我们可以在productFlavors下对变种添加配置,分别设置applicationId,这样,编译出来的apk就可以在手机上共存。

productFlavors{
        variant1{
            applicationId "com.sariki.variants1"
        }
        variant2{
            applicationId "com.sariki.variants2"
        }
    }

功能模块配置

变种搭建完成后,那么就到了代码环节,需要注意的有两点:
1.变种中的文件继承main目录中的文件夹结构,所以新建变种目录时需要将包结构与main的包结构保持一致


目录结构

2.变种与main中不能存在同级同名的文件,应在main中删除该文件后在变种目录中创建
(例如:main目录下activityA中需要跳转到activityB,但这个activityB根据变种有不同的功能,这时就需要在不同变种下创建该activityB并将main目录下对应的activityB文件删除)


在这里插入图片描述

既然打包多份app,我们在一些界面可能就会根据变种来对某些组件进行显示隐藏等操作,如果大体上与本体保持一致且没有新增一些功能,我们可以不采取删除main目录下文件后在变种中创建的方式来处理,可以借助BuildConfig类来进行判断。
首先我们到gradle下的productFlavors中对对应的变种配置buildConfigField,
productFlavors{
        variant1{
            applicationId "com.sariki.variants1"
            buildConfigField("boolean", "SHOW_TOAST", "true")
        }
        variant2{
            applicationId "com.sariki.variants2"
            buildConfigField("boolean", "SHOW_TOAST", "false")
        }
    }

代码中可以使用该变量来进行判断处理


在这里插入图片描述

buildConfigField是在编译时就会根据你的参数创建一个静态成员变量,因此,我们也可以借由这个来进行网络端口配置来适应变种对应的服务器

productFlavors{
        variant1{
            applicationId "com.sariki.variants1"
            buildConfigField("boolean", "SHOW_TOAST", "true")
            buildConfigField("String", "SERVER_HOST", "\"http://111.111.11.1/\"")
        }
        variant2{
            applicationId "com.sariki.variants2"
            buildConfigField("String", "SHOW_TOAST", "true")
            buildConfigField("String", "SERVER_HOST", "\"http://222.222.22.2/\"")
        }
    }
在这里插入图片描述

当需要操作的变量过多时,使用buildConfigField会使gradle文件变得过于冗杂,这个时候我们可以新建一个java文件作为一个载体,在其中创建静态成员变量替代BuildConfig
(该文件也需要在不同变种文件夹的相同层级目录中分别创建)


在这里插入图片描述

在这里插入图片描述

资源文件配置

与java文件相同,资源文件如果根据变种有变更,也需要在对应的文件目录中创建相同的文件,但资源文件与java文件有一个不同点:变种同层级同名资源文件可与main目录下的同层级同名资源文件共存,编译时会选择变种目录下的文件。

举个栗子:
我们在application中设置icon,以background_test文件为例,我们分别在main和变种的资源文件目录下创建该文件
[图片上传失败...(image-dd08fd-1606287792466)]
[图片上传失败...(image-cfde7e-1606287792466)]
main目录下的background_test文件为蓝色;
variant1目录下的background_test为灰色;
variant2目录下的background_test为黑色;
而最后编译出来的apk显示的为变种对应background_test的颜色。
因此,在其他需要根据变种修改图片资源文件的地方都可以按这种方式来修改。
[图片上传失败...(image-dd8efd-1606287792466)]
说完图片文件,我们回到刚才所说的编译时会选择变种文件目录下的文件这一点。
在values文件夹下我们一般会使用strings.xml,colors.xml等文件来定义配置,通常这些xml文件里面会有大量的item配置,如果根据上述图片的操作去将xml文件完完整整的复制一份到变种目录下就会造成资源浪费以及扩大工程占用空间,但如果不复制这份xml文件又该怎么去修改文件里面的部分配置呢?
其实我们可以依靠xml配置的覆盖机制来解决这个问题。

举个栗子:
我们在main目录下的strings.xml中新建一行名为"test_txt"的string,
编译后需要在variant1中显示为“变种1”,variant2中显示为“变种2”。
那么,我们可以直接在对应变种目录中新建一个xml文件(命名无强制规定),然后在其中创建一个同样名为"test_txt"的string并赋值。
这样,编译后变种中xml的string会覆盖掉main目录下strings.xml中同名string的值。

main目录下strings.xml配置


在这里插入图片描述

variant1目录下variant_stings.xml配置


在这里插入图片描述

variant2目录下variant_stings.xml配置
在这里插入图片描述

测试界面的xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:textSize="30sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:text="@string/tip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <TextView
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:textSize="30sp"
        android:text="@string/test_txt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>

我们给textview设置内容后可以编译看一下效果

variants1:


在这里插入图片描述

variant2:


在这里插入图片描述

这样,我们就可以达到只修改少量配置来更改文本或颜色的目的

密钥配置

我们在开发过程中,可能会用到第三方的sdk来支持我们做一些功能,而这些sdk都会使用独特的密钥来获得使用权限,我们在构建变种时这些sdk也需要用到,那么我们该如何对这些密钥进行配置呢?

现在大部分的sdk密钥会需要在AndroidManifest中进行配置,而其实在AndroidManifest中,我们可以直接引用gradle中的配置。

以网易云信密钥配置为例:

<meta-data
            android:name="com.netease.nim.appKey"
            android:value="" />

我们需要在value一栏中填写从网易云信获得的密钥,这时,我们可以使用引用的方式来进行配置。

首先我们到gradle下的productFlavors中,配置对应变种的manifestPlaceholders参数

productFlavors{
        variant1{
            applicationId "com.sariki.variants1"
            manifestPlaceholders = [valueName : "xxxxxxxxxx"]
        }
        variant2{
            applicationId "com.sariki.variants2"
            manifestPlaceholders = [valueName : "xxxxxxxxxxxx"]
        }
    }

格式为 [自定义的变量名称 : "密钥值"]

然后回到清单文件中,使用${xxx}的方式来引用指定的值

<meta-data
            android:name="com.netease.nim.appKey"
            android:value="${valueName}" />

同样,也可以在部分需要完整包名结构的配置中,使用${applicationId}方式来引用包名,例如部分厂商的推送以及一些其他的sdk配置,例

<permission
        android:name="${applicationId}.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />

但也有部分无法直接使用引用的方式来使用包名的情况,如接入微信第三方登录时用到的微信sdk,需要我们手动创建WXEntryActivity,并在清单文件中声明,

<activity android:name=".wxapi.WXEntryActivity"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.Translucent.NoTitleBar"
            android:exported="true"
            android:launchMode="singleTask"
            />

但由于我们构建变种时使用的包名不同,name字段索引不到WXEntryActivity的位置,直接配置的话会出现WXEntryActivity无法被调起的情况,这时我们就需要使用别名来进行配置

<activity-alias
            android:name="${applicationId}.wxapi.WXEntryActivity"
            android:exported="true"
            android:targetActivity=".wxapi.WXEntryActivity" />

添加这些配置就可以正常索引了

多module变种配置

在部分开发环境中,我们可能会需要对多个module进行变种配置,与上述配置流程没有太大区别,但是需要注意的有两点:
1.多个module的flavorDimensions需要保持一致
2.多个module的productFlavors需要保持一致,即module1中productFlavors中配置了多少个,module2中同样需要配置多少个
基于以上第二点,建议不要在过多的module中进行变种配置,这样会导致打一个新包需要配置的地方变得过多,在配置环节变得繁琐,有违初衷。

结尾

以上就是变种在构建过程中会涉及的一些方法了,但值得一提的是,变种有着马甲包与渠道包区别,渠道包一般是为了统计上架不同应用市场的流量而在APP中设置相关统计渠道,而马甲包除了可以满足渠道包的需求外,可以针对不同的变种做出资源变更以及功能变更,更多使用于一份源码根据需求定制化不同APP的情景。

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

推荐阅读更多精彩内容