美文网首页android技术
热修复框架 - TinkerApplication启动(四) -

热修复框架 - TinkerApplication启动(四) -

作者: Stan_Z | 来源:发表于2020-08-14 08:22 被阅读0次

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

    大概就是这样子。

    相关文章

      网友评论

        本文标题:热修复框架 - TinkerApplication启动(四) -

        本文链接:https://www.haomeiwen.com/subject/mqzbdktx.html