由Tinker了解热修复原理

资源修复

首先必须了解 Instant Run 机制

Instant Run 是 Android Studio 2.0 以后新增的一个运行机制,在 Android Plugin 2.3 之前,它使用的 Multidex 实现。在 Android Plugin 2.3 之后,它使用 Android 5.0 新增的 Split APK 机制。

资源和 Manifest 都放在 Base APK 中, 在 Base APK 中代码只有 Instant Run 框架,应用的本身的代码都在 Split APK 中。

Instant Run 有三种模式,如果是热交换和温交换,我们都无需重新安装新的 Split APK,它们的区别在于是否重启 Activity。对于冷交换,我们需要通过 adb install-multiple -r -t 重新安装改变的  Split APK,应用也需要重启。

修改一个现有方法的代码时会采用热交换。修改或删除一个现有的资源文件时会采用温交换。添加、删除或修改一个字段和方法、添加一个类等时采用冷交换,App 需要重启。

Android 工程在运行的时候需要引用资源,不管是什么样的资源,最终都能通过一个入口来获取,而这个入口就是 Resources 对象。Resources 对象描述了 android 资源文件,可以认为,所有涉及到获取资源的地方,都可以使用 Resources 来获取。

当编译工程的时候,资源和源文件都将会打包到 apk 里面,Resources 中 AssetManager 的路径默认指向了该 apk 的文件,所以在启动应用的时候,Resources 通过 Assetmanager,AssetManager 再根据设定的路径查找 apk 中的资源,就可以正确的找到自己的资源了。所以如果我们让 AssetManager 指向自己设定的 Apk 资源路径,就可以完成提取 apk 资源的目的了。

LoadedApk源码

ActivityThread 执行 main 时会执行 attach() 方法,之后在与 ActivityManagerService 通信后,在创建 Context 之前创建了 LoadedApk。LoadedApk 的构造函数,它将 ActivityThread、ClassLoader 等进行了封装。LoadedApk 保存了一个 apk 的完整信息。

    public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
            CompatibilityInfo compatInfo, ClassLoader baseLoader,
            boolean securityViolation, boolean includeCode, boolean registerPackage) {

        mActivityThread = activityThread;
        setApplicationInfo(aInfo);
        mPackageName = aInfo.packageName;
        mBaseClassLoader = baseLoader;
        mSecurityViolation = securityViolation;
        mIncludeCode = includeCode;
        mRegisterPackage = registerPackage;
        mDisplayAdjustments.setCompatibilityInfo(compatInfo);
    }

其中 setApplicationInfo(aInfo);其中包含 mResDir 路径。

Resources源码

LoadedApk 创建后,创建了 Context

    static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
                null);
        context.setResources(packageInfo.getResources());
        return context;
    }

这里将 packageInfo 的 Resources 赋值给 context

    public Resources getResources() {
        if (mResources == null) {
            //...
            mResources = ResourcesManager.getInstance().getResources(null, mResDir,
                    splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
                    Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
                    getClassLoader());
        }
        return mResources;
    }

mResources 是由 ResourcesManager 创建的,其中 mResDir 特别注意下。getResources 中将参数封装成 ResourcesKey 对象,由 ResourcesKey 对象调用 createResourcesImpl() 方法得到 ResourcesImpl 对象。

    private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
        daj.setCompatibilityInfo(key.mCompatInfo);

        final AssetManager assets = createAssetManager(key);
        if (assets == null) {
            return null;
        }

        final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
        final Configuration config = generateConfig(key, dm);
        final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);

        if (DEBUG) {
            Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
        }
        return impl;
    }

可以看到由 AssetManager、DisplayMetrics、Configuration、DisplayAdjustments 创建出 ResourcesImpl 对象。其中 createAssetManager() 方法如下

    protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
        AssetManager assets = new AssetManager();

        if (key.mResDir != null) {
            if (assets.addAssetPath(key.mResDir) == 0) {
                Log.e(TAG, "failed to add asset path " + key.mResDir);
                return null;
            }
        }
        //...
    }

