Android 插件化

简介: Android 插件化

demo

如果要加载插件模块编译的apk插件包中的Activity类,需要执行如下流程:

1)加载类对象:使用DexClassLoader加载Activity对应的Class字节码类对象;

2)管理生命周期:处理加载进来的Activity类的生命周期,创建ProxyActivity,通过其生命周期回调,管理插件包中加载的未纳入应用管理的组件Activity类;

3)注入上下文:为加载进来的Activity类注入上下文;

4)加载资源:使用AssetManager将插件包apk中的资源主动加载进来。

 

1.创建工程

app为宿主app

plugin 为插件app

plugin_core为插件化的核心加载文件

2.app

//配置selinux权限:allow untrusted_app app_data_file:file { execute };
public class MainActivity extends AppCompatActivity {
 
    @Override
 
    protected void onCreate(Bundle savedInstanceState) {
 
        super.onCreate(savedInstanceState);
 
        setContentView(R.layout.activity_main);
 
 
        if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
                && checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { //有存储权限
//           路径为 /storage/emulated/0/Android/data/com.example.insert/files
            String path = getExternalFilesDir(null).getPath()+"/plugin-debug.apk"; //插件模块apk存放位置
 
            PluginManager.getInstance().loadPlugin( this, path); // 加载插件模块的apk文件
 
        } else { //没有存储权限,申请权限
 
            requestPermissions(new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
 
        }
 
    }
 
    public void onClick(View view) {
 
        //要跳转到plugin_core模块的代理Activity,首先要获取插件apk中的入口Activity类
 
        Intent intent = new Intent(this, ProxyActivity.class);
 
        ActivityInfo[] activityInfos = PluginManager.getInstance().getmPackageInfo().activities; // 获取插件apk中的Activity数组信息
 
        if (activityInfos.length > 0) { // 获取的插件包中的Activity不为空 , 才进行界面跳转
 
            // 取插件apk中的第1个Activity,次序就是在AndroidManifest.xml清单文件中定义Activity组件的次序,因此必须将Launcher Activity定义在第一个位置,不能在Launcher Activity之前定义Activity组件
 
            intent.putExtra("className", activityInfos[0].name); // 传入的是代理的目标组件的全类名,即插件apk中第一个activity的全类名
 
            startActivity(intent);
 
        }
 
    }
 
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.insert">
 
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
    <application
        android:requestLegacyExternalStorage="true"
        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.Insert">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
 
</manifest>

2.plugin_core


定义一个Activity基类BaseActivity,继承AppCompatActivity并实现PluginActivityInterface接口。插件模块中的Activity类都继承该类,它是具体的Activity业务类的父类。


BaseActivity中涉及到的生命周期函数重复了,如AppCompatActivity中的onCreate()方法与PluginActivityInterface接口中的onCreate()方法是重复的,这里在每个方法前面加上@SuppressLint("MissingSuperCall")注解,忽略该报错。

 

public class BaseActivity extends AppCompatActivity implements PluginActivityInterface {
 
    private Activity proxyActivity; //注入的Activity,代理该Activity类作为上下文
 
    @Override
 
    public void attach(Activity proxyActivity) {
 
        this.proxyActivity = proxyActivity;  //注入代理Activity,在PluginActivity中将代理Activity组件注入进来
 
    }
 
    @Override
 
    public void setContentView(int layoutResID) {
 
        // 调用代理Activity中的setContentView方法
 
        if (proxyActivity != null) {
 
            proxyActivity.setContentView( layoutResID);
 
        }else{
 
            super.setContentView(layoutResID);
 
        }
 
    }
 
    @Override
 
    public void setContentView(View view) {
 
        if (proxyActivity != null) {
 
            proxyActivity.setContentView(view);
 
        }else{
 
            super.setContentView(view);
 
        }
 
    }
 
    @Override
 
    public <T extends View> T findViewById(int id){
 
        if (proxyActivity != null) {
 
            return proxyActivity.findViewById(id);
 
        }else{
 
            return super.findViewById(id);
 
        }
 
    }
 
    @Override
 
    public void startActivity(Intent intent) {
 
        if (proxyActivity != null) {
 
            intent.putExtra("className", intent.getComponent().getClassName());
 
            proxyActivity.startActivity(intent);
 
        }else{
 
            super.startActivity(intent);
 
        }
 
    }
 
    @SuppressLint("MissingSuperCall")
 
    @Override
 
    public void onCreate(Bundle savedInstanceState) {
 
    }
 
    @SuppressLint("MissingSuperCall")
 
    @Override
 
    public void onStart() {
 
    }
 
    @SuppressLint("MissingSuperCall")
 
    @Override
 
    public void onResume() {
 
    }
 
    @SuppressLint("MissingSuperCall")
 
    @Override
 
    public void onPause() {
 
    }
 
    @SuppressLint("MissingSuperCall")
 
    @Override
 
    public void onStop() {
 
    }
 
    @SuppressLint("MissingSuperCall")
 
    @Override
 
    public void onDestroy() {
 
    }
 
    @SuppressLint("MissingSuperCall")
 
    @Override
 
    public void onSaveInstanceState(Bundle outState) {
 
    }
 
}
public interface PluginActivityInterface {
    //绑定代理Activity
 
    void attach(Activity proxyActivity);
 
    void onCreate(Bundle savedInstanceState);
 
    void onStart();
 
    void onResume();
 
    void onPause();
 
    void onStop();
 
    void onDestroy();
 
    void onSaveInstanceState(Bundle outState);
 
    boolean onTouchEvent(MotionEvent event);
 
    void onBackPressed();
 
}
public class PluginManager {
 
    private DexClassLoader mDexClassLoader;//类加载器,用于加载插件包apk中的classes.dex文件中的字节码对象
 
    private Resources mResources; //从插件包apk中加载的资源
 
    private PackageInfo mPackageInfo; //插件包信息类
 
    private Context mContext; //加载插件的上下文对象
 
    private static PluginManager instance;
 
    //获取单例类
 
    public static PluginManager getInstance(){
 
        if (instance == null) {
 
            instance = new PluginManager();
 
        }
 
        return instance;
 
    }
 
    // 加载插件,参数分别为加载插件的app的上下文、加载的插件包地址
 
    public void loadPlugin(Context context, String loadPath) {
 
        this.mContext = context;
 
        //DexClassLoader的optimizedDirectory目录必须是私有,即模式为Context.MODE_PRIVATE
 
        File optimizedDirectory = context.getDir( "cache_plugin", Context.MODE_PRIVATE);
        Log.e("TAG",loadPath);
 
        mDexClassLoader = new DexClassLoader( loadPath, optimizedDirectory.getAbsolutePath(), null, context.getClassLoader()); // 创建类加载器
 
        // 加载资源
 
        try {
 
            AssetManager assetManager = AssetManager.class.newInstance(); //通过反射创建AssetManager
 
            Method addAssetPathMethod = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class); //通过反射获取AssetManager中的addAssetPath隐藏方法
 
            addAssetPathMethod.invoke( assetManager, loadPath); //调用反射方法
 
            mResources = new Resources( assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration()); //获取资源
 
            mPackageInfo = context.getPackageManager().getPackageArchiveInfo(loadPath, PackageManager.GET_ACTIVITIES); // 获取插件包中的Activity类信息
 
        } catch (Exception e) {
            e.printStackTrace();
 
        }
 
    }
 
    //获取类加载器
 
    public DexClassLoader getmDexClassLoader(){
 
        return mDexClassLoader;
 
    }
 
    //获取插件包中的Package信息
 
    public PackageInfo getmPackageInfo() {
 
        return mPackageInfo;
 
    }
 
    //获取插件包中的资源
 
    public Resources getmResources() {
 
        return mResources;
 
    }
 
}

 

在宿主apk中需要跳转到插件apk时,首先创建一个跳转到ProxyActivity(在核心库里)的intent,在intent中加上插件apk的入口activity。这样ProxyActivity作为代理Activity ,它持有从插件apk中加载的PluginActivity类对象。


ProxyActivity是空的Activity,没有任何实际的业务逻辑,只是作为一个生命周期的转接代理接口。但是ProxyActivity有着完整的生命周期回调机制,在进入该界面时会回调onCreate、onStart、onResume生命周期方法,在退出该界面时会回调 onPause、onStop、onDestroy生命周期方法。因此只需要在ProxyActivity的生命周期方法中,调用PluginActivity相应的生命周期方法即可。而且ProxyActivity运行时会有上下文,PluginActivity使用上下文时调用ProxyActivity的上下文。


注意:插件模块的包名可以和宿主的包名一样也可以不一样。当插件apk的包名和宿主一样时,要注意插件apk里的Activity名字一定不要和宿主apk中Activity的名字一样,否则包名和Activity名都一样就无法跳转到插件Activity了。

//该Activity只是个空壳,主要用于持有从插件apk加载的Activity类,并在ProxyActivity生命周期方法中调用对应PluginActivity类的生命周期方法
 
public class ProxyActivity extends Activity {
 
