Tinker分析之Dex加载

使用Tinker时一般会用@DefaultLifeCycle注解,由注解处理器生成一个App类如

import com.tencent.tinker.loader.app.TinkerApplication;
/**
 *
 * Generated application for tinker life cycle
 *
 */
public class TestApp extends TinkerApplication {
    public TestApp() {
        super(7, "com.xxx.xx.xxx.ApplicationLike", "com.tencent.tinker.loader.TinkerLoader", false);
    }
}

该App继承TinkerApplication,TinkerApplication又继承Application,目的是把原本在application里执行的逻辑,放到代理中,使得这些逻辑可以通过补丁的方式动态修改(如果直接用Application是无法修改的,因为已经加载了),因为在TinkerApplication的onBaseContextAttached中先执行打补丁操作:

 private void onBaseContextAttached(Context base) {
        ....
        loadTinker();  //加载补丁
        ensureDelegate();  //创建Application代理
        applicationLike.onBaseContextAttached(base);
        //reset save mode
        ....
    }
private void loadTinker() {
        try {
            //reflect tinker loader, because loaderClass may be define by user!
            //loaderClassName如果没有自定义的话,默认是com.tencent.tinker.loader.TinkerLoader
            Class<?> tinkerLoadClass = Class.forName(loaderClassName, false, TinkerApplication.class.getClassLoader());
            //获取tryLoad()方法
            Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class);
            Constructor<?> constructor = tinkerLoadClass.getConstructor();
            //调用tryLoad()
            tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this);
        } catch (Throwable e) {
            //has exception, put exception error code
            tinkerResultIntent = new Intent();
            ShareIntentUtil.setIntentReturnCode(tinkerResultIntent, ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION);
            tinkerResultIntent.putExtra(INTENT_PATCH_EXCEPTION, e);
        }
    }

当Application回调onBaseContextAttached之后,会先执行loadTinker()方法,loadTinker通过反射的方式获取到实际执行加载dex的类,并且通过tryLoad方法去执行,加载类默认是TinkerLoader。

public Intent tryLoad(TinkerApplication app) {
        .....
        //加载补丁文件
        this.tryLoadPatchFilesInternal(app, resultIntent);
        .....
    }
private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
		.....
		//一系列校验
		//now we can load patch jar
		 if (isEnabledForDex) {
		 		 //加载dex
                 loadTinkerResources = TinkerDexLoader.loadTinkerJars(app, patchVersionDirectory, oatDex, resultIntent, isSystemOTA);
         }
		.....

}

tryLoadPatchFilesInternal方法一路看下来会发现有大量的校验,最后校验成功会调TinkerDexLoader.loadTinkerJars(),从方法名看,终于加载dex了。

/**
     * Load tinker JARs and add them to
     * the Application ClassLoader.
     *
     * @param application The application.
     */
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public static boolean loadTinkerJars(final TinkerApplication application, String directory, String oatDir, Intent intentResult, boolean isSystemOTA) {
        if (loadDexList.isEmpty() && classNDexInfo.isEmpty()) {
            Log.w(TAG, "there is no dex to load");
            return true;
        }
        ......
        String dexPath = directory + "/" + DEX_PATH + "/";

        ArrayList<File> legalFiles = new ArrayList<>();

        for (ShareDexDiffPatchInfo info : loadDexList) {
            //for dalvik, ignore art support dex
            if (isJustArtSupportDex(info)) {
                continue;
            }
            String path = dexPath + info.realName;
            File file = new File(path);
            ......
            legalFiles.add(file);
        }
        ......

loadTinkerJars首先回判断loadDexList和classNDexInfo是否为空,这两个什么时候赋值呢,继续分析发现在TinkerDexLoader.checkComplete()中进行了赋值,而tryLoadPatchFilesInternal进行的一些列校验中,校验完毕会执行一个回调即TinkerDexLoader.checkComplete()。

private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
		if (isEnabledForDex) {
            //tinker/patch.info/patch-641e634c/dex
            boolean dexCheck = TinkerDexLoader.checkComplete(patchVersionDirectory, securityCheck, oatDex, resultIntent);
            if (!dexCheck) {
                //file not found, do not load patch
                Log.w(TAG, "tryLoadPatchFiles:dex check fail");
                return;
            }
        }

在分析checkComplete()方法之前,先看一下补丁长什么样:

public static boolean checkComplete(String directory, ShareSecurityCheck securityCheck, String oatDir, Intent intentResult) {
		//DEX_MEAT_FILE = assets/dex_meta.txt
        String meta = securityCheck.getMetaContentMap().get(DEX_MEAT_FILE);
        //not found dex
        if (meta == null) {
            return true;
        }
        loadDexList.clear();
        classNDexInfo.clear();

        ArrayList<ShareDexDiffPatchInfo> allDexInfo = new ArrayList<>();
        //解析assets/dex_meta.txt 获取所有dex文件
        ShareDexDiffPatchInfo.parseDexDiffPatchInfo(meta, allDexInfo);

        if (allDexInfo.isEmpty()) {
            return true;
        }

        HashMap<String, String> dexes = new HashMap<>();

        ShareDexDiffPatchInfo testInfo = null;
		//校验并添加dex进loadDexList
        for (ShareDexDiffPatchInfo info : allDexInfo) {
            .....
                loadDexList.add(info);
            }
        }
 }