AssetManager 将 mResDir 保存,之后我们将 ResourcesImpl 生成 Resources 对象。这样利用 mResDir 设置 AssetManager 的属性,并创建 Resource 对象,resource 对象和 mResDir 一一对应。

由以上知识我们看下 Tinker 源码

单独一个初始化成员 isResourceCanPatch 就包含很多,详细分析下源码(反射相关方法这里就不贴了)

    public static void isResourceCanPatch(Context context) throws Throwable {
        //反射ActivityThread类得到当前的ActivityThread
        //getActivityThread方法中通过反射拿到了当前的ActivityThread的currentActivityThread方法
        //下面有列出来
        Class<?> activityThread = Class.forName("android.app.ActivityThread");
        currentActivityThread = ShareReflectUtil.getActivityThread(context, activityThread);

        //LoadedApk
        Class<?> loadedApkClass;
        try {
            loadedApkClass = Class.forName("android.app.LoadedApk");
        } catch (ClassNotFoundException e) {
            loadedApkClass = Class.forName("android.app.ActivityThread$PackageInfo");
        }
        //得到mResDir
        /**
         *    private String mResDir;
         */
        resDir = findField(loadedApkClass, "mResDir");
        //得到mPackages、mResourcePackages
        /**
         *    @GuardedBy("mResourcesManager")
         *     final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
         *     @GuardedBy("mResourcesManager")
         *     final ArrayMap<String, WeakReference<LoadedApk>> mResourcePackages = new ArrayMap<>();
         */
        packagesFiled = findField(activityThread, "mPackages");
        if (Build.VERSION.SDK_INT < 27) {
            resourcePackagesFiled = findField(activityThread, "mResourcePackages");
        }

        //得到现有的assets
        final AssetManager assets = context.getAssets();
        //拿到addAssetPath方法
        /**
         *     public final int addAssetPath(String path) {
         *         return  addAssetPathInternal(path, false);
         *     }
         */
        addAssetPathMethod = findMethod(assets, "addAssetPath", String.class);


        try {
            //拿到mStringBlocks属性和ensureStringBlocks方法
            /**
             *     private StringBlock mStringBlocks[] = null;
             *    final StringBlock[] ensureStringBlocks() {
             *         synchronized (this) {
             *             if (mStringBlocks == null) {
             *                 makeStringBlocks(sSystem.mStringBlocks);
             *             }
             *             return mStringBlocks;
             *         }
             *     }
             */
            stringBlocksField = findField(assets, "mStringBlocks");
            ensureStringBlocksMethod = findMethod(assets, "ensureStringBlocks");
        } catch (Throwable ignored) {
            // Ignored.
        }

        //创建新的AssetManager
        newAssetManager = (AssetManager) findConstructor(assets).newInstance();

        // 版本判断我这边源码时8.0
        if (SDK_INT >= KITKAT) {
            //得到ResourcesManager
            final Class<?> resourcesManagerClass = Class.forName("android.app.ResourcesManager");
            //单例
            /**
             *     public static ResourcesManager getInstance() {
             *         synchronized (ResourcesManager.class) {
             *             if (sResourcesManager == null) {
             *                 sResourcesManager = new ResourcesManager();
             *             }
             *             return sResourcesManager;
             *         }
             *     }
             */
            final Method mGetInstance = findMethod(resourcesManagerClass, "getInstance");
            final Object resourcesManager = mGetInstance.invoke(null);
            try {
                //得到mActiveResources
                Field fMActiveResources = findField(resourcesManagerClass, "mActiveResources");
                final ArrayMap<?, WeakReference<Resources>> activeResources19 =
                        (ArrayMap<?, WeakReference<Resources>>) fMActiveResources.get(resourcesManager);
                references = activeResources19.values();
            } catch (NoSuchFieldException ignore) {
                // android N将资源移到了 mResourceReferences中
                /**
                 *    private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
                 */
                final Field mResourceReferences = findField(resourcesManagerClass, "mResourceReferences");
                references = (Collection<WeakReference<Resources>>) mResourceReferences.get(resourcesManager);
            }
        } else {
            //从activityThread中获取mActiveResources
            final Field fMActiveResources = findField(activityThread, "mActiveResources");
            final HashMap<?, WeakReference<Resources>> activeResources7 =
                    (HashMap<?, WeakReference<Resources>>) fMActiveResources.get(currentActivityThread);
            references = activeResources7.values();
        }
        // check resource
        if (references == null) {
            throw new IllegalStateException("resource references is null");
        }

        final Resources resources = context.getResources();


        if (SDK_INT >= 24) {
            try {
                /**
                 *     private ResourcesImpl mResourcesImpl;
                 */
                resourcesImplFiled = findField(resources, "mResourcesImpl");
            } catch (Throwable ignore) {
                // for safety
                assetsFiled = findField(resources, "mAssets");
            }
        } else {
            assetsFiled = findField(resources, "mAssets");
        }

        try {
            //外部解析的资源路径
            /**
             *     public String publicSourceDir;
             */
            publicSourceDirField = findField(ApplicationInfo.class, "publicSourceDir");
        } catch (NoSuchFieldException ignore) {
            // Ignored.
        }
    }

