本系列 Tinker 源码解析基于 Tinker v1.9.12
校验so补丁流程
与加载资源补丁类似,加载so补丁也要先从校验开始看起。
其实总体来说,Tinker 中加载 so 补丁文件的关键代码就一句:
System.load(String filePath)
tryLoadPatchFilesInternal
1 2 3 4 5 6 7 8 9 10 11
| final boolean isEnabledForNativeLib = ShareTinkerInternals.isTinkerEnabledForNativeLib(tinkerFlag);
if (isEnabledForNativeLib) { boolean libCheck = TinkerSoLoader.checkComplete(patchVersionDirectory, securityCheck, resultIntent); if (!libCheck) { Log.w(TAG, "tryLoadPatchFiles:native lib check fail"); return; } }
|
checkComplete
从 assets/so_meta.txt 中读取 so 补丁信息,每一条 so 补丁信息都会被封装成一个 ShareBsDiffPatchInfo 对象,然后放入 libraryList 中。
1 2 3 4 5 6 7 8 9 10 11
| String meta = securityCheck.getMetaContentMap().get(SO_MEAT_FILE);
if (meta == null) { return true; } ArrayList<ShareBsDiffPatchInfo> libraryList = new ArrayList<>(); ShareBsDiffPatchInfo.parseDiffPatchInfo(meta, libraryList);
if (libraryList.isEmpty()) { return true; }
|
然后遍历 libraryList ,去校验里面的 ShareBsDiffPatchInfo 对象中 md5 和 name 值是否合法。合法的 ShareBsDiffPatchInfo 对象再放入 libs 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| String libraryPath = directory + "/" + SO_PATH + "/";
HashMap<String, String> libs = new HashMap<>();
for (ShareBsDiffPatchInfo info : libraryList) { if (!ShareBsDiffPatchInfo.checkDiffPatchInfo(info)) { intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL); return false; } String middle = info.path + "/" + info.name;
libs.put(middle, info.md5); }
|
接着会校验 so 补丁文件夹是否存在
1 2 3 4 5 6
| File libraryDir = new File(libraryPath);
if (!libraryDir.exists() || !libraryDir.isDirectory()) { ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_LIB_DIRECTORY_NOT_EXIST); return false; }
|
再校验上面的从 so_meta.txt 中获取到的 so 补丁文件路径是否真的存在并且 so 文件是可读的
1 2 3 4 5 6 7 8 9
| for (String relative : libs.keySet()) { File libFile = new File(libraryPath + relative); if (!SharePatchFileUtil.isLegalFile(libFile)) { ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_LIB_FILE_NOT_EXIST); intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISSING_LIB_PATH, libFile.getAbsolutePath()); return false; } }
|
都没问题的话,就通过校验
1 2 3
| intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_LIBS_PATH, libs); return true;
|
加载so补丁流程
加载so补丁的入口:TinkerLoadLibrary.loadArmLibrary / TinkerLoadLibrary.loadArmV7Library ,区别就是前者用来加载 armeabi 平台的,后者是用来加载 armeabi-v7a 平台的。我们就来看 loadArmLibrary 方法吧。
loadArmLibrary
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static void loadArmLibrary(Context context, String libName) { if (libName == null || libName.isEmpty() || context == null) { throw new TinkerRuntimeException("libName or context is null!"); }
Tinker tinker = Tinker.with(context);
if (tinker.isEnabledForNativeLib()) { if (TinkerLoadLibrary.loadLibraryFromTinker(context, "lib/armeabi", libName)) { return; } } System.loadLibrary(libName); }
|
loadLibraryFromTinker
loadLibraryFromTinker 一开始校验了 libName 的名字是否是 lib 开头、 .so 结尾的。
1 2 3 4 5
| final Tinker tinker = Tinker.with(context);
libName = libName.startsWith("lib") ? libName : "lib" + libName; libName = libName.endsWith(".so") ? libName : libName + ".so"; String relativeLibPath = relativePath + "/" + libName;
|
然后就是用 relativeLibPath 去和之前 so 校验得到的 libs 去一一匹配。
如果匹配上了,就说明要加载的就是这个 so 文件,调用 System.load ,传入文件路径即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
if (tinker.isEnabledForNativeLib() && tinker.isTinkerLoaded()) { TinkerLoadResult loadResult = tinker.getTinkerLoadResultIfPresent(); if (loadResult.libs != null) { for (String name : loadResult.libs.keySet()) { if (name.equals(relativeLibPath)) { String patchLibraryPath = loadResult.libraryDirectory + "/" + name; File library = new File(patchLibraryPath); if (library.exists()) { boolean verifyMd5 = tinker.isTinkerLoadVerify(); if (verifyMd5 && !SharePatchFileUtil.verifyFileMd5(library, loadResult.libs.get(name))) { tinker.getLoadReporter().onLoadFileMd5Mismatch(library, ShareConstants.TYPE_LIBRARY); } else { System.load(patchLibraryPath); TinkerLog.i(TAG, "loadLibraryFromTinker success:" + patchLibraryPath); return true; } } } } } }
|
到这里,Tinker 中关于 so 补丁加载的流程就讲完了。
番外
大家有没有发现,一个个单独去调用 TinkerLoadLibrary.loadArmLibrary 会很麻烦,因为如果我的 so 补丁文件有很多个,就需要调用很多次。所以从 Tinker v1.7.7 之后,提供了一键反射的方案来加载 so 补丁文件。
具体方法 TinkerLoadLibrary.installNavitveLibraryABI
installNavitveLibraryABI
来看一下具体的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public static boolean installNavitveLibraryABI(Context context, String currentABI) { Tinker tinker = Tinker.with(context); if (!tinker.isTinkerLoaded()) { TinkerLog.i(TAG, "tinker is not loaded, just return"); return false; } TinkerLoadResult loadResult = tinker.getTinkerLoadResultIfPresent(); if (loadResult.libs == null) { TinkerLog.i(TAG, "tinker libs is null, just return"); return false; } File soDir = new File(loadResult.libraryDirectory, "lib/" + currentABI); if (!soDir.exists()) { TinkerLog.e(TAG, "current libraryABI folder is not exist, path: %s", soDir.getPath()); return false; } ClassLoader classLoader = context.getClassLoader(); if (classLoader == null) { TinkerLog.e(TAG, "classloader is null"); return false; } TinkerLog.i(TAG, "before hack classloader:" + classLoader.toString()); try { installNativeLibraryPath(classLoader, soDir); return true; } catch (Throwable throwable) { TinkerLog.e(TAG, "installNativeLibraryPath fail:" + throwable); return false; } finally { TinkerLog.i(TAG, "after hack classloader:" + classLoader.toString()); } }
|
在做了一堆的检查之后,具体 so 文件加载是在 installNativeLibraryPath 方法中。
installNativeLibraryPath
installNativeLibraryPath 中做的事情主要有两点:
- 如果 classloader 中没有注入 so 补丁文件夹的路径的话,就执行注入;
- 如果 classloader 中已经有 so 补丁文件夹的路径了,就先删除,再进行注入;
具体 hook 的代码根据 SDK 版本而定,这里就不展开讲了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| private static void installNativeLibraryPath(ClassLoader classLoader, File folder) throws Throwable { if (folder == null || !folder.exists()) { TinkerLog.e(TAG, "installNativeLibraryPath, folder %s is illegal", folder); return; } if ((Build.VERSION.SDK_INT == 25 && Build.VERSION.PREVIEW_SDK_INT != 0) || Build.VERSION.SDK_INT > 25) { try { V25.install(classLoader, folder); } catch (Throwable throwable) { TinkerLog.e(TAG, "installNativeLibraryPath, v25 fail, sdk: %d, error: %s, try to fallback to V23", Build.VERSION.SDK_INT, throwable.getMessage()); V23.install(classLoader, folder); } } else if (Build.VERSION.SDK_INT >= 23) { try { V23.install(classLoader, folder); } catch (Throwable throwable) { TinkerLog.e(TAG, "installNativeLibraryPath, v23 fail, sdk: %d, error: %s, try to fallback to V14", Build.VERSION.SDK_INT, throwable.getMessage());
V14.install(classLoader, folder); } } else if (Build.VERSION.SDK_INT >= 14) { V14.install(classLoader, folder); } else { V4.install(classLoader, folder); } }
|
综上所述,有了 TinkerLoadLibrary.installNavitveLibraryABI 之后,你就只需要传入当前手机系统的 ABI ,就无需再对 so 的加载做任何的介入。