前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Tinker-自定义扩展与流程分析(下)

Tinker-自定义扩展与流程分析(下)

作者头像
g小志
发布2018-09-11 17:23:46
7200
发布2018-09-11 17:23:46
举报
文章被收录于专栏:Android常用基础Android常用基础
前言

上一篇我们讲解了Tinker的使用,现在我们讲解下一些功能的扩展与从源码角度查看流程分析。


功能扩展

在扩展功能之前我们要先来了解下。我们可以扩展那些功能。下面我们重Tinker的初始化函数入手。修改TinkerManager代码如下:

代码语言:javascript
复制
  /**
     * 完成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类。我们看下:

代码语言:javascript
复制
/**
 * 功能   :决定在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,并将原本杀死进程的代码注释掉,这样我们就可以在用户无感知的情况下完成补丁的安装,并且在用户下次启动的时候生效。其他的代码不变,重新走一遍流程。就会看到效果的生成


多渠道打包修复

还记得我们在第一篇中我们将所有关于多渠道打包的代码都注释了,现在我们将注释的代码放开,然后按照步骤。

首先先打出多渠道签名文件包

图片.png

第二步配置gradle脚本。

图片.png

可以看到多渠道打包后,基准包路径的配置还是有些不同的。(这里我没有开启混淆。不过是没有影响的)

第三步就是修改代码,然后生成补丁文件

图片.png

生成后的目录:

图片.png

这样就针对两个渠道的签名包,生成补丁文件,剩下的流程就与之前一样了。


从源码的角度分析流程

Tinker的源码是比较的复杂的尤其的它的Dexdiff算法。本身我还是个菜鸟,所以只能够源码角度理清下流程。有人可能问,一个框架,能使用有效果,问题能排查就好啦。确实我个人觉得一个技术或是框架日新月异。总有些新的技术出现并且我也不是大神,感觉有些浪费时间。不过我还是逼自己去看源码。从中我认为最好的好处有两个:1.能更好的定位为题,和自定义扩展功能 2.能学习优秀框架的代码格式书写。使自己写出高质量的代码,这也是我认为最重要的。

从Tinker的注册方法可以看到,他用了外观模式。所以我们也从 TinkerInstaller.install()进去

代码语言:javascript
复制
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吗?这个就是实现这个方法的类。

代码语言:javascript
复制
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);这个方法:

代码语言:javascript
复制
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:

代码语言:javascript
复制
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版本的配置源码

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.02.03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 功能扩展
      • 多渠道打包修复
        • 从源码的角度分析流程
          • 引入Tinker后的代码管理
          • 结语
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档