下来看下替换方法

    public static void monkeyPatchExistingResources(Context context, String externalResourceFile) throws Throwable {
        //检查外部资源路径
        if (externalResourceFile == null) {
            return;
        }

        final ApplicationInfo appInfo = context.getApplicationInfo();

        final Field[] packagesFields;
        //根据不同的版本得到成员变量 packagesFields
        if (Build.VERSION.SDK_INT < 27) {
            packagesFields = new Field[]{packagesFiled, resourcePackagesFiled};
        } else {
            packagesFields = new Field[]{packagesFiled};
        }
        //遍历,注意类型ArrayMap<String, WeakReference<LoadedApk>>
        for (Field field : packagesFields) {
            final Object value = field.get(currentActivityThread);

            //?是LoadedApk,遍历弱引用集合
            for (Map.Entry<String, WeakReference<?>> entry
                    : ((Map<String, WeakReference<?>>) value).entrySet()) {
                final Object loadedApk = entry.getValue().get();
                if (loadedApk == null) {
                    continue;
                }
                //原来的内部sourceDir替换为externalResourceFile
                final String resDirPath = (String) resDir.get(loadedApk);
                if (appInfo.sourceDir.equals(resDirPath)) {
                    resDir.set(loadedApk, externalResourceFile);
                }
            }
        }

        // 用addAssetPath方法加载外部的资源
        if (((Integer) addAssetPathMethod.invoke(newAssetManager, externalResourceFile)) == 0) {
            throw new IllegalStateException("Could not create new AssetManager");
        }


        if (stringBlocksField != null && ensureStringBlocksMethod != null) {
            //stringBlocksField和ensureStringBlocksMethod将newAssetManager替换
            stringBlocksField.set(newAssetManager, null);
            ensureStringBlocksMethod.invoke(newAssetManager);
        }

        //遍历Resources的弱引用集合
        for (WeakReference<Resources> wr : references) {
            final Resources resources = wr.get();
            if (resources == null) {
                continue;
            }
            //根据不同的版本,遍历并得到弱引用集合中的Resources,将Resources的mAssets字段引用替换成新的newAssetManager
            try {
                assetsFiled.set(resources, newAssetManager);
            } catch (Throwable ignore) {
                // N
                final Object resourceImpl = resourcesImplFiled.get(resources);
                // for Huawei HwResourcesImpl
                final Field implAssets = findField(resourceImpl, "mAssets");
                implAssets.set(resourceImpl, newAssetManager);
            }

            clearPreloadTypedArrayIssue(resources);
            //更新配置
            resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics());
        }

        // Handle issues caused by WebView on Android N.
        // Issue: On Android N, if an activity contains a webview, when screen rotates
        // our resource patch may lost effects.
        // for 5.x/6.x, we found Couldn't expand RemoteView for StatusBarNotification Exception
        if (Build.VERSION.SDK_INT >= 24) {
            try {
                if (publicSourceDirField != null) {
                    //外部路径也同样设置
                    publicSourceDirField.set(context.getApplicationInfo(), externalResourceFile);
                }
            } catch (Throwable ignore) {
                // Ignored.
            }
        }

        if (!checkResUpdate(context)) {
            throw new TinkerRuntimeException(ShareConstants.CHECK_RES_INSTALL_FAIL);
        }
    }

