Bootstrap

Android插件化-Activity篇,2024年最新字节跳动技术面试有几面

====================

在正式撸代码之前我们需要知道的是,在插件里面不论是我们经常使用到的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

;