app插件化-绑定生命周期版本
1.概述
2.实现原理
3.注意事项
4.优点和缺点
1.概述
app插件化-绑定生命周期版本是为了实现app自升级和功能插件化,所研究的一个版本。
如果需要实现加载第三方界面,需要用到《app插件化-替换系统变量》版本原理,我们会在这个版本的文档中进行具体概述。
在这里要多谢网上的大大们提供了思路和demo,可能有的我已经记不住是谁了,不过谢谢各位能遵循开源的思想将自己的成果分享出来,我也是基于前人的努力,把我自己的新的分享出来。
2.实现原理
2.1 实现demo工程
1.ApkLoadRes 插件apk工程
2.ApkLoadTest 宿主apk工程
2.2 实现原理
相关资料:http://www.cnblogs.com/Androider123/archive/2013/01/23/2873193.html
http://blog.csdn.net/jefferyyangkai/article/details/9260841
http://www.cnblogs.com/over140/archive/2011/11/23/2259367.html
http://www.blogjava.net/zh-weir/archive/2011/10/29/362294.html
http://blog.csdn.net/singwhatiwanna/article/details/23387079
该插件化实现的思路,实质是通过获取插件apk的dexClassLoader 和 resource,然后实现对插件资源文件的获取。
我们以demo工程的思路来看,在ApkLoadTest工程的mainActivity中。
2.2.1宿主apk实现原理
先获取插件的dexClassLoader 和 resource
我们通过反射调用的方式将我们宿主获取的这些资源注入到插件当中,图片小的话可以滚轮放大看。
1.我们通过插件的dexClassLoader加载并实例化我们要调用的插件的类 TestClass
(注意:在其他逻辑中,这一步其实可以直接在宿主的callActivity中,通过绑定生命周期来调用插件的activity,但是因为我们支付sdk调用支付的逻辑不同,所以这里以支付sdk为准,要先调用支付sdk中的某个单独的类,再进行界面跳转)
2.反射调用插件类里的setActivity方法,将宿主当前activity的context注入插件,用来和插件的dexClassLoader和resource一起生成插件的LocalContext,并主要做界面跳转功能
3.注入插件的dexClassLoader和resource到插件的类TestClass中
那么现在这个插件的TestClass类,就已经被我们的宿主apk给调用起来了。
我们在插件的这个TestClass类中可以做什么处理呢。
我们可以显示view,跳转界面。
我们可以看到在这个里面 layout view的获取要通过UnInstallViewLoad.loadRemoteView方法获取,等下再看这个方法。
获取了view之后,实现一个dialog,将view给set进去,然后show。
但是要注意,Dialog dialog = new Dialog(otherActivity),用的是宿主的activity变量。
这里用红字来标红,因为这个是最重要的。因为我们是用的反射获取的插件的class或者activity,他们没有被系统安装,没有被虚拟机执行过,所以我们这个方式实现的activity是没有任何生命周期的,所以他的context也是不存在的。
所以插件内部所有的view的初始化、显示,需要context的 都需要使用宿主注入的context变量。
这样就实现了调用插件,并显示插件内部view的功能了。
回头来看UnInstallViewLoad.loadRemoteView方法
方法说明:
获取插件apk的layout资源,通过插件的localContext获取插件内部布局资源
方法参数:
unInstallApkContext 插件apk的localContext,从宿主注入到插件
unInstallApkPackaeName 插件apk的包名
layoutName layout资源文件的名字
跳转界面:
可以发现是通过我们生成的插件的localConext来跳转界面的,而且有2个类名的参数,具体内部实现我们看LacalContext的源码
注释中说明了,实际上是通过宿主的context来实现的跳转界面
先是加载了实际要跳转到的宿主的callBaseActivity (即startActivityPckAndName)
然后intent中put了2个参数
START_ACTIVITY: 当跳转到宿主的callBassActivity后,要反射调用的插件类名(表面显示界面)
RESAPK_PATH : 插件apk的路径,需要通过这个路径获取插件相关资源
到这里,就是此插件化版本的核心了,跳转界面。
下面我们先看宿主中callBaseActivity
这个activity实际上是真正控制插件activity的
CallBaseActivity继承自activity
我们重点要关注 initLocalClass方法 和 下面的try块里的2个反射方法
看到initLocalClass方法的2个参数都是我们在前面startActivity的时候 intent的参数
通过传进来的apkPath 获取插件的resource和dexClassLoader
并通过dexClassLoader 加载并实例化传进来的activtiy 作为全局参数
后面的try里的内容,就是我们在宿主activity调用插件activity的核心了
可以看到在baseActivity里面,onCreate,onResume,onPause,onStop,onDestory等等生命周期的方法中,我们都通过反射调用了插件activity的同名生命周期方法。
因为插件的activity没有安装,没有被注册到虚拟机中,所以是没有生命周期的,当我们通过这种方式绑定了插件activity和宿主activity的生命周期后,就可以实现插件activity的相关功能和逻辑了
每个生命周期会先在baseActivity中被回调,然后我们不做任何处理,在反射调用插件的同名生命周期方法。
通过这个reflectMethod方法来反射调用插件的接口
第一个参数是我们在最开始initLocalClass中获取到的插件的dexClassLoader
第二个参数是我们在initLocalClass中实例化的插件activity的实例
第三个参数是接口的参数列表,是Class数组
第四个参数是参数的值的数组,存放参数对象
要注意的是,我们要调用每个插件activity的时候,都要再onCreate里反射setActivity方法和onCreate方法,而且setActivity方法一定要再onCreate前面
因为setActivity方法是将插件activtity中必要的系统变量给注入进去
2.2.2 插件apk实现原理
上节讲了宿主apk如何调用插件apk的activity,和普通的类,显示view
本节讲插件apk中的activity被宿主调用之后的实现
首先,我们插件apk中的activity都必须要继承自ResBaseActivity
ResBaseActivity实质也是继承的Activity
上节讲了,CallBaseActivity调用插件Activity的时候,setActivity方法必须要再onCreate前面调用,因为setActivity是注入了插件所需要的resources,decClassLoader变量,还有宿主activity。
接下来的onCreate方法就是要创建界面了
在ResBaseActivity的onCreate中我们做了个判断,如果 bunler参数未空,就调用父类的oncreate方法
我们看下ResBaseActivity的子类,基本所有的业务逻辑以后都是这么实现
Oncreate方法不做任何解释了。
重点看下setContentView方法,直接看ResBaseActivity源码
有2个重载的方法,一个是直接用view做参数,一个是用layoutId做参数
可以看到,实际上最后调用的都是以view做参数的那个方法。
这里要关注的是,我们获取LayoutInflater的方法实际不是系统的,而是我们通过反射自己newInstance的一个LayoutInflater
从下面的图可以看出我们是通过插件的localContext作为参数,创建的这个layoutInflat
和前面的UnInstallViewLoad.loadRemoteView其实是一样的。
然后setContentView之后,要获取layout里的控件
findViewById前面都要用this ,具体细节我们看ResBaseActivity
实际是用前面setContentView里获取到的decortView来查找内部的控件
这样,插件activity中ui资源我们就结束了。
跳转界面startActivity实现原理
在前文中我们已经讲过,插件中的startActivity实际是用的宿主activity的context来执行跳转,先跳转到宿主apk中的负责中转的baseActivity,然后通过START_ACTIVITY参数来确定要调用的插件activity是哪个,并开始同步执行生命周期
然后startService实现原理,和startActivity原理是一样的
结尾:要注意的是,插件activity中,除了获取资源文件和resource用的是插件的context,
其他的getApplicationContext,注册广播,finish()方法,都是要用宿主注入的context对象
这样子,绑定生命周期版本的插件化的原理就讲完了,下一节是我们要注意的地方。
3.注意事项
1.插件apk里对全局静态常量赋值,只在当前类里有效
原因:apk未被安装运行,这些常量信息也就没有被加载到内存空间中
2.在插件的apk的baseActivity中实现的onResume等回调不要superActivity的同名方法
原因:插件apk的activity实际不是一个真正的activity可以认为是一个普通类,当调用super方法会报空指针异常。
3.在插件apk内,只要是由baseResActivity的子类activtiy中的所有view都要遵循 uninstallView的获取规范
4.优点和缺点
优点:
当把这个框架都布置完成后,实现插件化可以很迅速,适合插件功能,插件界面没那么多的需求
缺点:
1.当插件功能过多,activity界面过多的时候,业务逻辑实现会比较复杂
2.静态常量不能保存,需要每次调用一个界面的时候,都要单独加一个参数注入必要的数据(公用的常量数据可考虑保存在sd卡)
3.不能调用第三方jar包中的activity,因为是人家写好的activity,不能遵循我们的规范
(怎么调用第三方jar包中的activity可以参考下篇 《支付sdk插件化-替换系统变量版本》)
demo下载地址:
插件工程:
http://download.csdn.net/detail/fengyagang/8273299
宿主工程:
http://download.csdn.net/detail/fengyagang/8273589