插件化框架shadow的总结
1、插件框架有两个作用:一是“自解耦”,二是“免安装”。
目标:“像Web一样开发App”则是一个我们后期达成的目标,这大概是“自解耦”和“免安装”的组合形式
1.自解耦指的是:一个应用原本由一份代码编译而成,希望改成将其中的一些功能单独编译,像插件一样动态插在主应用上。这样一来可是使主应用体积变小,下载安装更方便。二来可以是比较独立的功能可以单独开发调试,甚至单独更新版本。
2.免安装指的是:一个应用原本需要安装过程才能启动运行,希望改为无需安装即可从一个已经安装运行的App中启动起来。这一需求的主要目的是提高流量复用的能力。
还未具备的能力:1、下载一部分启动一部分的能力。2、ContentProvider
2、宿主和插件使用同一个包名的好处
在执行插件代码的过程中,系统可能会调用一些(公有的或私有的)接口获取应用的applicationId,
然而插件从真正意义上来说并没有安装到设备上,如果插件的applicationId和宿主的applicationId不相同,系统获取到插件的applicationId是一个没有安装过的包名,系统就因此crash。
为了避免出现上述情况,有两种方法:
- hook系统接口,需要兼容各种OEM系统以及Android各版本
- 插件的applicationId和宿主的applicationId保持一致
3、Fragment调试很麻烦:
比如业务插件里有一个com.xx.GiftFragment类,实际运行时这个类的名字就变成了com.xx.GiftFragment_。这就导致在com.xx.GiftFragment的源码上打断点是断不下来的。必须在程序运行起来之后,用IDE的重命名功能把它改名为com.xx.GiftFragment_,使得源码和运行时类名字一致才能断点。
4、缺点:
Shadow开源的代码目前没有包括插件下载和版本检查实现的。
加固
卸载插件
ContentProvider
Fragment调试很麻烦
插件A同插件B有冲突so
插件化要解决的关键问题是:插件Activity能收到生命周期回调
旧框架使用反射的地方:attach初始化activity,super.onCreate()获取私有变量传递给我们自己的new的activity
shdow的关键点:使用AOP和字节码编辑将插件activity继承自activity的类替换为插件Activity继承shadowActivity
Activity和Service最大的区别是Activity是有状态的,Service是无状态的。
对于插件框架来说,有两点十分必要。
一是插件一般都是热更新的,质量上要求可能会降低一些,一旦出现Crash不会影响其他进程的组件。
二是Android的JVM虚拟机不支持Native动态库反加载,所以在同一个进程中相同so库的不同版本即不能同时加载,也不能换着加载,会造成插件和宿主存在so库冲突。
动态化原理:
C语言需要编译和连接两个步骤,java则编译成字节码,在运行时才会去查找用到的类,在用到的时候再去替换对应真正要启动的类
container代理壳子:
我们可以通过修改ClassLoader的parent,为ClassLoader新增一个parent。将原本的PathClassLoader->BootCalssLoader结构变为PathClassLoader ——> DexClassLoader ——> BootCalssLoader
Container的动态化是使用了唯一一次反射修改私有变量。ClassLoader的parent域不属于非公开API,甚至不是Android的代码,而是JDK的代码
view同名问题:
插件和宿主存在同名的view会报错,因为LayoutInflater在inflated的时候做了一层缓存,以View的类名作为Key保存了Class对象。
一个自定义的Factory,然后复制了原本内置构造逻辑的代码。在这段逻辑中,也有缓存机制。但是我们将缓存的Key添加了标记插件apk的“partKey”作为一部分,这样相同名字的View在缓存中就是不同的Key了
webview加载插件资源的问题:
替换ShadowWebView,拦截请求,将file:///android_asset/协议都修改成http://android.asset/协议
so加载的问题
插件A同插件B有冲突so,又需要在同一个进程中工作,也需要so的设计方自行解决。
Android系统不允许在一个进程中混用64位的so和32位的so,目前的解决方案是让宿主先加载一个32位的so