由此可以看出 Instant Run 中的资源修复可以简单总结为两个步骤

1、先创建新的 AssetManager,通过反射调用 addAssetPath 方法加载外部的资源,这样新创建的 AssetManager 就含有了外部资源。

2、将 AssetManager 类型的 mAssets 字段的引用全部替换为新的 AssetManager。

代码修复

类加载方案

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

首先先看下类加载方案

    public Class<?> findClass(String name, List<Throwable> suppressed) {
        //遍历Element数组dexElements
        for (Element element : dexElements) {
            //调用element的findClass方法,其内部会调用DexFile的loadClassBinaryName方法查找类
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            //找到了该类,直接返回,没有找到继续
            if (clazz != null) {
                return clazz;
            }
        }
        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

Element 内部封装了 DexFile,DexFile 用于加载 dex 文件。

我们可以将有 Bug 的类进行修改,再将修改后的类打包成 dex 补丁包,放在 Element 数组的第一个元素,这样会先加载此类,排在后面的之前有 Bug 的类根据 ClassLoader 的双亲委托模式就不会加载。

由以上知识我们看下 Tinker 源码

installDexes 是加载 patch 文件的方法

    public static void installDexes(Application application, ClassLoader loader, File dexOptDir, List<File> files, boolean isProtectedApp)
        throws Throwable {
        Log.i(TAG, "installDexes dexOptDir: " + dexOptDir.getAbsolutePath() + ", dex size:" + files.size());
        //files表示 Patch文件集合
        if (!files.isEmpty()) {
            //文件排序
            files = createSortedAdditionalPathEntries(files);
            ClassLoader classLoader = loader;
            //不同版本判断
            if (Build.VERSION.SDK_INT >= 24 && !isProtectedApp) {
                classLoader = NewClassLoaderInjector.inject(application, loader, dexOptDir, files);
            } else {
                //这里以V23为例
                if (Build.VERSION.SDK_INT >= 23) {

                    V23.install(classLoader, files, dexOptDir);
                } else if (Build.VERSION.SDK_INT >= 19) {
                    V19.install(classLoader, files, dexOptDir);
                } else if (Build.VERSION.SDK_INT >= 14) {
                    V14.install(classLoader, files, dexOptDir);
                } else {
                    V4.install(classLoader, files, dexOptDir);
                }
            }
            //install done
            sPatchDexCount = files.size();
            Log.i(TAG, "after loaded classloader: " + classLoader + ", dex size:" + sPatchDexCount);

            if (!checkDexInstall(classLoader)) {
                //reset patch dex
                SystemClassLoaderAdder.uninstallPatchDex(classLoader);
                throw new TinkerRuntimeException(ShareConstants.CHECK_DEX_INSTALL_FAIL);
            }
        }
    }

这里判断不同版本,在不同版本下执行不同的方法。这里以 V23 为例

        private static void install(ClassLoader loader, List<File> additionalClassPathEntries, File optimizedDirectory)
            throws IllegalArgumentException, IllegalAccessException,
            NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
            //找到父类BaseDexClassLoader的pathList
            /**
             *     private final DexPathList pathList;
             */
            Field pathListField = ShareReflectUtil.findField(loader, "pathList");
            //pathList
            Object dexPathList = pathListField.get(loader);
            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList,
                new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
                suppressedExceptions));
            if (suppressedExceptions.size() > 0) {
                for (IOException e : suppressedExceptions) {
                    Log.w(TAG, "Exception in makePathElement", e);
                    throw e;
                }

            }
        }

其中 makePathElements 方法是调用系统的 makePathElements 方法去构建 dexElements 对象。

        private static Object[] makePathElements(
            Object dexPathList, ArrayList<File> files, File optimizedDirectory,
            ArrayList<IOException> suppressedExceptions)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {

            /**
             * dexPathList  原数组
             * files        补丁包
             */
            Method makePathElements;
            try {
                //反射得到方法 makePathElements
                /**
                 *    private static Element[] makePathElements(List<File> files, File optimizedDirectory,
                 *             List<IOException> suppressedExceptions) {
                 *         return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);
                 *     }
                 */
                makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class, File.class,
                    List.class);
            } catch (NoSuchMethodException e) {
                //...
            }
            //传入参数
            return (Object[]) makePathElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions);
        }
    }

