背景
理由很简单: 有这样一个需求,一个维护很久了的项目,不断更新迭代,突然有一天产品说我们要做产品的换肤,但是旧版本还接着用,. 产品的一句话,内心真是一百万草泥马呼啸而过,. 根据我多年的偷懒和采坑经验,接到需求的我立马意识到后期至少要有以下几个问题(坑):
坑一 : 新版本可能不是单纯的改改xml或者切图文件就能完成换肤的,估计会有小的逻辑改动.甚至大的,
坑二 : 新旧两个版本后期可能会同步更新,如果采用版本工具新建分支,岂不是要维护两套代码太恶心了
坑三 : 如果出现多渠道打包 , 如果新 旧版本更新功能不同步, 对于日里万行(不要问我李万航是谁)代码的程序员们来说, 用不了多久,新旧两个项目就已经忘了 上个版本改了几个地方实现的某个变态的需求了.
废话太多了直接开始.
开始
等等 开始之前先介绍一下多渠道打包
多渠道打包 顾名思义 ,就是一套代码放在多个推广渠道上,但是在统计的时候,要知道哪个渠道推广效果好 ,所以这就需要对不同的APK 加上不同的标识. 大家最常用的可能就是友盟统计,当然你也可以自己撸,不拦着, 这里已友盟统计来做举例,简单介绍一下::
1.注册友盟账号, 创建应用,下载SDK,配置项目
这些就不赘述了,直接去找友盟开发文档就OK了
2. Manifest配置
在友盟的使用时 有一条是 Manifest的配置, 为了区分渠道我们会配置不同的Value 然后分别打包
<meta-data
android:name="UMENG_CHANNEL"
android:value="wandoujia" />
3. Manifest配置(升级)
俗话说: 不会偷懒的程序员不是好的CV搬运工 所以我们会借助gradle 方式来进行多渠道配置,然后一次性打包多个不同渠道的APK
<meta-data
android:name="UMENG_CHANNEL"
android:value="${CHANNEL}" />
// 多渠道打包
flavorDimensions "company"
productFlavors {
wandoujia {
manifestPlaceholders = [CHANNEL: "wandoujia"]
}
baidu {
manifestPlaceholders = [CHANNEL: "baidu"]
}
c360 {
manifestPlaceholders = [CHANNEL: "c360"]
}
uc {
manifestPlaceholders = [CHANNEL: "uc"]
}
}
4. 差异化开发
思考: 既然一套代码,通过多渠道方式能打包出,不同UMENG_CHANNEL 友盟标记的APK ,那可不可以通过这种方式做更多代码的区分呢
答案: 可以 ,要不 我还说个毛啊
方案: 与多渠道打包方式相似,也许gradle 的配合 (gradle 这是个好东西啊)
4.1 gradle 配置
借助 gradle 配置两个不同的渠道(productFlavors 风味风情 ) A和B, 在不同渠道配置 应用名(app_name) , 进程ID( applicationId ) ,软件版本 (versionName ) 和 渠道标识(CHANNEL )
flavorDimensions "company"
productFlavors {
productA{
dimension "company"
resValue "string", "app_name", "channel_A"
applicationId "com.example.a"
versionName "1.0.0.1"
manifestPlaceholders = [CHANNEL : "productA",
app_icon: "@mipmap/ic_laun"]
}
bproductB {
dimension "company"
resValue "string", "app_name", "channel_A"
applicationId "com.example.a"
versionName "1.0.0.1"
manifestPlaceholders = [CHANNEL : "productA",
app_icon: "@mipmap/ic_laun"]
}
}
4.2 AndroidManifest 配置
说明 label 不采用引用的方式 也可以生效, 读者可以亲测下
<application
android:icon="${app_icon}"
android:label="@string/app_name"
<activity android:name="----" />
<meta-data
android:name="UMENG_CHANNEL"
android:value="${CHANNEL}" />
</application>
4.3 创建渠道包 (重点)
用AS 在你新建项目的时候 ,AS会帮我们创建三个不同的工程, main工程 androidTest工程 和test工程 . 我们进行单元测试的时候会发现 , main工程的方法我们是可以调用的, 所以
- 首先,跟这个三个工程 同级目录再新建两个目录 即: productA 和productB ,这里名称必须与gradle内 productFlavors 的渠道名相同 ,
- 然后,在 productA 和 productB 是可以有自己的 java 目录 res目录 甚至 AndroidManifest.xml的, 然后开始创建这些目录就可以了
- 接着: 在 productA 和 productB 两个工程 的java目录里创建包名 例如 com.example, 记住两个工程的包名要相同但与main包名不同
- 接着: 在 productA 和 productB 的包名下再创建相同名的activity(也可以不同,因为我的需求是换肤,所以是activity要相同,xml不同即可) ,
文字描述太苍白直接上图
再次强调
- productA 和productB 工程名必须与gradle内 productFlavors 的渠道名相同
- productA 和productB 与main 工程目录是同级的
- productA 和productB 必须至少有一个相同的包名
- productA 和productB 相同包名下 必须至少有一个相同的类, 目的显而易见,就是给main工程一个相同的入口,不同的实现
5 productA 和 productB 不能与main的 四大组件和res资源 的名称相同 否自会报错
- 继续 不同皮肤的页面在两个项目里 做不同的实现 例如上图的 两个 TwoActivity
package com.example;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.zhf.lib_statusbar.BaseBarBlackActivity;
import com.zhf.module_channel.R;
// productA 工程代码
public class TwoActivity extends BaseBarBlackActivity {
@Override
protected int setContentResouceId() {
return R.layout.activity_two;
}
@Override
protected void initView() {
super.initView();
tvCentre.setText("A/OneActivity");
findViewById(R.id.textView).setOnClickListener(this);
}
@Override
public void onClick(View v) {
super.onClick(v);
Log.i(">>>>>>>", "我来自分支A");
Toast.makeText(mContext, "我来自分支A", Toast.LENGTH_SHORT).show();
}
}
package com.example;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.zhf.lib_statusbar.BaseBarBlackActivity;
import com.zhf.module_channel.R;
// productB 工程代码
public class TwoActivity extends BaseBarBlackActivity {
@Override
protected int setContentResouceId() {
return R.layout.activity_two;
}
@Override
protected void initView() {
super.initView();
tvCentre.setText("B/OneActivity");
findViewById(R.id.textView).setOnClickListener(this);
}
@Override
public void onClick(View v) {
super.onClick(v);
Log.i(">>>>>>>", "我来自分支B");
Toast.makeText(mContext, "我来自分支B", Toast.LENGTH_SHORT).show();
}
}
- 继续 在main项目中打开 TwoActivity 下面代码特别把 导包也贴上了, 发现 可以直接导包就行了 import com.example.TwoActivity; 而且在代码里不用做区分 A或者B .
package com.zhf.module_channel;
import android.content.Intent;
import android.view.View;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.example.TwoActivity;
import com.zhf.lib_statusbar.BaseBarBlackActivity;
@Route(path = "/channel/ChannelActivity")
public class ChannelActivity extends BaseBarBlackActivity {
@Override
protected int setContentResouceId() {
return R.layout.activity_channel;
}
@Override
protected void initView() {
super.initView();
tvCentre.setText("/channel/ChannelActivity");
findViewById(R.id.tv_text).setOnClickListener(this);
}
@Override
public void onClick(View v) {
super.onClick(v);
if (v.getId() == R.id.tv_text) {
Intent intent = new Intent();
intent.setClass(this, TwoActivity.class);
startActivity(intent);
}
}
}
- 最后 直接运行打包就可以了
补充 : 如果想单独调试 productA 直接在gradle 注释掉其他的只留productA就行了, productB 同理
参考文献:
https://www.jianshu.com/p/552c8f5ce203
https://www.cnblogs.com/charles04/p/9017501.html
https://blog.csdn.net/mingtiannihao0522/article/details/78390365