前言
上一篇我们讲解了Tinker的使用,现在我们讲解下一些功能的扩展与从源码角度查看流程分析。
功能扩展
在扩展功能之前我们要先来了解下。我们可以扩展那些功能。下面我们重Tinker的初始化函数入手。修改TinkerManager代码如下:
/**
* 完成Tinker初始化
*
* @param applicationLike
*/
public static void installedTinker(ApplicationLike applicationLike) {
mApplicationLike = applicationLike;
if (isInstalled) {
return;
}
// TinkerInstaller.install(mApplicationLike);
mPatchListener = new DefaultPatchListener(getApplicationContext());//一些补丁文件的校验工作
//这两个是监听patch文件安装的日志上报结果 也就是补丁文件安装监听
LoadReporter loadReporter = new DefaultLoadReporter(getApplicationContext());//一些在加载补丁文件时的回调
PatchReporter patchReporter = new DefaultPatchReporter(getApplicationContext());//补丁文件在合成时一些事件的回调
AbstractPatch abstractPatch = new UpgradePatch();//决定patch文件安装策略 不会去修改与自定义
TinkerInstaller.install(mApplicationLike,
loadReporter,
patchReporter,
mPatchListener,
CustomResultService.class,//我们自定义的
abstractPatch);
isInstalled = true;
}
可以看到我们把上一篇的初始化函数注释掉,而是采用6个参数的注册方法。这些参数的作用在官方文档中都非常的详细自定义扩展。我这里全都是使用的默认的。这里根据实际开发区决定要自定义那些内容。我就不过多介绍了。不过我重写了CustomResultService类。我们看下:
/**
* 功能 :决定在patch安装以后的后续操作 默认实现是杀死进程
*/
public class CustomResultService extends DefaultTinkerResultService {
private static final String TAG = "Tinker.DefaultTinkerResultService";
@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));
<!-- if (checkIfNeedKill(result)) {
android.os.Process.killProcess(android.os.Process.myPid());
} else {
TinkerLog.i(TAG, "I have already install the newly patch version!");
}-->
}
}
}
我们知道,Tinker在补丁文件安装完成后默认是杀死当前进程的,显然这样做的效果不是很好。所以我们重写了DefaultTinkerResultService,并将原本杀死进程的代码注释掉,这样我们就可以在用户无感知的情况下完成补丁的安装,并且在用户下次启动的时候生效。其他的代码不变,重新走一遍流程。就会看到效果的生成
多渠道打包修复
还记得我们在第一篇中我们将所有关于多渠道打包的代码都注释了,现在我们将注释的代码放开,然后按照步骤。
首先先打出多渠道签名文件包
第二步配置gradle脚本。
可以看到多渠道打包后,基准包路径的配置还是有些不同的。(这里我没有开启混淆。不过是没有影响的)
第三步就是修改代码,然后生成补丁文件
生成后的目录:
图片.png
这样就针对两个渠道的签名包,生成补丁文件,剩下的流程就与之前一样了。
从源码的角度分析流程
Tinker的源码是比较的复杂的尤其的它的Dexdiff算法。本身我还是个菜鸟,所以只能够源码角度理清下流程。有人可能问,一个框架,能使用有效果,问题能排查就好啦。确实我个人觉得一个技术或是框架日新月异。总有些新的技术出现并且我也不是大神,感觉有些浪费时间。不过我还是逼自己去看源码。从中我认为最好的好处有两个:1.能更好的定位为题,和自定义扩展功能 2.能学习优秀框架的代码格式书写。使自己写出高质量的代码,这也是我认为最重要的。
从Tinker的注册方法可以看到,他用了外观模式。所以我们也从 TinkerInstaller.install()进去
public class TinkerInstaller {
private static final String TAG = "Tinker.TinkerInstaller";
/**
* install tinker with default config, you must install tinker before you use their api
* or you can just use {@link TinkerApplicationHelper}'s api
*
* @param applicationLike
*/
public static Tinker install(ApplicationLike applicationLike) {
Tinker tinker = new Tinker.Builder(applicationLike.getApplication()).build();
Tinker.create(tinker);
tinker.install(applicationLike.getTinkerResultIntent());
return tinker;
}
/**
* install tinker with custom config, you must install tinker before you use their api
* or you can just use {@link TinkerApplicationHelper}'s api
*
* @param applicationLike
* @param loadReporter
* @param patchReporter
* @param listener
* @param resultServiceClass
* @param upgradePatchProcessor
*/
public static Tinker install(ApplicationLike applicationLike, LoadReporter loadReporter, PatchReporter patchReporter,
PatchListener listener, Class<? extends AbstractResultService> resultServiceClass,
AbstractPatch upgradePatchProcessor) {
Tinker tinker = new Tinker.Builder(applicationLike.getApplication())
.tinkerFlags(applicationLike.getTinkerFlags())
.loadReport(loadReporter)
.listener(listener)
.patchReporter(patchReporter)
.tinkerLoadVerifyFlag(applicationLike.getTinkerLoadVerifyFlag()).build();
Tinker.create(tinker);
tinker.install(applicationLike.getTinkerResultIntent(), resultServiceClass, upgradePatchProcessor);
return tinker;
}
/**
* clean all patch files!
*
* @param context
*/
public static void cleanPatch(Context context) {
Tinker.with(context).cleanPatch();
}
/**
* new patch file to install, try install them with :patch process
*
* @param context
* @param patchLocation
*/
public static void onReceiveUpgradePatch(Context context, String patchLocation) {
Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);
}
/**
* set logIml for TinkerLog
*
* @param imp
*/
public static void setLogIml(TinkerLog.TinkerLogImp imp) {
TinkerLog.setTinkerLogImp(imp);
}
}
这个类也比较简单,就是2个注册的方法,前面我们也都用到了,还有就是加载补丁的方法和清除补丁的方法。可以看到Tinker处理的核心类是Tinker类。Tinker类中就是利用单例和构建者模式创建Tinker,并将我们传入的参数利用构建者进行初始化。下面我们就看下加载补丁的方法onReceiveUpgradePatch。这里调用 Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);方法并将我们补丁文件的路径出入进去。点击去发现是一个接口。下面我们就来看下这个接口的实现类。还记得我们在参数中传入的DefaultPatchListener吗?这个就是实现这个方法的类。
public class DefaultPatchListener implements PatchListener {
protected final Context context;
public DefaultPatchListener(Context context) {
this.context = context;
}
/**
* when we receive a patch, what would we do?
* you can overwrite it
*
* @param path
* @return
*/
@Override
public int onPatchReceived(String path) {
//对补丁文件的校验
int returnCode = patchCheck(path);
if (returnCode == ShareConstants.ERROR_PATCH_OK) {
//启动加载补丁文件并修复BUG的服务
TinkerPatchService.runPatchService(context, path);
} else {
Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode);
}
return returnCode;
}
//对补丁文件的校验 我们可以重写这个方法,去实现我们自己的校验,比如MD5检验等 也可以重写ShareConstants来实现我们自己的错误码
protected int patchCheck(String path) {
Tinker manager = Tinker.with(context);
//check SharePreferences also
if (!manager.isTinkerEnabled() || !ShareTinkerInternals.isTinkerEnableWithSharedPreferences(context)) {
return ShareConstants.ERROR_PATCH_DISABLE;
}
File file = new File(path);
if (!SharePatchFileUtil.isLegalFile(file)) {
return ShareConstants.ERROR_PATCH_NOTEXIST;
}
//patch service can not send request
if (manager.isPatchProcess()) {
return ShareConstants.ERROR_PATCH_INSERVICE;
}
//if the patch service is running, pending
if (TinkerServiceInternals.isTinkerPatchServiceRunning(context)) {
return ShareConstants.ERROR_PATCH_RUNNING;
}
return ShareConstants.ERROR_PATCH_OK;
}
}
在这个onPatchReceived方法中启动了一个服务我们继续跟踪 TinkerPatchService.runPatchService(context, path);这个方法:
public class TinkerPatchService extends IntentService {
......
public static void runPatchService(Context context, String path) {
try {
Intent intent = new Intent(context, TinkerPatchService.class);
intent.putExtra(PATCH_PATH_EXTRA, path);
intent.putExtra(RESULT_CLASS_EXTRA, resultServiceClass.getName());
context.startService(intent);
} catch (Throwable throwable) {
TinkerLog.e(TAG, "start patch service fail, exception:" + throwable);
}
}
......
@Override
protected void onHandleIntent(Intent intent) {
final Context context = getApplicationContext();
Tinker tinker = Tinker.with(context);
tinker.getPatchReporter().onPatchServiceStart(intent);
if (intent == null) {
TinkerLog.e(TAG, "TinkerPatchService received a null intent, ignoring.");
return;
}
String path = getPatchPathExtra(intent);
if (path == null) {
TinkerLog.e(TAG, "TinkerPatchService can't get the path extra, ignoring.");
return;
}
File patchFile = new File(path);
long begin = SystemClock.elapsedRealtime();
boolean result;
long cost;
Throwable e = null;
increasingPriority();
PatchResult patchResult = new PatchResult();
try {
if (upgradePatchProcessor == null) {
throw new TinkerRuntimeException("upgradePatchProcessor is null.");
}
//处理补丁文件核心方法
result = upgradePatchProcessor.tryPatch(context, path, patchResult);
} catch (Throwable throwable) {
e = throwable;
result = false;
//将处理的结果通知到我们传入的DefaultPatchListener中
tinker.getPatchReporter().onPatchException(patchFile, e);
}
cost = SystemClock.elapsedRealtime() - begin;
tinker.getPatchReporter().
onPatchResult(patchFile, result, cost);
patchResult.isSuccess = result;
patchResult.rawPatchFilePath = path;
patchResult.costTime = cost;
patchResult.e = e;
//开启加载补丁完成后服务,这就是我们上面重写安装补丁后的如何自定义行为的service类,默认是杀死当前进程。
AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));
}
....
}
可以看到TinkerPatchService是一个自带子线程的服务开启这个服务后,就会走到onHandleIntent方法中,里面做了一些异常的判断。处理补丁文件的方法就是upgradePatchProcessor.tryPatch(context, path, patchResult);这个方法。点击入也是一个抽象的方法。我们来看他的默认的实现类UpgradePatch:
public class UpgradePatch extends AbstractPatch {
private static final String TAG = "Tinker.UpgradePatch";
.....
//we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process
//修复dex文件的核心方法入口
if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed");
return false;
}
//修复lib文件的核心方法入口
if (!BsDiffPatchInternal.tryRecoverLibraryFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch library failed");
return false;
}
//修复资源文件的核心方法入口
if (!ResDiffPatchInternal.tryRecoverResourceFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch resource failed");
return false;
}
}
这个类的核心方法就是上面提到的三个方面的修复,再往里走下去就比较底层,涉及到Dexdiff算法,有兴趣的可以看下鸿洋大神关于diff算法的文章这个就不分析了。下面我们来用流程图总结下:
Tinker流程分析.png大致的流程就是这样,具体的大家也可以自行研究下。
引入Tinker后的代码管理
在引入Tinker可以在git分支上专门创建一个hotFix分支专门是用来修改bug的分支,与开发分支(如dev)和主分支(master)区分开。具体情况以自己实际情况为主。
结语
经过几篇文章,分别讲解了AndFix与Tinker。相信大家对他们的差别也有了一定的认识。Tinker在使用中感觉还是有不少坑的,但是相对于AndFix,Tinker支持的比较全,并且支持在微信上也在使用。同时现在又支持Android热更新服务平台与Bulgy,也更加方便。本人是菜鸟,难免分析不那么细致。如果有错误欢迎大家指出。如果有什么问题也可以留言大家一起解决。
源码地址 里面包含本文代码和Tinker-1.9.1版本的配置源码
网友评论