tinker dex文件修复简单流程跟踪

上篇文章写了tinker的使用方法
tinker使用
本篇文章简单的分析一下dex文件的修复流程
在几年前我对热修复的理解为
新apk和旧的apk通过dexbiff算法对比生成差异包,差异包通过比对文件的MD5值,把修改过的文件打进差异包,差异包下发到服务器,下载到手机指定的路径,
通过这个下载的路径生成dexclassloader,获取dexclassloader中的的pathlist,在获取Element数组,然后获取系统的pathclassloader和他的element数组,把这个差异包的Element数组塞到系统默认的element数组里面。通过反射把新的element数组设置给pathclassloader然后下次启动的时候就会加载差异包的dex类

倒是最近发现阅读tinker的源码的时候,发现还是有一些不一样的地方
首先从入口代码开始看

TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path);

这个tinker加载patch包的代码
继续跟踪

 public static void onReceiveUpgradePatch(Context context, String patchLocation) {
        Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);
    }
    getPatchListenr是一个接口,直接看它的实现类DefaultPatchListener
    
    @Override
    public int onPatchReceived(String path) {
        final File patchFile = new File(path);
        final String patchMD5 = SharePatchFileUtil.getMD5(patchFile);
        final int returnCode = patchCheck(path, patchMD5);
        if (returnCode == ShareConstants.ERROR_PATCH_OK) {
            runForgService();
            TinkerPatchService.runPatchService(context, path);
        } else {
            Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode);
        }
        return returnCode;
    }
    
   有一个MD5的校验
   接下来看这一行
    TinkerPatchService.runPatchService(context, path);

  public static void runPatchService(final Context context, final String path) {
        Intent intent = new Intent(context, TinkerPatchService.class);
        intent.putExtra(PATCH_PATH_EXTRA, path);
        intent.putExtra(RESULT_CLASS_EXTRA, resultServiceClass.getName());
        try {
            context.startService(intent);
        } catch (Throwable thr) {
            ShareTinkerLog.e(TAG, "run patch service fail, exception:" + thr);
        }
    }
    

直接开启了一个server,而且这个server是有自己独立的进程的在manifest里面可以看到 android:process=":patch"

 <service
            android:name="com.tencent.tinker.lib.service.TinkerPatchService"
            android:exported="false"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:process=":patch" />

在这个server的onHandleIntent中加载了补丁包

 @Override
    protected void onHandleIntent(Intent intent) {
        increasingPriority();
        doApplyPatch(this, intent);
    }
 private static void doApplyPatch(Context context, Intent intent) {
       ....
 		result = upgradePatchProcessor.tryPatch(context, path, patchResult);
        AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));

        sIsPatchApplying.set(false);
    }