public static void parseDexDiffPatchInfo(String meta, ArrayList<ShareDexDiffPatchInfo> dexList) {
        if (meta == null || meta.length() == 0) {
            return;
        }
        String[] lines = meta.split("\n");
        for (final String line : lines) {
            if (line == null || line.length() <= 0) {
                continue;
            }
            //分割“,”拿到信息,assets/dex_meta.txt里每个dex都有8个值
            final String[] kv = line.split(",", 8);
            if (kv == null || kv.length < 8) {
                continue;
            }

            // key
            final String name = kv[0].trim();
            final String path = kv[1].trim();
            final String destMd5InDvm = kv[2].trim();
            final String destMd5InArt = kv[3].trim();
            final String dexDiffMd5 = kv[4].trim();
            final String oldDexCrc = kv[5].trim();
            final String newDexCrc = kv[6].trim();

            final String dexMode = kv[7].trim();

            ShareDexDiffPatchInfo dexInfo = new ShareDexDiffPatchInfo(name, path, destMd5InDvm, destMd5InArt,
                dexDiffMd5, oldDexCrc, newDexCrc, dexMode);
            dexList.add(dexInfo);
        }

    }

checkComplete()做的事情就是解析补丁文件的dex_meta.txt 文本,拿到补丁里所有dex的相关信息封装成ShareDexDiffPatchInfo,解析方法是ShareDexDiffPatchInfo.parseDexDiffPatchInfo(),里面通过分割","拿到dex的信息,如名字、路径、MD5等。这里已经知道了loadDexList的值从哪里来,继续分析tryLoadPatchFilesInternal()

    public static boolean loadTinkerJars(final TinkerApplication application, String directory, String oatDir, Intent intentResult, boolean isSystemOTA) {
        .....
        //校验dex文件MD5
        for (ShareDexDiffPatchInfo info : loadDexList) {
            //for dalvik, ignore art support dex
            if (isJustArtSupportDex(info)) {
                continue;
            }
            String path = dexPath + info.realName;
            File file = new File(path);
            ......
            legalFiles.add(file);
        }
      //接下来是systemOTA的处理,看不懂,直接跳过
      ......
      try {
      		//执行dex 合并
            SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles);
        } catch (Throwable e) {
            Log.e(TAG, "install dexes failed");
//            e.printStackTrace();
            intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e);
            ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION);
            return false;
        }

loadTinkerJars()主要是对loadDexList进行MD5校验,校验完毕后开始执行dex合并SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles);

public static void installDexes(Application application, PathClassLoader loader, File dexOptDir, List<File> files)
        throws Throwable {
        Log.i(TAG, "installDexes dexOptDir: " + dexOptDir.getAbsolutePath() + ", dex size:" + files.size());

        if (!files.isEmpty()) {
        	//对dex文件进行排序
            files = createSortedAdditionalPathEntries(files);
            ClassLoader classLoader = loader;
            //兼容24以上的版本,避免出现dex file register with multiple classloader异常
            if (Build.VERSION.SDK_INT >= 24 && !checkIsProtectedApp(files)) {
                classLoader = AndroidNClassLoader.inject(loader, application);
            }
            //because in dalvik, if inner class is not the same classloader with it wrapper class.
            //it won't fail at dex2opt
            if (Build.VERSION.SDK_INT >= 23) {
                V23.install(classLoader, files, dexOptDir);
            } else if (Build.VERSION.SDK_INT >= 19) {
                V19.install(classLoader, files, dexOptDir);
            } else if (Build.VERSION.SDK_INT >= 14) {
                V14.install(classLoader, files, dexOptDir);
            } else {
                V4.install(classLoader, files, dexOptDir);
            }
            //install done
            sPatchDexCount = files.size();
            Log.i(TAG, "after loaded classloader: " + classLoader + ", dex size:" + sPatchDexCount);

            if (!checkDexInstall(classLoader)) {
                //reset patch dex
                SystemClassLoaderAdder.uninstallPatchDex(classLoader);
                throw new TinkerRuntimeException(ShareConstants.CHECK_DEX_INSTALL_FAIL);
            }
        }
    }

