热修复框架 - TinkerApplication启动(四) - 加载so补丁过程

代码: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,如果是静态注册则直接返回
}

大概就是这样子。

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352