调用 expandFieldArray 方法将新旧两种 dexElements 合并

    public static void expandFieldArray(Object instance, String fieldName, Object[] extraElements)
        throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        //instance      dexPathList
        //fieldName     "dexElements"
        //extraElements NativeLibraryElement[]

        //得到dexElements数组
        /**
         *     private Element[] dexElements;
         */
        Field jlrField = findField(instance, fieldName);
        //dexElements原有的数组
        Object[] original = (Object[]) jlrField.get(instance);
        //组成新的数组,设置长度为原数组长度+patch长度
        /**
         * getComponentType() 返回数组中元素的Class对象,如果不是Class对象那么返回null
         */
        Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length);

        /**
         * public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
         *   Object src : 原数组
         *    int srcPos : 从元数据的起始位置开始
         *   Object dest : 目标数组
         *   int destPos : 目标数组的开始起始位置
         *   int length  : 要copy的数组的长度
         */
        //将patch包数组拷贝到combined中,从0开始完全拷贝
        System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
        //将原来数组original拷贝到combined中,从上次拷贝位置开始
        System.arraycopy(original, 0, combined, extraElements.length, original.length);

        //设置dexElements数组为combined
        jlrField.set(instance, combined);
    }

其中两个 System.arraycopy 看出新的 patch 包放在前面。

类加载方案需要重启 App 后让 ClassLoader 重新加载新的类,不能即使生效。因为类是无法被卸载的,要想重新加载新的类就需要重启 App。

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

底层替换方案

底层替换不会再次加载类,而是直接在 Native 层修改原有类,由于在原有类进行修改限制比较多,且不能增减原有类的方法和字段(如果增加了方法,方法索引数也会增加,这样访问方法时会无法通过索引正确找到方法)。

可以看下 Method 的 invoke() 方法

 

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

native 方法

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);
}

调用了 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();
//传入的javaMethod在ART虚拟机中对应一个ArtMethod指针
//ArtMethod结构体中包含了Java方法的所有信息,包括执行入口、访问权限、所属类、代码执行地址等
  ArtMethod* m = executable->GetArtMethod();
//...
}

ArtMethod 结构如下,art\runtime\art_method.hart_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 {
    //方法的执行入口
    mirror::MethodDexCacheType* dex_cache_resolved_methods_;
    void* data_;
    void* entry_point_from_quick_compiled_code_;
  } ptr_sized_fields_;


}

替换 ArtMethod 结构体中的字段或者替换整个 ArtMethod 结构体,可以达到修复 bug 的目的。

AndFix 采用的是替换 ArtMethod 结构体中的字段,这样会出现部分厂商修改 ArtMethod 结构体所出现的问题。Spohix 采用的是替换整个 ArtMethod 结构体。

底层替换方案直接替换了方法,可以立即生效,不需要重启。采用底层替换的主要是阿里系。包括AndFix、Dexposed、阿里百川、Sophix。

Instant Run

首先了解下 ASM,ASM 是一个 Java 字节码操控的架构,它能动态生成类或者增强现有类的功能。ASM 的优势是提供了一个 Visitor 模式的访问接口(Core API),使用者可以不用关心字节码的格式,只需要在每个 Visitor 的位置关心自己所修改的结构即可。

由于本地变量表的最大数和操作数栈的最大深度是在编译时就确定的,所以在使用 ASM 进行字节码操作后需要调用 ASM 提供的 visitMaxs 方法来设置 maxLocal 和 maxStack 数。

关于AMS可以参考以下

ASM 6 Developer Guide

github相关:HunterHibeaverlancet

这里我也不太熟悉,正在学习此方面知识。

Robust、Aceso 等热修复框架就是基于此实现的。

so库

了解下so库的加载过程