    private String className = ""; //被代理的目标Activity组件的全类名
 
    private PluginActivityInterface pluginActivity;//插件包中的Activity界面组件
 
    @Override
 
    protected void onCreate(Bundle savedInstanceState) {
 
        super.onCreate(savedInstanceState);
 
        setContentView(R.layout.activity_proxy);
 
        className = getIntent().getStringExtra("className"); //得到要代理的目标组件的全类名,即插件apk中的入口activity的全类名
 
        try {
 
            Class<?> clazz = getClassLoader().loadClass(
                    className); //使用类加载器加载插件中的界面组件。注意这里类加载器必须是PluginManager中的类加载器,即getClassLoader()是重写的方法,该方法返回PluginManager中的类加载器
 
            Activity activity = (Activity) clazz.newInstance(); //使用反射创建插件apk中的Activity
 
            if (activity instanceof PluginActivityInterface) { // 判断Activity组件是否是PluginActivityInterface接口类型的
 
                this.pluginActivity =
                        (PluginActivityInterface) activity; // 如果是PluginActivityInterface类型 , 则强转为该类型
 
                pluginActivity.attach(
                        this);  // 上下文注入。将ProxyActivity绑定注入到插件包的PluginActivity类中。该ProxyActivity具有运行的上下文,一旦绑定注入成功,则被代理的PluginActivity也具有了上下文
 
                pluginActivity.onCreate(savedInstanceState);  //调用pluginActivity的onCreate生命周期
 
            }
 
        } catch (Exception e) {
 
            e.printStackTrace();
 
        }
 
    }
 
