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

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

文章目录

Android 插件化系列文章目录

前言

一、" 宿主 " 模块启动 " 插件 " 模块过程

1、" 插件 " 模块类名获取

2、" 插件 " 模块类加载器获取

二、" 宿主 " 模块加载 " 插件 " 模块中的资源文件

三、博客资源

前言

上一篇博客 【Android 插件化】“ 插桩式 “ 插件化框架 ( 注入上下文的使用 ) 中对注入的上下文进行了相关代理操作 , 插件包中的界面组件 PluginActivity 凡是涉及上下文的操作 , 都统一委托给注入的上下文来完成 ;





一、" 宿主 " 模块启动 " 插件 " 模块过程



1、" 插件 " 模块类名获取


加载 " 插件 " 模块 apk 安装包 :


拷贝插件包 : 应用启动后 , 先将插件包 apk 文件从 assets 目录拷贝到 getExternalFilesDir(null) 目录中 ;


加载插件包 : 使用 PluginManager 加载插件包 ;


/*
    加载 " 插件 " 模块的 apk 文件
    先将该插件包拷贝到
 */
String path = getExternalFilesDir(null).getAbsolutePath() + "/plugin-debug.apk";
PluginManager.getInstance().loadPlugin(this, path);


获取 " 插件 " 模块中的 Activity 数组信息 :


// 获取  " 插件 " 模块中的 Activity 数组信息
ActivityInfo[] activityInfos = PluginManager.getInstance().getmPackageInfo().activities;


获取插件包信息 : 其中的 loadPath , 就是 getExternalFilesDir(null).getAbsolutePath()

+ "/plugin-debug.apk" 路径 ;
/**
 * 插件化框架核心类
 */
public class PluginManager {
    /**
     * 插件包信息类
     */
    private PackageInfo mPackageInfo;
    /**
     * 加载插件
     * @param context 加载插件的应用的上下文
     * @param loadPath 加载的插件包地址
     */
    public void loadPlugin(Context context, String loadPath) {
        this.mContext = context;
            // 获取插件包中的 Activity 类信息
            mPackageInfo = context.getPackageManager().getPackageArchiveInfo(
                    loadPath, // 加载的插件包路径
                    PackageManager.GET_ACTIVITIES); // 获取的包信息类名
    }
    /**
     * 获取插件包中的 Package 信息
     * @return
     */
    public PackageInfo getmPackageInfo() {
        return mPackageInfo;
    }
}




获取要启动的 Activity 全类名 : 获取插件包中的 Launcher Activity 的全类名 , 并封装到 Intent 中 , 启动 Activity ;


public void onClick(View view) {
        // 跳转到 plugin_core 模块的代理 Activity
        // 首先要获取 " 插件 " 模块中的入口 Activity 类
        Intent intent = new Intent(this, ProxyActivity.class);
        // 获取  " 插件 " 模块中的 Activity 数组信息
        ActivityInfo[] activityInfos = PluginManager.getInstance().getmPackageInfo().activities;
        // 获取的插件包中的 Activity 不为空 , 才进行界面跳转
        if (activityInfos.length > 0) {
            // 这里取插件包中的第 0 个 Activity
            // 次序就是在 AndroidManifest.xml 清单文件中定义 Activity 组件的次序
            // 必须将 Launcher Activity 定义在第一个位置
            // 不能在 Launcher Activity 之前定义 Activity 组件
            // 传入的是代理的目标组件的全类名
            intent.putExtra("CLASS_NAME", activityInfos[0]);
            startActivity(intent);
        }
    }


特别注意在插件包的 AndroidManifest.xml 清单文件中定义 Activity 组件的次序 , Launcher Activity 必须定义为底 1 11 个 ;


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="kim.hsl.plugin">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Plugin">
        <activity android:name=".PluginActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>



2、" 插件 " 模块类加载器获取


拿到了插件包中要启动 Activity 的全类名 , 在 ProxyActivity 中 , 就可以根据该 Activity 全类名 , 使用 从 PluginManager 中获取的 类加载器 , 使用 反射 加载该全类名对应的 Activity 类 , 并使用反射初始化该类 ;


/**
 * 该 Activity 只是个空壳 ;
 * 主要用于持有从 apk 加载的 Activity 类
 * 并在 ProxyActivity  声明周期方法中调用对应 PluginActivity 类的生命周期方法
 */
