注册

移动架构 (八) 人人都能看得懂的动态化加载插件技术模型实现

移动架构 (一) 架构第一步,学会画各种 UML 图

移动架构 (二) Android 中 Handler 架构分析,并实现自己简易版本 Handler 框架

动架构 (三) AMS 源码分析

移动架构 (四) EventBus 3.1.1 源码分析及实现自己的轻量级 EventBus 框架,根据 TAG 发送接收事件。

移动架构 (五) 仅仅对 Java Bean 的操作,就能完成对数据持久化

移动架构 (六) 轻量级进程间通信框架设计

移动架构 (七) 人人都能看得懂的组件化框架模型


基本概念


插件化其实也就是 模块化->组件化 演变而来, 属于动态加载技术,主要用于解决应用越来越庞大以及功能模块的解耦,小项目中一般用的不多。


原理: 插件化的原理其实就是在 APP 壳运行过程中,动态加载一些程序中原本不存在的可执行文件并运行这些文件中的代码逻辑。可执行文件总的来说分为两个,其一是动态链接库 so,其二是 dex 相关文件包括 jar/apk 文件。


发展历史


很早以前插件化这项技术已经有公司在研究了,淘宝,支付宝做的是比较早,但是淘宝这项技术一直都是保密的,直到 2015 年左右市面上才出现了一些关于插件化的框架,Android 插件化分为很多技术流派,实现的方式都不太一样。下面我就简单以时间线来举例几个比较有代表性的插件框架:


时间框架名称作者框架简介
2014年底dynamic-load-apk主席任玉刚动态加载技术 + 代理实现
2015年 8 月DroidPlugin360 手机助手可以直接运行第三方的独立 APK 文件,完全不需要对 APK 进行修改或安装。一种新的插件机制,一种免安装的运行机制,是一个沙箱(但是不完全的沙箱。就是对于使用者来说,并不知道他会把 apk 怎么样), 是模块化的基础。
2015年底SmallwequickSmall 是一种实现轻巧的跨平台插件化框架,基于“轻量、透明、极小化、跨平台”的理念
2017年 6 月VirtualAPK滴滴VirtualAPK 对插件没有额外的约束,原生的 apk 即可作为插件。插件工程编译生成 apk 后,即可通过宿主 App 加载,每个插件 apk 被加载后,都会在宿主中创建一个单独的 LoadedPlugin 对象。通过这些 LoadedPlugin 对象,VirtualAPK 就可以管理插件并赋予插件新的意义,使其可以像手机中安装过的 App 一样运行。
2017年 7 月RePlgin360手机卫士RePlugin 是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,由 360 手机卫士的RePlugin Team 研发,也是业内首个提出”全面插件化“(全面特性、全面兼容、全面使用)的方案。
2019Shadow腾讯Shadow 是一个腾讯自主研发的 Android 插件框架,经过线上亿级用户量检验。 Shadow 不仅开源分享了插件技术的关键代码,还完整的分享了上线部署所需要的所有设计(零反射)

插件化必备知识



  1. Binder
  2. APP 打包流程
  3. APP 安装流程
  4. APP 启动流程
  5. 资源加载机制
  6. 反射,ClassLoader
  7. ...

实现简易版本插件化框架


今天我们这里就以动态加载技术,ClassLoader + 反射 + 代理模式 等基本技术来实现动态加载 APK 中的(Activity, Broadcast, Service ,资源)项目地址


先来看一个我们最终实现的效果


cFKq7.gif


加载插件 APK


在加载 APK 之前我们先来了解下 ClassLoader 家族,继承关系图


cFg8c.png


DexClassLoader 加载流程


DexClassLoader.png


从上面 2 张图中,我们得知动态加载 APK 需要用到 DexClassLoader ,既然知道了用 DexClassLoader 来加载 APK , 那么native 中将 apk -> dex 解析出来,class 又怎么加载勒? 通过 DexClassLoader 流程图得知可以直接调用 loadClass(String classPath) 来加载,下面我们就正式进行今天的主题了。


