Android 插件化“ 插桩式 “ 插件化框架 ( 类加载器创建 | 资源加载 )

Posted 韩曙亮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 插件化“ 插桩式 “ 插件化框架 ( 类加载器创建 | 资源加载 )相关的知识,希望对你有一定的参考价值。

【Android 插件化】系列博客 :




参考 【Android 插件化】“ 插桩式 “ 插件化框架 ( 原理与实现思路 ) 中给出的实现思路 , 逐步实现 “ 插桩式 “ 插件化框架 ;




一、创建核心依赖库



创建 " android Library " 依赖库 , 作为 " 插件化 " 框架 核心依赖库 ;

在这里插入图片描述

" 宿主 " 模块 应用 , 依赖该 “ 插桩式 “ 插件化框架 核心库 , 依靠该框架核心库 , 管理 " 插件 " 模块 编译打包成的 apk 文件 ;





二、创建类加载器



创建 DexClassLoader , 使用其构造函数创建 , 需要传入四个参数到构造函数中 ;

package dalvik.system;

import java.io.File;

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }
}

DexClassLoader 构造函数 参数说明 :

String dexPath : 插件包加载路径 ;

String optimizedDirectory : 开发者指定的 apk 插件包解压后的缓存路径 ;

String librarySearchPath : 函数库的搜索路径 , 可设置为空 , 忽略 ;

ClassLoader parent : DexClassLoader 加载器的父类加载器 ;


创建插件包解压后的缓存路径 : 注意 String optimizedDirectory 参数对应的路径必须是私有的 ;

// DexClassLoader 的 optimizedDirectory 操作目录必须是私有的
// ( 模式必须是 Context.MODE_PRIVATE )
File optimizedDirectory = context.getDir("plugin", Context.MODE_PRIVATE);

创建类加载器 : 传入上述 4 4 4 个参数 , 创建类加载器 ;

// 创建 DexClassLoader
mDexClassLoader = new DexClassLoader(
        loadPath, // 加载路径
        optimizedDirectory.getAbsolutePath(), // apk 解压缓存目录
        null,
        context.getClassLoader() // DexClassLoader 加载器的父类加载器
);

注意 : 类加载时 , 只会加载一次 , 如果有重复的类 , 不会重复加载 ;

BootClassLoader 主要作用是加载 JDK 中的字节码类对象 ;

DexClassLoaderPathClassLoader 主要作用是加载 Android 和 引入的第三方库 中的字节码类对象 ;





三、加载资源



加载资源时需要使用到 AssetManager , 但是其构造函数是 隐藏 的 , 被 @Hide 注解 , 开发者无法直接调用 , 需要使用反射进行调用 ;

通过反射创建 AssetManager 对象 : 注意异常捕获 ;

// 加载资源
try {
    // 通过反射创建 AssetManager
    AssetManager assetManager = AssetManager.class.newInstance();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InstantiationException e) {
    e.printStackTrace();
}

创建 AssetManager 对象后 , 调用 addAssetPath 方法 , 添加资源的路径 , 用于加载插件包路径下的资源文件 ;

addAssetPath 也是隐藏方法 , 也是需要使用反射调用该方法 ;

// 通过反射获取 AssetManager 中的 addAssetPath 隐藏方法
Method addAssetPathMethod = assetManager.
        getClass().
        getDeclaredMethod("addAssetPath");
// 调用反射方法
addAssetPathMethod.invoke(assetManager, loadPath);

AssetManager 示例代码 :

public final class AssetManager implements AutoCloseable {
    /**
     * @hide
     */
    @UnsupportedAppUsage
    public AssetManager() {
    }
    
    /**
     * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
     * @hide
     */
    @Deprecated
    @UnsupportedAppUsage
    public int addAssetPath(String path) {
        return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
    }
}

获取 Resources 资源对象 : 通过上述加载插件资源后的 AssetManager 对象来创建 Resources 资源对象 ;

// 获取资源
mResources = new Resources(
        assetManager,
        context.getResources().getDisplayMetrics(),
        context.getResources().getConfiguration()
);

传入的 DisplayMetrics metrics 和 Configuration config 参数从调用插件包的上下文中获取 ;


加载资源部分代码示例 :

首先 , 通过反射创建 AssetManager 对象 ;
然后 , 通过反射调用并执行 AssetManager 对象的 addAssetPath 方法 , 加载插件包资源 ;
最后 , 调用 Resources 构造函数 , 创建资源 , 传入 AssetManager 对象 和 上下文相关参数 ;

