Tinker接入及源码分析(三)

来源:互联网 发布:opencmf 源码 编辑:程序博客网 时间:2024/05/17 03:17

该系列文章分析基于 Tinker1.7.6 版本

Tinker项目地址:https://github.com/Tencent/tinker

Tinker接入及源码分析(一):简单介绍以及如何接入

Tinker接入及源码分析(二):加载补丁源码分析

Tinker接入及源码分析(三):合成补丁源码分析

上篇文章分析了加载补丁的源码,本篇文章会继续分析tinker初始化过程以及合成补丁的过程。

之前也说过,使用Tinker之前必须通过如下代码初始化Tinker:

TinkerInstaller.install(applicationLike);

这是最简单的初始化方法,也支持很多自定义参数,等我们分析完默认的情况,自定义参数也就好理解了。

先看一下这个方法的实现:

/**     * 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;    }

Tinker自定义参数很多,所以这里使用了Builder模式初始化Tinker,这里主要看一下Builder类里面的默认实现,后面分析会用到这些默认参数:

public static class Builder {        private final Context context;        private final boolean mainProcess;        private final boolean patchProcess;        private int status = -1;        private LoadReporter  loadReporter;        private PatchReporter patchReporter;        private PatchListener listener;        private File          patchDirectory;        private File          patchInfoFile;        private File          patchInfoLockFile;        private Boolean       tinkerLoadVerifyFlag;        /**         * Start building a new {@link Tinker} instance.         */        public Builder(Context context) {            if (context == null) {                throw new TinkerRuntimeException("Context must not be null.");            }            this.context = context;            this.mainProcess = TinkerServiceInternals.isInMainProcess(context);            this.patchProcess = TinkerServiceInternals.isInTinkerPatchServiceProcess(context);            this.patchDirectory = SharePatchFileUtil.getPatchDirectory(context);            if (this.patchDirectory == null) {                TinkerLog.e(TAG, "patchDirectory is null!");                return;            }            this.patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory.getAbsolutePath());            this.patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory.getAbsolutePath());            TinkerLog.w(TAG, "tinker patch directory: %s", patchDirectory);        }        //省略了set方法        public Tinker build() {            if (status == -1) {                status = ShareConstants.TINKER_ENABLE_ALL;            }            if (loadReporter == null) {                loadReporter = new DefaultLoadReporter(context);            }            if (patchReporter == null) {                patchReporter = new DefaultPatchReporter(context);            }            if (listener == null) {                listener = new DefaultPatchListener(context);            }            if (tinkerLoadVerifyFlag == null) {                tinkerLoadVerifyFlag = false;            }            return new Tinker(context, status, loadReporter, patchReporter, listener, patchDirectory,                patchInfoFile, patchInfoLockFile, mainProcess, patchProcess, tinkerLoadVerifyFlag);        }    }

上面代码省略了set方法,我们只关注默认设置。其中mainProcess,patchProcess判断当前是否是应用进程和补丁合成进程。loadReporter,patchReporter 顾名思义是一些过程的回调。PatchListener 是我们关注的重点,也是补丁合成的入口,它的默认实现是DefaultPatchListener,下面分析会用到。

patchDirectory,patchInfoFile,patchInfoLockFile分别是:

  • /data/data/package_name/tinker/
  •  /data/data/package_name/tinker/patch.info
  • /data/data/package_name/tinker/info.lock

tinkerLoadVerifyFlag是新建Application时传进去的参数,用于判断是否每次加载都做md5校验。

初始化好Tinker之后再调用Tinker.create(tinker);

/**     * create custom tinker by {@link Tinker.Builder}     * please do it when very first your app start.     *     * @param tinker     */    public static void create(Tinker tinker) {        if (sInstance != null) {            throw new TinkerRuntimeException("Tinker instance is already set.");        }        sInstance = tinker;    }

sInstance是静态变量,保证Tinker是单例的,并且只初始化一次。

最后调用tinker.install(applicationLike.getTinkerResultIntent());

