文章目录
一、概述
-
DL实现原理
- 使用DexClassLoader加载未安装apk
- 启动Activity:使用代理模式去执行插件Activity,加载对应的生命周期
- 访问R资源:通过反射调用AssetManager中的addAssetPath方法
-
Dl的特性
- 开源
- 轻量级,仅需引用 dl-ib.jar,DL的工作过程对开发者完全透明
- 采用代理模式,容易进行扩展
- 基本无反射调用
- 支持 android2.x版本
- 支持用R访问plugin资源
- 插件安装后认可独立运行从而编译调试
- 多种开发方式,适用于实际开发中的不同场景。前两种模式适用于plugin开发者无法获得host代码的情况,最后适用于plugin开发者能获得host代码的情况。
- 插件不依赖宿主模式(推荐)。无调用(但仍然可以用反射调用:main-host、main-plugin
- 插件部分依赖宿主模式(接口下沉)。host可公开部分接口供plugin调用:doi-host、doi-plugin、doi-common接口工程
- 插件完全依赖宿主: depend_on_host、depend_on_plugin
- 支持多进程模式,插件可以运行在单独的DL进程中(代码在lab分支)(DL 2.0的新特性)
- 支持插件中的so库(代码在dev分支)(DL 2.0的新特性)
-
Dl开发规范
- 目前支持Activity和FragmentActivity基类,其他类型可自行实现
- 启动新activity的约束:启动外部activity不受限制,启动apk内部的activity有限制,首先由于apk中的activity没注册,所以不支持隐式调用,其次必须通过BaseActivity中定义的新方法startActivityByProxy和startActivityForResultByProxy,还有就是不支持LaunchMode
- 只支持动态注册广播
- 不支持插件中的assets
- 插件所需要权限需要在宿主工程中声明
- 调用context的时候,适当使用that。that是apk中activity的基类BaseActivity系列中的一个成员,它在apk安装运行的时候指向this,而在未安装的时候指向宿主程序中的代理activity,由于that的动态分配特性,通过that去调用activity的成员方法,在apk安装以后仍然可以正常运行
- 慎重使用this,因为this指向的是当前对象,即apk中的activity,但是由于activity已经不是常规意义上的activity,所以this是没有意义的,但是,当this表示的不是Context对象的时候除外,比如this表示一个由activity实现的接口
- activity的成员方法调用问题:原则来说,需要通过that来调用成员方法,但是由于大部分常用的api已经被重写,所以仅仅是针对部分api才需要通过that去调用用。同时,apk安装以后仍然可以正常运行
- 目前支持style和系统主题,不支持自定义主题
-
核心库介绍
- master分支
- 类加载器管理 DLClassLoader
- 生命周期代理接口 DLPlugin
- 代理基类 DLProxyActivity、DLProxyFragmentActivity
- dev分支
- DLIntent 自定义Activity跳转
- DLPluginManager插件管理
- master分支
-
注意事项
- dl-lib包的集成
- 插件编译的时候依赖jar包,但是打包成apk的时候不要把jar包打入
- 更改lib工程后,重新build后,bin目录可获取新的jar包
- 实际部署和插件工程升级
- 插件的安全性校验
- 插件工程需要又主项目进行更新,关闭自己的升级行为
- 检查插件是否可用,约定协议差异过大时,建议降级安装。 目前DL也支持插件的独立安装和运行
- dl-lib包的集成
1.1 DL对activity生命周期管理的改进
DL最开始的时候采用反射去管理activity的生命周期,这样存在一些不便,比如反射代码写起来复杂,并且过多使用反射有一定的性能开销
采用了接口机制,将activity的大部分生命周期方法提取出来作为一个接口(DLPlugin),然后通过代理activity(DLProxyActivity)去调用插件activity实现的生命周期方法,这样就完成了插件activity的生命周期管理,并且没有采用函数反射
public interface DLPlugin {
public void onStart();
public void onRestart();
public void onActivityResult(int requestCode, int resultCode, Intent data);
public void onResume();
public void onPause();
public void onStop();
public void onDestroy();
public void onCreate(Bundle savedInstanceState);
public void setProxy(Activity proxyActivity, String dexPath);
public void onSaveInstanceState(Bundle outState);
public void onNewIntent(Intent intent);
public void onRestoreInstanceState(Bundle savedInstanceState);
public boolean onTouchEvent(MotionEvent event);
public boolean onKeyUp(int keyCode, KeyEvent event);
public void onWindowAttributesChanged(LayoutParams params);
public void onWindowFocusChanged(boolean hasFocus);
public void onBackPressed();
}
...
@Override
protected void onStart() {
mRemoteActivity.onStart();
super.onStart();
}
@Override
protected void onRestart() {
mRemoteActivity.onRestart();
super.onRestart();
}
@Override
protected void onResume() {
mRemoteActivity.onResume();
super.onResume();
}
...
1.2 DL对类加载器的支持
为了更好地对多插件进行支持,我们提供了一个DLClassoader类,专门去管理各个插件的DexClassoader,这样,同一个插件就可以采用同一个ClassLoader去加载类从而避免多个classloader加载同一个类时所引发的类型转换错误
public class DLClassLoader extends DexClassLoader {
private static final String TAG = "DLClassLoader";
private static final HashMap<String, DLClassLoader> mPluginClassLoaders = new HashMap<String, DLClassLoader>();
protected DLClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
super(dexPath, optimizedDirectory, libraryPath, parent);
}
/**
* return a available classloader which belongs to different apk
*/
public static DLClassLoader getClassLoader(String dexPath, Context context, ClassLoader parentLoader) {
DLClassLoader dLClassLoader = mPluginClassLoaders.get(dexPath);
if (dLClassLoader != null)
return dLClassLoader;
File dexOutputDir = context.getDir("dex", Context.MODE_PRIVATE);
final String dexOutputPath = dexOutputDir.getAbsolutePath();
dLClassLoader = new DLClassLoader(dexPath, dexOutputPath, null, parentLoader);
mPluginClassLoaders.put(dexPath, dLClassLoader);
return dLClassLoader;
}
}
1.3 DL对宿主(host)和插件(plugin)通信的支持
【图 dl 三种模式】
因为往往宿主需要和插件进行各种通信,因此DL对宿主和插件的通信做了很好的支持,目前总共有3中模式
【图DL对宿主(host)和插件(plugin)通信的支持】
-
main:插件不依赖宿主的模式,这是DL推荐的模式
- host指宿主工程,plugin指插件工程
-
depend_on_interface:插件部分依赖宿主的模式,或者说插件依赖宿主提供的接口,适合能够拿到宿主的接口的情况
- host指宿主工程,plugin指插件工程,common指接口工程
-
depend_on_host:插件完全依赖宿主的模式,适合于能够能到宿主的源代码的情况
- host指宿主工程,plugin指插件工程
1.4 DL对插件独立运行的支持
为了便于调试,采用DL所开发的插件都可以独立运行,当然,这要分情况来说:
对于模式1(不依赖),如果插件想独立运行,只需要把external-jars下的jar包拷贝一份到插件的libs目录下即可
对于模式2(部分依赖),只需要提供一个宿主接口的默认实现即可
对于模式3(完全依赖),只需要apk打包时把所引用的宿主代码打包进去即可,具体方式可以参看sample/depend_on_host目录。
在开发过程中,应该先开启插件的独立运行功能以便于调试,等功能开发完毕后再将其插件化
1.5 DL对activity随意跳转的支持(DLIntent)和 对插件管理的支持(DLPluginManager)
这两项都属于加强功能,目前正在dev分支进行code review,大家感兴趣可以去dev分支上查看,等验证通过即merge到稳定版master分支。
DLIntent:通过DLIntent来完成activity的无约束调起
DLPluginManager:对宿主的所有插件提供综合管理功能
二、利用DL框架进行开发的步骤
2.1 引入
宿主工程中只要将dl-lib.jar加入libs即可,然后在gradle中引用
compile fileTree(dir: 'libs', include: ['*.jar'])
但是插件中则不同,因为DL插件需要用到DL库的类(),所以需要引入DL库,但是插件是最终要加载到宿主程序中的,宿主程序中也是引入了DL库的,如果常规办法导入DL库,则会有两份DL的拷贝,为了解决这个问题,我们让插件中的DL只是编译的时候用,但是不打包进apk。如何让它参与编译却不被打包进apk呢?在Android-studio中
只需要在插件工程中创建一个目录,比如external-jars,然后把dl-lib.jar和放进去,同时在gradle中追加如下代码即可:
provided files('external-jars/dl-lib.jar')
2.2 插件
- 插件中的所有Activity 必须是继承自DLBasePluginActivity或者是DLBasePluginFragmentActivity。如果原有的为Activity,这里需要改为继承DLBasePluginActivity,如果原来为FragmentActivity,那么需要继承DLBasePluginFragmentActivity
public class MainActivity extends DLBasePluginActivity
TestFragmentActivity extends DLBasePluginFragmentActivity
-
原有activity中所有代表context引用的this都必须改写为that
-
插件中启动另外一个activity,不能使用startActivity(),而是使用startPluginActivity,并且intent也要变为DLIntent
DLIntent intent = new DLIntent(getPackageName(), ListActivity.class);
intent.putExtra(TYPE, item.getNavigationInfo());
startPluginActivity(intent);
2.3 host
- 通过Class.forName的方式获取我们需要调用的插件apk中MainActivity的class对象
- 就上面提到的,我们需要判断该对象继承自DLBasePluginActivity还是DLBasePluginFragmentActivity,得到对应的代理class对象
- 使用对应的代理class对象调起插件apk
PluginItem item = mPluginItems.get(position);
Class<?> proxyCls = null;
try {
Class<?> cls = Class.forName(item.launcherActivityName, false,
DLClassLoader.getClassLoader(item.pluginPath, getApplicationContext(), getClassLoader()));
if (cls.asSubclass(DLBasePluginActivity.class) != null) {
proxyCls = DLProxyActivity.class;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
Toast.makeText(this,
"load plugin apk failed, load class " + item.launcherActivityName + " failed.",
Toast.LENGTH_SHORT).show();
} catch (ClassCastException e) {
// ignored
} finally {
if (proxyCls == null) {
proxyCls = DLProxyFragmentActivity.class;
}
Intent intent = new Intent(this, proxyCls);
intent.putExtra(DLConstants.EXTRA_DEX_PATH,
mPluginItems.get(position).pluginPath);
startActivity(intent);
}
三、源码解析
3.1 核心概念
DynamicLoadApk 原理的核心思想可以总结为两个字:代理。通过在 Manifest 中注册代理组件,当启动插件组件时首先启动一个代理组件,然后通过这个代理组件来构建、启动插件组件
- 宿主:主 App,可以加载插件,也称 Host
- 代理组件:在宿主的 Manifest 中注册,启动插件组件时首先被启动的组件。目前包括 DLProxyActivity(代理 Activity)、DLProxyFragmentActivity(代理 FragmentActivity)、DLProxyService(代理 Service)
- 插件:插件 App,被宿主加载的 App,也称 Plugin,可以是跟普通 App 一样的 Apk 文件
- 插件组件:插件中的组件
- Base插件组件:插件组件的基类,目前包括 DLBasePluginActivity(插件 Activity 的基类)、DLBasePluginFragmentActivity(插件 FragmentActivity 的基类)、DLBasePluginService(插件 Service 的基类)
- 组件:指 Android 中的Activity、Service、BroadcastReceiver、ContentProvider,目前 DL 支持Activity、Service以及动态的BroadcastReceiver
【DynamicLoadApk 源码解析设计图】
- DLPluginManager
- 插件管理模块,负责插件的加载、管理以及启动插件组件
- Proxy
- 代理组件模块
- 目前包括 DLProxyActivity(代理 Activity)、DLProxyFragmentActivity(代理 FragmentActivity)、DLProxyService(代理 Service)
- Proxy Impl
- 代理组件公用逻辑模块,与(2)中的 Proxy 不同的是,这部分并不是一个组件,而是负责构建、加载插件组件的管理器
- 这些 Proxy Impl 通过反射得到插件组件,然后将插件与 Proxy 组件建立关联,最后调用插件组件的 onCreate 函数进行启动
- Base Plugin
- 插件组件的基类模块
- 目前包括 DLBasePluginActivity(插件 Activity 的基类)、DLBasePluginFragmentActivity(插件 FragmentActivity 的基类)、DLBasePluginService(插件 Service 的基类)
3.2 流程
【DynamicLoadApk 源码解析流程图】
- 首先通过 DLPluginManager 的 loadApk 函数加载插件,这步每个插件只需调用一次。
- 通过 DLPluginManager 的 startPluginActivity 函数启动代理 Activity。
- 代理 Activity 启动过程中构建、启动插件 Activity
3.3 DLPluginManager
3.3.1 单例模式:构建函数
- mNativeLibDir: 插件 Native Library 拷贝到宿主中后的存放目录路径
- mPackagesHolder: 存储已经加载过的插件信息
public class DLPluginManager {
//key 为包名,value 为表示插件信息的DLPluginPackage,存储已经加载过的插件信息
private final HashMap<String, DLPluginPackage> mPackagesHolder = new HashMap<String, DLPluginPackage>();
private static DLPluginManager sInstance;
private Context mContext;
private String mNativeLibDir = null;
private int mFrom = DLConstants.FROM_INTERNAL;
private int mResult;
private DLPluginManager(Context context) {
mContext = context.getApplicationContext();
//在私有构造函数中将mNativeLibDir变量赋值为宿主 App 应用程序数据目录下名为pluginlib子目录的全路径
mNativeLibDir = mContext.getDir("pluginlib", Context.MODE_PRIVATE).getAbsolutePath();
}
public static DLPluginManager getInstance(Context context) {
if (sInstance == null) {
synchronized (DLPluginManager.class) {
if (sInstance == null) {
sInstance = new DLPluginManager(context);
}
}
}
return sInstance;
}
}
3.3.2 loadApk:加载插件。
- 参数 dexPath 为插件的文件路径
- hasSoLib 表示插件是否含有 so 库
注意:在启动插件的组件前,必须先调用loadApk函数加载插件,并且只能在宿主中调用
public class DLPluginManager {
/**
* 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);
}
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;
}
}
【DynamicLoadApk 源码解析loadapk流程图】
loadApk 函数调用 preparePluginEnv 函数加载插件,图中虚线框为 preparePluginEnv 的流程图
/**
* 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;
}
public class DLPluginPackage {
public String packageName;//插件的包名
public String defaultActivity;//插件的 Launcher Main Activity
public DexClassLoader classLoader;//加载插件的 ClassLoader
public AssetManager assetManager;//加载插件资源的 AssetManager
public Resources resources;//s利用assetManager中已经加载的资源创建的Resources,代理组件中会从这个Resources中读取资源
public PackageInfo packageInfo;//被PackageManager解析后的插件信息
//这些信息都会在DLPluginManager#loadApk(…)时初始化
}
3.3.2.1 createDexClassLoader(String dexPath)
利用DexClassLoader加载插件,DexClassLoader 初始化函数如下:
//dexPath为插件的路径
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;
}
利用系统的DexClassLoader加载插件,DexClassLoader 初始化函数如下:
public DexClassLoader (String dexPath
, String optimizedDirectory
, String libraryPath
, ClassLoader parent)
- optimizedDirectory为宿主内dex存放路径。这里将路径设置为当前 App 应用程序数据目录下名为dex的子目录中。
- libraryPath为 Native Library 存放的路径。这里将路径设置为mNativeLibDir属性,其在getInstance(Context)函数中已经初始化
- parent父 ClassLoader,ClassLoader 采用双亲委托模式查找类
3.3.2.2 createAssetManager(String dexPath)
创建 AssetManager,加载插件资源。
在 Android 中,资源是通过 R.java 中的 id 来调用访问的。但是实现插件化之后,宿主是无法通过 R 文件访问插件的资源,所以这里使用反射来生成属于插件的AssetManager,并利用addAssetPath函数加载插件资源
AssetManager 的无参构造函数以及addAssetPath函数都被hide了,通过反射调用
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;
}
}
3.3.2.3 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;
}
3.3.2.4 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);
}
3.3.3 启动Acitity
【DynamicLoadApk 源码解析启动Activity流程图】
- 启动插件 Activity,会直接调用startPluginActivityForResult(…)函数
public class DLIntent extends Intent {
private String mPluginPackage;
private String mPluginClass;
}
/**
* {@link #startPluginActivityForResult(Activity, DLIntent, int)}
*/
public int startPluginActivity(Context context, DLIntent dlIntent) {
return startPluginActivityForResult(context, dlIntent, -1);
}
/**
* @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;
}
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;
}
/**
* get the proxy activity class, the proxy activity will delegate the plugin
* activity
* 得到启动Activity类的代理类,内部主要是根据这个Activity继承自DLBasePluginActivity还是DLBasePluginFragmentActivity来获取相应的代理
* @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;
}
3.3.4 启动Service
startPluginService(final Context context, final DLIntent dlIntent)
主要逻辑在函数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;
}
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;
}
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;
}
3.4 Proxy
- DLAttachable
- DLAttachable 是一个接口,主要作用是以统一所有不同类型的代理 Activity,如DLProxyActivity、DLProxyFragmentActivity,方便作为同一接口统一处理。
- DLProxyActivity和DLProxyFragmentActivity都实现了这个类
- DLServiceAttachable
- 类似,作用是统一所有不同类型的代理 Service,实现插件Service和代理Service的绑定。
- 目前只有DLProxyService
public interface DLAttachable {
/**
* when the proxy impl ( {@see DLProxyImpl#launchTargetActivity()} ) launch
* the plugin activity , dl will call this method to attach the proxy activity
* and pluginManager to the plugin activity. the proxy activity will load
* the plugin's resource, so the proxy activity is a resource delegate for
* plugin activity.
* 抽象函数,表示将插件Activity和代理Activity绑定在一起,其中的proxyActivity参数就是指插件Activity
* @param proxyActivity a instance of DLPlugin, {@see DLBasePluginActivity}
* and {@see DLBasePluginFragmentActivity}
* @param pluginManager DLPluginManager instance, manager the plugins
*/
public void attach(DLPlugin proxyActivity, DLPluginManager pluginManager);
}
public interface DLServiceAttachable {
public void attach(DLServicePlugin remoteService, DLPluginManager pluginManager);
}
3.4.1 DLProxyActivity.java/DLProxyFragmentActivity.java
代理 Activity,他们是在宿主 Manifest 中注册的组件,也是启动插件 Activity 时,真正被启动的 Activity,他们的内部会完成插件 Activity 的初始化和启动。
这两个类大同小异,所以这里只分析DLProxyActivity
3.4.1.1 DLProxyActivity
代理Acitvity,它的生命周期最终都会触发插件对应接口
public class DLProxyActivity extends Activity implements DLAttachable {
protected DLPlugin mRemoteActivity;
private DLProxyImpl impl = new DLProxyImpl(this);
//remoteActivity是 插件Activity
@Override
public void attach(DLPlugin remoteActivity, DLPluginManager pluginManager) {
mRemoteActivity = remoteActivity;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
impl.onCreate(getIntent());
}
@Override
public AssetManager getAssets() {
return impl.getAssets() == null ? super.getAssets() : impl.getAssets();
}
@Override
public Resources getResources() {
return impl.getResources() == null ? super.getResources() : impl.getResources();
}
@Override
public Theme getTheme() {
return impl.getTheme() == null ? super.getTheme() : impl.getTheme();
}
@Override
public ClassLoader getClassLoader() {
return impl.getClassLoader();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mRemoteActivity.onActivityResult(requestCode, resultCode, data);
super.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onStart() {
mRemoteActivity.onStart();
super.onStart();
}
@Override
protected void onRestart() {
mRemoteActivity.onRestart();
super.onRestart();
}
@Override
protected void onResume() {
mRemoteActivity.onResume();
super.onResume();
}
@Override
protected void onPause() {
mRemoteActivity.onPause();
super.onPause();
}
@Override
protected void onStop() {
mRemoteActivity.onStop();
super.onStop();
}
@Override
protected void onDestroy() {
mRemoteActivity.onDestroy();
super.onDestroy();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
mRemoteActivity.onSaveInstanceState(outState);
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
mRemoteActivity.onRestoreInstanceState(savedInstanceState);
super.onRestoreInstanceState(savedInstanceState);
}
@Override
protected void onNewIntent(Intent intent) {
mRemoteActivity.onNewIntent(intent);
super.onNewIntent(intent);
}
@Override
public void onBackPressed() {
mRemoteActivity.onBackPressed();
super.onBackPressed();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
return mRemoteActivity.onTouchEvent(event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
super.onKeyUp(keyCode, event);
return mRemoteActivity.onKeyUp(keyCode, event);
}
@Override
public void onWindowAttributesChanged(LayoutParams params) {
mRemoteActivity.onWindowAttributesChanged(params);
super.onWindowAttributesChanged(params);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
mRemoteActivity.onWindowFocusChanged(hasFocus);
super.onWindowFocusChanged(hasFocus);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
mRemoteActivity.onCreateOptionsMenu(menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
mRemoteActivity.onOptionsItemSelected(item);
return super.onOptionsItemSelected(item);
}
@Override
public ComponentName startService(Intent service) {
return super.startService(service);
}
}
3.4.1.2 DLProxyImpl
DLProxyImpl 主要封装了插件Activity的公用逻辑,如初始化插件 Activity 并和代理 Activity 绑定、获取资源等,相当于把DLProxyActivity和DLProxyFragmentActivity的公共实现部分提出出来,核心逻辑位于下面介绍的 onCreate() 函数
- onCreate 函数,会在代理 Activity onCreate 函数中被调用
【DynamicLoadApk 源码解析DlProxyImpl流程图】
public DLProxyImpl(Activity activity) {
mProxyActivity = activity;
}
public void onCreate(Intent intent) {
// set the extra's class loader
//设置intent的ClassLoader
intent.setExtrasClassLoader(DLConfigs.sPluginClassloader);
//获取PackageName
mPackageName = intent.getStringExtra(DLConstants.EXTRA_PACKAGE);
//获得待启动Activity的ClassName
mClass = intent.getStringExtra(DLConstants.EXTRA_CLASS);
Log.d(TAG, "mClass=" + mClass + " mPackageName=" + mPackageName);
mPluginManager = DLPluginManager.getInstance(mProxyActivity);
//获得插件信息PluginPackage
mPluginPackage = mPluginManager.getPackage(mPackageName);
//从PluginPackage中获取AssetManager
mAssetManager = mPluginPackage.assetManager;
//从PluginPackage中获取Resources
mResources = mPluginPackage.resources;
initializeActivityInfo();
handleActivityInfo();
launchTargetActivity();
}
//加载待启动插件 Activity 完成初始化流程,并通过DLPlugin和DLAttachable接口的 attach 函数实现和代理 Activity 的双向绑定。
//流程图见上图虚线框部分
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
protected void launchTargetActivity() {
try {
Class<?> localClass = getClassLoader().loadClass(mClass);
Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
Object instance = localConstructor.newInstance(new Object[] {});
mPluginActivity = (DLPlugin) instance;
//【此处 将plug射入proxy】
((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager);
Log.d(TAG, "instance = " + instance);
//【此处 将proxy射入plug】
// attach the proxy activity and plugin package to the mPluginActivity
mPluginActivity.attach(mProxyActivity, mPluginPackage);
Bundle bundle = new Bundle();
bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL);
mPluginActivity.onCreate(bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
//获得待启动插件的 ActivityInfo,其中对插件Activity的主题做了一些处理
private void initializeActivityInfo() {
PackageInfo packageInfo = mPluginPackage.packageInfo;
if ((packageInfo.activities != null) && (packageInfo.activities.length > 0)) {
if (mClass == null) {
mClass = packageInfo.activities[0].name;
}
//Finals 修复主题BUG
int defaultTheme = packageInfo.applicationInfo.theme;
for (ActivityInfo a : packageInfo.activities) {
if (a.name.equals(mClass)) {
mActivityInfo = a;
// Finals ADD 修复主题没有配置的时候插件异常
if (mActivityInfo.theme == 0) {
if (defaultTheme != 0) {
mActivityInfo.theme = defaultTheme;
} else {
if (Build.VERSION.SDK_INT >= 14) {
mActivityInfo.theme = android.R.style.Theme_DeviceDefault;
} else {
mActivityInfo.theme = android.R.style.Theme;
}
}
}
}
}
}
}
//设置代理 Activity 的主题等信息。
//其他的 get* 函数都是获取一些插件相关信息,会被代理 Activity 调用。
//同样 DLServiceProxyImpl 主要封装了插件Service的公用逻辑,如初始化插件 Service 并和代理 Activity 绑定
private void handleActivityInfo() {
Log.d(TAG, "handleActivityInfo, theme=" + mActivityInfo.theme);
if (mActivityInfo.theme > 0) {
mProxyActivity.setTheme(mActivityInfo.theme);
}
Theme superTheme = mProxyActivity.getTheme();
mTheme = mResources.newTheme();
mTheme.setTo(superTheme);
// Finals适配三星以及部分加载XML出现异常BUG
try {
mTheme.applyStyle(mActivityInfo.theme, true);
} catch (Exception e) {
e.printStackTrace();
}
// TODO: handle mActivityInfo.launchMode here in the future.
}
3.5 Plugin
- DLPlugin
- 是一个接口,包含Activity生命周期、触摸、菜单等抽象函数
- DLBase*Activity 都实现了这个类,这样插件的 Activity 间接实现了此类。
- 主要作用是统一所有不同类型的插件 Activity,如Activity、FragmentActivity,方便作为同一接口统一处理,所以这个类叫DLPluginActivity更合适
- DLServicePlugin
- 主要作用是统一所有不同类型的插件 Service,方便作为统一接口统一处理,目前包含Service生命周期等抽象函数
public interface DLPlugin {
public void attach(Activity proxyActivity, DLPluginPackage pluginPackage);
public void onCreate(Bundle savedInstanceState);
public void onStart();
public void onRestart();
public void onActivityResult(int requestCode, int resultCode, Intent data);
public void onResume();
public void onPause();
public void onStop();
public void onDestroy();
public void onSaveInstanceState(Bundle outState);
public void onNewIntent(Intent intent);
public void onRestoreInstanceState(Bundle savedInstanceState);
public boolean onTouchEvent(MotionEvent event);
public boolean onKeyUp(int keyCode, KeyEvent event);
public void onWindowAttributesChanged(LayoutParams params);
public void onWindowFocusChanged(boolean hasFocus);
public void onBackPressed();
public boolean onCreateOptionsMenu(Menu menu);
public boolean onOptionsItemSelected(MenuItem item);
}
public interface DLServicePlugin {
public void attach(Service proxyService, DLPluginPackage pluginPackage);
public void onCreate();
public void onStart(Intent intent, int startId);
public int onStartCommand(Intent intent, int flags, int startId);
public void onDestroy();
public void onConfigurationChanged(Configuration newConfig);
public void onLowMemory();
public void onTrimMemory(int level);
public IBinder onBind(Intent intent);
public boolean onUnbind(Intent intent);
public void onRebind(Intent intent);
public void onTaskRemoved(Intent rootIntent);
}
3.5.1 DLBasePluginActivity.java/DLBasePluginFragmentActivity.java
插件 Activity 基类,插件中的Activity都要继承 DLBasePluginActivity/DLBasePluginFragmentActivity 之一(目前尚不支持 ActionBarActivity)。
主要作用是根据是否被代理,确定一些函数直接走父类逻辑还是代理 Activity 或是空逻辑。
DLBasePluginActivity继承自Activity,同时实现了DLPlugin接口。这两个类大同小异,所以这里只分析DLBasePluginActivity。
- 主要变量:
- mProxyActivity为代理 Activity,通过attach(…)函数绑定。
- that与mProxyActivity等同,只是为了和this指针区分,表示真实的Context,这里真实指的是被代理情况下为代理 Activity,未被代理情况下等同于 this。
/**
* 代理activity,可以当作Context来使用,会根据需要来决定是否指向this
*/
protected Activity mProxyActivity;
/**
* 等同于mProxyActivity,可以当作Context来使用,会根据需要来决定是否指向this<br/>
* 可以当作this来使用
*/
protected Activity that;
protected DLPluginManager mPluginManager;
protected DLPluginPackage mPluginPackage;
- Attach方法,设置代理的Activity
@Override
public void attach(Activity proxyActivity, DLPluginPackage pluginPackage) {
Log.d(TAG, "attach: proxyActivity= " + proxyActivity);
mProxyActivity = (Activity) proxyActivity;
that = mProxyActivity;
mPluginPackage = pluginPackage;
}
- 根据是否被代理,确定一些函数直接走父类逻辑还是代理 Activity 或是空逻辑。(此处贴部分代码)
/**
* @param dlIntent
* @return may be {@link #START_RESULT_SUCCESS},
* {@link #START_RESULT_NO_PKG}, {@link #START_RESULT_NO_CLASS},
* {@link #START_RESULT_TYPE_ERROR}
*/
public int startPluginActivityForResult(DLIntent dlIntent, int requestCode) {
if (mFrom == DLConstants.FROM_EXTERNAL) {
if (dlIntent.getPluginPackage() == null) {
dlIntent.setPluginPackage(mPluginPackage.packageName);
}
}
return mPluginManager.startPluginActivityForResult(that, dlIntent, requestCode);
}
public int startPluginService(DLIntent dlIntent) {
if (mFrom == DLConstants.FROM_EXTERNAL) {
if (dlIntent.getPluginPackage() == null) {
dlIntent.setPluginPackage(mPluginPackage.packageName);
}
}
return mPluginManager.startPluginService(that, dlIntent);
}
3.5.2 DLBasePluginService
插件 Service 基类,插件中的 Service 要继承这个基类,主要作用是根据是否被代理,确定一些函数直接走父类逻辑还是代理 Service 或是空逻辑。
> PS:截止目前这个类还是不完善的,至少和DLBasePluginActivity对比,还不支持非代理的情况(贴部分代码)
@Override
public void attach(Service proxyService, DLPluginPackage pluginPackage) {
// TODO Auto-generated method stub
LOG.d(TAG, TAG + " attach");
mProxyService = proxyService;
mPluginPackage = pluginPackage;
that = mProxyService;
mFrom = DLConstants.FROM_EXTERNAL;
}
protected boolean isInternalCall() {
return mFrom == DLConstants.FROM_INTERNAL;
}
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
LOG.d(TAG, TAG + " onBind");
return null;
}
@Override
public void onCreate() {
// TODO Auto-generated method stub
LOG.d(TAG, TAG + " onCreate");
}
3.5.3 DLIntent
继承自 Intent,封装了待启动组件的 PackageName 和 ClassName
public class DLIntent extends Intent {
private String mPluginPackage;
private String mPluginClass;
public DLIntent() {
super();
}
public DLIntent(String pluginPackage) {
super();
this.mPluginPackage = pluginPackage;
}
public DLIntent(String pluginPackage, String pluginClass) {
super();
this.mPluginPackage = pluginPackage;
this.mPluginClass = pluginClass;
}
public DLIntent(String pluginPackage, Class<?> clazz) {
super();
this.mPluginPackage = pluginPackage;
this.mPluginClass = clazz.getName();
}
public String getPluginPackage() {
return mPluginPackage;
}
public void setPluginPackage(String pluginPackage) {
this.mPluginPackage = pluginPackage;
}
public String getPluginClass() {
return mPluginClass;
}
public void setPluginClass(String pluginClass) {
this.mPluginClass = pluginClass;
}
public void setPluginClass(Class<?> clazz) {
this.mPluginClass = clazz.getName();
}
@Override
public Intent putExtra(String name, Parcelable value) {
setupExtraClassLoader(value);
return super.putExtra(name, value);
}
@Override
public Intent putExtra(String name, Serializable value) {
setupExtraClassLoader(value);
return super.putExtra(name, value);
}
private void setupExtraClassLoader(Object value) {
ClassLoader pluginLoader = value.getClass().getClassLoader();
DLConfigs.sPluginClassloader = pluginLoader;
setExtrasClassLoader(pluginLoader);
}
}
3.6 SoLibManager
调用SoLibManager拷贝 so 库到 Native Library 目录
copyPluginSoLib(Context context, String dexPath, String nativeLibDir)
- 函数中以ZipFile形式加载插件,循环读取其中的文件,如果为.so结尾文件、符合当前平台 CPU 类型且尚未拷贝过最新版,则新建Runnable拷贝 so 文件。
/**
* copy so lib to specify directory(/data/data/host_pack_name/pluginlib)
*
* @param dexPath plugin path
* @param nativeLibDir nativeLibDir
*/
public void copyPluginSoLib(Context context, String dexPath, String nativeLibDir) {
String cpuName = getCpuName();
String cpuArchitect = getCpuArch(cpuName);
sNativeLibDir = nativeLibDir;
Log.d(TAG, "cpuArchitect: " + cpuArchitect);
long start = System.currentTimeMillis();
try {
ZipFile zipFile = new ZipFile(dexPath);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = (ZipEntry) entries.nextElement();
if (zipEntry.isDirectory()) {
continue;
}
String zipEntryName = zipEntry.getName();
if (zipEntryName.endsWith(".so") && zipEntryName.contains(cpuArchitect)) {
final long lastModify = zipEntry.getTime();
if (lastModify == DLConfigs.getSoLastModifiedTime(context, zipEntryName)) {
// exist and no change
Log.d(TAG, "skip copying, the so lib is exist and not change: " + zipEntryName);
continue;
}
mSoExecutor.execute(new CopySoTask(context, zipFile, zipEntry, lastModify));
}
}
} catch (IOException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
Log.d(TAG, "### copy so time : " + (end - start) + " ms");
}
//获取CPU类型
private String getCpuName() {
try {
FileReader fr = new FileReader("/proc/cpuinfo");
BufferedReader br = new BufferedReader(fr);
String text = br.readLine();
br.close();
String[] array = text.split(":\\s+", 2);
if (array.length >= 2) {
return array[1];
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//获取CPU架构
@SuppressLint("DefaultLocale")
private String getCpuArch(String cpuName) {
String cpuArchitect = DLConstants.CPU_ARMEABI;
if (cpuName.toLowerCase().contains("arm")) {
cpuArchitect = DLConstants.CPU_ARMEABI;
} else if (cpuName.toLowerCase().contains("x86")) {
cpuArchitect = DLConstants.CPU_X86;
} else if (cpuName.toLowerCase().contains("mips")) {
cpuArchitect = DLConstants.CPU_MIPS;
}
return cpuArchitect;
}
3.7 DLUtils
这个类中大都是无用或是不该放在这里的函数,也许是大版本升级及维护人过多后对工具函数的维护不够所致
四、DL插件开发注意事项
4.1 主题
- dl的插件必须每个activity都单独设置主题(插件的作者说的是也可以在application上设置主题),但我实际测试,即使application设置了主题也必须每个activity都单独设置主题。
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".SampleActivity"
android:theme="@android:style/Theme.Holo.Light.DarkActionBar"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
- 注意的是 插件只能用系统主题 不能直接定义主题
不能这样
android:theme="@style/AppTheme"
只能这样
android:theme="@android:style/Theme.Light"
虽然在某些插件上可能不按照此规则也可以正确运行 ,但是我试过绝大多数多需要满足此条件
4.2 插件APK的管理后台
使用动态加载的目的,就是希望可以绕过APK的安装过程升级应用的功能,如果插件APK是打包在主项目内部的那动态加载纯粹是多次一举。更多的时候我们希望可以在线下载插件APK,并且在插件APK有新版本的时候,主项目要从服务器下载最新的插件替换本地已经存在的旧插件。为此,我们应该有一个管理后台,它大概有以下功能:
- 上传不同版本的插件APK,并向APP主项目提供插件APK信息查询功能和下载功能;
- 管理在线的插件APK,并能向不同版本号的APP主项目提供最合适的插件APK;
- 万一最新的插件APK出现紧急BUG,要提供旧版本回滚功能;
- 出于安全考虑应该对APP项目的请求信息做一些安全性校验;
4.3 插件APK合法性校验
加载外部的可执行代码,一个逃不开的问题就是要确保外部代码的安全性,我们可不希望加载一些来历不明的插件APK,因为这些插件有的时候能访问主项目的关键数据。
最简单可靠的做法就是校验插件APK的MD5值,如果插件APK的MD5与我们服务器预置的数值不同,就认为插件被改动过,弃用
参考文献
- csdn伯努力不努力:Android插件化学习之路
- 拥有者csdn:singwhatiwanna