Android 插件化框架 Replugin 源码解读(五)startActivity

前面分析插件加载的过程中其实插件已经启动了,在callApp 方法中启动了插件的application,只是组件还没有加载,这里我们探索一下activity 组件的加载过程,startActivity。

先介绍一下Replugin 的 实现插件化加载插件组件的原理,我们知道Android 的组件需要在AndroidManifest.xml中进行注册,只有注册的组件才能够被启用,而插件中的activity 是注册在插件apk的Androidmanifest.xml中,如果我们使用classloader强行加载插件中的activity 那么这个activity 没用被宿主跟系统初始化过,那么这个activity 是没有生命周期等功能的。因此Replugin为了解决这个问题,使用了占坑的方式,占坑就是预先在宿主的AndroidManifest.xml中注册组件(比如:Activity的每种LaunchMode都各定义一个Activity节点,每种LaunchMode+声明为独立的进程名各定义一个Activity节点等等)然后在加载插件activity的时候先解析这个注册的activity 的信息(LaunchMode,Process,Theme,TaskAffinity等)然后根据这些信息分配一个相对应的这个信息的在宿主中注册activity坑位(例com.xx.xx.loader.a.ActivityN1NRNTS5)然后启动这个activity与AMS 交互欺骗系统,然后从系统回到ActivityThread 的时候再通过hook的 classloader 去根据坑位和目标activity的对应关系去load 目标activity,来实现插件activity加载。我们看看具体的过程。

startActivity


//com.qihoo360.loader2.PluginLibraryInternalProxy

 /**
     * @hide 内部方法,插件框架使用
     * 启动一个插件中的activity,如果插件不存在会触发下载界面
     * @param context 应用上下文或者Activity上下文
     * @param intent
     * @param plugin 插件名
     * @param activity 待启动的activity类名
     * @param process 是否在指定进程中启动
     * @param download 下载
     * @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
     */
    public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {
        

        // 是否启动下载
        // 若插件不可用(不存在或版本不匹配),则直接弹出“下载插件”对话框
        // 因为已经打开UpdateActivity,故在这里返回True,告诉外界已经打开,无需处理
        if (download) {
            if (PluginTable.getPluginInfo(plugin) == null) {
               
                // 如果用户在下载即将完成时突然点按“取消”,则有可能出现插件已下载成功,但没有及时加载进来的情况
                // 因此我们会判断这种情况,如果是,则重新加载一次即可,反之则提示用户下载
                // 原因:“取消”会触发Task.release方法,最终调用mDownloadTask.destroy,导致“下载服务”的Receiver被注销,即使文件下载了也没有回调回来
                // NOTE isNeedToDownload方法会调用pluginDownloaded再次尝试加载
                if (isNeedToDownload(context, plugin)) {
                    return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
                }
            }
        }

        /* 检查是否是动态注册的类 */
        // 如果要启动的 Activity 是动态注册的类,则不使用坑位机制,而是直接动态类。
        // 原因:宿主的某些动态注册的类不能运行在坑位中(如'桌面'插件的入口Activity)
     
        if (Factory2.isDynamicClass(plugin, activity)) {
            intent.putExtra(IPluginManager.KEY_COMPATIBLE, true);
            intent.setComponent(new ComponentName(IPC.getPackageName(), activity));
            context.startActivity(intent);
            return true;
        }

        // 如果插件状态出现问题,则每次弹此插件的Activity都应提示无法使用,或提示升级(如有新版)
        // Added by Jiongxuan Zhang
        if (PluginStatusController.getStatus(plugin) < PluginStatusController.STATUS_OK) {
                        return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
        }

        // 若为首次加载插件,且是“大插件”,则应异步加载,同时弹窗提示“加载中”
        // Added by Jiongxuan Zhang
        if (!RePlugin.isPluginDexExtracted(plugin)) {
            PluginDesc pd = PluginDesc.get(plugin);
            if (pd != null && pd.isLarge()) {
               
                return RePlugin.getConfig().getCallbacks().onLoadLargePluginForActivity(context, plugin, intent, process);
            }
        }

        // WARNING:千万不要修改intent内容,尤其不要修改其ComponentName
        // 因为一旦分配坑位有误(或压根不是插件Activity),则外界还需要原封不动的startActivity到系统中
        // 可防止出现“本来要打开宿主,结果被改成插件”,进而无法打开宿主Activity的问题

        // 缓存打开前的Intent对象,里面将包括Action等内容
        Intent from = new Intent(intent);

        // 帮助填写打开前的Intent的ComponentName信息(如有。没有的情况如直接通过Action打开等)
        if (!TextUtils.isEmpty(plugin) && !TextUtils.isEmpty(activity)) {
            from.setComponent(new ComponentName(plugin, activity));
        }

        ComponentName cn = mPluginMgr.mLocal.loadPluginActivity(intent, plugin, activity, process);
        if (cn == null) {
           
            return false;
        }

        // 将Intent指向到“坑位”。这样:
        // from:插件原Intent
        // to:坑位Intent
        intent.setComponent(cn);

        context.startActivity(intent);

        // 通知外界,已准备好要打开Activity了
        // 其中:from为要打开的插件的Intent,to为坑位Intent
        RePlugin.getConfig().getEventCallbacks().onPrepareStartPitActivity(context, from, intent);

        return true;
    }

