【android】插件化技术原理详解

  作为移动端的黑科技,插件化技术一直受大厂的青睐。插件化技术有减少宿主Apk体积,可以独立更新,模块化开发等优点,让宿主APP极具扩展性。那么,现在就来聊聊其中的技术实现,国际惯例,先上效果图

这里写图片描述
这篇文章将用到以下知识

  • dexClassLoader:用来加载插件中的activity
  • resources : 用来获取插件中的资源
  • PackageInfo:用来获取插件中activity信息
  • library:用来创建加载插件规则

一、插件管家——PluginManager

  由于插件是没有安装在手机中的,因此我们需要一个对象来管理插件中的资源,保存PackageI的信息,这点与之前一篇的换肤技术有点类似,在此基础上加上获取PackageInfo,这里就不赘述了,show the code

    private PackageInfo packageInfo ;
    private DexClassLoader dexClassLoader ;
    private Resources resources ;
    private AssetManager assetManager ;
    private Context mCtx ;

    public void loadApk(String path){
        File cache = mCtx.getDir("dex",mCtx.MODE_PRIVATE);
        dexClassLoader = new DexClassLoader(path,
                cache.getAbsolutePath(),null,mCtx.getClassLoader());
        try {
            assetManager = AssetManager.class.newInstance();
            Method method = AssetManager.class.getMethod("addAssetPath", String.class);
            method.invoke(assetManager,path);
            resources = new Resources(assetManager,mCtx.getResources().getDisplayMetrics(),
                    mCtx.getResources().getConfiguration());
            PackageManager packageManager = mCtx.getPackageManager() ;
            packageInfo = packageManager.getPackageArchiveInfo(path,PackageManager.GET_ACTIVITIES);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

另外,需要提供get、set来获取packageInfo 等参数,这里就不多写了。

  这个类比较简单,主要提供loadApk方法给外部来加载插件的本地路径,在loadApk 方法里,我们通过传进来的path去获取插件的dexClassLoader,然后通过反射的方式获取插件的assetManager对象,接着通过assetManager对象来获取resource对象,最后通过packageManager来获取插件的packageInfo

二、插件的壳

  插件没有安装到手机, 没有将组件注册到手机中,这就意味着插件中的activity没有生命周期,不能通过传统的startActivity去启动。那我们应该如何启动插件中的activity呢?这是插件化遇到的第一个难题,既然宿主apk是安装到手机中的,那么我们能不能通过宿主apk给予插件中的activity生命周期呢?答案是肯定的,我们可以在宿主apk中提供一个壳来加载插件中的activity,在空壳的生命周期中调用插件activity的方法,从而实现插件activity的重生~~
  既然如此,我们就需要定义一些规则去加载插件中的activity,首先,创建一个library,在Java文件目录下创建一个接口,该接口包含了activity的生命周期,目录如下
这里写图片描述
PluginInterface 接口代码如下

public interface PluginInterface {
    void attach(Activity activity);
    void onCreate(Bundle savedInstanceState);
    void onStart();
    void onStop();
    void onDestroy();
}

  定义好规则后,再回到宿主目录下创建一个壳,姑且叫PluginActivity吧,在插件中所有activity都要实现PluginInterface接口,因此,我们可以先获取插件的activity的name,获取对应的class,再将class强转为PluginInterface接口,不就可以传递生命周期了吗?
  我们先来看宿主的MainActivity

   @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        PluginManager.getInstance().setCtx(this);
        File file = new File(Environment.getExternalStorageDirectory(),"plugin-debug.apk");
        PluginManager.getInstance().loadApk(file.getAbsolutePath());
    }
    public void jump(View view){
        Intent intent = new Intent(this,PluginActivity.class);
        intent.putExtra("activityName",PluginManager.getInstance().getPackageInfo().activities[0].name);
        startActivity(intent);
    }

  mainactivity主要任务是,通过PluginManager来完成插件资源的初始化工作,在jump方法中我们会通过PackageInfo中的ActivityInfo数组来获取插件的MainActivity的name参数,再把该参数通过intent传递给PluginActivity

再来看看PluginActivity的工作

    private String activityName ;
    private PluginInterface pluginInterface ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        activityName = getIntent().getStringExtra("activityName");
        try {
             Class<?> loadClass = PluginManager.getInstance().getDexClassLoader().loadClass(activityName);
             pluginInterface = (PluginInterface) loadClass.newInstance();
             pluginInterface.attach(this);
             pluginInterface.onCreate(savedInstanceState);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onStart() {
        pluginInterface.onStart();
        super.onStart();
    }

  在oncreate方法中,通过获取到的DexClassLoader去加载目标activity, 然后进行强转为PluginInterface,这时候我们就已经拿到插件的activity了
还有两个重点,在壳activity中,所有获取到的资源应该都是插件中的资源,所以我们需要重写getResource 方法

    @Override
    public Resources getResources() {
        return PluginManager.getInstance().getResources() ;
    }

同时在插件中启动其他activity也是需要获取对应的name参数,再重新启动新的PluginActivity

   @Override
    public void startActivity(Intent intent) {
        String activityName = intent.getStringExtra("activityName");
        Intent pluginIntent = new Intent(this,PluginActivity.class);
        pluginIntent.putExtra("activityName",activityName);
        super.startActivity(pluginIntent);
    }

  再啰嗦几句,这里我们要明白,在插件中启动新的activity,实际上需要启动新的壳去加载对应的资源(列如xml资源),所以需要重写startActivity来不断启动自身。

三、插件本体

终于来到最关键的时刻了~~插件化技术的主角其实是插件(跟没说一样~_~),不啰嗦,show the code
先创建一个新的module,名叫 plugin,工程目录如下
这里写图片描述
有以下几点需要明确的

  • 插件本身没有上下文说法:因为插件没有生命周期,其用到的上下文,是代理壳的上下文
  • 插件中的activity所有常用的方法都要重写:由于其没有上下文,所有activity用到的方法都需要交个代理壳去实现
  • 在xml文件下的根节点尽量用系统的viewGroup,例如relativelayout,不要用support包下的ConstraintLayout,xml解析会出错(如有误区,欢迎留言)

先来看插件的基类—>BasePluginActivity

public class BasePluginActivity extends Activity implements PluginInterface {

    private static final String TAG = "BasePluginActivity";
    protected Activity that;

   @Override
    public void attach(Activity activity) {
        this.that = activity;
    }

    @Override
    public void startActivity(Intent intent) {
        that.startActivity(intent);
    }

    @Override
    public void setContentView(int layoutResID) {
        that.setContentView(layoutResID);
    }
       @Override
    public <T extends View> T findViewById(int id) {
        return that.findViewById(id);
    }
}

这里由于篇幅,我就列了几个比较重要的方法,记住,所有能用到的方法都要重写,交给代理壳去处理

接下来就跟平常开发一样,唯一不同就是在跳转activity的时候需要获取目标类的name,不能直接传一个string或者class给intent

MainActivity如下

public class MainActivity extends BasePluginActivity {

    private static final String TAG = "MainAct";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toast.makeText(that, "come to Plugin  ", Toast.LENGTH_SHORT).show();
        findViewById(R.id.jump).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(that,"plugin",Toast.LENGTH_SHORT).show();
                Intent intent = new Intent();
                intent.putExtra("activityName",SecondActivity.class.getName());
                startActivity(intent);
            }
        });
    }

}

第二个界面这里就不列出来了,就一个界面

四、权限、运行

给予宿主读写权限,这里要确定APP在运行时候能获取到该权限,详情请Google

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

将我们的插件APP打包,并将打包得到的apk文件copy到手机根目录下(其他目录也行),修改宿主MainActivity 加载apk路径,接着打包宿主APP吧!点击run it~~
源码下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值