Shadow解决Activity等组件生命周期的方法解析,微信小程序双向绑定
2022/1/29 17:34:58
本文主要是介绍Shadow解决Activity等组件生命周期的方法解析,微信小程序双向绑定,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
- 插件代码都是现有业务代码,不能因为接入插件框架而需要修改代码(即需要插件框架无代码侵入性)。
- 在宿主的AndroidManifest.xml中只能注册有限数量(大约10个)的组件。宿主AndroidManifest过大会使宿主安装变慢,跨进程通信出错。
- 不能使用非公开API。
大方向的选择
其实我们早就在用一款也是基于代理组件转调插件组件的插件框架了。只不过这款插件框架用到了大量反射使用私有API,眼看着是不可能再Android 9.0上继续使用了。我们也调研了外界口碑最好的RePlugin。所以大概就这两种方向,一是用代理Activity作为壳子注册在宿主中真正运行起来,然后让它持有插件Activity,想办法在收到系统的生命周期方法调用时转调插件Activity的对应生命周期方法。二是Hack修改宿主PathClassLoader,让它能在收到系统查询AndroidManifest中注册的Activity的类时返回插件的Activity类。
方法二就是RePlugin的关键技术。它利用了JVM的特性。我也不太肯定这算不算是bug,总之ClassLoader的loadClass方法返回的实际类可以和它被要求加载的类名字不一样。举个例子,宿主的AndroidManifest.xml注册一个Activity名叫A,插件里有一个Activity名叫B。宿主代码或者apk中最终是没有A这个类的,只有在AndroidManifest中注册的一个名字而已。当想要加载插件Activity B时,就发出一个启动Activity A的Intent。系统收到这个Intent后会检查宿主安装的AndroidManifest信息,从中确定A是哪个apk安装的,就会找到宿主的PathClassLoader。然后系统就会试图从PathClassLoader中加载A这个类,然后作为Activity类型的对象使用(这很正常)。所以如果我们把宿主的PathClassLoader给Hack了,控制它的加载逻辑,让它收到这个加载调用时实际返回的是插件Activity B的类。由于B也真的是Activity的子类,所以系统拿回去当作Activity类型使用没有任何问题。这里再扩展一下,如果类C继承自类A,在加载C时也会去加载A,如果这时拿B当A返回的话,C收到B之后是会发现B的名字不是A而出错的。关于RePlugin这段关键技术的实现,当时调研时就发现实现的有些麻烦了。RePlugin选择复制一个PathClassLoader,然后替换系统持有的
PathClassLoader。所以复制PathClassLoader需要反射使用PathClassLoader的私有API,拿出来它里面的数据,替换系统持有的PathClassLoader也要反射修改私有API。我们当时已经实现了“全动态插件框架”,其中代理壳子Activity的动态化使用的方法也能解决这个问题,我们的选择是在宿主PathClassLoader上给它加一个parent ClassLoader。因为PathClassLoader也是一个有正常“双亲委派”逻辑的ClassLoader,它加载什么类都会先问自己parent ClassLoader先加载。所以我们加上去的这个parent ClassLoader也能完成RePlugin想要做的事。不过我们用它的目的是不希望壳子Activity打包在宿主占用宿主很多方法数,还不能更新。这一点以后可能再单独讲。关于这个替换实现,最近给RePlugin提了一个PR:github.com/Qihoo360/Re… ,有兴趣的同学可以看一下。
RePlugin的这种方案还有一点非常不适合我们的业务,就是宿主AndroidManifest中注册的“坑位”Activity,就是上面举例的Activity A,是不能同时供多个插件Activity使用的。就是我不能在宿主AndroidManifest中注册一个Activity A,然后让它同时支持插件Activity B和C。这是因为ClassLoader在loadClass的时候,收到的参数只有一个A的类名,我们没有办法传递更多信息,让ClassLoader能在这个loadClass的调用中区分出应该返回B还是应该返回C。所以这种方案需要在宿主中注册大量Activity,这对于我们的宿主来说是不可接受的。而方法一是用代理Activity持有插件Activity转调的方案,就可以在启动代理Activity时通过Intent传递很多参数,代理Activity通过Intent中的参数就能决定该构造一个B还是一个C。这就使得这种方案下壳子是可复用的。
还有一点就是我们在旧框架上就已经设计了“全动态插件框架”,所以基于方法一的方向上开发新插件框架,我们可以不修改宿主的任何代码,不跟宿主版本就能更新插件框架。关于这一点,后续文章再解析。
所以我们探索的方向就这样确定在方法一这个方向上了。
旧框架为什么要用反射、要用私有API?
我们的旧框架就是代理Activity转调插件Activity的方案,市面上还有很多插件框架也是这种方案。大家只是在实现转调的手段上不一样。这里就不深入分析它们都是怎么实现的了,总之就是一个目的,让插件Activity能收到生命周期回调。我们的旧框架和其他一些框架有一种这样的方案,就是让壳子Activity直接反射调用插件Activity对应的生命周期方法。这样做要解决一个额外的问题。
Activity被系统构造出实例之后,并不是直接调用onCreate方法的。首先会调用它的attach方法。attach方法实际上就是Activity的初始化方法,系统通过这个方法向Activity注入一些私有变量,比如Window、ActivityThread等等。插件Activity由于是我们壳子Activity自己new出来的,所以系统不会调用插件Activity的attach方法初始化它。Activity如果没有初始化就被调用了onCreate会有什么问题呢?我们前面说了我们的一个前提是插件Activity要求也要能正常编译安装运行,所以插件Activity的onCreate方法里一定写了super.onCreate()
调用。我们还要求对插件代码无侵入性,所以也不能在这个调用外面包一层“if (不是插件模式)"。那么在插件环境下,这个super.onCreate()
就一定会执行。Activity基类的onCreate方法就会使用那些应该初始化过的私有变量,但是现在它们没有初始化。所以这一类插件框架方案就要解决这个问题。所以要么是反射调用attach方法,传入从壳子Activity拿到的私有变量,比如说反射出壳子Activity的ActivityThread对象,传给插件Activity的attach方法。要么就干脆直接用反射枚举读写壳子Activity和插件Activity的私有变量,把它们写成一样的完成这个初始化。所以这就是为什么旧框架需要使用反射和私有API。
Shadow如何解决的插件Activity生命周期问题
实际上通过前面的分析,我们发现其实根本不需要插件Activity执行super.onCreate()
方法。明确了这个方案原本的目的,就是在宿主中注册并启动一个壳子Activity,这个壳子Activity什么都不自己做,想办法让插件Activity的各个生命周期方法实现代码成为壳子Activity的各个生命周期实现方法的代码。因此我们根本不需要插件Activity是一个系统Activity的子类。我们只是因为需要插件Activity还能正常安装运行,才导致它是一个真正的系统Activity子类的。
我们也知道如果不要求对插件代码无侵入性,也不要求插件能独立安装运行,实际上是可以把让插件Activity不用继承系统Activity了,就简单继承一个普通类就行了。这个普通类上定义一些跟系统Activity类一样的生命周期方法,实现成空实现,然后这些生命周期方法可以设置成public的,这样壳子Activity以这个普通类类型持有插件Activity就可以直接调用插件Activity的生命周期方法了。这样实现既不用反射也不用私有API。
而我们实际上是不需要插件的apk能独立安装运行的,我们希望插件能独立安装运行的本质目的是节省人力,不要维护两套代码。所以看起来,这里只要引入AOP手段,通过AOP编程修改插件Activity的父类,把插件Activity的父类从系统Activity改成我们想要的普通类就行了。
我们先不说这个AOP手段怎么实现,因为问题还没彻底搞清。我们是真的想让A持有B,A收到什么调用就转调B的什么调用吗?这是我们的真正目的吗?不是的。单纯的只是让插件Activity中的super.onCreate()
调用失效,并不完美。因为这跟插件Activity正常安装运行时还有点不一样。现在插件Activity的onCreate方法的代码就相当于是壳子Activity的onCreate方法的代码的一部分了。比如:
class ShadowActivity {
public void onCreate(Bundle savedInstanceState) {
}
}
class PluginActivity extends ShadowActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
System.out.println(“Hello World!”);
}
}
class ContainerActivity extends Activity {
ShadowActivity pluginActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pluginActivity.onCreate(savedInstanceState);
}
}
上面的ShadowActivity
就是我们前面说的普通类。仔细看一下是不是就相当于ContainerActivity
原本就实现成这样:
class ContainerActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
System.out.println(“Hello World!”);
}
}
只要PluginActivity
是动态加载的,就相当于ContainerActivity
的实现是动态的。但是如果原本PluginActivity
的代码是这样的呢?
class PluginActivity extends ShadowActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
savedInstanceState.clear();
super.onCreate(savedInstanceState);
}
}
显然这种代码在正常安装运行时和插件环境运行时就不一样了。因为变成了:
;
super.onCreate(savedInstanceState);
}
}
显然这种代码在正常安装运行时和插件环境运行时就不一样了。因为变成了:
这篇关于Shadow解决Activity等组件生命周期的方法解析,微信小程序双向绑定的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-20微信小程序开发入门指南
- 2024-12-20小程序 createCameraContext() 怎么实现识别条形码功能?-icode9专业技术文章分享
- 2024-11-22微信小程序的接口信息py可以抓到吗?-icode9专业技术文章分享
- 2024-11-22怎样解析出微信小程序二维码带的参数?-icode9专业技术文章分享
- 2024-11-22微信小程序二维码怎样解析成链接?-icode9专业技术文章分享
- 2024-11-22微信小程序接口地址的域名需要怎么设置?-icode9专业技术文章分享
- 2024-11-22微信小程序的业务域名有什么作用-icode9专业技术文章分享
- 2024-11-22微信小程序 image有类似html5的onload吗?-icode9专业技术文章分享
- 2024-11-22微信小程序中怎么实现文本内容超出行数后显示省略号?-icode9专业技术文章分享
- 2024-11-22微信小程序怎么实现分享样式定制和图片定制功能?-icode9专业技术文章分享