Android插件化系列第(五)篇---Activity的插件化方案(代理模式)

2024-09-02 14:58

本文主要是介绍Android插件化系列第(五)篇---Activity的插件化方案(代理模式),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

这篇文章介绍Activity的插件化方案,Activity的插件化方案不止今天介绍的这一种。建议在看本文之前,先看我的前两篇博客,如果前两篇有认真看过,那么阅读本文至多十分钟完事儿!

  • Android插件化系列第(一)篇—Hook技术之Activity的启动过程拦截
  • Android插件化系列第(二)篇—动态加载技术之apk换肤
  • Android插件化系列第(四)篇—插件加载机制两种方案

看过上面文章,我们知道了怎么加载一个插件中的Activity类了,想做Activity的插件化,还需要解决几个问题。

  • 1、插件中所有的Activity都没有在宿主中注册,怎么欺骗AMS去启动一个清单文件不存在的Activity;
  • 2、把一个Activity类加载之后,怎么使插件里的Activity具有生命周期;
  • 3、插件apk中用过的各种资源,如何动态的加载资源。

关于问题1,在本系列的第一篇已经说过,首先在宿主中声明一个ProxyActivity,然后启动ProxyActivity,即:ProxyActivity + 插件中没注册的Activity = 标准的Activity

关于问题2,其实在本系列第一篇也有体现,准备在下一篇写的更清楚一点。

关于问题3,在本系列的第二篇也写过,以一个换肤的例子说明怎么样去加载插件中的资源。

本篇博客主要讲述的是第二个问题,把一个Activity类加载之后,怎么使插件里的Activity具有生命周期。

这里使用Activity代理模式。老套路,在宿主APK注册一个ProxyActivity(代理Activity),就是作为占坑使用。每次打开插件APK里的某一个Activity的时候,都是在宿主里使用启动ProxyActivity,然后在ProxyActivity的生命周期里方法中,调用插件中的Activity实例的生命周期方法,从而执行插件APK的业务逻辑。所以思路就来了。
第一、ProxyActivity中需要保存一个Activity实例,该实例记录着当前需要调用插件中哪个Activity的生命周期方法。
第二、ProxyActivity如何调用插件apk中Activity的所有生命周期的方法,使用反射呢?还是其他方式。