1.在调用 public static boolean startActivity(Context context, Intent intent) 或者public static boolean startActivity(Context context, Intent intent, String pluginName, String activity) 这两个方法之后,最终会调用PluginLibraryInternalProxy.startActivity方法。在方法中先检验插件不可用(不存在或者版本不对)就弹出下载提示框,提示用户去下载,然后检测是否是动态注册类,如果是动态注册的activity不使用坑位机制,直接加载这个类。然后判断插件状态,不可用的话提示用户升级。然后加载大插件的话采用异步加载同时显示加载提示框。接下去就是打开插件activity 的关键,将插件activity 替换成坑位activity来实现加载

//com.qihoo360.loader2.PluginCommImpl

/**
     * 加载插件Activity,在startActivity之前调用
     * @param intent
     * @param plugin 插件名
     * @param target 目标Service名,如果传null,则取获取到的第一个
     * @param process 是否在指定进程中启动
     * @return
     */
    public ComponentName loadPluginActivity(Intent intent, String plugin, String activity, int process) {

        ActivityInfo ai = null;
        String container = null;
        PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.ACTIVITY_REQUEST);

        try {
            // 获取 ActivityInfo(可能是其它插件的 Activity,所以这里使用 pair 将 pluginName 也返回)
            ai = getActivityInfo(plugin, activity, intent);
            if (ai == null) {
               
                return null;
            }

            // 存储此 Activity 在插件 Manifest 中声明主题到 Intent
            intent.putExtra(INTENT_KEY_THEME_ID, ai.theme);
            if (LOG) {
           
            // 根据 activity 的 processName,选择进程 ID 标识
            if (ai.processName != null) {
                process = PluginClientHelper.getProcessInt(ai.processName);
            }

            // 容器选择(启动目标进程)
            IPluginClient client = MP.startPluginProcess(plugin, process, info);
            if (client == null) {
                return null;
            }

            // 远程分配坑位
            container = client.allocActivityContainer(plugin, process, ai.name, intent);
            
        } catch (Throwable e) {
            
        }

        // 分配失败
        if (TextUtils.isEmpty(container)) {
            return null;
        }

        PmBase.cleanIntentPluginParams(intent);

        // TODO 是否重复
        // 附上额外数据,进行校验
//        intent.putExtra(PluginManager.EXTRA_PLUGIN, plugin);
//        intent.putExtra(PluginManager.EXTRA_ACTIVITY, activity);
//        intent.putExtra(PluginManager.EXTRA_PROCESS, process);
//        intent.putExtra(PluginManager.EXTRA_CONTAINER, container);

        PluginIntent ii = new PluginIntent(intent);
        ii.setPlugin(plugin);
        ii.setActivity(ai.name);
        ii.setProcess(IPluginManager.PROCESS_AUTO);
        ii.setContainer(container);
        ii.setCounter(0);
        return new ComponentName(IPC.getPackageName(), container);
    }

