【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



目录
相关文章
|
5天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
26天前
|
Android开发
Android面试之Activity启动流程简述
Android面试之Activity启动流程简述
70 6
|
23天前
|
Android开发 UED
Android 中加载 Gif 动画
【10月更文挑战第20天】加载 Gif 动画是 Android 开发中的一项重要技能。通过使用第三方库或自定义实现,可以方便地在应用中展示生动的 Gif 动画。在实际应用中,需要根据具体情况进行合理选择和优化,以确保用户体验和性能的平衡。可以通过不断的实践和探索,进一步掌握在 Android 中加载 Gif 动画的技巧和方法,为开发高质量的 Android 应用提供支持。
|
27天前
|
消息中间件 Android开发 索引
Android面试高频知识点(4) 详解Activity的启动流程
Android面试高频知识点(4) 详解Activity的启动流程
26 3
|
27天前
|
缓存 前端开发 Android开发
Android实战之如何截取Activity或者Fragment的内容?
本文首发于公众号“AntDream”,介绍了如何在Android中截取Activity或Fragment的屏幕内容并保存为图片。包括截取整个Activity、特定控件或区域的方法,以及处理包含RecyclerView的复杂情况。
18 3
|
27天前
|
Android开发
Android面试之Activity启动流程简述
Android面试之Activity启动流程简述
17 0
|
存储 缓存 Java
android 加载图片oom若干方案小结
本文根据网上提供的一些技术方案加上自己实际开发中遇到的情况小结。 众所周知,每个Android应用程序在运行时都有一定的内存限制,限制大小一般为16MB或24MB(视手机而定)。一般我们可以通过获取当前线程的可运行内存来判断,比如系统分给当前运行内存只有16M,而你的图片就有16M,这肯定会oom的。 相关知识介绍 1.颜色模型 常见的颜色模型有RGB、YUV、
1506 0
|
Android开发 数据格式 XML
android -- 加载gif 防止oom
项目中涉及到gif图片的展示,原来使用的是gifview,但是当频繁的,加载过大图片的时候会出现OOM的问题,后来去github上面找相关的库: https://github.com/koral--/android-gif-drawable android-gif-drawable是比较好的,并...
1138 0
|
Android开发 Java
Android加载大图Bitmap发生OOM(Out Of Memmory Error)解决方案
本文作者:Zhang Phil 原文链接: http://blog.csdn.net/zhangphil/article/details/48130053 Android加载大图Bitmap发生OOM(Out ...
1005 0
|
存储 缓存 编解码
android加载大图,防止oom
<h2 style="margin:0px; padding:0px; color:rgb(51,51,51); font-family:Arial; line-height:26px"> 高效加载大图片</h2> <span style="color:rgb(51,51,51); font-family:Arial; font-size:14px; line-height:26px"
1382 0