    @Override
 
    protected void onStart() {
 
        super.onStart();
 
        pluginActivity.onStart();
 
    }
 
    @Override
 
    protected void onResume() {
 
        super.onResume();
 
        pluginActivity.onResume();
 
    }
 
    @Override
 
    protected void onPause() {
 
        super.onPause();
 
        pluginActivity.onPause();
 
    }
 
    @Override
 
    protected void onStop() {
 
        super.onStop();
 
        pluginActivity.onStop();
 
    }
 
    @Override
 
    protected void onDestroy() {
 
        super.onDestroy();
 
        pluginActivity.onDestroy();
 
    }
 
    @Override
 
    protected void onSaveInstanceState(Bundle outState) {
 
        super.onSaveInstanceState(outState);
 
        pluginActivity.onSaveInstanceState(outState);
 
    }
 
    @Override
 
    public boolean onTouchEvent(MotionEvent event) {
 
        super.onTouchEvent(event);
 
        return pluginActivity.onTouchEvent(event);
 
    }
 
    @Override
 
    public void onBackPressed() {
 
        //super.onBackPressed();
 
        //pluginActivity.onBackPressed();
 
        finish();
 
    }
 
    //重写getClassLoader方法,因为这里需要使用插件包加载的类加载器
 
    @Override
 
    public ClassLoader getClassLoader() {
 
        return PluginManager.getInstance().getmDexClassLoader();
 
    }
 
    //重写getResources方法,因为这里需要使用插件包加载的资源
 
    @Override
 