public void install(Intent intentResult) {        install(intentResult, DefaultTinkerResultService.class, new UpgradePatch());}    /**     * you must install tinker first!!     *     * @param intentResult     * @param serviceClass     * @param upgradePatch     */    public void install(Intent intentResult, Class<? extends AbstractResultService> serviceClass,                        AbstractPatch upgradePatch) {        sInstalled = true;        TinkerPatchService.setPatchProcessor(upgradePatch, serviceClass);        if (!isTinkerEnabled()) {            TinkerLog.e(TAG, "tinker is disabled");            return;        }        if (intentResult == null) {            throw new TinkerRuntimeException("intentResult must not be null.");        }        tinkerLoadResult = new TinkerLoadResult();        tinkerLoadResult.parseTinkerResult(getContext(), intentResult);        //after load code set        loadReporter.onLoadResult(patchDirectory, tinkerLoadResult.loadCode, tinkerLoadResult.costTime);        if (!loaded) {            TinkerLog.w(TAG, "tinker load fail!");        }}

这里值得注意的是install方法的后面两个参数,serviceClass 是用于补丁合成成功后启动的Service来处理合成结果,upgradePatch 是真正合成补丁的类,分别提供了默认实现DefaultTinkerResultService和UpgradePatch,这两个参数也支持自定义。在install方法中会调用TinkerPatchService.setPatchProcessor(upgradePatch, serviceClass); 将这两个参数通过静态方法设置给TinkerPatchService类,TinkerPatchService类是合成补丁的Service,并且运行在新的进程中。

这样就完成了Tinker的初始化。

第一篇文章介绍过使用以下方法来加载补丁:

TinkerInstaller.onReceiveUpgradePatch(context, patchLocation)

看一下具体实现:

/**     * 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);    }

这里会调用PatchListener,还记得之前这个参数的默认实现吗?

我们来看一下DefaultPatchListener的onPatchReceived方法:

public int onPatchReceived(String path) {    int returnCode = patchCheck(path);    if (returnCode == ShareConstants.ERROR_PATCH_OK) {        TinkerPatchService.runPatchService(context, path);    } else {        Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode);    }    return returnCode;}

patchCheck(patch)方法会判断是否开启了Tinker,以及补丁文件是否存在。然后会启动TinkerPatchService:TinkerPatchService.runPatchService(context, path);

TinkerPatchService是继承于IntentService,IntentService与普通Service的区别这里就不说了,看它的onHandleIntent方法,继承IntentService必须实现该方法,并且可以进行耗时操作:

@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;            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;        AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));    }

先进行了参数校验,increasingPriority(),这个方法用于提高进程优先级,防止被回收:

private void increasingPriority() {//        if (Build.VERSION.SDK_INT > 24) {//            TinkerLog.i(TAG, "for Android 7.1, we just ignore increasingPriority job");//            return;//        }        TinkerLog.i(TAG, "try to increase patch process priority");        try {            Notification notification = new Notification();            if (Build.VERSION.SDK_INT < 18) {                startForeground(notificationId, notification);            } else {                startForeground(notificationId, notification);                // start InnerService                startService(new Intent(this, InnerService.class));            }        } catch (Throwable e) {            TinkerLog.i(TAG, "try to increase patch process priority error:" + e);        }    }

然后调用result = upgradePatchProcessor.tryPatch(context, path, patchResult);进行合成补丁,返回一个结果码,这里下面再详细说,先继续往下看, 最后会启动另一个Service:

AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));

这个Service就是之前传进来的DefaultTinkerResultService,并且将合成结果带给它回调onPatchResult方法:

public class DefaultTinkerResultService extends AbstractResultService {    private static final String TAG = "Tinker.DefaultTinkerResultService";    /**     * we may want to use the new patch just now!!     *     * @param result     */    @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) {            File rawFile = new File(result.rawPatchFilePath);            if (rawFile.exists()) {                TinkerLog.i(TAG, "save delete raw patch file");                SharePatchFileUtil.safeDeleteFile(rawFile);            }            if (checkIfNeedKill(result)) {                android.os.Process.killProcess(android.os.Process.myPid());            } else {                TinkerLog.i(TAG, "I have already install the newly patch version!");            }        }    }    public boolean checkIfNeedKill(PatchResult result) {        Tinker tinker = Tinker.with(getApplicationContext());        if (tinker.isTinkerLoaded()) {            TinkerLoadResult tinkerLoadResult = tinker.getTinkerLoadResultIfPresent();            if (tinkerLoadResult != null) {                String currentVersion = tinkerLoadResult.currentVersion;                if (result.patchVersion != null && result.patchVersion.equals(currentVersion)) {                    return false;                }            }        }        return true;    }}

在onPatchResult方法中会杀死补丁合成的进程,如果补丁合成成功,会将原始数据删掉,并且杀死当前进程。当然用户也可以自定义这个类,实现更好的逻辑,比如不直接杀死当前进程,而是当用户退出应用,切到后台,或者关闭屏幕的时候杀死应用,达到重启的目的,具体实现可以参考Simple中的实现。这样整个补丁的合成过程就结束了。目前为止大致Tinker初始化以及补丁合成流程已经讲完了,有兴趣的继续往下看真正合成补丁的调用

result = upgradePatchProcessor.tryPatch(context, path, patchResult);

还记得之前初始化的方法吗:

public void install(Intent intentResult) {        install(intentResult, DefaultTinkerResultService.class, new UpgradePatch());}

