Tinker多渠道打包支持
这里我们先在Gradle引入多渠道打包的支持:
//多渠道脚本支持
productFlavors {
googleplayer {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "googleplayer"]
}
baidu {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"]
}
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
}
相关的渠道信息在清单文件中进行配置:
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...
<!-- 友盟统计相关meta-data -->
<meta-data
android:name="UMENG_APPKEY"
android:value="57bfe3cbe0f55a0b2e0025b8" />
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_VALUE}" />
</application>
最后,我们在上一篇文章的App的Gradle文件的tinkerPatch块的最后按照官方Demo添加多渠道打包的支持(核心逻辑就是不同渠道的APK、Patch的拷贝,拷贝到指定的目录方便我们拾取):
//多渠道打包支持
project.afterEvaluate {
if (hasFlavors) {
task(tinkerPatchAllFlavorRelease) {
group = 'tinker'
def originOldPath = getTinkerBuildFlavorDirectory()
for (String flavor : flavors) {
def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release")
dependsOn tinkerTask
def preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest")
preAssembleTask.doFirst {
String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15)
project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk"
project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt"
project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt"
}
}
}
task(tinkerPatchAllFlavorDebug) {
group = 'tinker'
def originOldPath = getTinkerBuildFlavorDirectory()
for (String flavor : flavors) {
def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug")
dependsOn tinkerTask
def preAssembleTask = tasks.getByName("process${flavor.capitalize()}DebugManifest")
preAssembleTask.doFirst {
String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13)
project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk"
project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt"
project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt"
}
}
}
}
}
最后,在配置基准包(也就是old.apk)的信息的时候,直接写基准包所在的路径即可:
ext {
tinkerEnable = true
tinkerID = "1.0"
tinkerOldApkPath = "${bakPath}/app-1111-15-29-27"
tinkerApplyMappingPath = "${bakPath}/app-1111-15-29-27"
tinkerApplyResourcePath = "${bakPath}/app-1111-15-29-27"
tinkerBuildFlavorDirectory = "${bakPath}/app-1111-15-29-27"
}
Gradle同步成功之后,就可以使用如下的命令生成Patch文件:
./gradlew tinkerPatchAllRelease
或者
./gradlew tinkerPatch渠道名字Release
Tips:需要注意的事,向服务器请求Patch的时候,就需要带上渠道名。
Tinker自定义行为
如果要对Tinker的行为进行自定义,例如修改默认行为的重启、增加数据埋点统计、文件MD5检测等,就需要调用这个install方法。
sCustomPatchListener = new CustomPatchListener(getApplication());
TinkerInstaller.install(
mApplicationLike,
new DefaultLoadReporter(getApplication()),
new DefaultPatchReporter(getApplication()),
sCustomPatchListener,
CustomResultService.class,
new UpgradePatch()
);
这个install方法,除了传入ApplicationLike以外,还需要其他参数,例如:
- 补丁加载的时候的报告者
- 补丁应用的时候的报告者
- 与补丁加载的逻辑有关的UpgradePatch对象
上述一般使用Tinker默认的即可,下面我们介绍一些我们常用的:
一般我们会自定义一个PatchListener,监听Patch的加载过程,在里面我们进行Patch文件的MD5检验:
public class CustomPatchListener extends DefaultPatchListener {
private String currentMD5;
public void setCurrentMD5(String md5Value) {
this.currentMD5 = md5Value;
}
public CustomPatchListener(Context context) {
super(context);
}
@Override
protected int patchCheck(String path, String patchMd5) {
//patch文件ms5较验
if (!Utils.isFileMD5Matched(path, currentMD5)) {
return ShareConstants.ERROR_PATCH_DISABLE;
}
return super.patchCheck(path, patchMd5);
}
}
然后,我们也会自定义一个ResultService改变Patch安装之后的行为:防止Tinker在加载完补丁之后,直接把APP的进程重启:
/**
* 本类的作用:决定在patch安装完以后的后续操作,默认实现是杀进程
*/
public class CustomResultService extends DefaultTinkerResultService {
private static final String TAG = "Tinker.SampleResultService";
//返回patch文件的最终安装结果
@Override
public void onPatchResult(PatchResult result) {
if (result == null) {
TinkerLog.e(TAG, "DefaultTinkerResultService received null result!!!!");
return;
}
TinkerLog.i(TAG, "DefaultTinkerResultService received a result:%s ", result.toString());
//first, we want to kill the recover process
TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext());
// if success and newPatch, it is nice to delete the raw file, and restart at once
// only main process can load an upgrade patch!
if (result.isSuccess) {
deleteRawPatchFile(new File(result.rawPatchFilePath));
}
}
}
需要注意的是,CustomResultService跟一般的Service一样,必须要在清单中配置。
Tinker组件化
组件化的概念是:将一个大的软件系统按照分离关注点的形式,拆分成多个独立的组件,已达到降低模块之间耦合的目的。组件化的思想最早用于服务器端开发。
下面介绍组件化的过程。
1、没有使用组件化思想的时候,模块都是很杂乱的,模块之间的耦合度很高:
2、在单工程之下实现组件化的框架图如下所示,各个层次之间十分明确:
3、在单工程之下实现组件化的基础上,进一步抽取模块,实现多个工程(Module)的组件化:
组件化的优点比较多:
- 每个组件都有自己独立的版本,可以独立的编译、安装、测试,最大程度地达到互不干扰
- 团队的分工更加明确,小团队专注于自己的业务模块
- 提高代码的可复用性,提高团队的效率
具体代码实现的话,跟之前的AndFix组件化一样,都是自定义一个FixService,在闪屏页开启这个FixService。FixService的实现按照AndFix的流程进行封装即可:
Tinker的注意事项
最后,谈一下Tinker的注意事项:
- Tinker不支持加固,但是后续版本估计会支持
- Tinker支持修改资源文件,但是不支持修改清单文件
- TInker不支持新增四大组件、Transition动画、桌面图标等
- Tinker不支持部分三星Android21的机型
关于Tinker的原理,将在下一篇文章中继续...相信看完下一篇文章,回过头来看这篇文章,就一目了然了。