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中。
 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Android 插件是指将一个应用的功能分离为多个独立的模块,每个模块可以在运行时独立加载和卸载。这样可以优应用的体积和性能,同时也可以实现模块的动态更新和管理。插件在某些场景下被广泛运用,比如一个大型的应用需要提供不同的功能模块供用户选择,或者在多个应用中共享某些通用功能。插件的实现方式有很多种,其中最常用的是利用 Android插件框架和技术来实现。 通常情况下,一个 Android 应用的所有组件(Activity、Service、BroadcastReceiver、ContentProvider 等)都被编译打包进同一个 APK 文件中,这也就意味着只有在应用安装和更新的时候才能进行组件的更新和修改,无法实现动态更新。而插件技术则打破了这种限制,它将应用的不同组件打包成不同的插件(APK)文件,然后在运行时动态加载和卸载。这样一来,我们可以实现在不停止应用的情况下对部分组件进行更新、拓展、甚至是删除。 为了实现插件,我们需要解决一些技术难点。其中最主要的是解决插件和宿主的交互问题。在插件和宿主中间需要进行很多数据传递、资源访问和类加载等操作,如果没有基础的交互机制,插件是无法成功实现的。因此,在 Android 中,插件相关的机制主要包括四个方面:类加载器(ClassLoader)、组件的注册和管理、资源访问和切换主题(Theme)等。 总体来看,插件技术为 Android 应用带来了更大的可拓展性和灵活性,这对于一些复杂或大型的应用非常重要。同时,基于插件技术,也催生出了一些全新的业态,比如插件市场和插件开发社区,为 Android 生态系统带来了更多的创新力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaowang_lj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值