这里是真正实行合并的地方,兼容不同的android版本,分析V23.install(classLoader, files, dexOptDir)

private static final class V23 {

        private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
                                    File optimizedDirectory)
            throws IllegalArgumentException, IllegalAccessException,
            NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
            /* The patched class loader is expected to be a descendant of
             * dalvik.system.BaseDexClassLoader. We modify its
             * dalvik.system.DexPathList pathList field to append additional DEX
             * file entries.
             */
             //反射拿到pathList
            Field pathListField = ShareReflectUtil.findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            //反射执行makePathElements把补丁的dex文件转成Elements
            //把补丁的Elements添加进dexPathList的dexElements里(dexElement是一个Element数组)
            ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList,
                new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
                suppressedExceptions));
            if (suppressedExceptions.size() > 0) {
                for (IOException e : suppressedExceptions) {
                    Log.w(TAG, "Exception in makePathElement", e);
                    throw e;
                }

            }
        }

        /**
         * A wrapper around
         * {@code private static final dalvik.system.DexPathList#makePathElements}.
         */
        private static Object[] makePathElements(
            Object dexPathList, ArrayList<File> files, File optimizedDirectory,
            ArrayList<IOException> suppressedExceptions)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {

            Method makePathElements;
            try {
                makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class, File.class,
                    List.class);
            } catch (NoSuchMethodException e) {
                Log.e(TAG, "NoSuchMethodException: makePathElements(List,File,List) failure");
                try {
                    makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", ArrayList.class, File.class, ArrayList.class);
                } catch (NoSuchMethodException e1) {
                    Log.e(TAG, "NoSuchMethodException: makeDexElements(ArrayList,File,ArrayList) failure");
                    try {
                        Log.e(TAG, "NoSuchMethodException: try use v19 instead");
                        return V19.makeDexElements(dexPathList, files, optimizedDirectory, suppressedExceptions);
                    } catch (NoSuchMethodException e2) {
                        Log.e(TAG, "NoSuchMethodException: makeDexElements(List,File,List) failure");
                        throw e2;
                    }
                }
            }

            return (Object[]) makePathElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions);
        }
    }
//把补丁的Elements添加进dexPathList的dexElements里
public static void expandFieldArray(Object instance, String fieldName, Object[] extraElements)
        throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        Field jlrField = findField(instance, fieldName);
		//反射拿到dexPathList里dexElements的实例
        Object[] original = (Object[]) jlrField.get(instance);
        //创建一个长度为原始dexElements长度和补丁dexElements长度的新的dexElements
        Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length);
		//最重要的一步
		//先把补丁的dexElements拷贝进来
        System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
        //再把原始的dexElements拷贝进来
        System.arraycopy(original, 0, combined, extraElements.length, original.length);
		//反射重新给dexElements赋值
        jlrField.set(instance, combined);
    }

看到这里就会发现,这部分代码和MultiDex里的代码很相似,都是用反射对pathList里的Elements数组dexElements进行操作,不同之处在于Tinker把补丁的Elements放到最前面,原始apk的Elements放到后面,这样在类加载时,就会优先找到补丁的类,达到补丁的效果。放上MultiDex的部分源码对比一下

public final class MultiDex {
    private static void expandFieldArray(Object instance, String fieldName,
            Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException,
            IllegalAccessException {
        Field jlrField = findField(instance, fieldName);
        Object[] original = (Object[]) jlrField.get(instance);
        Object[] combined = (Object[]) Array.newInstance(
                original.getClass().getComponentType(), original.length + extraElements.length);
        //不同点:
        //先把原始的dexElements拷贝进来
        System.arraycopy(original, 0, combined, 0, original.length);
        //再把其他的dexElements拷贝进来
        System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
        jlrField.set(instance, combined);
    }
}

最后,这只是把dex加载的核心部分抽取出来,忽略了很多东西,比如校验、补丁dex合并成完整的dex等(因为打出来的补丁是跟原始APK的差分包)。

发布了19 篇原创文章 · 获赞 25 · 访问量 5596

猜你喜欢

转载自blog.csdn.net/pxq10422/article/details/103326131