前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Android 插件化】“ 插桩式 “ 插件化框架 ( 类加载器创建 | 资源加载 )

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

作者头像
韩曙亮
发布2023-03-29 13:23:17
1.3K0
发布2023-03-29 13:23:17
举报
文章被收录于专栏:韩曙亮的移动开发专栏

Android 插件化系列文章目录

【Android 插件化】插件化简介 ( 组件化与插件化 )

【Android 插件化】插件化原理 ( JVM 内存数据 | 类加载流程 )

【Android 插件化】插件化原理 ( 类加载器 )

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

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

【Android 插件化】“ 插桩式 “ 插件化框架 ( 注入上下文的使用 )

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

【Android 插件化】“ 插桩式 “ 插件化框架 ( 运行应用 | 代码整理 )


文章目录

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

一、创建核心依赖库


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

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

二、创建类加载器


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

代码语言:javascript
复制
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 参数对应的路径必须是私有的 ;

代码语言:javascript
复制
// DexClassLoader 的 optimizedDirectory 操作目录必须是私有的
// ( 模式必须是 Context.MODE_PRIVATE )
File optimizedDirectory = context.getDir("plugin", Context.MODE_PRIVATE);

创建类加载器 : 传入上述

4

个参数 , 创建类加载器 ;

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

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

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

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

三、加载资源


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

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

代码语言:javascript
复制
// 加载资源
try {
    // 通过反射创建 AssetManager
    AssetManager assetManager = AssetManager.class.newInstance();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InstantiationException e) {
    e.printStackTrace();
}

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

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

代码语言:javascript
复制
// 通过反射获取 AssetManager 中的 addAssetPath 隐藏方法
Method addAssetPathMethod = assetManager.
        getClass().
        getDeclaredMethod("addAssetPath");
// 调用反射方法
addAssetPathMethod.invoke(assetManager, loadPath);

AssetManager 示例代码 :

代码语言:javascript
复制
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 资源对象 ;

代码语言:javascript
复制
// 获取资源
mResources = new Resources(
        assetManager,
        context.getResources().getDisplayMetrics(),
        context.getResources().getConfiguration()
);

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

加载资源部分代码示例 :

首先 , 通过反射创建 AssetManager 对象 ;

然后 , 通过反射调用并执行 AssetManager 对象的 addAssetPath 方法 , 加载插件包资源 ;

最后 , 调用 Resources 构造函数 , 创建资源 , 传入 AssetManager 对象 和 上下文相关参数 ;

代码语言:javascript
复制
// 加载资源
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();
}

四、插件管理器完整代码


插件管理器完整代码 :

代码语言:javascript
复制
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();
        }
    }
}

五、博客资源


博客资源 :

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-06-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Android 插件化系列文章目录
    • 文章目录
    • 一、创建核心依赖库
    • 二、创建类加载器
    • 三、加载资源
    • 四、插件管理器完整代码
    • 五、博客资源
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档