占位式插件化框架

一.原理

在插件化中,宿主是安装在手机中的正在运行的APP,插件是没有经过安装的APK文件。插件化可以实现宿主APP直接加载插件APK,使插件APK不用安装就可以运行在宿主APP中。
由于插件APK没有安装,它是没有上下文(Context)环境的,想要运行插件APK,需要把宿主的Context传递给插件APK。在占位式插件化中,当需要启动插件的Activity时,实际是创建了宿主APP中的代理Activity,在代理Activity中,通过反射拿到插件APK的Activity对象,然后执行插件APK的Activity对象的onCreate等方法。从而实现在宿主中的代理Activity上加载插件的Activty。

二.Activity通信

1.通信标准
在项目中新建Android Library,该模块主要提供宿主和插件需要遵循的标准,宿主和插件都需要依赖该模块。首先新建ActivityInterface接口,
public interface ActivityInterface {
    //向插件注入宿主环境
        void insertAppContext(Activity activity);
    //与Activity中方法保持一致,方便调用
        void onCreate(Bundle savedInstanceState);

        void onStart();

        void onResume();

        void onDestroy();
    }
2.插件APK
新建一个Phone Module作为插件,新建一个BaseActivity继承Activity实现ActivityInterface接口,作为插件中所有Activity的父类。
    public class BaseActivity extends Activity implements ActivityInterface {
    //保存宿主环境
    public Activity appActivity;
    @Override
    public void insertAppContext(Activity activity) {
        this.appActivity = activity;
    }
    @SuppressLint("MissingSuperCall")
    @Override
    public void onCreate(Bundle savedInstanceState) {

    }
    ...
    }
新建PluginActivity继承BaseActivity,作为插件的第一个Activity。
public class PluginActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);     
    //插件本身没有环境,使用this时要用宿主环境appActivity代替 
    Toast.makeText(appActivity,"Plugin",Toast.LENGTH_SHORT).show();
        }
    }
