浅谈Android插件化
如今,插件化与组件化的开发越来越广泛的被我们所使用,尤其是许多大公司。
什么事插件化,什么是组件化呢?
组件化开发:就是将一个app分成多个模块,每个模块都是一个组件(Module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。
插件化开发:和组件化开发略有不用,插件化开发时将整个app拆分成很多模块,这些模块包括一个宿主和多个插件,每个模块都是一个apk(组件化的每个模块是个lib),最终打包的时候将宿主apk和插件apk分开或者联合打包。
有了组件化,为什么还要用插件化呢?插件化开发总的来说有以下几点好处(不同插件框架不一样):
宿主和插件分开编译
并发开发
动态更新插件
按需下载模块
方法数或变量数爆棚
可参考:http://android.jobbole.com/82746/
一、简单了解
压缩包:
1、zip包:qq表情
2、dex包:代码
3、apk(未安装 安装)
安装:PathClassLoader
非安装:DexClassLoader
插件化的由来:
1、65535/64k
2、功能解耦,更有助于分工合作
插件化要解决的问题 :
1、动态加载apk
2、资源加载
3、代码加载
看一下我们平时在Android应用程序中使用的插件(以qq为例):
可以看到在assets--plugins文件中,许多功能是通过插件形式加载的。
例如微云plugin虽然我们使用不多,但在我们使用的时候点击微云,他会把微云插件下载下来。
无论是360、支付宝(全部应用)、微信(小程序虽然是用h5开发的,但是架构最终转化到微信里面,是转化成原生代码)等,平时我们使用的很多大型的应用程序要满足不同用户需求,程序都有插件化开发的身影。
二、插件化
1、判断插件是否存在
所需插件-->判断本地是否有插件-->本地没有则下载(请求服务获取文件名、包名等)
2、插件化原理
每次使用activity都会在mainifest.xml禽蛋文件中注册,那如何在宿主程序中加载外来的Activity(实现生命周期)呢?很多人会想到Hook技术,但我们还可以通过插桩技术来实现。
如果理解的类加载器、反射及动态代理概念,那么便很容易理解插件化开发。
本文简单以避开AndroidMainfest.xml限制,插件化方式让外来apk自由加载为例。
动态加载apk原理:
替身Activity注册 ----> 找到插件Activity进行替换
资源加载原理图:
插件化资源加载原理图.png
3、从framework层源码分析插件化
加载插件apk,因为需要获得DexClassLoader和一个代表插件apk的Resource,所以需要实例化Resource,而Resources不能直接通过new对象直接获得,下面带大家分析一下源码:
framework层Resources.java这个文件(下面代码仅是Resources构造方法):
/**
* Create a new Resources object on top of an existing set of assets in an
* AssetManager.
*
* @deprecated Resources should not be constructed by apps.
* See {@link android.content.Context#createConfigurationContext(Configuration)}.
*
* @param assets Previously created AssetManager.
* @param metrics Current display metrics to consider when
* selecting/computing resource values.
* @param config Desired device configuration to consider when
* selecting/computing resource values (optional).
*/
@Deprecated
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
this(null);
mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
}
/**
* Creates a new Resources object with CompatibilityInfo.
*
* @param classLoader class loader for the package used to load custom
* resource classes, may be {@code null} to use system
* class loader
* @hide
*/
public Resources(@Nullable ClassLoader classLoader) {
mClassLoader = classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader;
}
/**
* Only for creating the System resources.
*/
private Resources() {
this(null);
final DisplayMetrics metrics = new DisplayMetrics();
metrics.setToDefaults();
final Configuration config = new Configuration();
config.setToDefaults();
mResourcesImpl = new ResourcesImpl(AssetManager.getSystem(), metrics, config,
new DisplayAdjustments());
}
Resources中有三个构造函数,其中第二个是被系统隐藏的,第三个是只能创建系统Resources,那么我们只能通过第一个构造方法去实例化Resources(AssetManager assets,DisplayMetrics metrics,Configuration config){...}去实例化Resources。
AssetManager 资源管理类,metrics和config这两个参数和插件apk无关直接用系统的就可以,因此可通过context.getResources().getDisplayMetrics()和context.getResources().getConfiguration()获得。
那如何获取AssetManager呢,我们看AssetManager.java文件(仅AssetManager构造方法码):
/**
* Create a new AssetManager containing only the basic system assets.
* Applications will not generally use this method, instead retrieving the
* appropriate asset manager with {@link Resources#getAssets}. Not for
* use by applications.
* {@hide}
*/
public AssetManager() {
synchronized (this) {
if (DEBUG_REFS) {
mNumRefs = 0;
incRefsLocked(this.hashCode());
}
init(false);
if (localLOGV) Log.v(TAG, "New asset manager: " + this);
ensureSystemAssets();
}
}
AssetManager的构造方法是也被系统所隐藏的,那我们可以通过反射实例化AssetManager,因为空的AssetManager是没有任何作用的,因此要给它设置路径。
/**
* Add an additional set of assets to the asset manager. This can be
* either a directory or ZIP file. Not for use by applications. Returns
* the cookie of the added asset, or 0 on failure.
* {@hide}
*/
public final int addAssetPath(String path) {
return addAssetPathInternal(path, false);
}
通过反射实例化AssetManager.png
invoke(Object obj,Object...args)第一个参数为类的实例,第二个参数为相应函数中的参数。
那么我们便获得到了Resources实例。
4、替身PorxyActivity
未完,待续...
如有问题欢迎交流。
如转载请注明出处,谢谢!