代码实现加载 APK


    /**
* 加载插件 APK
*/
public boolean loadPlugin(Context context, String filePath) {
if (context == null || filePath == null || filePath.isEmpty())
throw new NullPointerException("context or filePath is null ?");
this.mContext = context.getApplicationContext();
this.apkFilePath = filePath;
//拿到 包管理
packageManager = mContext.getPackageManager();

if (getPluginPackageInfo(apkFilePath) == null) {
return false;
}
//从包里获取 Activity
pluginPackageInfo = getPluginPackageInfo(apkFilePath);

//存放 DEX 路径
mDexPath = new File(Constants.IPluginPath.PlugDexPath);
if (mDexPath.exists())
mDexPath.delete();
else
mDexPath.mkdirs();

//通过 DexClassLoader 加载 apk 并通过 native 层解析 apk 输出 dex
//第二个参数可以为 null
if (getPluginClassLoader(apkFilePath, mDexPath.getAbsolutePath()) == null || getPluginResources(filePath) == null)
return false;
this.mDexClassLoader = getPluginClassLoader(apkFilePath, mDexPath.getAbsolutePath());
this.mResources = getPluginResources(filePath);
return true;

}
复制代码
/**
* @return 得到对应插件 APK 的 Resource 对象
*/
public Resources getPluginResources() {
return getPluginResources(apkFilePath);
}

/**
* 得到对应插件 APK 中的 加载器
*
* @param apkFile
* @param dexPath
* @return
*/
public DexClassLoader getPluginClassLoader(String apkFile, String dexPath) {
return new DexClassLoader(apkFile, dexPath, null, mContext.getClassLoader());
}


/**
* 得到对应插件 APK 中的 加载器
*
* @return
*/
public DexClassLoader getPluginClassLoader() {
return getPluginClassLoader(apkFilePath, mDexPath.getAbsolutePath());
}


/**
* 得到插件 APK 中 包信息
*/
public PackageInfo getPluginPackageInfo(String apkFilePath) {
if (packageManager != null)
return packageManager.getPackageArchiveInfo(apkFilePath, PackageManager.GET_ACTIVITIES);
return null;
}

/**
* 得到插件 APK 中 包信息
*/
public PackageInfo getPluginPackageInfo() {
return getPluginPackageInfo(apkFilePath);
}
复制代码

加载插件中 Activity


实现流程


--APK.png