这里的UpgradePatch对象便会赋值给upgradePatchProcessor,合成补丁的时候调用它的tryPatch方法:

    @Override    public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) {        Tinker manager = Tinker.with(context);        final File patchFile = new File(tempPatchPath);        //check the signature, we should create a new checker        ShareSecurityCheck signatureCheck = new ShareSecurityCheck(context);        int returnCode = ShareTinkerInternals.checkTinkerPackage(context, manager.getTinkerFlags(), patchFile, signatureCheck);        //it is a new patch, so we should not find a exist        SharePatchInfo oldInfo = manager.getTinkerLoadResultIfPresent().patchInfo;        String patchMd5 = SharePatchFileUtil.getMD5(patchFile);        //use md5 as version        patchResult.patchVersion = patchMd5;        SharePatchInfo newInfo;        //already have patch        if (oldInfo != null) {            newInfo = new SharePatchInfo(oldInfo.oldVersion, patchMd5, Build.FINGERPRINT);        } else {            newInfo = new SharePatchInfo("", patchMd5, Build.FINGERPRINT);        }        //check ok, we can real recover a new patch        final String patchDirectory = manager.getPatchDirectory().getAbsolutePath();        final String patchName = SharePatchFileUtil.getPatchVersionDirectory(patchMd5);        final String patchVersionDirectory = patchDirectory + "/" + patchName;        //it is a new patch, we first delete if there is any files        //don't delete dir for faster retry//        SharePatchFileUtil.deleteDir(patchVersionDirectory);        //copy file        File destPatchFile = new File(patchVersionDirectory + "/" + SharePatchFileUtil.getPatchVersionFile(patchMd5));        try {            SharePatchFileUtil.copyFileUsingStream(patchFile, destPatchFile);            TinkerLog.w(TAG, "UpgradePatch after %s size:%d, %s size:%d", patchFile.getAbsolutePath(), patchFile.length(),                destPatchFile.getAbsolutePath(), destPatchFile.length());        } catch (IOException e) {//            e.printStackTrace();            TinkerLog.e(TAG, "UpgradePatch tryPatch:copy patch file fail from %s to %s", patchFile.getPath(), destPatchFile.getPath());            manager.getPatchReporter().onPatchTypeExtractFail(patchFile, destPatchFile, patchFile.getName(), ShareConstants.TYPE_PATCH_FILE);            return false;        }        //we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process        if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed");            return false;        }        final File patchInfoFile = manager.getPatchInfoFile();        if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, newInfo, SharePatchFileUtil.getPatchInfoLockFile(patchDirectory))) {            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, rewrite patch info failed");            manager.getPatchReporter().onPatchInfoCorrupted(patchFile, newInfo.oldVersion, newInfo.newVersion);            return false;        }        TinkerLog.w(TAG, "UpgradePatch tryPatch: done, it is ok");        return true;    }

这个方法比较长,删除了一些校验的代码以及合成资源文件等方法,主要看dex文件的合成过程。开始是初始化一些目录,再将补丁文件拷贝到目标目录中:

SharePatchFileUtil.copyFileUsingStream(patchFile, destPatchFile);

再调用以下方法:

DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)

看具体实现:

    protected static boolean tryRecoverDexFiles(Tinker manager, ShareSecurityCheck checker, Context context,                                                String patchVersionDirectory, File patchFile) {        if (!manager.isEnabledForDex()) {            TinkerLog.w(TAG, "patch recover, dex is not enabled");            return true;        }        String dexMeta = checker.getMetaContentMap().get(DEX_META_FILE);        if (dexMeta == null) {            TinkerLog.w(TAG, "patch recover, dex is not contained");            return true;        }        long begin = SystemClock.elapsedRealtime();        boolean result = patchDexExtractViaDexDiff(context, patchVersionDirectory, dexMeta, patchFile);        long cost = SystemClock.elapsedRealtime() - begin;        TinkerLog.i(TAG, "recover dex result:%b, cost:%d", result, cost);        return result;    }