public class ProxyActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_proxy);
        // 要代理的目标组件的全类名
        className = getIntent().getStringExtra("CLASS_NAME");
        // 注意此处的 ClassLoader 类加载器必须是插件管理器中的类加载器
        try {
            // 使用类加载器加载插件中的界面组件
            Class<?> clazz = getClassLoader().loadClass(className);
            // 使用反射创建插件界面组件 Activity
            Activity activity = (Activity) clazz.newInstance();
            // 判断 Activity 组件是否是 PluginActivityInterface 接口类型的
            if (activity instanceof PluginActivityInterface){
                // 如果是 PluginActivityInterface 类型 , 则强转为该类型
                this.pluginActivity = (PluginActivityInterface) activity;
                // 上下文注入
                // 将该 ProxyActivity 绑定注入到 插件包的 PluginActivity 类中
                // 该 PluginActivity 具有运行的上下文
                // 一旦绑定注入成功 , 则被代理的 PluginActivity 也具有了上下文
                pluginActivity.attach(this);
                // 调用
                pluginActivity.onCreate(savedInstanceState);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}


注意 ProxyActivity 的 getClassLoader 必须被覆盖 , 否则 使用 " 宿主 " 模块的类加载器 无法查找到 " 插件 " 模块安装包中的字节码类 ;


/**
 * 该 Activity 只是个空壳 ;
 * 主要用于持有从 apk 加载的 Activity 类
 * 并在 ProxyActivity  声明周期方法中调用对应 PluginActivity 类的生命周期方法
 */
public class ProxyActivity extends AppCompatActivity {
    /**
     * 需要使用插件包加载的类加载器
     * @return
     */
    @Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance().getmDexClassLoader();
    }
}


插件包类加载器是通过创建 DexClassLoader 获得的 , 需要传入插件包中的 dex 字节码类 ;


/**
 * 插件化框架核心类
 */
public class PluginManager {
    /**
     * 类加载器
     * 用于加载插件包 apk 中的 classes.dex 文件中的字节码对象
     */
    private DexClassLoader mDexClassLoader;
    /**
     * 加载插件
     * @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 加载器的父类加载器
        );
    }
    /**
     * 获取类加载器
     * @return
     */
    public DexClassLoader getmDexClassLoader() {
        return mDexClassLoader;
    }
}


二、" 宿主 " 模块加载 " 插件 " 模块中的资源文件


在 " 宿主 " 模块中 , 使用 Resources 是无法获取到 " 插件 " 模块中的资源文件的 , 在使用 " 插件 " 模块中的资源文件之前 , 必须先加载其中的资源文件 ;


/**
 * 该 Activity 只是个空壳 ;
 * 主要用于持有从 apk 加载的 Activity 类
 * 并在 ProxyActivity  声明周期方法中调用对应 PluginActivity 类的生命周期方法
 */
public class ProxyActivity extends AppCompatActivity {
    /**
     * 需要使用插件包中加载的资源
     * @return
     */
    @Override
    public Resources getResources() {
        return PluginManager.getInstance().getmResources();
    }
}


插件包中的 Resources 加载过程如下 :


首先 , 通过反射创建 AssetManager ,

然后 , 通过反射获取 AssetManager 中的 addAssetPath 隐藏方法 ,

再后 , 调用 AssetManager 的 addAssetPath 方法 ,

最后 , 通过 AssetManager 创建 Resources 对象 ;


/**
 * 插件化框架核心类
 */
public class PluginManager {
    /**
     * 加载插件
     * @param context 加载插件的应用的上下文
     * @param loadPath 加载的插件包地址
     */
    public void loadPlugin(Context context, String loadPath) {
        this.mContext = context;
        // 加载资源
        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();
        }
    }
    /**
     * 获取插件包中的资源
     * @return
     */
    public Resources getmResources() {
        return mResources;
    }
}



三、博客资源


博客资源 :


GitHub : https://github.com/han1202012/Plugin

image.png



目录
相关文章
|
6月前
|
存储 消息中间件 人工智能
【03】AI辅助编程完整的安卓二次商业实战-本地构建运行并且调试-二次开发改注册登陆按钮颜色以及整体资源结构熟悉-优雅草伊凡
【03】AI辅助编程完整的安卓二次商业实战-本地构建运行并且调试-二次开发改注册登陆按钮颜色以及整体资源结构熟悉-优雅草伊凡
229 3
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
前端开发 Java 编译器
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
399 36
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
|
算法 JavaScript Android开发
|
XML 搜索推荐 前端开发
安卓开发中的自定义视图:打造个性化UI组件
在安卓应用开发中,自定义视图是一种强大的工具,它允许开发者创造独一无二的用户界面元素,从而提升应用的外观和用户体验。本文将通过一个简单的自定义视图示例,引导你了解如何在安卓项目中实现自定义组件,并探讨其背后的技术原理。我们将从基础的View类讲起,逐步深入到绘图、事件处理以及性能优化等方面。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
|
开发框架 Dart Android开发
安卓与iOS的跨平台开发:Flutter框架深度解析
在移动应用开发的海洋中,Flutter作为一艘灵活的帆船,正引领着开发者们驶向跨平台开发的新纪元。本文将揭开Flutter神秘的面纱,从其架构到核心特性,再到实际应用案例,我们将一同探索这个由谷歌打造的开源UI工具包如何让安卓与iOS应用开发变得更加高效而统一。你将看到,借助Flutter,打造精美、高性能的应用不再是难题,而是变成了一场创造性的旅程。
|
Android开发
【Android 插件化】“ 插桩式 “ 插件化框架 ( 获取插件入口 Activity 组件 | 加载插件 Resources 资源 )(二)
【Android 插件化】“ 插桩式 “ 插件化框架 ( 获取插件入口 Activity 组件 | 加载插件 Resources 资源 )(二)
216 0
|
5月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
855 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
5月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
728 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
5月前
|
移动开发 Rust JavaScript
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
941 4
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡

热门文章

最新文章