代码实现流程




  1. 代理类 ProxyActivity 实现


    public class ProxyActivity extends AppCompatActivity {

    /**
    * 需要加载插件的全类名
    */
    protected String activityClassName;

    private String TAG = this.getClass().getSimpleName();
    private IActivity iActivity;
    private ProxyBroadcast receiver;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    activityClassName = getLoadClassName();

    //拿到加载插件的的全类名 通过反射实例化
    try {
    Class<?> pluginClassName = getClassLoader().loadClass(activityClassName);
    //拿到构造函数
    Constructor<?> constructor = pluginClassName.getConstructor(new Class[]{});
    //实例化 拿到插件 UI
    Object pluginObj = constructor.newInstance(new Object[]{});
    if (pluginObj != null) {
    iActivity = (IActivity) pluginObj;
    iActivity.onActivityCreated(this, savedInstanceState);
    }
    } catch (Exception e) {
    Log.e(TAG, e.getMessage());
    }
    }
    }
    复制代码


  2. 重写代理类中的 startActivity


        /**
    * 这里的 startActivity 是插件促使调用的
    */
    @Override
    public void startActivity(Intent intent) {
    //需要开启插件 Activity 的全类名
    String className = getLoadClassName(intent);
    Intent proxyIntent = new Intent(this, ProxyActivity.class);
    proxyIntent.putExtra(Constants.ACTIVITY_CLASS_NAME, className);
    super.startActivity(proxyIntent);
    }
    复制代码


  3. 插件 Activity 实现 IActivity 的生命周期并且重写一些重要函数,都交于插件中处理


    public class BaseActivityImp extends AppCompatActivity implements IActivity {

    private final String TAG = getClass().getSimpleName();

    /**
    * 代理 Activity
    */
    protected Activity that;

    @Override
    public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle bundle) {
    this.that = activity;

    Log.i(TAG, " onActivityCreated");
    onCreate(bundle);
    }

    /**
    * 通过 View 方式加载
    *
    * @param view
    */
    @Override
    public void setContentView(View view) {
    Log.i(TAG, " setContentView --> view");
    if (that != null) {
    that.setContentView(view);
    } else {
    super.setContentView(view);
    }
    }

    /**
    * 通过 layoutID 加载
    *
    * @param layoutResID
    */
    @Override
    public void setContentView(int layoutResID) {
    Log.i(TAG, " setContentView --> layoutResID");
    if (that != null) {
    that.setContentView(layoutResID);
    } else {
    super.setContentView(layoutResID);
    }
    }

    /**
    * 通过代理 去找布局 ID
    *
    * @param id
    * @param <T>
    * @return
    */
    @Override
    public <T extends View> T findViewById(int id) {
    if (that != null)
    return that.findViewById(id);
    return super.findViewById(id);
    }

    /**
    * 通过 代理去开启 Activity
    *
    * @param intent
    */
    @Override
    public void startActivity(Intent intent) {
    if (that != null) {
    Intent tempIntent = new Intent();
    tempIntent.putExtra(Constants.ACTIVITY_CLASS_NAME, intent.getComponent().getClassName());
    that.startActivity(tempIntent);
    } else
    super.startActivity(intent);
    }



    @Override
    public String getPackageName() {
    return that.getPackageName();
    }

    @Override
    public void onActivityStarted(@NonNull Activity activity) {
    Log.i(TAG, " onActivityStarted");
    onStart();


    }

    @Override
    public void onActivityResumed(@NonNull Activity activity) {
    Log.i(TAG, " onActivityResumed");
    onResume();
    }

    @Override
    public void onActivityPaused(@NonNull Activity activity) {
    Log.i(TAG, " onActivityPaused");
    onPause();
    }

    @Override
    public void onActivityStopped(@NonNull Activity activity) {
    Log.i(TAG, " onActivityStopped");
    onStop();
    }

    @Override
    public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) {
    onSaveInstanceState(bundle);
    Log.i(TAG, " onActivitySaveInstanceState");
    }

    @Override
    public void onActivityDestroyed(@NonNull Activity activity) {
    Log.i(TAG, " onActivityDestroyed");
    onDestroy();

    }


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {

    }


    @Override
    protected void onStart() {

    }

    @Override
    protected void onResume() {

    }

    @Override
    protected void onStop() {

    }

    @Override
    protected void onPause() {

    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {

    }

    @Override
    protected void onDestroy() {

    }

    @Override
    public void onBackPressed() {

    }
    }
    复制代码


加载插件中 Broadcast


流程图


-.png




代码实现




  1. 代理 ProxyActivity 中重写注册广播


        @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
    IntentFilter proxyIntentFilter = new IntentFilter();
    for (int i = 0; i < filter.countActions(); i++) {
    //内部是一个数组
    proxyIntentFilter.addAction(filter.getAction(i));
    }
    //交给代理广播去注册
    this.receiver = new ProxyBroadcast(receiver.getClass().getName(), this);
    return super.registerReceiver(this.receiver, filter);
    }
    复制代码


  2. 加载插件中需要注册的广播全路径


        public ProxyBroadcast(String broadcastClassName, Context context) {
    this.broadcastClassName = broadcastClassName;
    this.iBroadcast = iBroadcast;

    //通过加载插件的 DexClassLoader loadClass
    try {
    Class<?> pluginBroadcastClassName = PluginManager.getInstance().getPluginClassLoader().loadClass(broadcastClassName);
    Constructor<?> constructor = pluginBroadcastClassName.getConstructor(new Class[]{});
    iBroadcast = (IBroadcast) constructor.newInstance(new Object[]{});
    //返回给插件中广播生命周期
    iBroadcast.attach(context);
    } catch (Exception e) {
    e.printStackTrace();
    Log.e(TAG, e.getMessage());
    }
    }
    复制代码


  3. 接收到消息返回给插件中


        @Override
    public void onReceive(Context context, Intent intent) {
    iBroadcast.onReceive(context, intent);
    }
    复制代码


  4. 插件中广播注册


        /**
    * 动态注册广播
    */
    public void register() {
    //动态注册广播
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("_DevYK");
    receiver = new PluginBroadReceiver();
    registerReceiver(receiver, intentFilter);
    }

    /**
    * 通过代理去注册广播
    *
    * @param receiver
    * @param filter
    * @return
    */
    @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
    if (that != null) {
    return that.registerReceiver(receiver, filter);
    } else
    return super.registerReceiver(receiver, filter);
    }
    复制代码


  5. 插件中实现代理广播中的生命周期并实现接收函数


    public class BaseBroadReceiverImp extends BroadcastReceiver implements IBroadcast {
    //代理广播中绑定成功插件广播
    @Override
    public void attach(Context context) {

    }

    //代理广播接收到数据转发给插件中
    @Override
    public void onReceive(Context context, Intent intent) {

    }
    }
    复制代码