主要就是计算耗时,最终方法是patchDexExtractViaDexDiff:

    private static boolean patchDexExtractViaDexDiff(Context context, String patchVersionDirectory, String meta, final File patchFile) {        String dir = patchVersionDirectory + "/" + DEX_PATH + "/";        if (!extractDexDiffInternals(context, dir, meta, patchFile, TYPE_DEX)) {            TinkerLog.w(TAG, "patch recover, extractDiffInternals fail");            return false;        }        final Tinker manager = Tinker.with(context);        File dexFiles = new File(dir);        File[] files = dexFiles.listFiles();        if (files != null) {            final String optimizeDexDirectory = patchVersionDirectory + "/" + DEX_OPTIMIZE_PATH + "/";            File optimizeDexDirectoryFile = new File(optimizeDexDirectory);            if (!optimizeDexDirectoryFile.exists() && !optimizeDexDirectoryFile.mkdirs()) {                TinkerLog.w(TAG, "patch recover, make optimizeDexDirectoryFile fail");                return false;            }            TinkerLog.w(TAG, "patch recover, try to optimize dex file count:%d", files.length);            boolean isSuccess = TinkerParallelDexOptimizer.optimizeAll(                    files, optimizeDexDirectoryFile,                    new TinkerParallelDexOptimizer.ResultCallback() {                        long startTime;                        @Override                        public void onStart(File dexFile, File optimizedDir) {                            startTime = System.currentTimeMillis();                            TinkerLog.i(TAG, "start to optimize dex %s", dexFile.getPath());                        }                        @Override                        public void onSuccess(File dexFile, File optimizedDir) {                            // Do nothing.                            TinkerLog.i(TAG, "success to optimize dex %s use time %d",                                dexFile.getPath(), (System.currentTimeMillis() - startTime));                        }                        @Override                        public void onFailed(File dexFile, File optimizedDir, Throwable thr) {                            TinkerLog.i(TAG, "fail to optimize dex %s use time %d",                                dexFile.getPath(), (System.currentTimeMillis() - startTime));                            SharePatchFileUtil.safeDeleteFile(dexFile);                            manager.getPatchReporter().onPatchDexOptFail(patchFile, dexFile, optimizeDexDirectory, dexFile.getName(), thr);                        }                    }            );            //list again            if (isSuccess) {                for (File file : files) {                    try {                        if (!SharePatchFileUtil.isLegalFile(file)) {                            TinkerLog.e(TAG, "single dex optimizer file %s is not exist, just return false", file);                            return false;                        }                        String outputPathName = SharePatchFileUtil.optimizedPathFor(file, optimizeDexDirectoryFile);                        File outputFile = new File(outputPathName);                        if (!SharePatchFileUtil.isLegalFile(outputFile)) {                            TinkerLog.e(TAG, "parallel dex optimizer file %s fail, optimize again", outputPathName);                            long start = System.currentTimeMillis();                            DexFile.loadDex(file.getAbsolutePath(), outputPathName, 0);                            TinkerLog.i(TAG, "success single dex optimize file, path: %s, use time: %d", file.getPath(), (System.currentTimeMillis() - start));                            if (!SharePatchFileUtil.isLegalFile(outputFile)) {                                manager.getPatchReporter()                                    .onPatchDexOptFail(patchFile, file, optimizeDexDirectory,                                        file.getName(), new TinkerRuntimeException("dexOpt file:" + outputPathName + " is not exist"));                                return false;                            }                        }                    } catch (Throwable e) {                        TinkerLog.e(TAG, "dex optimize or load failed, path:" + file.getPath());                        //delete file                        SharePatchFileUtil.safeDeleteFile(file);                        manager.getPatchReporter().onPatchDexOptFail(patchFile, file, optimizeDexDirectory, file.getName(), e);                        return false;                    }                }            }            return isSuccess;        }        return true;    }

首先extractDexDiffInternals(context, dir, meta, patchFile, TYPE_DEX)是合成全量补丁,后面是通过DexFile.loadDex生成优化后的dex文件,这个过程貌似做了两遍。主要看extractDexDiffInternals,哎,不贴代码了,代码好多,自己看吧。这个方法中会拿到两个文件,一个原始包文件,一个是补丁文件:

apk = new ZipFile(apkPath);patch = new ZipFile(patchFile);

安全性校验完了之后会分别调用extractDexFile(zipFile, entryFile,extractTo, dexInfo) 或者 patchDexFile(baseApk, patchPkg, oldDexEntry, patchFileEntry, patchInfo, patchedDexFile);这里分了三种情况,

第一种情况是直接将补丁包中的dex文件拷贝到了目标文件夹下,这种情况应该是下发的补丁包就是全量包;

第二种情况是直接拷贝原apk包的dex文件,有这么一段注释:

// Small patched dex generating strategy was disabled, we copy full original dex directly now.

为什么要把原始Apk包里的dex文件复制过去呢?我也想不明白,问了一下张绍文老大,他的回答是:

因为内联以及地址错乱的问题

对,就是这个原因(因回答过于简洁,还是不太明白)。有知道的小伙伴欢迎留言告知,说的稍微详细一点。

这两种情况调用的是extractDexFile,不同的是传进去的包不一样,一个是补丁包,一个是原始包。

第三种情况是将原始dex与补丁dex合成全量dex,调用patchDexFile,最终调用如下方法合成补丁:

new DexPatchApplier(zis, (int) entry.getSize(), patchFileStream).executeAndSaveTo(zos);

继续往下看DexPatchApplier类,这个可是合成dex文件的核心所在

额。。。不看了,看不下去了,有点想吐,晕代码。。。

等不晕的时候再来看吧,分析Tinker源码的文章就暂时告一段落了,对这系列文章有疑问的,或者发现写的有错误的欢迎在下方留言;如果接入遇到问题的也可以留言。

0 0
原创粉丝点击