Tinker源码分析一

作者: Allenlll | 来源:发表于2016-10-31 21:01 被阅读1487次

    从加载流程来分析

    处理下载之后的修复包:

    /** 
    * new patch file to install, try install them with :patch process
     * Generally you will not use it
     * 
    * @param context 
    * @param patchLocation 
    */
    public static void onReceiveUpgradePatch(Context context, String patchLocation) {   
    
     Tinker.with(context).getPatchListener().onPatchReceived(patchLocation, true);
    
    }
    

    调用TinkerInstalleronReceiveUpgradePatch方法,在这个方法里面初始化Tinker类,并且调用PatchListeneronPatchReceived(String path, boolean isUpgrade)方法。

    private Tinker(Context context, int tinkerFlags, LoadReporter loadReporter, PatchReporter patchReporter,               PatchListener listener, File patchDirectory, File patchInfoFile,               boolean isInMainProc, boolean isPatchProcess, boolean tinkerLoadVerifyFlag) {    
        this.context = context; 
        this.listener = listener;
        this.loadReporter = loadReporter;
        this.patchReporter = patchReporter;
        this.tinkerFlags = tinkerFlags;
        this.patchDirectory = patchDirectory;
        this.patchInfoFile = patchInfoFile;
        this.isMainProcess = isInMainProc;
        this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag;
        this.isPatchProcess = isPatchProcess;
    }
    

    其中:patchDirectory是patch包的存储位置。patchInfoFile是存储Patch包版本等信息的文件。

    一、对修复包的第一层验证

    这是Tinker 的初始化方法,初始化了LoaderReporterPatchReporter
    PatchListener等。

    值得注意的是Tinker初始化之前必须得先调用TinkerInstaller的install方法,这个后面再说。

    /** 
    * when we receive a patch, what would we do? 
    * you can overwrite it
     * 
    * @param path 
    * @param isUpgrade 
    * @return 
    */
    @Overridepublic int onPatchReceived(String path, boolean isUpgrade) {
         int returnCode = patchCheck(path, isUpgrade);    
         if (returnCode == ShareConstants.ERROR_PATCH_OK) {  
             TinkerPatchService.runPatchService(context, path, isUpgrade);   
          } else {         
           Tinker.with(context).getLoadReporter().
           onLoadPatchListenerReceiveFail(new File(path), returnCode, isUpgrade);   
          }    
        return returnCode;
    }
    

    DefaultPatchListener中,对修复包进行检查,如果修复包ok,则开启一个单独的进程合成全量包。如果修复包不完整或者有其它问题,则调用LoadReporteronLoadPatchListenerReceiveFail(File patchFile, int errorCode, boolean isUpgrade)方法通知。

    patchCheck验证了:

    • Tinker开关是否开启
    • 修复文件是否存在
    • 是不是通过合成进程执行的操作
    • 合成进程是否正在执行

    第二、开启合成进程

    public static void runPatchService(Context context, String path, boolean isUpgradePatch) {
        Intent intent = new Intent(context, TinkerPatchService.class);
        intent.putExtra(PATCH_PATH_EXTRA, path);
        intent.putExtra(PATCH_NEW_EXTRA, isUpgradePatch);
        context.startService(intent);
    }
    

    TinkerPatchService中开启合成进程的服务。它是一个IntentSerivce,是在单独的线程中进行的操作。下面来看一下onHandleIntent中的操作。

    @Overrideprotected void onHandleIntent(Intent intent) {
        final Context context = getApplicationContext();
        Tinker tinker = Tinker.with(context);
        tinker.getPatchReporter().onPatchServiceStart(intent);
        //调用patchReporter的onPatchServiceStart(Intent 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);
        boolean isUpgradePatch = getPatchUpgradeExtra(intent);
        long begin = SystemClock.elapsedRealtime();
        boolean result;
        long cost;
        Throwable e = null;
        increasingPriority();
        PatchResult patchResult = new PatchResult();
        try {
            if (isUpgradePatch) {
                if (upgradePatchProcessor == null) {
                    throw new TinkerRuntimeException("upgradePatchProcessor is null.");
                }
                result = upgradePatchProcessor.tryPatch(context, path, patchResult);
            } else { 
               //just recover from exist patch
                if (repairPatchProcessor == null) {
                    throw new TinkerRuntimeException("upgradePatchProcessor is null.");
                }
                result = repairPatchProcessor.tryPatch(context, path, patchResult);
            }
        } catch (Throwable throwable) {
            e = throwable;
            result = false;
            tinker.getPatchReporter().onPatchException(patchFile, e, isUpgradePatch);
        }
        cost = SystemClock.elapsedRealtime() - begin; 
       tinker.getPatchReporter(). 
           onPatchResult(patchFile, result, cost, isUpgradePatch); 
       patchResult.isSuccess = result;
        patchResult.isUpgradePatch = isUpgradePatch;
        patchResult.rawPatchFilePath = path;
        patchResult.costTime = cost;
        patchResult.e = e;
        AbstractResultService.runResultService(context, patchResult);
    }
    

    有几个操作需要注意的:

    • 调用patchReporteronPatchServiceStart(Intent intent)方法通知。

    • 让服务置于前台服务increasingPriority

      1.startForeground(int id, Notification notification)
      这个方法,可以让后台服务置于前台,就像音乐播放器的,播放服务一样,不会被系统杀死。
      2.开启一个InnerService降低被杀死的概率。

    • TinkerInstaller调用的install方法,会初始化两个AbstractPatchReparePatchUpgradePatch。在这里会调用它们的tryPatch(Context context, String tempPatchPath, PatchResult patchResult)方法来执行合并操作。并将结果返回。

    • 如果两个AbstractPatch为空,则会捕获异常。并且调用PatchReporteronPatchException来通知。

    • 修复成功后调用PatchReporteronPatchResult()来通知,有花费时间等信息。

    • 调用AbstactResultServicerunResultService(Context context, PatchResult result)方法。也是在TinkerInstallerInstall的时候赋值。也是一个IntentService

    这个Service会将补丁合成进程返回的结果返回给主进程,在单独的线程中执行。onHandleIntent中只是回调了runResultService(Context context, PatchResult result)方法。通过IntentService完成了进程间的通信。

    第三、最后的合成方法
    为了不至于离的太远,我们还顺着刚才的流程分析AbstractPatchtryPatch方法。我们先看下UpgradePatch

     @Override
        public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) {
            Tinker manager = Tinker.with(context);
            final File patchFile = new File(tempPatchPath);
            if (!manager.isTinkerEnabled() || !ShareTinkerInternals.isTinkerEnableWithSharedPreferences(context)) {
                TinkerLog.e(TAG, "UpgradePatch tryPatch:patch is disabled, just return");
                return false;
            }
            if (!patchFile.isFile() || !patchFile.exists()) {
                TinkerLog.e(TAG, "UpgradePatch tryPatch:patch file is not found, just return");
                return false; 
           }
            //check the signature, we should create a new checker
            ShareSecurityCheck signatureCheck = new ShareSecurityCheck(context);
            int returnCode = ShareTinkerInternals.checkTinkerPackage(context, manager.getTinkerFlags(), patchFile, signatureCheck);
            if (returnCode != ShareConstants.ERROR_PACKAGE_CHECK_OK) {            TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchPackageCheckFail");
                manager.getPatchReporter().onPatchPackageCheckFail(patchFile, true, returnCode);
                return false;
            }
            patchResult.patchTinkerID = signatureCheck.getNewTinkerID();
            patchResult.baseTinkerID = signatureCheck.getTinkerID();
            //it is a new patch, so we should not find a exist
            SharePatchInfo oldInfo = manager.getTinkerLoadResultIfPresent().patchInfo;
            String patchMd5 = SharePatchFileUtil.getMD5(patchFile);
            if (patchMd5 == null) {
                TinkerLog.e(TAG, "UpgradePatch tryPatch:patch md5 is null, just return");
                return false;
            }
            //use md5 as version
            patchResult.patchVersion = patchMd5;
            SharePatchInfo newInfo;
            //already have patch
            if (oldInfo != null) { 
               if (oldInfo.oldVersion == null || oldInfo.newVersion == null) {
                    TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchInfoCorrupted");
                    manager.getPatchReporter().onPatchInfoCorrupted(patchFile, oldInfo.oldVersion, oldInfo.newVersion, true);
                    return false;
                }
                if (oldInfo.oldVersion.equals(patchMd5) || oldInfo.newVersion.equals(patchMd5)) {
                    TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchVersionCheckFail");
                    manager.getPatchReporter().onPatchVersionCheckFail(patchFile, oldInfo, patchMd5, true); 
                   return false;
                }
                newInfo = new SharePatchInfo(oldInfo.oldVersion, patchMd5);        } else {
                newInfo = new SharePatchInfo("", patchMd5);
            }
            //check ok, we can real recover a new patch
            final String patchDirectory = manager.getPatchDirectory().getAbsolutePath();
            TinkerLog.i(TAG, "UpgradePatch tryPatch:dexDiffMd5:%s", patchMd5);
            final String patchName = SharePatchFileUtil.getPatchVersionDirectory(patchMd5); 
           final String patchVersionDirectory = patchDirectory + "/" + patchName; 
           TinkerLog.i(TAG, "UpgradePatch tryPatch:patchVersionDirectory:%s", patchVersionDirectory);
            //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, true);
                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, true)) {            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed");
                return false;
            }
            if (!BsDiffPatchInternal.tryRecoverLibraryFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile, true)) {            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch library failed"); 
               return false;
            }
            if (!ResDiffPatchInternal.tryRecoverResourceFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile, true)) {            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch resource 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, true); 
               return false;
            }
            TinkerLog.w(TAG, "UpgradePatch tryPatch: done, it is ok");        return true; 
       }
    

    方法有点长, 我们一步一步分析,里面有几个点需要注意。

    • ShareSecurityCheck是检查签名的类,里面封闭了签名检查的方法
    • ShareTinkerInternals的静态方法checkTinkerPackage(Context context, int tinkerFlag, File patchFile, ShareSecurityCheck securityCheck)传入了ShareSecurityCheck,它代理了:
      1.签名检查
      2.TinkerId检查
      3.Tinker开关检查,dex、resource、librirymeta信息检查。
      如果检查失败通过PatchReporter抛出onPatchPackageCheckFail
      -SharePatchInfo,存储了修复包的版本信息,有oldVersionnewVersionnewVersion是修复包的md5值。
      4.什么时候oldInfo会存在呢?加载成功过一次,也修复成功了。再次执行合成的时候。如果下载的包还是之前的包,则会报告onPatchVersionCheckFail。如果是新的修复包,则会把 oldVersion赋值给 SharePatchInfo(String oldVer, String newVew)中的ondVer。
    • 拷贝修复包到data/data目录
    • DexDiff合成dex、BsDiff合成library、BsDiff合成res。
    • 拷贝SharePatchInfoPatchInfoFile中,PatchInfoFileTinkerInstallerinstall方法中初始化。

    稍后再分别分析patch包的资源合成操作。先分析一下最早调用的TinkerInstallerinstall方法。

    四、安装方法
    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 void install(ApplicationLike applicationLike) {
        Tinker tinker = new Tinker.Builder(applicationLike.getApplication()).build();
        Tinker.create(tinker);
        tinker.install(applicationLike.getTinkerResultIntent());
    }
    

    这个是默认的方法,自定义方法只不过install的参数传入了自己自定义的一些类。

    /**
     * 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 
    * @param repairPatchProcessor
     */
    public static void install(ApplicationLike applicationLike, LoadReporter loadReporter, PatchReporter patchReporter,                           PatchListener listener, Class<? extends AbstractResultService> resultServiceClass,                           AbstractPatch upgradePatchProcessor, AbstractPatch repairPatchProcessor) {
        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, repairPatchProcessor);
    }
    

    传入的参数有:

    • ApplicationLike应用的代理application
    • LoadReporter加载合成的包的报告类
    • PatchReporter打修复包过程中的报告类
    • PatchListener对修复包最开始的检查
    • ResultService 从合成进程取合成结果
    • UpgradePatchProcessor 生成一个新的patch合成包
    • ReparePatchProcessor修复上一次合成失败的修复包

    下面看一下Tinker中核心的install方法

    /**
     * you must install tinker first!! 
    *
     * @param intentResult 
    * @param serviceClass 
    * @param upgradePatch
     * @param repairPatch 
    */public void install(Intent intentResult, Class<? extends 
    AbstractResultService> serviceClass,
                        AbstractPatch upgradePatch, AbstractPatch repairPatch) {
        sInstalled = true;
        AbstractResultService.setResultServiceClass(serviceClass);
        TinkerPatchService.setPatchProcessor(upgradePatch, repairPatch);
        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!");
        }}
    
    • 设置自定义的ResultService
    • 设置自定义的UpgradePatchReparePatch
    • 创建TinkerLoadResult调用parseTinkerResult(Context context, Intent intentResult)解析上次合成之后的信息:花费时间,返回值等。
    • 调用LoaderReporteronLoadResult方法,通知,加载结果。

    分析到些结束,下一篇将分析一下具体的合成过程。

    相关文章

      网友评论

      本文标题:Tinker源码分析一

      本文链接:https://www.haomeiwen.com/subject/dnrputtx.html