1. 插件化原理
DL框架的原理很简单:
在宿主apk中,有一个ProxyActivity,即代理Activity,这个Activity相当于一个空壳,插件中的Activity依靠ProxyActivity来对生命周期回调、资源加载以及启动另一个Activity等等。总而言之,ProxyActivity提供Context,插件Activity依靠ProxyActivity来做自己想做的事情。
2. DL的实现
首先看看ProxyActivity的实现:
public class ProxyActivity extends Activity {
private static final String TAG = "ProxyActivity";
public static final String FROM = "extra.from";
public static final int FROM_EXTERNAL = 0;
public static final int FROM_INTERNAL = 1;
public static final String EXTRA_DEX_PATH = "extra.dex.path";
public static final String EXTRA_CLASS = "extra.class";
private String mClass;
private String mDexPath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDexPath = getIntent().getStringExtra(EXTRA_DEX_PATH);
mClass = getIntent().getStringExtra(EXTRA_CLASS);
Log.d(TAG, "mClass=" + mClass + " mDexPath=" + mDexPath);
if (mClass == null) {
launchTargetActivity();
} else {
launchTargetActivity(mClass);
}
}
@SuppressLint("NewApi")
protected void launchTargetActivity() {
PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(
mDexPath, 1);
if ((packageInfo.activities != null)
&& (packageInfo.activities.length > 0)) {
String activityName = packageInfo.activities[0].name;
mClass = activityName;
launchTargetActivity(mClass);
}
}
@SuppressLint("NewApi")
protected void launchTargetActivity(final String className) {
Log.d(TAG, "start launchTargetActivity, className=" + className);
File dexOutputDir = this.getDir("dex", 0);
final String dexOutputPath = dexOutputDir.getAbsolutePath();
ClassLoader localClassLoader = ClassLoader.getSystemClassLoader();
DexClassLoader dexClassLoader = new DexClassLoader(mDexPath,
dexOutputPath, null, localClassLoader);
try {
Class<?> localClass = dexClassLoader.loadClass(className);
Constructor<?> localConstructor = localClass
.getConstructor(new Class[] {});
Object instance = localConstructor.newInstance(new Object[] {});
Log.d(TAG, "instance = " + instance);
Method setProxy = localClass.getMethod("setProxy",
new Class[] { Activity.class });
setProxy.setAccessible(true);
setProxy.invoke(instance, new Object[] { this });
Method onCreate = localClass.getDeclaredMethod("onCreate",
new Class[] { Bundle.class });
onCreate.setAccessible(true);
Bundle bundle = new Bundle();
bundle.putInt(FROM, FROM_EXTERNAL);
onCreate.invoke(instance, new Object[] { bundle });
} catch (Exception e) {
e.printStackTrace();
}
}
}
可以看到,这里的ProxyActivity目前只是实现从插件apk中加载类并实例化,然后再将这个ProxyActivity实例对象通过setProxy函数赋值给插件apk中的类。再调用apk中类的onCreate函数。
也就是说,插件apk中的Activity需要持有ProxyActivity的引用,即插件apk的Activity有了Context对象的引用,以后插件中的Activity启动第二个Activity或者是调用其他需要Context参数的函数时候,就可以通过ProxyActivity来做到了。
因为每个插件Activity都需要提供setProxy()函数,以及还需要判断:
当前的插件apk是用户自己安装的还是通过宿主apk加载出来的
所以,插件apk中所有的Activity需要一系列的相同代码,我们只需创建一个BaseActivity,把这些工作放到BaseActivity,这样就可以避免写重复代码,而且也可以让我们插件中的Activity更“像”正常的Activity。
看看BaseActivity:
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
public class BaseActivity extends Activity {
private static final String TAG = "Client-BaseActivity";
public static final String FROM = "extra.from";
public static final int FROM_EXTERNAL = 0;
public static final int FROM_INTERNAL = 1;
public static final String EXTRA_DEX_PATH = "extra.dex.path";
public static final String EXTRA_CLASS = "extra.class";
public static final String PROXY_VIEW_ACTION = "com.ryg.dynamicloadhost.VIEW";
public static final String DEX_PATH = "/mnt/sdcard/DynamicLoadHost/plugin.apk";
protected Activity mProxyActivity;
protected int mFrom = FROM_INTERNAL;
public void setProxy(Activity proxyActivity) {
Log.d(TAG, "setProxy: proxyActivity= " + proxyActivity);
mProxyActivity = proxyActivity;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
mFrom = savedInstanceState.getInt(FROM, FROM_INTERNAL);
}
if (mFrom == FROM_INTERNAL) {
super.onCreate(savedInstanceState);
mProxyActivity = this;
}
Log.d(TAG, "onCreate: from= " + mFrom);
}
protected void startActivityByProxy(String className) {
if (mProxyActivity == this) {
Intent intent = new Intent();
intent.setClassName(this, className);
this.startActivity(intent);
} else {
Intent intent = new Intent(PROXY_VIEW_ACTION);
intent.putExtra(EXTRA_DEX_PATH, DEX_PATH);
intent.putExtra(EXTRA_CLASS, className);
mProxyActivity.startActivity(intent);
}
}
@Override
public void setContentView(View view) {
if (mProxyActivity == this) {
super.setContentView(view);
} else {
mProxyActivity.setContentView(view);
}
}
@Override
public void setContentView(View view, LayoutParams params) {
if (mProxyActivity == this) {
super.setContentView(view, params);
} else {
mProxyActivity.setContentView(view, params);
}
}
@Deprecated
@Override
public void setContentView(int layoutResID) {
if (mProxyActivity == this) {
super.setContentView(layoutResID);
} else {
mProxyActivity.setContentView(layoutResID);
}
}
@Override
public void addContentView(View view, LayoutParams params) {
if (mProxyActivity == this) {
super.addContentView(view, params);
} else {
mProxyActivity.addContentView(view, params);
}
}
}
可以看到,在BaseActivity的onCreate函数中,通过判断当前的Activity启动时来自宿主Apk还是用户自己安装来设置mProxyActivity对象,如果是用户自己安装的,直接赋值this,如果是来自宿主apk的调用,则赋值为宿主中的ProxyActivity对象。
3. DL的资源管理和生命周期
前面我们的ProxyActivity只是简单回调了onCreate函数,还有其他的函数根本没有调用。另外,插件APK中的Activity的布局文件是插件APK中的资源文件,并不存在于宿主的APK中,无法通过ProxyActivity的Context来加载,因此,如果插件中的Activity使用类似R.layout.XXX的方式会出错。接下来要解决两个问题。
3.1 资源管理
既然插件中的Activity无法直接通过ProxyActivity这个Context来加载,那么我们可以通过反射机制,修改ProxyActivity中跟加载资源相关的api,使得能直接通过ProxyActivity加载资源。
Context中,跟资源相关的api就是如下两个抽象函数:
/** Return an AssetManager instance for your application's package. */
public abstract AssetManager getAssets();
/** Return a Resources instance for your application's package. */
public abstract Resources getResources();
也就是说,只要我们重写这两个函数,使得返回的AssetManager和Resources对象在加载资源时,能够去我们的插件apk中寻找资源,这样就可以解决问题啦。
首先,我们看看如何构造能够从插件apk中查找资源的AssetManager和Resources对象。通过loadResources函数来构造这两个对象,代码如下所示:
protected void loadResources() {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
//通过反射机制,调用AssetManager对象的addAssetPath函数
//传入的String参数mDexPath就是我们的插件apk路径
//为assetManager对象添加一条资源查找目录
addAssetPath.invoke(assetManager, mDexPath);
//将我们构造出来的AssetManager对象保存到成员变量
mAssetManager = assetManager;
} catch (Exception e) {
e.printStackTrace();
}
Resources superRes = super.getResources();
//构造Resources对象
mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),
superRes.getConfiguration());
mTheme = mResources.newTheme();
mTheme.setTo(super.getTheme());
}
说明:加载的方法是通过反射.
通过调用AssetManager中的addAssetPath方法,我们可以将一个apk中的资源加载到Resources中.
由于addAssetPath是隐藏api我们无法直接调用,所以只能通过反射,下面是它的声明,通过注释我们可以看出,传递的路径可以是zip文件也可以是一个资源目录,而apk就是一个zip,所以直接将apk的路径传给它,资源就加载到AssetManager中了,然后再通过AssetManager来创建一个新的Resources对象,这个对象就是我们可以使用的apk中的资源了,这样我们的问题就解决了。
/**
* 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) {
int res = addAssetPathNative(path);
return res;
}
接下来就是,我们重写那两个抽象函数:
@Override
public AssetManager getAssets() {
return mAssetManager == null ? super.getAssets() : mAssetManager;
}
@Override
public Resources getResources() {
return mResources == null ? super.getResources() : mResources;
}
好啦,资源加载的问题解决了,我们可以在插件中的Activity使用R开头的资源啦!
3.2 生命周期
个人感觉,生命周期比较好管理,因为ProxyActivity本身就是有生命周期的,只需在ProxyActivity中回调插件Activty的对应的生命周期函数即可。
首先,把需要回调的生命周期函数通过反射的方法,保存起来:
//传入的localClass即为插件中的Activty
protected void instantiateLifecircleMethods(Class<?> localClass) {
String[] methodNames = new String[] {
"onRestart",
"onStart",
"onResume",
"onPause",
"onStop",
"onDestory"
};
for (String methodName : methodNames) {
Method method = null;
try {
method = localClass.getDeclaredMethod(methodName, new Class[] { });
method.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
mActivityLifecircleMethods.put(methodName, method);
}
Method onCreate = null;
try {
onCreate = localClass.getDeclaredMethod("onCreate", new Class[] { Bundle.class });
onCreate.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
mActivityLifecircleMethods.put("onCreate", onCreate);
Method onActivityResult = null;
try {
onActivityResult = localClass.getDeclaredMethod("onActivityResult",
new Class[] { int.class, int.class, Intent.class });
onActivityResult.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
mActivityLifecircleMethods.put("onActivityResult", onActivityResult);
}
然后,在ProxyActivity的各个生命周期函数中,对插件Activity的生命周期函数进行回调:
@Override
protected void onResume() {
super.onResume();
Method onResume = mActivityLifecircleMethods.get("onResume");
if (onResume != null) {
try {
onResume.invoke(mRemoteActivity, new Object[] { });
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onPause() {
Method onPause = mActivityLifecircleMethods.get("onPause");
if (onPause != null) {
try {
onPause.invoke(mRemoteActivity, new Object[] { });
} catch (Exception e) {
e.printStackTrace();
}
}
super.onPause();
}
现在就解决了生命周期的问题啦!
4. 总结
总结一下DL框架的思想:
其本质就是,整个过程中,在Android系统中管理的Activity只是宿主apk中的ProxyActivity,而插件中的Activity本质就是个普通类而已,只不过ProxyActivity所有的逻辑代码(资源加载、布局显示等等)通过插件中的Activity来完成。
因此,在理解的时候,可以参照如下:
站在宿主apk的角度去想的时候:
插件中的Activity只是ProxyActivity调用的“工具类”,这个“工具类”帮助ProxyActivity执行布局显示等等相关的逻辑。
站在插件Activity的角度去想的时候:
当前插件Activity没有实际的上下文,我们需要借助ProxyActivity提供的上下文来完成Android系统环境中的api的调用。