热修复知识预备

网友投稿 645 2022-11-17

热修复知识预备

热修复知识预备

本篇学习基于《Android进阶解密》第13章热修复原理。接下来的会把热修复、Hook、插件化的知识学习完。这章来学习热修复的三种基本方案。

1.热修复的出现

日常开发中,我们可能遇到下面的情况:

线上版本出现了严重的bug,如果才刚刚发版,或者下个版本已经规定了具体的计划发布,这个时候为了解决bug,需要fix+测试+在各个应用市场重新发布,这会消耗大量的人力物力,代价比较大版本升级率不高,并且需要很长时间来完成版本覆盖,此前版本的Bug就会一直影响不升级版本的用户有一个小而重要的功能,需要短时间内完成版本覆盖,比如节日活动

而热修复框架就应运而生,它可以解决上述这些问题,对线上版本进行修复。

2.热修复框架的种类和对比

热修复框架的种类很多,按照公司团队划分主要有下面几种:

虽然修复框架很多,但热修复框架的核心技术主要有三类,分别是:

代码修复资源修复动态链接库修复

其中每个核心技术又有很多不同技术方案,另外这些热修复框架仍然在不断的更新迭代中,所以我们不是特别需要去了解所有框架的代码,而是了解其本质的原理,和基本方案,这样在实际使用其他热修复框架时,我们需要搞清楚该框架能够完成哪些功能, 结合我们的情况再去选择框架就很方便了。

3.资源修复

很多热修复框架都参考了​​Instant Run​​的资源修复原理。我们需要先了解Instant Run是怎么实现资源修复的。

3.1 Instant Run概述

Instant Run是Android Studio2.0以后新增的一个运行机制,能够明显减少开发人员第二次及以后的构建和部署项目的时间,有热重载内味了,在没有使用 Instant Run前,我们编译部署应用程序的流程如下图所示:

从图中可以看出传统的编译部署需要重新安装和重启App,这显然会很耗时,Instant Run会避免这种情况:

从图可以看出Instant Run的构建和部署都是基于更改的部分,Instant Run部署有三种方式,Instant Run会根据代码的情况决定采用哪一种部署方式,无论哪种方式都不需要重新安装App,这一点就提高了不少的效率。

Hot swap从名称也可以看出Hot Swap是效率最高的部署方式,代码的增量改变不需要重启App,甚至不需要重启当前的Activity。修改一个现有方法中的代码时会采用Hot SwapWarm SwapApp不需要重启,但是Activity需要重启,修改或删除一个现有的资源文件时会采用 Warm SwapCold SwapApp需要重启,但是不需要重新安装,采用Cold Swap的情况很多,比如添加、删除、修改一个字段和方法、添加一个类等。

3.2 Instant Run资源修复

我们需要了解Instant Run的资源修复原理,Instant Run并不是Android源码,需要通过反编译获取。 Instant Run资源修复的核心逻辑在 ​​​MonkeyPatcher​​​的 ​​monkeyPatchExistingResources()​​:

public static void monkeyPatchExistingResources(@Nullable Context context, @Nullable String externalResourceFile, @Nullable Collection activities) { if (externalResourceFile == null) { return; } try { // 1 创建一新的 AssetManager AssetManager newAssetManager = AssetManager.class.getConstructor().newInstance(); // 2 Method mAddAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class); mAddAssetPath.setAccessible(true); //3 通过反射调用 addAssetPath方法加载外部的资源(SD卡) if (((Integer) mAddAssetPath.invoke(newAssetManager, externalResourceFile)) == 0) { throw new IllegalStateException("Could not create new AssetManager"); } Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks"); mEnsureStringBlocks.setAccessible(true); mEnsureStringBlocks.invoke(newAssetManager); if (activities != null) { for (Activity activity : activities) { // 4 Resources resources = activity.getResources(); try { //5 反射得到Resources的AssetManager类型的 mAssets字段 Field mAssets = Resources.class.getDeclaredField("mAssets"); mAssets.setAccessible(true); //6 将mAssets字段的引用替换为新创建的 AssetManager mAssets.set(resources, newAssetManager); } catch (Throwable ignore) { ... } ... ... pruneResourceCaches(resources); } } /** * 根据SDK版本的不同,用不同的方式得到Resources的弱引用集合 */ Collection> references; if (SDK_INT >= KITKAT) { Class resourcesManagerClass = Class.forName("android.app.ResourcesManager"); Method mGetInstance = resourcesManagerClass.getDeclaredMethod("getInstance"); mGetInstance.setAccessible(true); Object resourcesManager = mGetInstance.invoke(null); try { Field fMActiveResources = resourcesManagerClass.getDeclaredField("mActiveResources"); fMActiveResources.setAccessible(true); @SuppressWarnings("unchecked") ArrayMap> arrayMap = (ArrayMap>) fMActiveResources.get(resourcesManager); references = arrayMap.values(); } catch (NoSuchFieldException ignore) { Field mResourceReferences = resourcesManagerClass.getDeclaredField("mResourceReferences"); mResourceReferences.setAccessible(true); references = (Collection>) mResourceReferences.get(resourcesManager); } } else { Class activityThread = Class.forName("android.app.ActivityThread"); Field fMActiveResources = activityThread.getDeclaredField("mActiveResources"); fMActiveResources.setAccessible(true); Object thread = getActivityThread(context, activityThread); @SuppressWarnings("unchecked") HashMap> map = (HashMap>) fMActiveResources.get(thread); references = map.values(); } // 遍历并得到弱引用集合中的Resources,将Resources的mAssets字段引用替换成新的AssetManager for (WeakReference wr : references) { Resources resources = wr.get(); if (resources != null) { try { Field mAssets = Resources.class.getDeclaredField("mAssets"); mAssets.setAccessible(true); mAssets.set(resources, newAssetManager); } catch (Throwable ignore) { .... } resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics()); } } } ...

注释1:创建一个新的 ​​AssetManager​​​ 注释2:、注释3:通过反射调用 ​​addAssetPath()​​加载外部(SD卡)的资源 注释4:遍历Activity列表,得到每个Activity的Resources 注释5:通过反射得到Resources的 ​​AssetManager​​类型的 mAssets字段 注释6:改写 mAssets字段的引用为新的 AssetManager 注释7:采用同样的方式,将 Resources.Theme的 mAssets字段的引用替换为新创建的 AssetManager 紧接着根据SDK版本的不同,用不同方式得到 Resources的弱引用集合,再遍历这个弱引用集合,将弱引用集合中的 Resources的 mAssets字段引用替换成新创建的 AssetManager。

可以看出Instant Run中的资源热修复可以简单的总结为两点

创建新的AssetManager,通过反射调用​​addAssetPath()​​加载外部的资源,这样新创建的 AssetManager就含有了外部的资源将AssetManager类型的mAssets字段引用全部替换为新创建的 AssetManager。

4.代码修复

代码修复主要有3个方案,分别是底层替换方案、类加载方案和Instant Run方案。

4.1 类加载方案

类加载方案基于 Dex分包方案。为了理解Dex分包,我们将从65536限制和 LinearAlloc限制说起。

1.65536限制 随着应用功能越来越复杂,代码量不断增大,引入的库也越来越多,可能会在编译时提示如下异常:

com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536

这说明应用中引用的方法数超过了最大数65536个,产生这一问题的原因就是系统的65536限制,65536限制的主要原因是 DVM Bytecode的限制,DVM指令集的方法调用指令invoke-kind 索引为16bits,所以最多能引用65535个方法。

2.LinearAlloc限制 在安装应用时可能会提示 ​​​INSTALL_FAILED_DEXOPT​​,产生的原因就是 LinearAlloc限制,DVM中的LinearAlloc是一个固定的缓存区,当方法数超出了缓存区的大小时会报错。

为了解决上面两种限制,从而产生了 Dex分包方案:打包时将代码分成多个Dex,将应用启动时必须用到的类和这些类的直接引用类放到主Dex中,其他代码放到次Dex中。 当应用启动时先加载主Dex,等到应用启动后再动态地加载次Dex,从而缓解了主Dex的65536限制和 LinearAlloc限制。

Dex分包方案主要有两种,分别是:

Google官方方案Dex自动拆包和动态加载方案

这里就不再讲解分包方案,接着来学习类加载方案,在之前学习了ClassLoader加载过程,其中一个环节就是 ​​DexPathList.findClass()​​:

// DexPathList.java public Class findClass(String name, List suppressed) { for (Element element : dexElements) { //1 Class clazz = element.findClass(name, definingContext, suppressed); // 2 if (clazz != null) { return clazz; } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null; }

Element内部封装了DexFile,DexFile用于加载dex文件,因此每个dex文件对应一个Element。 多个Element组成了有序的Element数组 dexElements。当要查找类时,会在注释1处遍历dexElements,注释2处调用 ​​​Element.findClass()​​,最终会调用Native方法查找,如果找到了就返回该类,如果找不到就接着在下一个Element中进行查找。

根据上面的流程,我们将有Bug的类 Key.class进行修改,再将 Key.class打包成含dex的补丁包Patch.jar,放在Element数组 dexElements的第一个元素,这样首先会找到 Patch.dex中的Key.class去替换之前存在Bug的 Key.class,排在数组后面的 dex文件中存在Bug的Key.class根据ClassLoader双亲委托模式就不会被加载,这就是类加载的方案,如下图所示:

类加载方案需要重启App后让ClassLoader重新加载新的类,为什么要重启呢?这是因为类是无法被卸载的。要向重新加载新的类就需要重启App,因此采用类加载方案的热修复框架时不能即时生效的。

很多热修复框架都采用了类加载的方案,但他们在细节上面都有不同。

4.2 底层替换方案

与类加载方案不同的是,底层替换方案不会再次加载新类,而是直接在Native层修改原有类,由于在原有类进行修改限制会比较多,且不能增减原有的方法和字段,如果我们增加了方法数,那么方法索引数也会增加,这样访问方法时会无法通过索引找到正确的方法,同样的字段也是类似的情况。 底层替换方案和反射的原理有些关联,就拿方法替换来说,方法反射我们可以调用 java.lang.Class.getDeclaredMethod,假设我们要反射Key的show方法,会调用如下所示的代码:

Key.class.getDeclaredMethod("show").invoke(Ket.class.newInstance());

Android8.0的​​invoke()​​方法:

// Method.java @FastNative public native Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;

它是一个native方法,对应JNI层的代码为:

// java_lang_reflect_Method.ccstatic jobject Method_invoke(JNIEnv* env, jobject javaMethod, jobject javaReceiver, jobject javaArgs) { ScopedFastNativeObjectAccess soa(env); return InvokeMethod(soa, javaMethod, javaReceiver, javaArgs);}

调用了 ​​InvokeMethod()​​:

// relection.ccjobject InvokeMethod(const ScopedObjectAccessAlreadyRunnable& soa, jobject javaMethod, jobject javaReceiver, jobject javaArgs, size_t num_frames) { ... ObjPtr executable = soa.Decode(javaMethod); const bool accessible = executable->IsAccessible(); ArtMethod* m = executable->GetArtMethod(); // 1 ...}

注释1:获取传入了 javaMethod在ART虚拟机中对应一个 ArtMethod指针,ArtMethod结构体中包含了Java方法的所有信息,包括执行入口、访问权限、所属类和代码执行地址等,ArtMethod结构如下所示:

// art_method.hclass ArtMethod FINAL {... protected: GcRoot declaring_class_; std::atomic access_flags_; uint32_t dex_code_item_offset_; uint32_t dex_method_index_; uint16_t method_index_; uint16_t hotness_count_; struct PtrSizedFields { ArtMethod** dex_cache_resolved_methods_; //1 void* data_; void* entry_point_from_quick_compiled_code_; // 2 } ptr_sized_fields_;}

在ArtMethod中结构中比较重要的字段是上述代码中注释1和注释2,他们表示的是方法的执行入口,当我们调用某一个方法时,就会取得这个方法(就比如前面的“show”)的执行入口。通过执行入口就可以跳过去执行show方法。替换ArtMethod结构体中的字段或者替换掉整个ArtMethod结构体,这就是底层替换方案。 AndFix采用的是替换ArtMethod结构体中的字段,这样会有兼容问题,因为厂商可能会修改ArtMethod结构体,导致方法替换失败。Sophix采用的是替换整个ArtMethod结构体,这样就不会存在兼容问题。底层替换方案直接替换了方法,可以理解生效而不需重启。采用底层替换方案主要使 阿里系为主,包括AndFix、Dexposed、阿里百川、Sophix。

4.3 Instant Run方案

除了资源的修复,代码修复同样可以借鉴Instant Run的原理,可以说Instant Run的出现推动了修复框架的发展。 Instant Run在第一次构建APK时,使用ASM在每一个方法中注入了类似如下的代码:

IncrementalChange localIncrementalChange = $change; //1 if (localIncrementalChange != null) { //2 localIncrementalChange.access$dispatch( "onCreate.(Landroid/os/Bundle;)V", new Object[]{this, paramBundle}); return; }

上面有个概念,什么是ASM? ASM是一个Java字节码操控框架,它能够动态生成类或增强现有类的功能,ASM可以直接产生class文件,也可以在类被加载到虚拟机之前动态改变类的行为。

5.动态链接库修复

Android平台的动态链接库主要是指 ​​so库​​,热修复框架的 so的修复主要是更新 so,换句话所就是重新加载so库,因此so库的修复的基础原理就是加载so库。

5.1 System的load和loadLibrary方法

加载so库主要用到了 System类的load和 loadLibrary(),如下所示

public final class System {... @CallerSensitive public static void load(String filename) { Runtime.getRuntime().load0(VMStack.getStackClass1(), filename); // 1 } @CallerSensitive public static void loadLibrary(String libname) { Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname); // 2 }}

System的​​load()​​​传入的参数是so库在磁盘的完整路径,用于加载路径的so。System的 ​​loadLibrary()​​​传入的参数时so的名称,用于加载App安装后自动从apk包中复制到 ​​/data/data/packagename/lib​​​ 下的so库。 目前so库的修复都是基于这两个方法,这里分别对这两个方法进行讲解。

1. System的load() 注释1处的 ​​​Runtime.getRuntime()​​​会得到当前Java应用程序需的运行环境 Runtime,​​Runtime.load()​​如下:

// 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"); } // 1 String error = doLoad(filename, fromClass.getClassLoader()); if (error != null) { throw new UnsatisfiedLinkError(error); } }

注释1之前都是对路径名进行查错,然后调用 ​​doLoad()​​,并将该类的类加载器作为参数传了进去!

// Runtime.java private String doLoad(String name, ClassLoader loader) { String librarySearchPath = null; if (loader != null && loader instanceof BaseDexClassLoader) { BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader; librarySearchPath = dexClassLoader.getLdLibraryPath(); } synchronized (this) { return nativeLoad(name, loader, librarySearchPath); } }

doLoad() 会调用native方法​​nativeLoad()​​,关于这个方法后面会讲到。

2. System的loadLibrary() 接着来查看System的loadLibrary(),它会调用 ​​​Runtime.loadLibrary0()​​:

// Runtime.java synchronized void loadLibrary0(ClassLoader loader, String libname) { if (libname.indexOf((int)File.separatorChar) != -1) { throw new UnsatisfiedLinkError( "Directory separator should not appear in library name: " + libname); } String libraryName = libname; if (loader != null) { // 1 String filename = loader.findLibrary(libraryName); if (filename == null) { throw new UnsatisfiedLinkError(loader + " couldn't find \"" + System.mapLibraryName(libraryName) + "\""); } // 2 String error = doLoad(filename, loader); if (error != null) { throw new UnsatisfiedLinkError(error); } return; } String filename = System.mapLibraryName(libraryName); List candidates = new ArrayList(); String lastError = null; // 3 for (String directory : getLibPaths()) { // 4 String candidate = directory + filename; candidates.add(candidate); if (IoUtils.canOpenReadOnly(candidate)) { // 5 String error = doLoad(candidate, loader); if (error == null) { return; } lastError = error; } } if (lastError != null) { throw new UnsatisfiedLinkError(lastError); } throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates); }

​​loadLibrary0()​​​ 分成两个部分,一个是传入的 ClassLoader不为null的部分,另一个是ClassLoader为null的部分。 (1)我们先来看看 传入的ClassLoader为null的情况: 注释3:遍历 ​​​getLibPaths()​​​这个方法,这个方法会返回 java.library.path选项配置的路径数组。 注释4:拼接一条so库的路径,当然这个路径是暴力拼的,为了验证其是否是正确的,就把它丢到 ​​​doLoad()​​中。直到找到它。

(2)当ClassLoader不为null时的情况: 注释2:同样的调用了 ​​​doLoad()​​​,其中第一个参数时通过注释1处的 ​​ClassLoader.findLibrary()​​​获取到的路径。 findLibrary() 在 ClassLoader的实现类 ​​​BaseDexClassLoader​​中实现:

// BaseDexClassLoader.java @Override public String findLibrary(String name) { return pathList.findLibrary(name); }

调用了 ​​DexPathList.findLibrary()​​:

// DexPathList public String findLibrary(String libraryName) { String fileName = System.mapLibraryName(libraryName); for (NativeLibraryElement element : nativeLibraryPathElements) { // 1 String path = element.findNativeLibrary(fileName); if (path != null) { return path; } } return null; }

这个方法和 ​​findClass()​​​类似,在 ​​NativeLibraryElement​​​数组的每一个 NativeLibraryElement对应一个so库,在注释1处调用 ​​NativeLibraryElement.findNativeLibrary()​​​就可以返回so库的路径。 上面结合类加载方案,就可以得到so的修复的一种方案,就是将so补丁插入到 NativeLibraryElement数组的前面,让so补丁的路径先被返回,并调用Runtime的​​​doLoad()​​​进行加载,在doLoad中会调用 native方法 ​​nativeLoad()​​。

也就是说 ​​load()​​​和 ​​loadLibrary()​​​这两个方法殊途同归,最终都会调用native方法 ​​nativeLoad()​​,那我们就深入到JNI去了解这个方法。

5.2 nativeLoad()分析

先来看看其JNI层中函数

// Runtime.cJNIEXPORT jstring JNICALLRuntime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename, jobject javaLoader, jstring javaLibrarySearchPath){ return JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath);}

在 Runtime_nativeLoad中调用了 ​​JVM_NativeLoad()​​:

// OpenjdkJvm.ccJNIEXPORT jstring JVM_NativeLoad(JNIEnv* env, jstring javaFilename, jobject javaLoader, jstring javaLibrarySearchPath) { // 将so的文件名称转换为ScopedUtfChars类型 ScopedUtfChars filename(env, javaFilename); if (filename.c_str() == NULL) { return NULL; } std::string error_msg; { // 获取当前运行时的虚拟机 art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM(); // 虚拟机加载so库 bool success = vm->LoadNativeLibrary(env, filename.c_str(), javaLoader, javaLibrarySearchPath, &error_msg); if (success) { return nullptr; } } env->ExceptionClear(); return env->NewStringUTF(error_msg.c_str());}

上面的代码是先获取当前运行时的JVM指针,然后调用JVM的 ​​LoadNativeLibrary()​​​来加载so库,也就是说 :so库是被JVM加载的,它的加载方法是 LoadNativeLibrary()​​​LoadNativeLibrary()​​的方法有点多,这里分成3个part来讲:

part.1 判断是否加载过该so库

bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader, jstring library_path, std::string* error_msg) { error_msg->clear(); SharedLibrary* library; Thread* self = Thread::Current(); { MutexLock mu(self, *Locks::jni_libraries_lock_); // 1 library = libraries_->Get(path); } ... // 2 if (library != nullptr) { // 3 if (library->GetClassLoaderAllocator() != class_loader_allocator) { StringAppendF(error_msg, "Shared library \"%s\" already opened by " "ClassLoader %p; can't open in ClassLoader %p", path.c_str(), library->GetClassLoader(), class_loader); LOG(WARNING) << error_msg; return false; } // 4 if (!library->CheckOnLoadResult()) { StringAppendF(error_msg, "JNI_OnLoad failed on a previous attempt " "to load \"%s\"", path.c_str()); return false; } return true; }

注释1:根据so名称从 ​​libraries_​​​中获取对应的 ​​SharedLibrary​​​类型指针 library,如果满足注释2处的条件就说明此前加载过该so。 在注释3处如果此前加载用的ClassLoader和当前传入的ClassLoader不相同的话,就会返回false 注释4:判断上次加载so的结果,如果有异常也会返回false,中断so加载。如果满足了注释2、注释3、注释4的条件就会返回true,不再重复加载so。

part.2 获取so库对应的SharedLibrary指针

... Locks::mutator_lock_->AssertNotHeld(self); const char* path_str = path.empty() ? nullptr : path.c_str(); bool needs_native_bridge = false; /** * 1 打开路径 path_str的so库,得到so句柄handle */ void* handle = android::OpenNativeLibrary(env, runtime_->GetTargetSdkVersion(), path_str, class_loader, library_path, &needs_native_bridge, error_msg); VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_NOW) returned " << handle << "]"; if (handle == nullptr) { // 2 VLOG(jni) << "dlopen(\"" << path << "\", RTLD_NOW) failed: " << *error_msg; return false; } if (env->ExceptionCheck() == JNI_TRUE) { LOG(ERROR) << "Unexpected exception:"; env->ExceptionDescribe(); env->ExceptionClear(); } bool created_library = false; { /** * 3 创建SharedLibrary */ std::unique_ptr 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); // 4 if (library == nullptr) { // We won race to get libraries_lock. library = new_library.release(); libraries_->Put(path, library); created_library = true; } } ...

注释1:根据so的路径path_str来打开该so库,并返回得到的so句柄 注释2:如果获取so句柄失败,就会返回fasle,中断so加载 注释3:创建​​​SharedLibrary​​​,并将so句柄作为参数传入进去 注释4:获取传入path对应的library,如果library为空指针,就将新创建的SahredLibrary赋值给library,并将library存储到 libraries_中。

part.3 找到JNI_OnLoad注册so库方法

... bool was_successful = false; // 1 void* sym = library->FindSymbol("JNI_OnLoad", nullptr); // 2 if (sym == nullptr) { VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]"; was_successful = true; } else { ScopedLocalRef old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride())); self->SetClassLoaderOverride(class_loader); VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]"; typedef int (*JNI_OnLoadFn)(JavaVM*, void*); JNI_OnLoadFn jni_on_load = reinterpret_cast(sym); // 3 int version = (*jni_on_load)(this, nullptr); if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) { EnsureFrontOfChain(SIGSEGV); } self->SetClassLoaderOverride(old_class_loader.get()); if (version == JNI_ERR) { StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str()); } else if (JavaVMExt::IsBadJniVersion(version)) { StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d", path.c_str(), version); } else { // 4 was_successful = true; } VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure") << " from JNI_OnLoad in \"" << path << "\"]"; } library->SetResult(was_successful); return was_successful;}

注释1:查找JNI_OnLoad函数指针并赋值给空指针sym,我们在JNI学习中知道 ​​JNI_OnLoad()​​​用于native方法的动态注册 注释2:如果没有找到JNI_OnLoad函数也算加载成功,将 was_successful 置为true,这是因为并不是所有的so都定义了JNI_OnLoad函数,因为native方法出去了动态注册,还有静态注册。 如果找到了 ​​​JNI_OnLoad()​​​,就在注释3处执行 ​​JNI_OnLoad()​​​并将结果复制给version,如果version为JNI_ERR或者 BadJniVersion,说明没有执行成功,was_successful 为fasle。 如果执行成功,则置 was_successful为true,表明已经将so库中的方法注册成功了。

part.4 LoadNativeLibrary总结 so库加载可以总结以下每步工作:

判断传入so库是否加载库,两次的ClassLoader是否是同一个,避免重复加载获取so句柄,创建新的SharedLibrary,如果原来的library指针为空,就把这个新的SharedLibrary赋值给它,并存储到libraries中查找 so库中的​​JNI_OnLoad​​的函数指针并执行它,根据不同的情况设置 was_successful的值,最终返回该 was_successful。

讲到这里,可以总结一下so修复主要有两个方案:

将so补丁插入到 NativeLibraryElement数组的前部,让so补丁的路径先被返回和加载调用System的load方法来接管so的加载入口。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Docker基础概念及操作
下一篇:docker-3-常用命令(下)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~