// 加载资源
try {
    // 通过反射创建 AssetManager
    AssetManager assetManager = AssetManager.class.newInstance();
    
    // 通过反射获取 AssetManager 中的 addAssetPath 隐藏方法
    Method addAssetPathMethod = assetManager.
            getClass().
            getDeclaredMethod("addAssetPath");
            
    // 调用反射方法
    addAssetPathMethod.invoke(assetManager, loadPath);
    
    // 获取资源
    mResources = new Resources(
            assetManager,
            context.getResources().getDisplayMetrics(),
            context.getResources().getConfiguration()
    );
    
} catch (IllegalAccessException e) {
    // 调用 AssetManager.class.newInstance() 反射构造方法异常
    e.printStackTrace();
} catch (InstantiationException e) {
    // 调用 AssetManager.class.newInstance() 反射构造方法异常
    e.printStackTrace();
} catch (NoSuchMethodException e) {
    // getDeclaredMethod 反射方法异常
    e.printStackTrace();
} catch (InvocationTargetException e) {
    // invoke 执行反射方法异常
    e.printStackTrace();
}




四、插件管理器完整代码



插件管理器完整代码 :

package com.example.plugin_core;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.res.AssetManager;
import android.content.res.Resources;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import dalvik.system.DexClassLoader;

/**
 * 插件化框架核心类
 */
public class PluginManager {

    /**
     * 类加载器
     * 用于加载插件包 apk 中的 classes.dex 文件中的字节码对象
     */
    private DexClassLoader mDexClassLoader;

    /**
     * 从插件包 apk 中加载的资源
     */
    private Resources mResources;

    /**
     * 插件包信息类
     */
    private PackageInfo mPackageInfo;

    /**
     * 加载插件的上下文对象
     */
    private Context mContext;

    /**
     * PluginManager 单例
     */
    private static PluginManager instance;

    private PluginManager(){

    }

    /**
     * 获取单例类
     * @return
     */
    public static PluginManager getInstance(){
        if (instance == null) {
            instance = new PluginManager();
        }
        return instance;
    }

    /**
     * 加载插件
     * @param context 加载插件的应用的上下文
     * @param loadPath 加载的插件包地址
     */
    public void loadPlugin(Context context, String loadPath) {
        this.mContext = context;

        // DexClassLoader 的 optimizedDirectory 操作目录必须是私有的
        // ( 模式必须是 Context.MODE_PRIVATE )
        File optimizedDirectory = context.getDir("plugin", Context.MODE_PRIVATE);

        // 创建 DexClassLoader
        mDexClassLoader = new DexClassLoader(
                loadPath, // 加载路径
                optimizedDirectory.getAbsolutePath(), // apk 解压缓存目录
                null,
                context.getClassLoader() // DexClassLoader 加载器的父类加载器
        );

        // 加载资源
        try {
            // 通过反射创建 AssetManager
            AssetManager assetManager = AssetManager.class.newInstance();

            // 通过反射获取 AssetManager 中的 addAssetPath 隐藏方法
            Method addAssetPathMethod = assetManager.
                    getClass().
                    getDeclaredMethod("addAssetPath");

            // 调用反射方法
            addAssetPathMethod.invoke(assetManager, loadPath);

            // 获取资源
            mResources = new Resources(
                    assetManager,
                    context.getResources().getDisplayMetrics(),
                    context.getResources().getConfiguration()
            );

        } catch (IllegalAccessException e) {
            // 调用 AssetManager.class.newInstance() 反射构造方法异常
            e.printStackTrace();
        } catch (InstantiationException e) {
            // 调用 AssetManager.class.newInstance() 反射构造方法异常
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            // getDeclaredMethod 反射方法异常
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // invoke 执行反射方法异常
            e.printStackTrace();
        }
    }
}




五、博客资源



博客资源 :

以上是关于Android 插件化“ 插桩式 “ 插件化框架 ( 类加载器创建 | 资源加载 )的主要内容,如果未能解决你的问题,请参考以下文章

Android 插件化“ 插桩式 “ 插件化框架 ( 获取插件入口 Activity 组件 | 加载插件 Resources 资源 )

Android 插件化“ 插桩式 “ 插件化框架 ( 代理 Activity 组件开发 )

Android 插件化“ 插桩式 “ 插件化框架 ( 类加载器创建 | 资源加载 )

Android 插件化“ 插桩式 “ 插件化框架 ( 原理与实现思路 )

Android 插件化Hook 插件化框架 ( 加载插件包资源 )

Android 插件化Hook 插件化框架 ( hook 插件化原理 | 插件包管理 )