代码:tinker 1.9.14.7 android 8.0
加载so补丁是通过TinkerLoadLibrary.loadArmLibrary,但是这个方法并没有在TinkerApplication启动过程中直接被调用到,原则上不属于TinkerApplication启动这一part,但是出于想把加载各种补丁归类到一起的目的,就在这里写了。
一、Tinker加载so补丁
TinkerLoadLibrary.java
/**
* you can use TinkerInstaller.loadLibrary replace your System.loadLibrary for auto update library!
* only support auto load lib/armeabi library from patch.
* for other library in lib/* or assets,
* you can load through {@code TinkerInstaller#loadLibraryFromTinker}
*/
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);
}
/**
* sample usage for native library
*
* @param context
* @param relativePath such as lib/armeabi
* @param libName for the lib [libTest.so](http://libtest.so/), you can pass Test or libTest, or [libTest.so](http://libtest.so/)
* @return boolean
* @throws UnsatisfiedLinkError
*/
public static boolean loadLibraryFromTinker(Context context, String relativePath, String libName) throws UnsatisfiedLinkError {
final Tinker tinker = Tinker.with(context);
//检查加载文件前后缀
libName = libName.startsWith("lib") ? libName : "lib" + libName;
libName = libName.endsWith(".so") ? libName : libName + ".so";
String relativeLibPath = relativePath + "/" + libName;
//当前tinker开启了so补丁修复功能 且 完成 tryLoad补丁加载之后
//TODO we should add cpu abi, and the real path later
if (tinker.isEnabledForNativeLib() && tinker.isTinkerLoaded()) {
TinkerLoadResult loadResult = tinker.getTinkerLoadResultIfPresent();
if (loadResult.libs == null) {
return false;
}
for (String name : loadResult.libs.keySet()) {
if (!name.equals(relativeLibPath)) {
continue;
}
String patchLibraryPath = loadResult.libraryDirectory + "/" + name;
File library = new File(patchLibraryPath);
if (!library.exists()) {
continue;
}
//whether we check md5 when load
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;
}
}
}
return false;
}
so加载非常简单,直接通过System.load(patchLibraryPath)来加载对应路径的so文件。
该方法是一个个加载so文件,如果so文件有多个,需要多次调用,为了优化多个so文件的加载,从 Tinker v1.7.7 之后,提供了一键反射的方案来加载 so 补丁文件。使用如下方法:
/**
* you can reflect your current abi to classloader library path
* as you don't need to use load*Library method above
* @param context
* @param currentABI
*/
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 {
//加载so
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());
}
}
这里其实就是按不同架构平台来分文件夹加载。
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;
}
// android o sdk_int 26
// for android o preview sdk_int 25
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) {
// install fail, try to treat it as v23
// some preview N version may go here
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) {
// install fail, try to treat it as v14
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);
}
}
看看V25的
private static final class V25 {
private static void install(ClassLoader classLoader, File folder) throws Throwable {
final Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");
final Object dexPathList = pathListField.get(classLoader);
final Field nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories");
List<File> origLibDirs = (List<File>) nativeLibraryDirectories.get(dexPathList);
if (origLibDirs == null) {
origLibDirs = new ArrayList<>(2);
}
final Iterator<File> libDirIt = origLibDirs.iterator();
while (libDirIt.hasNext()) {
final File libDir = libDirIt.next();
if (folder.equals(libDir)) {
libDirIt.remove();
break;
}
}
origLibDirs.add(0, folder);
final Field systemNativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "systemNativeLibraryDirectories");
List<File> origSystemLibDirs = (List<File>) systemNativeLibraryDirectories.get(dexPathList);
if (origSystemLibDirs == null) {
origSystemLibDirs = new ArrayList<>(2);
}
final List<File> newLibDirs = new ArrayList<>(origLibDirs.size() + origSystemLibDirs.size() + 1);
newLibDirs.addAll(origLibDirs);
newLibDirs.addAll(origSystemLibDirs);
final Method makeElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class);
final Object[] elements = (Object[]) makeElements.invoke(dexPathList, newLibDirs);
final Field nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements");
nativeLibraryPathElements.set(dexPathList, elements);
}
}
反射DexPathList中的makePathElements方法,注入so到nativeLibraryPathElements数组中。这部分跟dexElements类似
简单分析installNativeLibraryPath 中做的事情主要有两点:
- 如果 classloader 中没有注入 so 补丁文件夹的路径的话,就执行注入。
- 如果 classloader 中已经有 so 补丁文件夹的路径了,就先删除,再进行注入。
二、so加载流程
这里看看System.load(patchLibraryPath),它的加载流程大体上跟System.loadLibrary("native-lib”)差不多
System.java
public static void load(String filename) {
Runtime.getRuntime().load0(VMStack.getStackClass1(), filename);
}
Runtime.java
synchronized void load0(Class fromClass, String filename) {
if (!(new File(filename).isAbsolute())) {
throw new UnsatisfiedLinkError(
"Expecting an absolute path of the library: " + filename);
}
if (filename == null) {
throw new NullPointerException("filename == null");
}
String error = doLoad(filename, fromClass.getClassLoader());
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
}
private String doLoad(String name, ClassLoader loader) {
String librarySearchPath = null;
if (loader != null && loader instanceof BaseDexClassLoader) {
BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
librarySearchPath = dexClassLoader.getLdLibraryPath();
}
// nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless
// of how many ClassLoaders are in the system, but dalvik doesn't support synchronized
// internal natives.
synchronized (this) {
return nativeLoad(name, loader, librarySearchPath);
}
}
JNI 执行nativeLoad
Runtime.c
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
jobject javaLoader, jstring javaLibrarySearchPath)
{
return JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath);
}
Openjdkjvm.cc
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
jstring javaFilename,
jobject javaLoader,
jstring javaLibrarySearchPath) {
ScopedUtfChars filename(env, javaFilename);
if (filename.c_str() == NULL) {
return NULL;
}
std::string error_msg;
{
art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
bool success = vm->LoadNativeLibrary(env,
filename.c_str(),
javaLoader,
javaLibrarySearchPath,
&error_msg);
if (success) {
return nullptr;
}
}
// Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
env->ExceptionClear();
return env->NewStringUTF(error_msg.c_str());
}
java_vm_ext.cc
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
const std::string& path,
jobject class_loader,
jstring library_path,
std::string* error_msg) {
SharedLibrary* library;
…
// Create SharedLibrary ahead of taking the libraries lock to maintain lock ordering.
std::unique_ptr<SharedLibrary> new_library(
new SharedLibrary(env,
self,
path,
handle,
needs_native_bridge,
class_loader,
class_loader_allocator));
MutexLock mu(self, *Locks::jni_libraries_lock_);
library = libraries_->Get(path); //获取library实例
if (library == nullptr) {
library = new_library.release();//获取为空则实例化一个SharedLibrary
libraries_->Put(path, library);//载入so库
created_library = true;
}
…
void* sym = library->FindSymbol("JNI_OnLoad", nullptr);
//后面的逻辑是如果是动态注册则回调JNI_OnLoad,如果是静态注册则直接返回
}
大概就是这样子。
网友评论