这种Activity的插件化思想最初来自dynamic-load-apk,dynamic-load-apk调用插件apk中Activity的所有生命周期的方法最初使用的是反射的方式,因为反射会影响效率,最后换成接口的方式。先看反射这种方式,在代理activity中,第一步要获取将要反射所有生命周期方法。

    protected void instantiateLifecircleMethods(Class<?> localClass) {String[] methodNames = new String[] {"onRestart","onStart","onResume","onPause","onStop","onDestory"};for (String methodName : methodNames) {Method method = null;try {method = localClass.getDeclaredMethod(methodName, new Class[] { });method.setAccessible(true);} catch (NoSuchMethodException e) {e.printStackTrace();}//mActivityLifecircleMethods是一个map集合mActivityLifecircleMethods.put(methodName, method);}Method onCreate = null;try {onCreate = localClass.getDeclaredMethod("onCreate", new Class[] { Bundle.class });onCreate.setAccessible(true);} catch (NoSuchMethodException e) {e.printStackTrace();}mActivityLifecircleMethods.put("onCreate", onCreate);Method onActivityResult = null;try {onActivityResult = localClass.getDeclaredMethod("onActivityResult",new Class[] { int.class, int.class, Intent.class });onActivityResult.setAccessible(true);} catch (NoSuchMethodException e) {e.printStackTrace();}mActivityLifecircleMethods.put("onActivityResult", onActivityResult);}

第二步,反射所有Activity的生命周期方法

    @Overrideprotected void onResume() {super.onResume();Method onResume = mActivityLifecircleMethods.get("onResume");if (onResume != null) {try {onResume.invoke(mRemoteActivity, new Object[] { });} catch (Exception e) {e.printStackTrace();}}}@Overrideprotected void onPause() {Method onPause = mActivityLifecircleMethods.get("onPause");if (onPause != null) {try {onPause.invoke(mRemoteActivity, new Object[] { });} catch (Exception e) {e.printStackTrace();}}super.onPause();}protected void setRemoteActivity(Object activity) {try {//插件中Activity对象mRemoteActivity = (Activity) activity;} catch (ClassCastException e) {e.printStackTrace();}}

这是反射的方式,后面dynamic-load-apk经过优化,改成接口的方式,将activity的生命周期方法封装一个接口,代理activity中实现一下这个接口,然后还是通过代理activity去调用插件activity实现的生命周期方法。

public interface DLPlugin {public void onStart();public void onRestart();public void onActivityResult(int requestCode, int resultCode, Intent data);public void onResume();public void onPause();public void onStop();public void onDestroy();public void onCreate(Bundle savedInstanceState);public void setProxy(Activity proxyActivity, String dexPath);public void onSaveInstanceState(Bundle outState);public void onNewIntent(Intent intent);public void onRestoreInstanceState(Bundle savedInstanceState);public boolean onTouchEvent(MotionEvent event);public boolean onKeyUp(int keyCode, KeyEvent event);public void onWindowAttributesChanged(LayoutParams params);public void onWindowFocusChanged(boolean hasFocus);
}
    @Overrideprotected void onStart() {mRemoteActivity.onStart();super.onStart();}@Overrideprotected void onRestart() {mRemoteActivity.onRestart();super.onRestart();}@Overrideprotected void onResume() {mRemoteActivity.onResume();super.onResume();}@Overrideprotected void onPause() {mRemoteActivity.onPause();super.onPause();}

这就是dynamic-load-apk之中Activity插件化方案,如果要使用这种方案,我们开发插件需要遵循一定的规范,dynamic-load-apk要求遵循的规范如下:

  • 1、慎用this(接口除外):因为this指向的是当前对象,即apk中的activity,但是由于activity已经不是常规意义上的activity,所以this是没有意义的,但是如果this表示的是一个接口而不是context,比如activity实现了而一个接口,那么this继续有效。

  • 2、使用that:既然this不能用,那就用that,that是apk中activity的基类BaseActivity中的一个成员,它在apk安装运行的时候指向this,而在未安装的时候指向宿主程序中的代理activity,anyway,that is better than this。

-3、activity的成员方法调用问题:原则来说,需要通过that来调用成员方法,但是由于大部分常用的api已经被重写,所以仅仅是针对部分api才需要通过that去调用用。同时,apk安装以后仍然可以正常运行。

  • 4、启动新activity的约束:启动外部activity不受限制,启动apk内部的activity有限制,首先由于apk中的activity没注册,所以不支持隐式调用,其次必须通过BaseActivity中定义的新方法startActivityByProxy和startActivityForResultByProxy,还有就是不支持LaunchMode。

  • 5、目前暂不支持Service、BroadcastReceiver等需要注册才能使用的组件,但广播可以采用代码动态注册。

可以看到限制非常大,现在估计不会有谁采用这种方式做插件化开发了,但是2014年那个时候,dynamic-load-apk这种思想还是非常先进的,相信这为后来的插件化方案提供了很宝贵的经验,下一篇博客讲解用Hook方式,实现业界比较倡导的一种Activity插件化方案。

Please accept mybest wishes for your happiness and success !

参考:https://github.com/singwhatiwanna/dynamic-load-apk

这篇关于Android插件化系列第(五)篇---Activity的插件化方案(代理模式)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1130275

相关文章

MySQL 迁移至 Doris 最佳实践方案(最新整理)

《MySQL迁移至Doris最佳实践方案(最新整理)》本文将深入剖析三种经过实践验证的MySQL迁移至Doris的最佳方案,涵盖全量迁移、增量同步、混合迁移以及基于CDC(ChangeData... 目录一、China编程JDBC Catalog 联邦查询方案(适合跨库实时查询)1. 方案概述2. 环境要求3.

SpringBoot3.X 整合 MinIO 存储原生方案

《SpringBoot3.X整合MinIO存储原生方案》本文详细介绍了SpringBoot3.X整合MinIO的原生方案,从环境搭建到核心功能实现,涵盖了文件上传、下载、删除等常用操作,并补充了... 目录SpringBoot3.X整合MinIO存储原生方案:从环境搭建到实战开发一、前言:为什么选择MinI

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

java使用protobuf-maven-plugin的插件编译proto文件详解

《java使用protobuf-maven-plugin的插件编译proto文件详解》:本文主要介绍java使用protobuf-maven-plugin的插件编译proto文件,具有很好的参考价... 目录protobuf文件作为数据传输和存储的协议主要介绍在Java使用maven编译proto文件的插件

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

Knife4j+Axios+Redis前后端分离架构下的 API 管理与会话方案(最新推荐)

《Knife4j+Axios+Redis前后端分离架构下的API管理与会话方案(最新推荐)》本文主要介绍了Swagger与Knife4j的配置要点、前后端对接方法以及分布式Session实现原理,... 目录一、Swagger 与 Knife4j 的深度理解及配置要点Knife4j 配置关键要点1.Spri

Java设计模式---迭代器模式(Iterator)解读

《Java设计模式---迭代器模式(Iterator)解读》:本文主要介绍Java设计模式---迭代器模式(Iterator),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录1、迭代器(Iterator)1.1、结构1.2、常用方法1.3、本质1、解耦集合与遍历逻辑2、统一

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

SQLite3 在嵌入式C环境中存储音频/视频文件的最优方案

《SQLite3在嵌入式C环境中存储音频/视频文件的最优方案》本文探讨了SQLite3在嵌入式C环境中存储音视频文件的优化方案,推荐采用文件路径存储结合元数据管理,兼顾效率与资源限制,小文件可使用B... 目录SQLite3 在嵌入式C环境中存储音频/视频文件的专业方案一、存储策略选择1. 直接存储 vs

浏览器插件cursor实现自动注册、续杯的详细过程

《浏览器插件cursor实现自动注册、续杯的详细过程》Cursor简易注册助手脚本通过自动化邮箱填写和验证码获取流程,大大简化了Cursor的注册过程,它不仅提高了注册效率,还通过友好的用户界面和详细... 目录前言功能概述使用方法安装脚本使用流程邮箱输入页面验证码页面实战演示技术实现核心功能实现1. 随机