Android热修复原理

前言

热修复框架很多,但热修复框架的核心技术主要有三类,分别是代码修复、资源修复和动态链接库修复,其中每个核心技术又有很多不同的技术方案,每个技术方案又有不同的实现,另外这些热修复框架仍在不断的更新迭代中,可见热修复框架的技术实现是繁多可变的。

1.代码修复

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

1.1 类加载方案

类加载方案基于Dex分包方案,什么是Dex分包方案呢?这个得先从65536限制和LinearAlloc限制说起。
65536限制
随着应用功能越来越复杂,代码量不断地增大,引入的库也越来越多,可能会在编译时提示如下异常:

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

这说明应用中引用的方法数超过了最大数65536个。产生这一问题的原因就是系统的65536限制,65536限制的主要原因是DVM Bytecode的限制,DVM指令集的方法调用指令invoke-kind索引为16bits,最多能引用 65535个方法。
LinearAlloc限制
在安装时可能会提示INSTALL_FAILED_DEXOPT。产生的原因就是LinearAlloc限制,DVM中的LinearAlloc是一个固定的缓存区,当方法数过多超出了缓存区的大小时会报错。

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

Dex分包方案主要有两种,分别是Google官方方案、Dex自动拆包和动态加载方案。因为Dex分包方案不是本章的重点,这里就不再过多的介绍,我们接着来学习类加载方案。
Android解析ClassLoader(二)Android中的ClassLoader中讲到了ClassLoader的加载过程,其中一个环节就是调用DexPathList的findClass的方法,如下所示。
libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

 public Class<?> findClass(String name, List<Throwable> 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处遍历Element数组dexElements(相当于遍历dex文件数组),注释2处调用Element的findClass方法,其方法内部会调用DexFile的loadClassBinaryName方法查找类。如果在Element中(dex文件)找到了该类就返回,如果没有找到就接着在下一个Element中进行查找。
根据上面的查找流程,我们将有bug的类Key.class进行修改,再将Key.class打包成包含dex的补丁包Patch.jar,放在Element数组dexElements的第一个元素,这样会首先找到Patch.dex中的Key.class去替换之前存在bug的Key.class,排在数组后面的dex文件中的存在bug的Key.class根据ClassLoader的双亲委托模式就不会被加载,这就是类加载方案,如下图所示。
VnQD61.png

类加载方案需要重启App后让ClassLoader重新加载新的类,为什么需要重启呢?这是因为类是无法被卸载的,因此要想重新加载新的类就需要重启App,因此采用类加载方案的热修复框架是不能即时生效的。
虽然很多热修复框架采用了类加载方案,但具体的实现细节和步骤还是有一些区别的,比如QQ空间的超级补丁和Nuwa是按照上面说得将补丁包放在Element数组的第一个元素得到优先加载。微信Tinker将新旧apk做了diff,得到patch.dex,然后将patch.dex与手机中apk的classes.dex做合并,生成新的classes.dex,然后在运行时通过反射将classes.dex放在Element数组的第一个元素。饿了么的Amigo则是将补丁包中每个dex 对应的Element取出来,之后组成新的Element数组,在运行时通过反射用新的Element数组替换掉现有的Element 数组。

采用类加载方案的主要是以腾讯系为主,包括微信的Tinker、QQ空间的超级补丁、手机QQ的QFix、饿了么的Amigo和Nuwa等等。

1.2 底层替换方案(拓展:https://blog.csdn.net/rikkatheworld/article/details/105841838

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

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

Android 8.0的invoke方法,如下所示。
libcore/ojluni/src/main/java/java/lang/reflect/Method.java

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

invoke方法是个native方法,对应Jni层的代码为:
art/runtime/native/java_lang_reflect_Method.cc

static jobject Method_invoke(JNIEnv* env, jobject javaMethod, jobject javaReceiver,
                             jobject javaArgs) {
  ScopedFastNativeObjectAccess soa(env);
  return InvokeMethod(soa, javaMethod, javaReceiver, javaArgs);

Method_invoke函数中又调用了InvokeMethod函数:
art/runtime/reflection.cc

jobject InvokeMethod(const ScopedObjectAccessAlreadyRunnable& soa, jobject javaMethod,
                     jobject javaReceiver, jobject javaArgs, size_t num_frames) {

...
  ObjPtr<mirror::Executable> executable = soa.Decode<mirror::Executable>(javaMethod);
  const bool accessible = executable->IsAccessible();
  ArtMethod* m = executable->GetArtMethod();//1
...
}

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

class ArtMethod FINAL {
...
 protected:
  GcRoot<mirror::Class> declaring_class_;
  std::atomic<std::uint32_t> 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处的dex_cache_resolved_methods_和注释2处的entry_point_from_quick_compiled_code_,它们是方法的执行入口,当我们调用某一个方法时(比如Key的show方法),就会取得show方法的执行入口,通过执行入口就可以跳过去执行show方法。
替换ArtMethod结构体中的字段或者替换整个ArtMethod结构体,这就是底层替换方案。
AndFix采用的是替换ArtMethod结构体中的字段,这样会有兼容问题,因为厂商可能会修改ArtMethod结构体,导致方法替换失败。Sophix采用的是替换整个ArtMethod结构体,这样不会存在兼容问题。
底层替换方案直接替换了方法,可以立即生效不需要重启。采用底层替换方案主要是阿里系为主,包括AndFix、Dexposed、阿里百川、Sophix。

1.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;
		}

其中注释1处是一个成员变量localIncrementalChange ,它的值为$change$change实现了IncrementalChange这个抽象接口。当我们点击InstantRun时,如果方法没有变化则$change为null,就调用return,不做任何处理。如果方法有变化,就生成替换类,这里我们假设MainActivity的onCreate方法做了修改,就会生成替换类MainActivity$override,这个类实现了IncrementalChange接口,同时也会生成一个AppPatchesLoaderImpl类,这个类的getPatchedClasses方法会返回被修改的类的列表(里面包含了MainActivity),根据列表会将MainActivity的$change设置为MainActivity$override,因此满足了注释2的条件,会执行MainActivity$overrideaccess$dispatch方法,accessd i s p a t c h 方 法 中 会 根 据 参 数 &quot; o n C r e a t e . ( L a n d r o i d / o s / B u n d l e ; ) V &quot; 执 行 ‘ M a i n A c t i v i t y dispatch方法中会根据参数&quot;onCreate.(Landroid/os/Bundle;)V&quot;执行`MainActivitydispatch方法中会根据参数"onCreate.(Landroid/os/Bundle;)V"执行‘MainActivityoverride`的onCreate方法,从而实现了onCreate方法的修改。
借鉴Instant Run的原理的热修复框架有Robust和Aceso。

2.资源修复

Android资源热修复是指在App不重新安装的情况下,利用下发的补丁包直接更新App中的资源。目前市面上很多资源热修复方案基本上都参考了Instant Run的实现。关于Instant Run如何进行资源替换的,请看这篇热修复原理学习 第3.2节。

简单来说,Instant Run中的资源热修复分为两步:

  1. 构造一个新的AssetManager,并通过反射调用 addAssetPath(),把这个完整的新资源包加入到AssetManager中。这样就得到了一个含有所有新资源的AssetManager
  2. 找到之前引用原有AssetManager的地方,通过反射,将AssetManager类型的mAssets字段引用全部替换为新创建的 AssetManager。

这其中的重点,自然是 AssetManager.addAssetPath()这个函数,Java层的AssetManager只是个包装,所以这个方法真正的实现,是位于Native层中。

执行addAssetPath就是解析这个资源格式,然后构造出底层数据的过程。整个解析资源的调用链是:

  • public final int addAssetPath(String path)
  • android_content_AssetManager_addAssetPath
  • AssetManager::addAssetPath
  • AssetManager::AppendPathToResTable
  • ResTable::add
  • ResTable::addInternal
  • ResTable::parsePackage

解析的细节比较繁琐,这里就不详细说明了,有兴趣的可以一层层深究下去。

大致过程就是,通过传入的资源包路径,先得到其中的 resources.arsc,然后解析它的格式,存放在低层的 AssetManager的mResources成员中。

// frameworks/base/include/androidfw/AssetManager.h
class AssetManager : public AAssetManager {
    ....
    mutable ResTable* mResources;
    ....
}

AssetManager的 mResources成员是一个ResTable结构体。

class ResTable
{
    mutable Mutex mLock;  //互斥锁,用于多进程间互斥操作
    status_t mError;
    ResTable_config mParams;
   
    Vector<Header*> mHeaders; //表示所有resources.arsc原始数据,这就等同于所有通过addAssetPath加载进来的路径的资源ID信息
    Vector<PackageGroup*> mPackageGroups  //资源包的实体,包含所有加载进来的package id所对应的资源
    uint8_t mPackageMap[256]; //索引表,表示0~255的package id,每个元组分别存放该ID所属PackageGroup在mPackageGroups中的index

    uint8_t mNextPackageId; 
}

一个Android进程只包含一个ResTable,ResTable的成员变量mPackageGroups就是所有解析过的资源包的集合。
任何一个资源包中都含有resources.arsc,它记录了所有资源的ID分配情况,以及资源中的所有字符串。这些信息是以二进制数的方式存储的。底层的AssetManager做的事就是解析这个资源文件,然后把相关信息存储到 mPackageGroups里面。

3.动态链接库修复

Android平台的动态链接库主要是指 so库,热修复框架的 so的修复主要是更新 so,换句话所就是重新加载so库,因此so库的修复的基础原理就是加载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<String> candidates = new ArrayList<String>();
        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去了解这个方法。

nativeLoad()分析

先来看看其JNI层中函数

// Runtime.c
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
                   jobject javaLoader, jstring javaLibrarySearchPath)
{
    return JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath);
}

在 Runtime_nativeLoad中调用了 JVM_NativeLoad()

// OpenjdkJvm.cc

JNIEXPORT 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<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);  // 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<jobject> 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<JNI_OnLoadFn>(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库加载可以总结以下每步工作:

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

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

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

 

转载:https://blog.csdn.net/itachi85/article/details/79522200

https://blog.csdn.net/rikkatheworld/article/details/105931744

https://blog.csdn.net/rikkatheworld/article/details/105285291

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值