3.宿主APP
在宿主中,新建PluginManager类,要运行插件APK,需要获得APK的类对象和资源对象,因此,在PluginManager中需要提供可以加载插件类的类加载器DexClassLoader和插件资源文件Resources。
public void loadPlugin(){
        try {
            //插件存放位置
            File file = new File(Environment.getExternalStorageDirectory() + File.separator + "plugin_package-debug.apk");
            if (!file.exists()){
                return;
            }
            /**
             * 加载class
             */
            String pluginPath = file.getAbsolutePath();
            // /data/data当前应用包名/pDir
            File fileDir = context.getDir("pDir",Context.MODE_PRIVATE);
            //得到类加载器
            dexClassLoader = new DexClassLoader(pluginPath,fileDir.getAbsolutePath(),null,context.getClassLoader());
            /**
             * 加载layout
             */
            //加载资源
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPathmethod = assetManager.getClass().getMethod("addAssetPath",String.class);
            addAssetPathmethod.invoke(assetManager,pluginPath);
            Resources r = context.getResources();
            this.resources = new Resources(assetManager,r.getDisplayMetrics(),r.getConfiguration());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
新建ProxyActivity,每次对插件Activity的加载,实际都是创建的ProxyActivity,在ProxyActivity的onCreate方法中,使用类加载器加载插件中的Activity类。
public class ProxyActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //得到插件Activity类名
        String className = getIntent().getStringExtra("className");
        try {
            //使用PluginManager中创建的类加载器加载插件Activity
            Class pluginActivityClass = getClassLoader().loadClass(className);
            Constructor constructor = pluginActivityClass.getConstructor(new Class[]{});
            Object pluginActivity = constructor.newInstance(new Object[]{});
            ActivityInterface activityInterface = (ActivityInterface)pluginActivity;
            //把宿主环境传递给插件
            activityInterface.insertAppContext(this);
            Bundle bundle = new Bundle();
            bundle.putString("info","from ProxyActivity");
            //执行插件的onCreate
            activityInterface.onCreate(bundle);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    }
当在宿主MainActivity中跳转至插件的Activity时,进行如下操作:
public void startActivity(View view) {
        File file = new File(Environment.getExternalStorageDirectory() + File.separator + "plugin_package-debug.apk");
        PackageManager packageManager = getPackageManager();
        //获得插件的AndroidManifest中注册的Activity
        PackageInfo packageInfo = packageManager.getPackageArchiveInfo(file.getAbsolutePath(), PackageManager.GET_ACTIVITIES);
        ActivityInfo activityInfo = packageInfo.activities[0];
        //实际启动ProxyActivity
        Intent intent = new Intent(this,ProxyActivity.class);
        //把插件Activity类名传过去
        intent.putExtra("className",activityInfo.name);
        startActivity(intent);
    }
这样就完成了从宿主Activiyu至插件Activity的跳转。当从插件Activity跳转至另一插件Activity时,新建一个SecondActivity作为跳转后的Activity。流程是在插件Activity再启动一个ProxyActivity,在ProxyActivity加载SecondActivity。首先重写插件BaseActivity的startActivity:
@Override
    public void startActivity(Intent intent) {
        //不能直接调用appActivity.startActivity(intent),因为SecondActivity在宿主中不存在。
        Intent intentNew = new Intent();
    //把SecondActivity类名传过去     intentNew.putExtra("className",intent.getComponent().getClassName());
    //appActivity就是ProxyActivity上下文
        appActivity.startActivity(intentNew);
    }
以上代码最后执行到ProxyActivity.startActivity,重写该方法:
@Override
    public ComponentName startService(Intent intent) {
        String className = intent.getStringExtra("className");
        Intent intentNew = new Intent(this,ProxyService.class);
        intentNew.putExtra("className",className);
        //执行界面跳转
        return super.startService(intentNew);
    }
PluginActivity跳转SecondActivity时只需执行startActivity(new Intent(appActivity,SecondActivity.class))即可。Service和Broadcast的通信原理与Activity相同,都是通过ProxyService和ProxyReceiver实现。

三.Broadcast静态注册

APK中对AndroidManifest.xml的解析是在PackageManagerService进行的,通过PackageParser类的parsePackage方法。因此,要得到插件中AndroidManifest.xml的内容,需要通过反射调用PackageParser类的parsePackage方法。

在这里插入图片描述

首先获取PackageParser对象,并执行parsePackage方法。
	Class packageParserClass = Class.forName("android.content.pm.PackageParser");
    Object packageParser = packageParserClass.newInstance();
    Method parsePackageMethod = packageParserClass.getMethod("parsePackage", File.class, int.class);
    //执行parsePackage方法返回package对象,file是插件文件
    Object mPackage = parsePackageMethod.invoke(packageParser,file, PackageManager.GET_ACTIVITIES);
parsePackage方法返回Package对象,Package是PackageParser的内部类,有一个成员变量ArrayList receivers保存了AndroidManifest.xml中所有receiver。
//package中有一个成员变量receivers
     Field receiversField = mPackage.getClass().getDeclaredField("receivers");
     ArrayList receivers = (ArrayList) receiversField.get(mPackage);
遍历每个receiver,拿到其名称和对应的所有intentFilter。
首先拿到所有intentFilter。
	Class componentClass = Class.forName("android.content.pm.PackageParser$Component");
    //Activity的父类Component中有个成员变量intents(此处的Activity是PackageParser的内部类,不是四大组件之一的Activity)
    Field intentsField = componentClass.getDeclaredField("intents");
    ArrayList<IntentFilter> intents = (ArrayList<IntentFilter>) intentsField.get(activity);
拿到receiver的name
//ActivityInfo的父类PackageItemInfo中的name属性保存了receiver名称,ActivityInfo通过PackageParser的generateActivityInfo获得
                //static final generateActivityInfo(Activity a, int flags,PackageUserState state, int userId)
    Class mPackageUserStateClass = Class.forName("android.content.pm.PackageUserState");
    Method generateActivityInfoMethod = packageParserClass.getMethod("generateActivityInfo", activity.getClass(), int.class, mPackageUserStateClass, int.class);
    generateActivityInfoMethod.setAccessible(true);
                //userId在UserHandle中,static getCallingUserId
    Class userHandleClass = Class.forName("android.os.UserHandle");
    int userId = (int)userHandleClass.getMethod("getCallingUserId").invoke(null);
    ActivityInfo activityInfo = (ActivityInfo)generateActivityInfoMethod.invoke(null, activity, 0,mPackageUserStateClass.newInstance(), userId);
    String receiverClassName = activityInfo.name;
拿到BroadcastReceiver对象,并注册所有intentFilter
	Class receiverClass = getDexClassLoader().loadClass(receiverClassName);
	BroadcastReceiver broadcastReceiver = (BroadcastReceiver)receiverClass.newInstance();
	for (IntentFilter intentFilter : intents){
	    context.registerReceiver(broadcastReceiver,intentFilter);
	}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值