libcore\ojluni\src\main\java\java\lang\System.java

    //filename表示so在磁盘的完整路径,用于加载指定的so
    public static void load(String filename) {
        Runtime.getRuntime().load0(VMStack.getStackClass1(), filename);
    }
    //libname表示so的名称
    @CallerSensitive
    public static void loadLibrary(String libname) {
        Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
    }

两个方法都调用到了 doLoad() 方法,这里贴出loadLibrary0方法

    synchronized void loadLibrary0(ClassLoader loader, String libname) {
      //...
        String libraryName = libname;
        //loader为null,创建
        if (loader != null) {
            /**
             *     public String findLibrary(String name) {
             *         return pathList.findLibrary(name);
             *     }
             */
            String filename = loader.findLibrary(libraryName);
            if (filename == null) {
                throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
                                               System.mapLibraryName(libraryName) + "\"");
            }
            //调用doload
            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;
        //遍历getLibPaths,这个方法返回java.library.path选项配置的路径数组
        for (String directory : getLibPaths()) {
            //拼接出so路径
            String candidate = directory + filename;
            candidates.add(candidate);

            if (IoUtils.canOpenReadOnly(candidate)) {
                //调用doLoad
                String error = doLoad(candidate, loader);
                if (error == null) {
                    return; // We successfully loaded the library. Job done.
                }
                lastError = error;
            }
        }

        if (lastError != null) {
            throw new UnsatisfiedLinkError(lastError);
        }
        throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
    }

其中 loader.findLibrary 在代码中标注了,看下 pathList.findLibrary

    public String findLibrary(String libraryName) {
        String fileName = System.mapLibraryName(libraryName);

        for (NativeLibraryElement element : nativeLibraryPathElements) {
            //返回so的路径
            String path = element.findNativeLibrary(fileName);

            if (path != null) {
                return path;
            }
        }

        return null;
    }

我们可以将 so 补丁插入到 nativeLibraryPathElements 数组的前部,让 so 补丁先被返回。

看下 Tinker 源码, installNavitveLibraryABI 方法

    public static boolean installNavitveLibraryABI(Context context, String currentABI) {
        // 检查 tinker 有没有安装

        // 检查 tinker 加载的结果

        // 检查当前 ABI 的 so 文件夹是否存在

        // 获取 classloader

        // 加载当前 ABI 的所有 so 补丁文件
        try {
            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());
        }
    }

installNativeLibraryPath 中判断版本去处理相关逻辑,这里选取V25

private static final class V25 {
    private static void install(ClassLoader classLoader, File folder)  throws Throwable {
        //由classLoader得到 pathList 对象
        final Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");
        final Object dexPathList = pathListField.get(classLoader);
        //得到DexPathList 中的 nativeLibraryDirectories
        /**
         *     private final List<File> nativeLibraryDirectories;
         */
        final Field nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories");

        List<File> origLibDirs = (List<File>) nativeLibraryDirectories.get(dexPathList);
        if (origLibDirs == null) {
            origLibDirs = new ArrayList<>(2);
        }
        //遍历原来的,与folder一致则删除
        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);
        //得到DexPathList 中的 systemNativeLibraryDirectories
        /**
         *     private final List<File> systemNativeLibraryDirectories;
         */
        final Field systemNativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "systemNativeLibraryDirectories");
        List<File> origSystemLibDirs = (List<File>) systemNativeLibraryDirectories.get(dexPathList);
        if (origSystemLibDirs == null) {
            origSystemLibDirs = new ArrayList<>(2);
        }
        //创建新的newLibDirs 集合,并添加上面反射得到的集合
        final List<File> newLibDirs = new ArrayList<>(origLibDirs.size() + origSystemLibDirs.size() + 1);
        newLibDirs.addAll(origLibDirs);
        newLibDirs.addAll(origSystemLibDirs);
        //调用的makePathElements方法去构建 NativeLibraryElement[] 对象。
        final Method makeElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class);

        final Object[] elements = (Object[]) makeElements.invoke(dexPathList, newLibDirs);
        //得到 nativeLibraryPathElements,并设置
        /**
         *     private final NativeLibraryElement[] nativeLibraryPathElements;
         */
        final Field nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements");
        nativeLibraryPathElements.set(dexPathList, elements);
    }
}