    public Resources getResources() {
 
        return PluginManager.getInstance().getmResources();
 
    }
}

 

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.plugin_core">
    <application>
        <activity android:name=".ProxyActivity"/>
    </application>
 
</manifest>

3.plugin

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.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.Insert">
        <activity
            android:name=".PluginActivity"
            android:exported="true">
        </activity>
    </application>
 
</manifest>

 

所有的插件包中的Activity都要继承 BaseActivity。


这样写的目的是为了方便在代理Activity中可以随意调用插件apk中的Activity类的生命周期函数,这些生命周期函数都是protected方法,不能直接调用,否则每个方法调用时,还要先反射修改访问性,才能调用

 

public class PluginActivity extends BaseActivity {
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}


代码完成,接下来看如何运行:


①首先编译插件模块,生成插件apk安装包:


②拷贝插件apk

将编译的插件包apk拷贝到 /storage/emulated/0/Android/data/com.example.insert/files目录中。在 " Device FIle Explorer " 面板中 , 右键点击/storage/emulated/0/DCIM目录 , 点击"Upload " 即可将apk上传到指定位置。


③运行宿主模块,在onCreate方法中就会加载解析插件apk,解析dex文件与资源文件。此时点击跳转按钮,即可跳转到插件模块Activity中。

 

目录
相关文章
|
存储 Java Android开发
Android插件化动态加载apk
支付宝作为一个宿主apk提前将要集成的apk作为一个插件(plugin)下载到本地,然后当使用该plugin(apk)的时候再去加载对应plugin(apk)的资源文件以及对应的native页面。就是不去安装plugin(apk)就可以直接运行该plugin(apk)中的页面。
773 0
|
2天前
|
存储 Android开发
Android插件化-Broadcast篇,2024年最新安卓面试自我介绍
Android插件化-Broadcast篇,2024年最新安卓面试自我介绍
|
2天前
|
Android开发 Kotlin API
Android插件化探索与发现,kotlin协程切换线程
Android插件化探索与发现,kotlin协程切换线程
|
2天前
|
缓存 Android开发
Android插件化——高手必备的Hook技术,零基础开发android
Android插件化——高手必备的Hook技术,零基础开发android
|
9月前
|
Android开发
Android手写占位式插件化框架之apk解析原理系统源码分析
Android手写占位式插件化框架之apk解析原理系统源码分析
84 0
|
9月前
|
Android开发
Android手写占位式插件化框架之Activity通信、Service通信和BroadcastReceiver通信(二)
Android手写占位式插件化框架之Activity通信、Service通信和BroadcastReceiver通信
81 0
|
9月前
|
Android开发
Android手写占位式插件化框架之Activity通信、Service通信和BroadcastReceiver通信(一)
Android手写占位式插件化框架之Activity通信、Service通信和BroadcastReceiver通信
72 0
|
XML Java 程序员
插件化框架设计(二) Android 资源加载机制详解(一)
Android 提供了一种非常灵活的资源系统,可以根据不同的条件提供可替代资源。因此,系统基于很少的改造就能支持新特性,比如 Android N 中的分屏模式。这也是 Android 强大部分之一。本文主要讲述 Android 资源系统的实现原理,以及在应用开发中需要注意的事项。
157 0
|
Android开发
【Android 插件化】VirtualApp 安装并启动资源中自带的 APK 插件 ( 添加依赖库 | 准备插件 APK | 启动插件引擎 | 拷贝 APK 插件 | 安装插件 | 启动插件 )(二)
【Android 插件化】VirtualApp 安装并启动资源中自带的 APK 插件 ( 添加依赖库 | 准备插件 APK | 启动插件引擎 | 拷贝 APK 插件 | 安装插件 | 启动插件 )(二)
394 0
【Android 插件化】VirtualApp 安装并启动资源中自带的 APK 插件 ( 添加依赖库 | 准备插件 APK | 启动插件引擎 | 拷贝 APK 插件 | 安装插件 | 启动插件 )(二)
|
存储 Android开发
插件化框架设计(二) Android 资源加载机制详解(二)
Android 提供了一种非常灵活的资源系统,可以根据不同的条件提供可替代资源。因此,系统基于很少的改造就能支持新特性,比如 Android N 中的分屏模式。这也是 Android 强大部分之一。本文主要讲述 Android 资源系统的实现原理,以及在应用开发中需要注意的事项。
253 0
插件化框架设计(二) Android 资源加载机制详解(二)