加载插件中 Service


流程图


-service.png


代码实现




  1. ProxyAcitivy 开启插件中服务


        /**
    * 加载插件中 启动服务
    * @param service
    * @return
    */
    @Override
    public ComponentName startService(Intent service) {
    String className = getLoadServiceClassName(service);
    Intent intent = new Intent(this,ProxyService.class);
    intent.putExtra(Constants.SERVICE_CLASS_NAME,className);
    return super.startService(intent);
    }
    复制代码

    ProxyService.java


    public class ProxyService extends Service {

    private IService iService;

    @Override
    public IBinder onBind(Intent intent) {
    return iService.onBind(intent);
    }

    @Override
    public void onCreate() {
    super.onCreate();

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    if (iService == null)
    init(intent);
    return iService.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onStart(Intent intent, int startId) {
    super.onStart(intent, startId);
    iService.onStart(intent,startId);
    }

    @Override
    public boolean onUnbind(Intent intent) {
    iService.onUnbind(intent);
    return super.onUnbind(intent);
    }


    @Override
    public void onDestroy() {
    super.onDestroy();
    iService.onDestroy();
    }


    //初始化
    public void init(Intent proIntent) {
    //拿到需要启动服务的全类名
    String serviceClassName = getServiceClassName(proIntent);
    try {
    Class<?> pluginService = PluginManager.getInstance().getPluginClassLoader().loadClass(serviceClassName);
    Constructor<?> constructor = pluginService.getConstructor(new Class[]{});
    iService = (IService) constructor.newInstance(new Object[]{});
    iService.onCreate(getApplicationContext());
    } catch (Exception e) {
    //加载 class
    }
    }

    @Override
    public ClassLoader getClassLoader() {
    return PluginManager.getInstance().getPluginClassLoader();
    }

    public String getServiceClassName(Intent intent) {
    return intent.getStringExtra(Constants.SERVICE_CLASS_NAME);
    }
    }
    复制代码


  2. 插件服务实现 IService


    public class BaseServiceImp extends Service implements IService {
    ...
    }
    复制代码


  3. 插件中重写 startService 交于代理中处理


        /**
    * 加载插件中服务,交于代理处理
    * @param service
    * @return
    */
    @Override
    public ComponentName startService(Intent service) {
    String className = getLoadServiceClassName(service);
    Intent intent = new Intent(this,ProxyService.class);
    intent.putExtra(Constants.SERVICE_CLASS_NAME,className);
    return super.startService(intent);
    }
    复制代码


总结


动态加载 Activity, Broadcast , Service 其实基本原理就是将插件中需要启动四大组件的信息告诉代理类中,让代理类来负责处理插件中的逻辑,代理类中处理完之后通过 IActivity, IBroadcast, IService 来通知插件。


动态加载插件我们这篇文章就讲到这里了,感兴趣的可以参考项目地址 ,这个实现方案不适合线上商业项目,使用需谨慎。如果项目中只用到了插件中的生命周期可以选择性的使用。


感谢阅览本篇文章,谢谢!


参考文章


《Android 插件化开发指南》


Android插件化框架总结


深入理解Android插件化技术


DroidPlugin


作者:DevYK
链接:https://juejin.cn/post/6844903924000882695
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册