最后进入 Android 源码看下 doLoad 函数

    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);
        }
    }

调到了 native 层的 nativeLoad

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();
  {
    //根据so的名称从libraries_中获取对应的SharedLibrary类型指针library
    library = libraries_->Get(path);
  }
  void* class_loader_allocator = nullptr;
  {
    ScopedObjectAccess soa(env);
    ObjPtr<mirror::ClassLoader> loader = soa.Decode<mirror::ClassLoader>(class_loader);

    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
    if (class_linker->IsBootClassLoader(soa, loader.Ptr())) {
      loader = nullptr;
      class_loader = nullptr;
    }

    class_loader_allocator = class_linker->GetAllocatorForClassLoader(loader.Ptr());
    CHECK(class_loader_allocator != nullptr);
  }
//判断so是否被加载过
  //如果满足,说明此前加载过该so
  if (library != nullptr) {   
    //加载过,判断此前加载的ClassLoader 和当前传入的ClassLoader 是否相同,避免重复加载
    if (library->GetClassLoaderAllocator() != class_loader_allocator) {
         return false;
    }   
    //判断上次加载so的结果
    if (!library->CheckOnLoadResult()) {
      return false;
    }
    //判断正确,返回true
    return true;
  }

  Locks::mutator_lock_->AssertNotHeld(self);
  const char* path_str = path.empty() ? nullptr : path.c_str();
  bool needs_native_bridge = false;
  //打开path_str的so库,得到so句柄handle
  void* handle = android::OpenNativeLibrary(env,
                                            runtime_->GetTargetSdkVersion(),
                                            path_str,
                                            class_loader,
                                            library_path,
                                            &needs_native_bridge,
                                            error_msg);
    //判断handle,句柄打开失败,返回false
  if (handle == nullptr) {
    return false;
  }

  if (env->ExceptionCheck() == JNI_TRUE) {
    env->ExceptionDescribe();
    env->ExceptionClear();
  }
  bool created_library = false;
  {
    //创建SharedLibrary,并将so的句柄作为参数传入进去
    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_);
    //传入path对应的library
    library = libraries_->Get(path);
    if (library == nullptr) {  // We won race to get libraries_lock.
      library = new_library.release();
      libraries_->Put(path, library);
      created_library = true;
    }
  }
  if (!created_library) {  
    return library->CheckOnLoadResult();
  }

  bool was_successful = false;
  //查找“JNI_OnLoad”函数的指针并赋值给sym,JNI_OnLoad用于native方法动态注册
  void* sym = library->FindSymbol("JNI_OnLoad", nullptr);
  //没有找到JNI_OnLoad函数,说明加载成功。因为并不是所有的so都定义了JNI_OnLoad的函数,native方法除了动态注册还有静态注册
  if (sym == nullptr) {
    was_successful = true;
  } else {//执行JNI_OnLoad函数
    ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
    self->SetClassLoaderOverride(class_loader);

    typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);

    int version = (*jni_on_load)(this, nullptr);

    if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) {
      EnsureFrontOfChain(SIGSEGV);
    }

    self->SetClassLoaderOverride(old_class_loader.get());
    //判断version,为JNI_ERR、BadJniVersion执行失败
    if (version == JNI_ERR) {
      //err
    } else if (JavaVMExt::IsBadJniVersion(version)) {
      //err
    } else {
      was_successful = true;
    }  
  }
    
  library->SetResult(was_successful);
  return was_successful;
}

总结

热修复给国内的 Android 生态也带来一些不太好的影响,比如增加用户 ROM 体积占用、App 启动变慢 15%、OTA 首次卡顿等。特别是 Android Q 之后,动态加载的 Dex 都只使用解释模式执行,会加剧对启动性能的影响。建议阅读移动开发的罗曼蒂克消亡史这篇文章,但是我们对技术的学习必须要认真。

Google 在 Android P 新增了 AppComponentFactory API,并且在 Android Q 增加了替换 Classloader 的接口 instantiateClassloader。在 Android Q 以后,我们可以实现在运行时替换已经存在 ClassLoader 和四大组件。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值