2.通过plugin activity intent 获取activityInfo , 查找过程先通过参数plugin 利用load方法找到对应的插件,在插件的加载中我们知道,插件加载过程中会生成ComponentList 类里面维护这4大组件的map表,直接通过activity 名字在map中找到对应的activityInfo。然后通过activityInfo获取主题存储到intent中,然后根据activity 的 processName,选择进程 ID 标识。之后是容器选择启动目标进程,PluginProcessMain.getPluginHost().startPluginProcess(plugin, process, info);这里PluginProcessMain.getPluginHost()很熟悉就是PmHostSvc这个类已经用了很多次了。常驻进程的管理类,然后又调用了PmBase.startPluginProcessLocked方法。通过常驻进程拉起对应的插件进程。



//com.qihoo360.loader2.PmBase

final IPluginClient startPluginProcessLocked(String plugin, int process, PluginBinderInfo info) {
        
        // 强制使用UI进程
        if (Constant.ENABLE_PLUGIN_ACTIVITY_AND_BINDER_RUN_IN_MAIN_UI_PROCESS) {
            if (info.request == PluginBinderInfo.ACTIVITY_REQUEST) {
                if (process == IPluginManager.PROCESS_AUTO) {
                    process = IPluginManager.PROCESS_UI;
                }
            }
            if (info.request == PluginBinderInfo.BINDER_REQUEST) {
                if (process == IPluginManager.PROCESS_AUTO) {
                    process = IPluginManager.PROCESS_UI;
                }
            }
        }

        //
        StubProcessManager.schedulePluginProcessLoop(StubProcessManager.CHECK_STAGE1_DELAY);

        // 获取
        IPluginClient client = PluginProcessMain.probePluginClient(plugin, process, info);
        if (client != null) {
            
            return client;
        }

        // 分配
        int index = IPluginManager.PROCESS_AUTO;
        try {
            index = PluginProcessMain.allocProcess(plugin, process);
            
        } catch (Throwable e) {
            
        }
        // 分配的坑位不属于UI、自定义进程或Stub坑位进程,就返回。(没找到有效进程)
        if (!(index == IPluginManager.PROCESS_UI
                || PluginProcessHost.isCustomPluginProcess(index)
                || PluginManager.isPluginProcess(index))) {
            return null;
        }

        // 启动
        boolean rc = PluginProviderStub.proxyStartPluginProcess(mContext, index);
        
        if (!rc) {
            return null;
        }

        // 再次获取
        client = PluginProcessMain.probePluginClient(plugin, process, info);
        if (client == null) {
            
            return null;
        }

        return client;
    }

3. 默认使用UI进程(也可以自定义进程) 然后通过缓存去获取IPluginClient 这个也是Binder对象,具体的实现类是PluginProcessPer  插件进程管理类,如果缓存命中则直接返回client,没有的话 通过 PluginProcessMain.allocProcess 方法分配进程,分配好进程后,通过PluginProviderStub.proxyStartPluginProcess 方法启动进程

  //com.qihoo360.loader2.PluginProviderStub


  static final boolean proxyStartPluginProcess(Context context, int index) {
        //
        ContentValues values = new ContentValues();
        values.put(KEY_METHOD, METHOD_START_PROCESS);
        values.put(KEY_COOKIE, PMF.sPluginMgr.mLocalCookie);
        Uri uri = context.getContentResolver().insert(ProcessPitProviderBase.buildUri(index), values);
        
        if (uri == null) {
            
            return false;
        }

        return true;
    }

       4.这里通过ContentProvider 的insert方法拉起进程。ProcessPitProviderBase.buildUri(index)获取ContentProvider的Uri uri = Uri.parse("content://" + AUTHORITY_PREFIX + str + "/main"); 拼接后的uri 就是通过gradle 动态在AndroidManifest.xml中注册的

