DynamicLoadApk是插件化比较优秀的框架,为了更好的理解插件化,我们来分析一下DynamicLoadApk的源码。
(1) 宿主:主 App,可以加载插件,也称 Host。 (2) 插件:插件 App,被宿主加载的 App,也称 Plugin,可以是跟普通 App 一样的 Apk 文件。 (3) 组件:指 Android 中的Activity、Service、BroadcastReceiver、ContentProvider,目前 DL 支持Activity、Service以及动态的BroadcastReceiver。 (4) 插件组件:插件中的组件。 (5) 代理组件:在宿主的 Manifest 中注册,启动插件组件时首先被启动的组件。目前包括 DLProxyActivity(代理 Activity)、DLProxyFragmentActivity(代理 FragmentActivity)、DLProxyService(代理 Service)。 (6) Base 组件:插件组件的基类,目前包括 DLBasePluginActivity(插件 Activity 的基类)、DLBasePluginFragmentActivity(插件 FragmentActivity 的基类)、DLBasePluginService(插件 Service 的基类)。
DynamicLoadApk 原理的核心思想可以总结为两个字:代理。通过在 Manifest 中注册代理组件,当启动插件组件时首先启动一个代理组件,然后通过这个代理组件来构建、启动插件组件。
上面是 DynamicLoadApk 的总体设计图,DynamicLoadApk 主要分为四大模块: (1) DLPluginManager 插件管理模块,负责插件的加载、管理以及启动插件组件。
(2) Proxy 代理组件模块,目前包括 DLProxyActivity(代理 Activity)、DLProxyFragmentActivity(代理 FragmentActivity)、DLProxyService(代理 Service)。
(3) Proxy Impl 代理组件公用逻辑模块,与(2)中的 Proxy 不同的是,这部分并不是一个组件,而是负责构建、加载插件组件的管理器。这些 Proxy Impl 通过反射得到插件组件,然后将插件与 Proxy 组件建立关联,最后调用插件组件的 onCreate 函数进行启动。
(4) Base Plugin 插件组件的基类模块,目前包括 DLBasePluginActivity(插件 Activity 的基类)、DLBasePluginFragmentActivity(插件 FragmentActivity 的基类)、DLBasePluginService(插件 Service 的基类)。
上面是调用插件 Activity 的流程图,其他组件调用流程类似。 (1) 首先通过 DLPluginManager 的 loadApk 函数加载插件,这步每个插件只需调用一次。 (2) 通过 DLPluginManager 的 startPluginActivity 函数启动代理 Activity。 (3) 代理 Activity 启动过程中构建、启动插件 Activity。
4.1 DLPluginManager.java DynamicLoadApk 框架的核心类,主要功能包括: (1) 插件的加载和管理; (2) 启动插件的组件,目前包括 Activity、Service。 主要属性: mNativeLibDir为插件 Native Library 拷贝到宿主中后的存放目录路径。在 getInstance(Context context)中进行赋值
private String mNativeLibDir = null;
mPackagesHolder HashMap,key 为包名,value 为表示插件信息的DLPluginPackage,存储已经加载过的插件信息。
private final HashMap<String, DLPluginPackage> mPackagesHolder = new HashMap<String, DLPluginPackage>();
主要函数: (1) getInstance(Context context) 获取 DLPluginManager 对象的单例。 在私有构造函数中将mNativeLibDir变量赋值为宿主 App 应用程序数据目录下名为pluginlib子目录的全路径。
private DLPluginManager(Context context) {
mContext = context.getApplicationContext();
mNativeLibDir = mContext.getDir("pluginlib", Context.MODE_PRIVATE).getAbsolutePath();
}
(2) loadApk(String dexPath) 加载插件。参数 dexPath 为插件的文件路径。 这个函数直接调用 loadApk(final String dexPath, boolean hasSoLib)。
/**
* Load a apk. Before start a plugin Activity, we should do this first.<br/>
* NOTE : will only be called by host apk.
*
* @param dexPath
*/
public DLPluginPackage loadApk(String dexPath) {
// when loadApk is called by host apk, we assume that plugin is invoked
// by host.
return loadApk(dexPath, true);
}
(3) loadApk(final String dexPath, boolean hasSoLib) 加载插件 Apk。参数 dexPath 为插件的文件路径,hasSoLib 表示插件是否含有 so 库。 注意:在启动插件的组件前,必须先调用上面两个函数之一加载插件,并且只能在宿主中调用。 流程图如下:
/**
* @param dexPath
* plugin path
* @param hasSoLib
* whether exist so lib in plugin
* @return
*/
public DLPluginPackage loadApk(final String dexPath, boolean hasSoLib) {
mFrom = DLConstants.FROM_EXTERNAL;
PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(dexPath,
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
if (packageInfo == null) {
return null;
}
DLPluginPackage pluginPackage = preparePluginEnv(packageInfo, dexPath);
if (hasSoLib) {
copySoLib(dexPath);
}
return pluginPackage;
}
loadApk 函数调用 preparePluginEnv 函数加载插件,图中虚线框为 preparePluginEnv 的流程图。
(4) preparePluginEnv(PackageInfo packageInfo, String dexPath) 加载插件及其资源。流程图如上图。 调用createDexClassLoader(…)、createAssetManager(…)、createResources(…)函数完成相应初始化部分。
/**
* prepare plugin runtime env, has DexClassLoader, Resources, and so on.
*
* @param packageInfo
* @param dexPath
* @return
*/
private DLPluginPackage preparePluginEnv(PackageInfo packageInfo, String dexPath) {
DLPluginPackage pluginPackage = mPackagesHolder.get(packageInfo.packageName);
if (pluginPackage != null) {
return pluginPackage;
}
DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
AssetManager assetManager = createAssetManager(dexPath);
Resources resources = createResources(assetManager);
// create pluginPackage
pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
mPackagesHolder.put(packageInfo.packageName, pluginPackage);
return pluginPackage;
}
(5) createDexClassLoader(String dexPath) 利用DexClassLoader加载插件,DexClassLoader 初始化函数如下:
public DexClassLoader (String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)
其中dexPath为插件的路径。 optimizedDirectory为宿主内dex存放路径。这里将路径设置为当前 App 应用程序数据目录下名为dex的子目录中。 libraryPath为 Native Library 存放的路径。这里将路径设置为mNativeLibDir属性,其在getInstance(Context)函数中已经初始化。
parent父 ClassLoader,ClassLoader 采用双亲委托模式查找类,具体加载方式可见 Android插件化学习之路(二)之ClassLoader完全解析。
private DexClassLoader createDexClassLoader(String dexPath) {
File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
dexOutputPath = dexOutputDir.getAbsolutePath();
DexClassLoader loader = new DexClassLoader(dexPath, dexOutputPath, mNativeLibDir, mContext.getClassLoader());
return loader;
}
(6) createAssetManager(String dexPath)
创建 AssetManager,加载插件资源。 在 Android 中,资源是通过 R.java 中的 id 来调用访问的。但是实现插件化之后,宿主是无法通过 R 文件访问插件的资源,所以这里使用反射来生成属于插件的AssetManager,并利用addAssetPath函数加载插件资源。
private AssetManager createAssetManager(String dexPath) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, dexPath);
return assetManager;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
AssetManager 的无参构造函数以及addAssetPath函数都被hide了,通过反射调用。
(7) createResources(AssetManager assetManager) 利用AssetManager中已经加载的资源创建Resources,代理组件中会从这个Resources中读取资源。
private Resources createResources(AssetManager assetManager) {
Resources superRes = mContext.getResources();
Resources resources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
return resources;
}
(8) copySoLib(String dexPath) 调用SoLibManager拷贝 so 库到 Native Library 目录。
/**
* copy .so file to pluginlib dir.
*
* @param dexPath
* @param hasSoLib
*/
private void copySoLib(String dexPath) {
// TODO: copy so lib async will lead to bugs maybe, waiting for
// resolved later.
// TODO : use wait and signal is ok ? that means when copying the
// .so files, the main thread will enter waiting status, when the
// copy is done, send a signal to the main thread.
// new Thread(new CopySoRunnable(dexPath)).start();
SoLibManager.getSoLoader().copyPluginSoLib(mContext, dexPath, mNativeLibDir);
}
(9) startPluginActivity(Context context, DLIntent dlIntent) 启动插件 Activity,会直接调用startPluginActivityForResult(…)函数。 插件自己内部 Activity 启动依然是调用Context#startActivity(…)方法。
/**
* {@link #startPluginActivityForResult(Activity, DLIntent, int)}
*/
public int startPluginActivity(Context context, DLIntent dlIntent) {
return startPluginActivityForResult(context, dlIntent, -1);
}
10) startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) 启动插件 Activity,流程图如下:
/**
* @param context
* @param dlIntent
* @param requestCode
* @return One of below: {@link #START_RESULT_SUCCESS}
* {@link #START_RESULT_NO_PKG} {@link #START_RESULT_NO_CLASS}
* {@link #START_RESULT_TYPE_ERROR}
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
//内部调用,原生方式启动Activity
if (mFrom == DLConstants.FROM_INTERNAL) {
dlIntent.setClassName(context, dlIntent.getPluginClass());
performStartActivityForResult(context, dlIntent, requestCode);
return DLPluginManager.START_RESULT_SUCCESS;
}
//拿出Intent中的packageName
String packageName = dlIntent.getPluginPackage();
if (TextUtils.isEmpty(packageName)) {
throw new NullPointerException("disallow null packageName.");
}
//在Map集合mPackagesHolder中根据packageName拿出插件信息DLPluginPackage
DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
//判断插件是否存在于mPackagesHolder中
if (pluginPackage == null) {
return START_RESULT_NO_PKG;
}
//根据pluginPackage得到待启动Activity全路径
final String className = getPluginActivityFullPath(dlIntent, pluginPackage);
//通过反射加载这个待启动Activity类
Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);
if (clazz == null) {
return START_RESULT_NO_CLASS;
}
// get the proxy activity class, the proxy activity will launch the
// plugin activity.
//得到启动Activity类的代理类
Class<? extends Activity> activityClass = getProxyActivityClass(clazz);
if (activityClass == null) {
return START_RESULT_TYPE_ERROR;
}
// put extra data
//设置dlIntent的相关参数,将代理类设置进dlIntent中,还要设置Activity全路径,插件的包名packageName
dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
dlIntent.setClass(mContext, activityClass);
performStartActivityForResult(context, dlIntent, requestCode);
return START_RESULT_SUCCESS;
}
再看一下里面一些方法的具体实现: 启动Activity的方法
private void performStartActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
Log.d(TAG, "launch " + dlIntent.getPluginClass());
if (context instanceof Activity) {
((Activity) context).startActivityForResult(dlIntent, requestCode);
} else {
context.startActivity(dlIntent);
}
}
根据pluginPackage得到待启动Activity全路径的方法
private String getPluginActivityFullPath(DLIntent dlIntent, DLPluginPackage pluginPackage) {
String className = dlIntent.getPluginClass();
className = (className == null ? pluginPackage.defaultActivity : className);
if (className.startsWith(".")) {
className = dlIntent.getPluginPackage() + className;
}
return className;
}
通过反射加载待启动Activity类
// zhangjie1980 重命名 loadPluginActivityClass -> loadPluginClass
private Class<?> loadPluginClass(ClassLoader classLoader, String className) {
Class<?> clazz = null;
try {
clazz = Class.forName(className, true, classLoader);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return clazz;
}
得到启动Activity类的代理类,内部主要是根据这个Activity继承自DLBasePluginActivity还是DLBasePluginFragmentActivity来获取相应的代理
/**
* get the proxy activity class, the proxy activity will delegate the plugin
* activity
*
* @param clazz
* target activity's class
* @return
*/
private Class<? extends Activity> getProxyActivityClass(Class<?> clazz) {
Class<? extends Activity> activityClass = null;
if (DLBasePluginActivity.class.isAssignableFrom(clazz)) {
activityClass = DLProxyActivity.class;
} else if (DLBasePluginFragmentActivity.class.isAssignableFrom(clazz)) {
activityClass = DLProxyFragmentActivity.class;
}
return activityClass;
}
(11) startPluginService(final Context context, final DLIntent dlIntent) 启动插件 Service。 主要逻辑在函数fetchProxyServiceClass(…)中,流程与startPluginActivity(…)类似,只是换成了回调的方式,在各种条件成立后调用原生方式启动代理 Service,不再赘述。
public int startPluginService(final Context context, final DLIntent dlIntent) {
if (mFrom == DLConstants.FROM_INTERNAL) {
dlIntent.setClassName(context, dlIntent.getPluginClass());
context.startService(dlIntent);
return DLPluginManager.START_RESULT_SUCCESS;
}
fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() {
@Override
public void onFetch(int result, Class<? extends Service> proxyServiceClass) {
// TODO Auto-generated method stub
if (result == START_RESULT_SUCCESS) {
dlIntent.setClass(context, proxyServiceClass);
// start代理Service
context.startService(dlIntent);
}
mResult = result;
}
});
return mResult;
}
(12) stopPluginService(final Context context, final DLIntent dlIntent)
public int stopPluginService(final Context context, final DLIntent dlIntent) {
if (mFrom == DLConstants.FROM_INTERNAL) {
dlIntent.setClassName(context, dlIntent.getPluginClass());
context.stopService(dlIntent);
return DLPluginManager.START_RESULT_SUCCESS;
}
fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() {
@Override
public void onFetch(int result, Class<? extends Service> proxyServiceClass) {
// TODO Auto-generated method stub
if (result == START_RESULT_SUCCESS) {
dlIntent.setClass(context, proxyServiceClass);
// stop代理Service
context.stopService(dlIntent);
}
mResult = result;
}
});
return mResult;
}
(13) bindPluginService(…) unBindPluginService(…) bind 或是 unBind 插件 Service。逻辑与startPluginService(…)类似,不再赘述。
public int bindPluginService(final Context context, final DLIntent dlIntent, final ServiceConnection conn,
final int flags) {
if (mFrom == DLConstants.FROM_INTERNAL) {
dlIntent.setClassName(context, dlIntent.getPluginClass());
context.bindService(dlIntent, conn, flags);
return DLPluginManager.START_RESULT_SUCCESS;
}
fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() {
@Override
public void onFetch(int result, Class<? extends Service> proxyServiceClass) {
// TODO Auto-generated method stub
if (result == START_RESULT_SUCCESS) {
dlIntent.setClass(context, proxyServiceClass);
// Bind代理Service
context.bindService(dlIntent, conn, flags);
}
mResult = result;
}
});
return mResult;
}
public int unBindPluginService(final Context context, DLIntent dlIntent, final ServiceConnection conn) {
if (mFrom == DLConstants.FROM_INTERNAL) {
context.unbindService(conn);
return DLPluginManager.START_RESULT_SUCCESS;
}
fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() {
@Override
public void onFetch(int result, Class<? extends Service> proxyServiceClass) {
// TODO Auto-generated method stub
if (result == START_RESULT_SUCCESS) {
// unBind代理Service
context.unbindService(conn);
}
mResult = result;
}
});
return mResult;
}
由于篇幅原因,剩下的源码分析,下一篇博客带来。