====================
在正式撸代码之前我们需要知道的是,在插件里面不论是我们经常使用到的Activity、broadcast、Service、ContentProvider还是那些普通的Java类,对于宿主而言它们都是普通的java类,并没有任何的特殊性可言;因此如果想要插件中的四大组件拥有和正常Android应用四大组件同等的生命周期,这里我们就需要采用一种欺骗AMS方式来达到目的。既然需要去欺骗AMS,那么对于Activity的启动流程我们就必须有所了解才行。有需要的直戳Android9.0 ActivityManagerService源码之启动Activity开篇和Android中Activity启动流程后篇
1、动态代理
由于动态代理在后续的出现频率较高,所以我们简单的去了解一下动态代理的实现方式
1.1 什么是动态代理
简单来说就是在代码运行时,为某一个接口动态的生成实现类,即代理对象。
1.2 插件化中运用场景
我们可以通过动态代理动态的为framework层的某个系统接口生成代理对象,然后再通过反射将framework层生成的系统接口的对象替换成动态生成的代理对象,这样的话我们就能够对framework层的方法实现进行入侵了。当然上述所有的操作都只针对当前应用进程。
比如我们通过动态代理的方式为IActivityManager接口生成代理对象a,然后将系统中的ActivityManager.getService()方法中需要获取的对象替换成对象a,这样我们就能够知道应用在什么时候调用了比如startActivity等方法。具体实现后续会有对应的源码。
1.3 动态代理实现方式
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{interface.class}, new xxInvocationHandler());
public class xxInvocationHandler implements InvocationHandler {
//被代理的原始对象
private Object mBase;
public xxInvocationHandler(Object base) {
mBase = base;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//对想要入侵的方式实现入侵
//最后再通过反射的方式调用到被代理的对象实现
return method.invoke(mBase, objects);
}
}
2、加载插件中的Activity
在有了Activity启动流程、动态代理等知识之后,Activity的插件化具体实现我们就可以正式的开始了。当然这个实现过程我们肯定会遇到各种各样的问题点;不要怂,我们对着framework层源码一个一个的去解决。
2.1 插件apk预备
为了实现的方便,我们自己将对应的插件apk放置在当前的assets目录下,然后在代码的运行过程将apk复制到data/user/0/应用包名/xx目录下。基本代码如下:
/**
*
- @param context
- @param pluginName 插件名
*/
public static void copyApk(Context context, String pluginName) {
DePluginSP sp = DePluginSP.getInstance(context);
//获取插件apk保存路径
String filePath = sp.getString(Constants.COPY_FILE_PATH, “”);
if (TextUtils.isEmpty(filePath)) {
//如果插件apk保存路径为空,说明没有copy插件apk到对应目录成功
File saveApkFile = context.getFileStreamPath(pluginName);
if (null == saveApkFile) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
saveApkFile = context.getDataDir();
filePath = saveApkFile.getAbsolutePath() + pluginName;
} else {
filePath = “data/user/0/” + context.getPackageName() + “/” + pluginName;
}
} else {
filePath = saveApkFile.getAbsolutePath();
}
boolean result = extractAssets(context, filePath, pluginName);
if (result) {
sp.setString(Constants.COPY_FILE_PATH, filePath);
}
Log.i(TAG, "copy " + result);
} else {
//如果插件apk保存路径不为空,并且本地存在了apk则不在进行二次copy,否则可能已经被删除则重新复制一份到对应到目录下
//当然在实际到开发中这里到情况会复杂的多,比如与服务器插件版本进行对比判断是否需要重新下载等
File file = new File(filePath);
if (file.exists()) {
Log.i(TAG, “had copy apk before,so no need copy again”);
} else {
Log.i(TAG, “althogh save apk file path success,but file not exists”);
extractAssets(context, filePath, pluginName);
}
}
}
上述方法就是判断是否需要复制插件apk到对应的目录下,接下来就是copy部分了。因为代码量不到逻辑也很简单,就直接看代码了。
public static boolean extractAssets(Context context, String filePath, String pluginName) {
AssetManager assetManager = context.getAssets();
FileOutputStream fileOutputStream = null;
InputStream inputStream = null;
try {
L