<provider android:name='com.qihoo360.replugin.component.process.ProcessPitProviderP0'
    android:authorities='com.xx.xxx.loader.p.mainN100'
    android:process=':p0'
    android:exported='false' />

通过拉起这个provider 拉起这个provider 所在的':p0'进程。然后回到2 调用PluginProcessMain.probePluginClient(plugin, process, info);再次获取IPluginClient 也就是PluginProcessPer对象。进程已经调用起来了,然后就是去启动activity组件, container = client.allocActivityContainer(plugin, process, ai.name, intent);先远程分配坑位。

  //com.qihoo360.loader2.PluginProcessPer


  public String allocActivityContainer(String plugin, int process, String target, Intent intent) throws RemoteException {
        // 一旦有分配,则进入监控状态(一是避免不退出的情况,二也是最重要的是避免现在就退出的情况)
        RePlugin.getConfig().getEventCallbacks().onPrepareAllocPitActivity(intent);

        String loadPlugin = null;
        // 如果UI进程启用,尝试使用传过来的插件,强制用UI进程
        if (Constant.ENABLE_PLUGIN_ACTIVITY_AND_BINDER_RUN_IN_MAIN_UI_PROCESS) {
            if (IPC.isUIProcess()) {
                loadPlugin = plugin;
                process = IPluginManager.PROCESS_UI;
            } else {
                loadPlugin = plugin;
            }
        }
        // 如果不成,则再次尝试使用默认插件
        if (TextUtils.isEmpty(loadPlugin)) {
            if (mDefaultPlugin == null) {
                
                return null;
            }
            loadPlugin = mDefaultPlugin.mInfo.getName();
        }
        //
        String container = bindActivity(loadPlugin, process, target, intent);
    
        return container;
    }

        5.这里通过获取的PluginProcessPer加载activity,先判断是否启用UI 进程,如果启用就强制使用UI进程作为插件进程,然后如果传进来的插件信息是空的,就去默认插件中去寻找这个activity。通过PluginProcessPer.bindActivity 方法加载目标activity。

    //com.qihoo360.loader2.PluginProcessPer



    /**
     * 加载插件;找到目标Activity;搜索匹配容器;加载目标Activity类;建立临时映射;返回容器
     *
     * @param plugin   插件名称
     * @param process  进程
     * @param activity Activity 名称
     * @param intent   调用者传入的 Intent
     * @return 坑位
     */
    final String bindActivity(String plugin, int process, String activity, Intent intent) {

        /* 获取插件对象 */
        Plugin p = mPluginMgr.loadAppPlugin(plugin);
        if (p == null) {
           
            return null;
        }

        /* 获取 ActivityInfo */
        ActivityInfo ai = p.mLoader.mComponents.getActivity(activity);
        if (ai == null) {
            
            return null;
        }

        if (ai.processName == null) {
            ai.processName = ai.applicationInfo.processName;
        }
        if (ai.processName == null) {
            ai.processName = ai.packageName;
        }

        /* 获取 Container */
        String container;

        // 自定义进程
        if (ai.processName.contains(PluginProcessHost.PROCESS_PLUGIN_SUFFIX2)) {
            String processTail = PluginProcessHost.processTail(ai.processName);
            container = mACM.alloc2(ai, plugin, activity, process, intent, processTail);
        } else {
            container = mACM.alloc(ai, plugin, activity, process, intent);
        }

        if (TextUtils.isEmpty(container)) {
           
            return null;
        }

        
        /* 检查 activity 是否存在 */
        Class<?> c = null;
        try {
            c = p.mLoader.mClassLoader.loadClass(activity);
        } catch (Throwable e) {
            
        }
        if (c == null) {
           
            return null;
        }

        return container;
    }

          6.这里仍旧是先获取plugin 然后通过ComponentList中缓存的 map 获取activity对应的activityInfo 然后根据是否设置了自定义进程分别通过alloce2 和alloc 方法去获取坑位。而这辆个方法最终调用的是 allocLocked 方法。