继续看tryPatch,位于UpgradePatch类中

	   @Override
    public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) {
    	...
    	  //we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process
        if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile, patchResult)) {
            ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed");
            return false;
        }
        ...
    }
	 因为只是简单看一下dex文件的修复,所以只截取了相关的代码
	 关键代码为
	 DexDiffPatchInternal.tryRecoverDexFiles
	
	protected static boolean tryRecoverDexFiles(Tinker manager, ShareSecurityCheck checker, Context context,
                                                String patchVersionDirectory, File patchFile, PatchResult patchResult) {
       ...
        boolean result = patchDexExtractViaDexDiff(context, patchVersionDirectory, dexMeta, patchFile, patchResult);
        long cost = SystemClock.elapsedRealtime() - begin;
        ShareTinkerLog.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, PatchResult patchResult) {
       ...
        return dexOptimizeDexFiles(context, legalFiles, optimizeDexDirectory, patchFile, patchResult);
    }
    
    ->dexOptimizeDexFiles
 private static boolean dexOptimizeDexFiles(Context context, List<File> dexFiles, String optimizeDexDirectory, final File patchFile, final PatchResult patchResult) {
        final Tinker manager = Tinker.with(context);

        optFiles.clear();

        if (dexFiles != null) {
            File optimizeDexDirectoryFile = new File(optimizeDexDirectory);

            if (!optimizeDexDirectoryFile.exists() && !optimizeDexDirectoryFile.mkdirs()) {
                ShareTinkerLog.w(TAG, "patch recover, make optimizeDexDirectoryFile fail");
                return false;
            }
            // add opt files
            for (File file : dexFiles) {
                String outputPathName = SharePatchFileUtil.optimizedPathFor(file, optimizeDexDirectoryFile);
                optFiles.add(new File(outputPathName));
            }

            ShareTinkerLog.i(TAG, "patch recover, try to optimize dex file count:%d, optimizeDexDirectory:%s", dexFiles.size(), optimizeDexDirectory);
           ...
            TinkerDexOptimizer.optimizeAll(
                  context, dexFiles, optimizeDexDirectoryFile,
                  useDLC,
                  new TinkerDexOptimizer.ResultCallback() {
                      long startTime;

                      @Override
                      public void onStart(File dexFile, File optimizedDir) {
                          startTime = System.currentTimeMillis();
                          ShareTinkerLog.i(TAG, "start to parallel optimize dex %s, size: %d", dexFile.getPath(), dexFile.length());
                      }

                      @Override
                      public void onSuccess(File dexFile, File optimizedDir, File optimizedFile) {
                          ShareTinkerLog.i(TAG, "success to parallel optimize dex %s, opt file:%s, opt file size: %d, use time %d",
                              dexFile.getPath(), optimizedFile.getPath(), optimizedFile.length(), (System.currentTimeMillis() - startTime));
                          if (!optimizedFile.exists()) {
                              synchronized (anyOatNotGenerated) {
                                  anyOatNotGenerated[0] = true;
                              }
                          }
                      }

                      @Override
                      public void onFailed(File dexFile, File optimizedDir, Throwable thr) {
                          ShareTinkerLog.i(TAG, "fail to parallel optimize dex %s use time %d",
                              dexFile.getPath(), (System.currentTimeMillis() - startTime));
                          failOptDexFile.add(dexFile);
                          throwable[0] = thr;
                      }
                  }
            );
		...
        return true;
    }

->optimizeAll
public static boolean optimizeAll(Context context, Collection<File> dexFiles, File optimizedDir,
                                      boolean useDLC, ResultCallback cb) {
        return optimizeAll(context, dexFiles, optimizedDir, false, useDLC, null, cb);
    }

    public static boolean optimizeAll(Context context, Collection<File> dexFiles, File optimizedDir,
                                      boolean useInterpretMode, boolean useDLC,
                                      String targetISA, ResultCallback cb) {
        ArrayList<File> sortList = new ArrayList<>(dexFiles);
        // sort input dexFiles with its file length in reverse order.
       ...
        for (File dexFile : sortList) {
            OptimizeWorker worker = new OptimizeWorker(context, dexFile, optimizedDir, useInterpretMode,
                  useDLC, targetISA, cb);
            if (!worker.run()) {
                return false;
            }
        }
        return true;
    }
    ->OptimizeWorker.run
 boolean run() {
            try {
             ...
                if (!ShareTinkerInternals.isArkHotRuning()) {
                    if (useInterpretMode) {
                        interpretDex2Oat(dexFile.getAbsolutePath(), optimizedPath);
                    } else if (Build.VERSION.SDK_INT >= 26
                            || (Build.VERSION.SDK_INT >= 25 && Build.VERSION.PREVIEW_SDK_INT != 0)) {
                        NewClassLoaderInjector.triggerDex2Oat(context, optimizedDir,
                                                              useDLC, dexFile.getAbsolutePath());
                        // Android Q is significantly slowed down by Fallback Dex Loading procedure, so we
                        // trigger background dexopt to generate executable odex here.
                        triggerPMDexOptOnDemand(context, dexFile.getAbsolutePath(), optimizedPath);
                    } else {
                        DexFile.loadDex(dexFile.getAbsolutePath(), optimizedPath, 0);
                    }
                }
                ...
            return true;
        }
        
        因为现在andorid 版本都比较新,所以直接看(Build.VERSION.SDK_INT >= 26)里面的代码
        ->NewClassLoaderInjector.triggerDex2Oat
        public static void triggerDex2Oat(Context context, File dexOptDir, boolean useDLC,
                                      String... dexPaths) throws Throwable {
        final ClassLoader triggerClassLoader = createNewClassLoader(context.getClassLoader(), dexOptDir, useDLC, dexPaths);
    }
->createNewClassLoader
这里就是dex文件修复的关键代码了
private static ClassLoader createNewClassLoader(ClassLoader oldClassLoader,
                                                    File dexOptDir,
                                                    boolean useDLC,
                                                    String... patchDexPaths) throws Throwable {
        final Field pathListField = findField(
                Class.forName("dalvik.system.BaseDexClassLoader", false, oldClassLoader),
                "pathList");
        final Object oldPathList = pathListField.get(oldClassLoader);
        final StringBuilder dexPathBuilder = new StringBuilder();
        final boolean hasPatchDexPaths = patchDexPaths != null && patchDexPaths.length > 0;
        if (hasPatchDexPaths) {
            for (int i = 0; i < patchDexPaths.length; ++i) {
                if (i > 0) {
                    dexPathBuilder.append(File.pathSeparator);
                }
                dexPathBuilder.append(patchDexPaths[i]);
            }
        }
        final String combinedDexPath = dexPathBuilder.toString();
        final Field nativeLibraryDirectoriesField = findField(oldPathList.getClass(), "nativeLibraryDirectories");
        List<File> oldNativeLibraryDirectories = null;
        if (nativeLibraryDirectoriesField.getType().isArray()) {
            oldNativeLibraryDirectories = Arrays.asList((File[]) nativeLibraryDirectoriesField.get(oldPathList));
        } else {
            oldNativeLibraryDirectories = (List<File>) nativeLibraryDirectoriesField.get(oldPathList);
        }
        final StringBuilder libraryPathBuilder = new StringBuilder();
        boolean isFirstItem = true;
        for (File libDir : oldNativeLibraryDirectories) {
            if (libDir == null) {
                continue;
            }
            if (isFirstItem) {
                isFirstItem = false;
            } else {
                libraryPathBuilder.append(File.pathSeparator);
            }
            libraryPathBuilder.append(libDir.getAbsolutePath());
        }

        final String combinedLibraryPath = libraryPathBuilder.toString();

        ClassLoader result = null;
        if (useDLC && Build.VERSION.SDK_INT >= 27) {
            result = new DelegateLastClassLoader(combinedDexPath, combinedLibraryPath, ClassLoader.getSystemClassLoader());
            final Field parentField = ClassLoader.class.getDeclaredField("parent");
            parentField.setAccessible(true);
            parentField.set(result, oldClassLoader);
        } else {
            result = new TinkerClassLoader(combinedDexPath, dexOptDir, combinedLibraryPath, oldClassLoader);
        }

        // 'EnsureSameClassLoader' mechanism which is first introduced in Android O
        // may cause exception if we replace definingContext of old classloader.
        if (Build.VERSION.SDK_INT < 26) {
            findField(oldPathList.getClass(), "definingContext").set(oldPathList, result);
        }

        return result;
    }
   

到这里就开始分开分析了
首先看前面的代码

 final StringBuilder dexPathBuilder = new StringBuilder();
        final boolean hasPatchDexPaths = patchDexPaths != null && patchDexPaths.length > 0;
        if (hasPatchDexPaths) {
            for (int i = 0; i < patchDexPaths.length; ++i) {
                if (i > 0) {
                    dexPathBuilder.append(File.pathSeparator);
                }
                dexPathBuilder.append(patchDexPaths[i]);
            }
        }

        final String combinedDexPath = dexPathBuilder.toString();

patchDexPaths是patch包的路径
最后转换成了combinedDexPath用File.pathSeparator分隔

final Field nativeLibraryDirectoriesField = findField(oldPathList.getClass(), "nativeLibraryDirectories");
        List<File> oldNativeLibraryDirectories = null;
        if (nativeLibraryDirectoriesField.getType().isArray()) {
            oldNativeLibraryDirectories = Arrays.asList((File[]) nativeLibraryDirectoriesField.get(oldPathList));
        } else {
            oldNativeLibraryDirectories = (List<File>) nativeLibraryDirectoriesField.get(oldPathList);
        }
        final StringBuilder libraryPathBuilder = new StringBuilder();
        boolean isFirstItem = true;
        for (File libDir : oldNativeLibraryDirectories) {
            if (libDir == null) {
                continue;
            }
            if (isFirstItem) {
                isFirstItem = false;
            } else {
                libraryPathBuilder.append(File.pathSeparator);
            }
            libraryPathBuilder.append(libDir.getAbsolutePath());
        }

        final String combinedLibraryPath = libraryPathBuilder.toString();

这里和上面一样是将老的oldPathList的路径通过StringBuilder append到一起组合成了combinedLibraryPath
最后通过反射把新的classloader替换了旧的

result = new DelegateLastClassLoader(combinedDexPath, combinedLibraryPath, ClassLoader.getSystemClassLoader());
            final Field parentField = ClassLoader.class.getDeclaredField("parent");
            parentField.setAccessible(true);
            parentField.set(result, oldClassLoader);

关于这个DelegateLastClassLoader可以在这里看到详细的介绍
DelegateLastClassLoader

最后回到doApplyPatch方法中
加载完成之后开启了一个AbstractResultService.runResultService

在这个server的onHandleIntent调用了onPatchResult
 @Override
protected void onHandleIntent(Intent intent) {
    if (intent == null) {
        ShareTinkerLog.e(TAG, "AbstractResultService received a null intent, ignoring.");
        return;
    }
    PatchResult result = (PatchResult) ShareIntentUtil.getSerializableExtra(intent, RESULT_EXTRA);

    onPatchResult(result);
}
public abstract void onPatchResult(PatchResult result);
 这个类是一个抽象类,实现类为DefaultTinkerResultService
 
  @Override
public void onPatchResult(PatchResult result) {
 
    TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext());
    if (result.isSuccess) {
        deleteRawPatchFile(new File(result.rawPatchFilePath));//删除原来的路径的文件
        if (checkIfNeedKill(result)) {
            android.os.Process.killProcess(android.os.Process.myPid());
        } else {
            ShareTinkerLog.i(TAG, "I have already install the newly patch version!");
        }
    }
}

最后把这个补丁包文件删除,然后杀掉开启了的新进程
到这里dex文件修复的流程就跟踪完了,
那核心方法就是通过反射把原来的类加载器换成了新的,和我文章开头的理解还是有一点不一样的,不过在我的印象中,好像tinker的某一个版本是用的我文章开头使用的方法后续因为android版本的原因才替换的?
参考资料
混合编译对tinker的影响
tinker

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值