【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和变种的资源文件目录下创建该文件
在这里插入图片描述
在这里插入图片描述
main目录下的background_test文件为蓝色;
variant1目录下的background_test为灰色;
variant2目录下的background_test为黑色;
而最后编译出来的apk显示的为变种对应background_test的颜色。
因此,在其他需要根据变种修改图片资源文件的地方都可以按这种方式来修改。
在这里插入图片描述
说完图片文件,我们回到刚才所说的编译时会选择变种文件目录下的文件这一点。
在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的情景。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值