//com.qihoo360.loader2.PluginContainers


 private final ActivityState allocLocked(ActivityInfo ai, HashMap<String, ActivityState> map,
                                            String plugin, String activity, Intent intent) {
        // 坑和状态的 map 为空
        if (map == null) {
            
            return null;
        }

        // 首先找上一个活的,或者已经注册的,避免多个坑到同一个activity的映射
        for (ActivityState state : map.values()) {
            if (state.isTarget(plugin, activity)) {
                
                return state;
            }
        }

        // 新分配:找空白的,第一个
        for (ActivityState state : map.values()) {
            if (state.state == STATE_NONE) {
               
                state.occupy(plugin, activity);
                return state;
            }
        }

        ActivityState found;

        // 重用:则找最老的那个
        found = null;
        for (ActivityState state : map.values()) {
            if (!state.hasRef()) {
                if (found == null) {
                    found = state;
                } else if (state.timestamp < found.timestamp) {
                    found = state;
                }
            }
        }
        if (found != null) {
            
            found.occupy(plugin, activity);
            return found;
        }

        // 强挤:最后一招,挤掉:最老的那个
        found = null;
        for (ActivityState state : map.values()) {
            if (found == null) {
                found = state;
            } else if (state.timestamp < found.timestamp) {
                found = state;
            }
        }
        if (found != null) {
            
            found.finishRefs();
            found.occupy(plugin, activity);
            return found;
        }

        
        // never reach here
        return null;
    }

         7. 先获取进程中所有的符合条件的坑位集合 然后首先找上一个活的,或者已经注册的,避免多个坑到同一个activity的映射,没有的话 分配一个新的空白的坑位 设置对应关系并返回。还是没有找到就重用最老的那个坑位。最后是强行挤掉最老的一个坑位。坑位分配好后,回到第6步 代码 检查一下这个插件中这个activity是否存在。存在的话 就返回这个坑位。回到 第1 步  将Intent指向到“坑位”。利用坑位去startActivity和系统AMS交互获取生命周期等。而activity的生命周期是通过ActivityThread 中的Handler 进行消息的分发 Handler里处理了各种类型的消息,通过函数handleLaunchActivity处理LAUNCH_ACTIVITY消息,然后在handleLaunchActivity 方法中调用performLaunchActivity启动Activity。在这个方法中我们获取了packageInfo中的classloader 去加载activity。而我们在hook 一章中知道Replugin通过Hook宿主Application#LoadedApk中的ClassLoader对象(通常一个应用对应一个LoadedApk对象,而Application中的LoadedApk就是在应用启动时创建的,在ActivityThread#handleBindApplication完成的,那么应用组件的Class的加载都是基于这个LoadedApk来完成的),然后将Replugin中自定义的PathClassLoader注入到LoadedApk中,再把LoadedApk 赋值给packageInfo。因此 原本调用系统的classloader的loadClass方法就变成了框架创建的RePluginClassLoader 的 loadClass方法

//com.qihoo360.loader2.PluginProcessPer


 /**
     * 类加载器根据容器解析到目标的activity
     * @param container
     * @return
     */
    final Class<?> resolveActivityClass(String container) {
        String plugin = null;
        String activity = null;

        // 先找登记的,如果找不到,则用forward activity
        PluginContainers.ActivityState state = mACM.lookupByContainer(container);
        if (state == null) {
           
            return ForwardActivity.class;
        }
        plugin = state.plugin;
        activity = state.activity;

       
        Plugin p = mPluginMgr.loadAppPlugin(plugin);
        if (p == null) {
            
            return null;
        }

        ClassLoader cl = p.getClassLoader();
        
        Class<?> c = null;
        try {
            c = cl.loadClass(activity);
        } catch (Throwable e) {
            
        }
        

        return c;
    }

          8. 最终调用的是PluginProcessPer.resolveActivityClass方法,这个方法中我们根据之前坑位对应目标activity 的关系找到目标 插件和 目标activity,然后 获取这个插件,然后获取插件的classloader 去加载插件的activity 。 这样整个加载插件的activity组件的流程就实现了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值