DroidPlugin源码分析插件进程管理以及预注册Activity,Service,ContentProvide的选择

在360对DroidPlugin的特点介绍中有云:
插件的四大组件完全不需要在Host程序中注册,支持Service、Activity、BroadcastReceiver、ContentProvider四大组件。
实现了进程管理,插件的空进程会被及时回收,占用内存低。

之所以支持Service,Activity,ContentProvider三大组件,是因为DroidPlugin在AndroidManifest文件中预先注册了8个运行插件的进程,每个进程预注册Service一个, ContentProvide一个,启动模式为Standard的Activity一个,启动模式为SingleTask,SingleTop和SingleInstance的Activity分别四个,启动模式为Standard的Dialog类型Activity一个,启动模式为SingleTask,SingleTop和SingleInstance的Dialog类型Activity分别四个,另外还有不运行在指定进程Service一个, ContentProvide一个,启动模式为Standard的Activity一个,启动模式为SingleTask,SingleTop和SingleInstance的Activity分别四个,启动模式为Standard的Dialog类型Activity一个,启动模式为SingleTask,SingleTop和SingleInstance的Dialog类型Activity分别四个。当需要启动一个Activity时,先用预先定义的来启动骗过系统,实际创建实例化和回调生命周期时,换回真的要启动的Activity。
那么问题来了:

问题1: 预注册了这么多的组件和进程,当在一个插件APP启动两个Activity并且都没有指定需要启动新的进程时,是不是需要找两个注册在同一个进程中的Activity来骗过系统的检查。同理当插件APP已经启动了Activity,又要启动一个Service时,如果启动的Service需要在新的进程中运行,那么我们需要选择一个没有被使用过的预注册的进程来启动它,如果Service指定在新的进程中运行,那么我们需要找到运行插件APP的进程,并使用此进程预注册的Service来启动它。

问题2 : 如果宿主进程有许多的插件Apk,他们的个数操作了预定义的进程数,当用户启动了插件Apk1,退出了,又启动了插件Apk2,又退出了,此时可能有两个空闲的进程,那么这两个空闲的进程就需要及时清理掉。以保证进程被占用满了,导致别的插件启动不起来。

那么此文就分析DroidPlugin如何处理这两个问题的。在源码分析插件运行环境初始化的一文中在IPluginManagerImpl的构造函数中,创建一个MyActivityManagerService实例。他就是这篇文章的主角。
先说说MyActivityManagerService两个重要的成员变量。
mStaticProcessList: StaticProcessList类实例,保存了所有预注册的进程,以及在每一个进程上预注册的Activity,Service,ContentProvider。当插件Apk需要运行Activity先找替代的Activity或者进程时就找它了。
mRunningProcessList:RunningProcesList类实例,保存已经因为启动插件Apk而使用的进程,以及在这个进程中运行的Activity,Service,contentProvider等等。只有这样保存起来,才能方便管理,清除那些空闲的进程。接下来开始分析MyActivityManagerService。

第一步:MyActivityManagerService初始化:

    private StaticProcessList mStaticProcessList = new StaticProcessList();
    private RunningProcesList mRunningProcessList = new RunningProcesList();

    public MyActivityManagerService(Context hostContext) {
        super(hostContext);
        mRunningProcessList.setContext(mHostContext);
}

首先在加载MyActivityManagerService时,会创建两个全局变量mStaticProcessList,mRunningProcessList。
在构造函数中调用mRunningProcessList.setContext(mHostContext);
这个也比较简单只是把Context保存到mRunningProcessList中。

B:MyActivityManagerService初始化之onCreate函数:
MyActivityManagerService的onCreate函数是在IPluginManagerImpl调用所有插件函数loadAllPlugin中调用的。也就是说插件运行环境初始化完成之后,立刻调用MyActivityManagerService的onCreate函数。

    public void onCreate(IPluginManagerImpl pluginManagerImpl) throws Exception {
        super.onCreate(pluginManagerImpl);
        AttributeCache.init(mHostContext);
        mStaticProcessList.onCreate(mHostContext);
        mRunningProcessList.setContext(mHostContext);
}

1 调用AttributeCache.init(mHostContext)
这个函数主要用于属性缓存主要用于缓存Window属性,判断window是否是一下三种类型,windowIsTranslucent,windowIsFloating,windowShowWallpaper。如果是的话Activity的选择会使用与定义的Dialog类型。以后在选择Activity的时候会用到。

2 mStaticProcessList.onCreate(mHostContext);
mStaticProcessList是StaticProcessList类对象相关成员变量:
mOtherProcessNames: List 主要保存宿主进程列表(除去为插件预定义的进程的其他进程)
items: Map对象,主要是以ProcessName为key,以ProcessItem为Value
processName就是AndroidManifest文件中的Android:process属性
类 ProcessItem: name String 对象,保存进程名字
activityInfos 保存name进程中预定义的Activity信息
serviceInfos 保存name进程中预定义的service信息
providerInfos 保存name进程中预定义的contentProvider信息
有了对上面成员变量的了解,再来看StaticProcessList.oncreate函数就比较容易理解了。


    void onCreate(Context mHostContext) throws NameNotFoundException {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(CATEGORY_ACTIVITY_PROXY_STUB);
        intent.setPackage(mHostContext.getPackageName());


        PackageManager pm = mHostContext.getPackageManager();
        List<ResolveInfo> activities = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
        for (ResolveInfo activity : activities) {
            addActivityInfo(activity.activityInfo);
        }

        List<ResolveInfo> services = pm.queryIntentServices(intent, 0);
        for (ResolveInfo service : services) {
            addServiceInfo(service.serviceInfo);
        }

        PackageInfo packageInfo = pm.getPackageInfo(mHostContext.getPackageName(), PackageManager.GET_PROVIDERS);
        if (packageInfo.providers != null && packageInfo.providers.length > 0) {
            for (ProviderInfo providerInfo : packageInfo.providers) {
                if (providerInfo.name != null && providerInfo.name.startsWith(ContentProviderStub.class.getName())) {
                    addProviderInfo(providerInfo);
                }
            }
        }

        mOtherProcessNames.clear();
        PackageInfo packageInfo1 = pm.getPackageInfo(mHostContext.getPackageName(), PackageManager.GET_ACTIVITIES
                | PackageManager.GET_RECEIVERS
                | PackageManager.GET_PROVIDERS
                | PackageManager.GET_SERVICES);
        if (packageInfo1.activities != null) {
            for (ActivityInfo info : packageInfo1.activities) {
                if (!mOtherProcessNames.contains(info.processName) && !items.containsKey(info.processName)) {
                    mOtherProcessNames.add(info.processName);
                }
            }
        }

        if (packageInfo1.receivers != null) {
            for (ActivityInfo info : packageInfo1.receivers) {
                if (!mOtherProcessNames.contains(info.processName) && !items.containsKey(info.processName)) {
                    mOtherProcessNames.add(info.processName);
                }
            }
        }

        if (packageInfo1.providers != null) {
            for (ProviderInfo info : packageInfo1.providers) {
                if (!mOtherProcessNames.contains(info.processName) && !items.containsKey(info.processName)) {
                    mOtherProcessNames.add(info.processName);
                }
            }
        }

        if (packageInfo1.services != null) {
            for (ServiceInfo info : packageInfo1.services) {
                if (!mOtherProcessNames.contains(info.processName) && !items.containsKey(info.processName)) {
                    mOtherProcessNames.add(info.processName);
                }
            }
        }
    }

一部分:搜集所有为插件预定义的进程以及对应进程中预定义的Activity,Service, ContentProvider保存在items中。
通过PackageManagerService查找Category为CATEGORY_ACTIVITY_PROXY_STUB为插件预定义的所有Activity,Service, ContentProvider,然后分别调用addActivityInfo,addServiceInfo,addProviderInfo保存到Items中processName对应的ProcessItem的activityInfos,serviceInfos, providerInfos。
addActivityInfo,addServiceInfo,addProviderInfo三个函数的逻辑基本一致以addActivityInfo为例:

    private void addActivityInfo(ActivityInfo info) {
        if (TextUtils.isEmpty(info.processName)) {
            info.processName = info.packageName;
        }
        ProcessItem item = items.get(info.processName);
        if (item == null) {
            item = new ProcessItem();
            item.name = info.processName;
            items.put(info.processName, item);
        }
        item.addActivityInfo(info);
    }

首先查看info.processfName是否为空,即判断Activity,service,contentprovider,他们的android:process标签是否为空,如果不为空则以Info.processName为key从items中取出ProcessItem,如果ProcessItem为空则创建一个ProcessItem实例,然后添加到Items中,最后调用ProcessItem的addActivityInfo函数讲Activity添加到ProcessItem的ActivityInfos中。

二部分:获取所有除去为插件预定义的进程,宿主应用所需要的进程。
通过PackageManagerService获取PackageInfo,包含宿主进程的Activity, Service, ContentProvider, Receiver的信息,然后遍历这些信息,他们是否运行在新的进程中,如果是,是否为插件预定义的,如果不是为插件预定义的,则把processname保存在mOtherProcessNames中。

C mRunningProcessList.setContext(mHostContext);
初始化的最后一步比较简单,只是把mHostContext保存到RunningProcessList中。

第二步:MyActivityManagerService之Activity,Service, ContentProvider选择。
在分析这个之前,先看看与之密切相关的类RunningProcesList的成员变量。
mHostContext :Context 保存宿主进程的Context
items :Map以进程id(pid)为key,ProcessItem为value这里的ProcessItem和StaticProcessList的ProcessItem是两个不同的内部类。
看看RunningProcesList的内部类ProcessItem的成员变量:
stubProcessName:保存运行的代理插件进程的进程名(这个进程是预定义给插件用的真正运行的进程)。
targetProcessName:保存被代理的插件中定义的进程名(这个进程名不是真正运行的进程)。
插件中定义的指定的进程,是不能用的,因为插件是没有真正安装到系统的,所以只能让插件启动再一个预定义的进程中,这里两个参数主要是为了关联预定义的代理插件进程代理了哪个插件进程。方便后面同一个被代理的插件进程中Activity或者service或者provider的选择。
pid :运行的进程id
uid:运行进程的用户id
pkgs: 预定义的进程中运行了哪些包名的插件应用。之前不是保存过应用的签名吗,想用签名的应用是可以运行在同一个进程中的。
targetActivityInfos,targetProviderInfos,targetServiceInfos : 保存在进程中运行的所有插件Activity,provider,service。
activityInfosMap,providerInfosMap,serviceInfosMap,Map 保存预定义代理的activity,provider,service,代理了插件中哪些activity,provider,service,在运行。
有了这几个参数就能轻松管理预定义代理进程,运行了哪些插件activity,service,provider。
其实Activity,Service, ContentProvider选择的逻辑基本一致,这里以activity为例来分析:

Activity的选择函数selectStubActivityInfo

   public ActivityInfo selectStubActivityInfo(int callingPid, int callingUid, ActivityInfo targetInfo) {
        runProcessGC();//下面再分析

       A://判断是否是dialogStyle
       B://先从正在运行的进程中查找看是否有符合条件的进程,如果有则直接使用之
       C://遍历预定义进程,mStaticProcessList
       1  预定义进程正在运行且为空(没有运行任何插件包)
       2  预定义进程正在运行,且不为空则检查要运行的进程和已经运行的进程签名是否相同。
       3  预定义进程没有运行。
    }

上面大概说了函数的基本功能,接下来就以A, B, C 三段来分析这个函数:
A 判断窗口DialogStyle。

    boolean Window_windowIsTranslucent = false;
        boolean Window_windowIsFloating = false;
        boolean Window_windowShowWallpaper = false;
        try {
            Class<?> R_Styleable_Class = Class.forName("com.android.internal.R$styleable");
            int[] R_Styleable_Window = (int[]) FieldUtils.readStaticField(R_Styleable_Class, "Window");
            int R_Styleable_Window_windowIsTranslucent = (int) FieldUtils.readStaticField(R_Styleable_Class, "Window_windowIsTranslucent");
            int R_Styleable_Window_windowIsFloating = (int) FieldUtils.readStaticField(R_Styleable_Class, "Window_windowIsFloating");
            int R_Styleable_Window_windowShowWallpaper = (int) FieldUtils.readStaticField(R_Styleable_Class, "Window_windowShowWallpaper");

            AttributeCache.Entry ent = AttributeCache.instance().get(targetInfo.packageName, targetInfo.theme,
                    R_Styleable_Window);
            if (ent != null && ent.array != null) {
                Window_windowIsTranslucent = ent.array.getBoolean(R_Styleable_Window_windowIsTranslucent, false);
                Window_windowIsFloating = ent.array.getBoolean(R_Styleable_Window_windowIsFloating, false);
                Window_windowShowWallpaper = ent.array.getBoolean(R_Styleable_Window_windowShowWallpaper, false);
            }
        } catch (Throwable e) {
            Log.e(TAG, "error on read com.android.internal.R$styleable", e);
        }

        boolean useDialogStyle = Window_windowIsTranslucent || Window_windowIsFloating || Window_windowShowWallpaper;

这段代码比较简单,主要是通过包名和theme以及R_Styleable_Window读出window相关属性并缓存在AttributeCache中。
然后判断当前窗口是否是 windowIsTranslucent,windowIsFloating ,windowShowWallpaper 。如果是其中一种则设置useDialogStyle为真。

B 查找运行的进程中是否符合条件:

    String stubProcessName1 = mRunningProcessList.getStubProcessByTarget(targetInfo);
        if (stubProcessName1 != null) {
            List<ActivityInfo> stubInfos = mStaticProcessList.getActivityInfoForProcessName(stubProcessName1, useDialogStyle);
            for (ActivityInfo stubInfo : stubInfos) {
                if (stubInfo.launchMode == targetInfo.launchMode) {
                    if (stubInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) {
                        mRunningProcessList.setTargetProcessName(stubInfo, targetInfo);
                        return stubInfo;
                    } else if (!mRunningProcessList.isStubInfoUsed(stubInfo, targetInfo, stubProcessName1)) {
                        mRunningProcessList.setTargetProcessName(stubInfo, targetInfo);
                        return stubInfo;
                    }
                }
            }
        }

1 调用getStubProcessByTarget找到符合条件的代理进程。
什么是符合条件的进程呢?这里就不贴出这个函数的源码了,大概步骤如下:
遍历正在运行的代理进程items,查看进程(ProcessItem)中运行的pkgs中是否包含targetInfo的包名,并且processItem.targetProcessName(及该代理进程代理的插件进程)是否和targetInfo.processName(activity指定要运行的进程)是否相同。
如果相同则表示targetInfo所在的插件进程已经运行,则直接返回该代理进程的名字processItem.stubProcessName。
如果第一个条件不满足,则遍历进程(ProcessItem)中运行的pkgs中是否有签名和targetInfo的包的签名相同的包名。
如果有则判断processItem.targetProcessName(及该代理进程代理的插件进程)是否和targetInfo.processName(activity指定要运行的进程)是否相同。
如果相同则返回该代理进程的名字processItem.stubProcessName。
如果以上条件都不满足则返回空,返回空说明targetInfo.processName还没有启动过。

2 假设返回不为空,targetInfo.processName所描述的进程已经运行在代理进程中。
首先调用mStaticProcessList.getActivityInfoForProcessName获取在这个代理进程中预定义的所有代理activityInfo,当useDialogStyle时,返回的是预定义的dialog型的代理ActivityInfo。
然后遍历ActivityInfo,判断代理ActivityInfo的启动模式是否和目标Activity(targetInfo)的启动模式相同。
如果相同且要启动的目标Activity的启动模式是Standard,则无需做其他判断,
调用setTargetProcessName函数将目标Activity的包名添加到ProcessItem的pkgs中,并设置processItem的targetProcessName为targetInfo.processName。
然后返回该代理ActivityInfo。此时表示,已经找到合适的代理ActivityInfo来代理启动目标Activity。
如果不是Standard模式,则调用isStubInfoUsed函数判断该代理ActivityInfo是否已经代理了该目标Activity。isStubInfoUsed函数比较简单,不贴源码了,大概就是通过前面找到的代理进程名字,找到代理进程ProcessItem,并从activityInfosMap中找到代理这个代理Activity所代理的所有目标Activity集合,然后判断集合中是否存在要启动的目标Activity。
如果没有代理要启动目标Activity,调用setTargetProcessName函数然后返回此代理Activity,
如果已经代理了,则继续从当前进程中查找合适的代理Activity,找到后调用调用setTargetProcessName函数然后返回。
这个返回的代理ActivityInfo就是调用系统接口去启动的Activity,主要用于骗过系统的检查。

C: 从预定义代理进程中查找。
1 遍历预定义代理进程,找到预定义进程正在运行,并且为空(及没有运行任何插件包)的情况:

        List<String> stubProcessNames = mStaticProcessList.getProcessNames();
        for (String stubProcessName : stubProcessNames) {
            List<ActivityInfo> stubInfos = mStaticProcessList.getActivityInfoForProcessName(stubProcessName, useDialogStyle);
            if (mRunningProcessList.isProcessRunning(stubProcessName)) {//该预定义的进程正在运行。
                if (mRunningProcessList.isPkgEmpty(stubProcessName)) {//空进程,没有运行任何插件包。
                    for (ActivityInfo stubInfo : stubInfos) {
                         //选择合适的代理Activity和前面的一样为了缩短篇幅不贴出代码。
                        }
                    }
                    throw throwException("没有找到合适的StubInfo");
                }

首先通过mStaticProcessList.getProcessNames获取所有预定义的进程名字。开始遍历。
通过mRunningProcessList.isProcessRunning函数判断这个预定义的进程是否正在运行,isProcessRunning函数就是从items中查找是否存在processItem的stubProcessName和当前要的stubProcessName相同的ProcessItem。
如果有,则调用mRunningProcessList.isPkgEmpty判断但前进程是否为空。isPkgEmpty比较简单,补贴源码,前面说过一个成员变量pkgs,表示运行在代理插件进程的插件包。如果pkgs大小为零,这说明该代理进程为空,没有运行任何插件。
如果确实为空,就从但前进程中选择一个合适的代理Activity,代理目标Activity,这个过程这个过程A段分析中已经详细分析了。

2 预定义进程正在运行,且不为空则检查要运行的进程和已经运行的进程签名是否相同

 else if (mRunningProcessList.isPkgCanRunInProcess(targetInfo.packageName, stubProcessName, targetInfo.processName)) {
                    for (ActivityInfo stubInfo : stubInfos) {
                        //选择合适的代理Activity和前面的一样为了缩短篇幅不贴出代码。
                    }
               } else {
                    //这里需要考虑签名一样的情况,多个插件公用一个进程。
                }
            }

首先调用mRunningProcessList.isPkgCanRunInProcess判断目标Activity是否可以启动在当前代理进程中。
isPkgCanRunInProcess函数和A段分析的函数getStubProcessByTarget非常相似,可自行查看。
当目标Activity可以运行在当前进程中时,就从当前进程中选择一个合适的代理Activity,代理目标Activity,这个过程这个过程A段分析中已经详细分析了。

3 预定义进程没有运行

    for (ActivityInfo stubInfo : stubInfos) {
        //选择合适的代理Activity和前面的一样为了缩短篇幅不贴出代码。
     }

当代码走到这一段说明,当前代理进程还没有运行,而且要启动的目标Activity所在的进程,也没有被代理启动(及已经运行的代理插件进程没有合适的进程来运行目标Activity)。那不是正好,一个代理进程没有运行,一个需要一个代理进程来运行,那就从当前进程中选择一个合适的代理Activity,代理目标Activity,这个过程这个过程A段分析中已经详细分析了。
另外需要指出,前面选择合适代理Activity的过程中了解到,当找到合适代理Activity返回之前调用mRunningProcessList.setTargetProcessName(stubInfo, targetInfo);保存目标Activity的包名到运行他的进程,并设置代理进程所代理的目标进程名。在3这种情况下是不起作用的。因为,mRunningProcessList.setTargetProcessName遍历的items都是正在运行的代理插件进程,而当前这个代理插件进程并没有运行启动。所以根本遍历不到。
最后,代理Activity选择的过程中,我们多次遍历了items这个已经运行的代理进程集合,而且这个集合帮助我们管理这些已经运行的插件代理进程起到了重要的作用,这个items是何时创建的呢?

第三步:MyActivityManagerService之代理插件进程的管理
代理插件进程的管理一部分是:管理好已经运行的代理插件进程以及在这个进程中运行了哪些插件包,和哪些组件。
另外一部分是:如何及时删除那些正在运行的空的代理插件进程。

A 添加管理正在运行的代理插件进程item的过程
onActivityCreated函是在目标Activity onCreate中回调的,(这里涉及插件Activity启动流程,下面的文章会详细分析)在这个函数中回调用。
mRunningProcessList.addActivityInfo(callingPid, callingUid, stubInfo, targetInfo);
addActivityInfo函数如下:

    void addActivityInfo(int pid, int uid, ActivityInfo stubInfo, ActivityInfo targetInfo) {
        ProcessItem item = items.get(pid);
        if (TextUtils.isEmpty(targetInfo.processName)) {
            targetInfo.processName = targetInfo.packageName;
        }
        if (item == null) {
            item = new ProcessItem();
            item.pid = pid;
            item.uid = uid;
            items.put(pid, item);
        }
        item.stubProcessName = stubInfo.processName;
        if (!item.pkgs.contains(targetInfo.packageName)) {
            item.pkgs.add(targetInfo.packageName);
        }
        item.targetProcessName = targetInfo.processName;
        item.addActivityInfo(stubInfo.name, targetInfo);
    }

前面已经分析过items, item.pid, item.uid, item.pkgs, item.subProcessName, item.targetProcessName, 他们的作用,这个函数就比较简单了。
当从items中找不到对应pid的processItem时,创建一个processItem设置pid,uid,然后添加到items中,在设置好,stubProcessName, 以及所代理的目标进程,targetProcessName, 并把代理的目标Activity的包名添加到pkgs中,
最后调用processItem 的addActivityInfo函数把代理的目标activity添加到另外两个变量targetActivityInfos和activityInfosMap中。

B 清除代理插件进程中运行的组件信息以Activity为列:
onActivityDestory是在目标Activity onDestory中回调的。
onActivityDestory如下:

   public void onActivityDestory(int callingPid, int callingUid, ActivityInfo stubInfo, ActivityInfo targetInfo) {
        mRunningProcessList.removeActivityInfo(callingPid, callingUid, stubInfo, targetInfo);
        runProcessGC();
    }

先调用mRunningProcessList.removeActivityInfo函数清除要销毁的目标Activity。
removeActivityInfo函数如下:

       ProcessItem item = items.get(pid);
        if (TextUtils.isEmpty(targetInfo.processName)) {
            targetInfo.processName = targetInfo.packageName;
        }
        if (item != null) {
            item.removeActivityInfo(stubInfo.name, targetInfo);
        }

首先从items中找到要销毁的目标activity所运行的插件代理进程,然后调用processItem的removeActivityInfo函数:
removeActivityInfo函数就不贴源码了,他主要完成了,从targetActivityInfos和activityInfosMap中移除目标Activity信息,然后更新代理插件进程中的pkgs。
当目标Activity是插件包中还在最后一个组件,那么目标Activity所对应的pkg就会从pkgs中移除。

C: 插件代理进程管理的最后一个部分,及时清理空的或者运行优先级比较低的插件代理进程,以保重有足够的插件代理进程来加载更多的插件。
runProcessGC在个函数在selectStubActivityInfo函数中有看到,而且在选择service,provider,以及它们的销毁函数中也能看到。这个函数的作用就是及时清除运行优先级低的或者空的插件代理进程:
runProcessGC函数如下:

    private void runProcessGC() {
        List<RunningAppProcessInfo> infos = am.getRunningAppProcesses();
        List<RunningAppProcessInfo> myInfos = new ArrayList<RunningAppProcessInfo>();
        if (infos == null || infos.size() < 0) {
            return;
        }

        List<String> pns = mStaticProcessList.getOtherProcessNames();
        pns.add(mHostContext.getPackageName());
        for (RunningAppProcessInfo info : infos) {
            if (info.uid == android.os.Process.myUid()
                    && info.pid != android.os.Process.myPid()
                    && !pns.contains(info.processName)
                    && mRunningProcessList.isPlugin(info.pid)
                    && !mRunningProcessList.isPersistentApplication(info.pid)
                    /*&& !mRunningProcessList.isPersistentApplication(info.pid)*/) {
                myInfos.add(info);
            }
        }
        Collections.sort(myInfos, sProcessComparator);
        for (RunningAppProcessInfo myInfo : myInfos) {
            if (myInfo.importance == RunningAppProcessInfo.IMPORTANCE_GONE) {
                doGc(myInfo);
            } else if (myInfo.importance == RunningAppProcessInfo.IMPORTANCE_EMPTY) {
                doGc(myInfo);
            } else if (myInfo.importance == RunningAppProcessInfo.IMPORTANCE_BACKGROUND) {
                doGc(myInfo);
            } else if (myInfo.importance == RunningAppProcessInfo.IMPORTANCE_SERVICE) {
                doGc(myInfo);
            } /*else if (myInfo.importance == RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE) {
                //杀死进程,不能保存状态。但是关我什么事?
            }*/ else if (myInfo.importance == RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE) {
                //杀死进程
            } else if (myInfo.importance == RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
                //看得见
            } else if (myInfo.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                //前台进程。
            }
        }
    }

分析这段代码前先了解两个问题:
1 应用持久化 android:persistent 和com.morgoo.droidplugin.EXTRA_APP_PERSISTENT
在应用在AndroidManifest文件中中设置了android:persistent ,说明该应用需要从开机就开始运行,中间不会退出,即使内存不足,也不会将这样的进程杀死。适合系统应用开发比如说Phone系统的Phone应用。
同样的原理,DroidPlugin设置了一个这样的标签:com.morgoo.droidplugin.EXTRA_APP_PERSISTENT当插件应用设置了这样一个标签后,代理该插件运行的代理插件进程启动后,只要该插件应用还有一个组件运行在该代理插件进程中,就不会被当前这套清除后台优先级低的代码逻辑清除。

2 进程优先级:Android系统试图尽可能长时间地保持应用程序进程,但为了新建或者运行更加重要的进程,总是需要清除过时进程来回收内存。为了决定保留或终止哪个进程,根据进程内运行的组件及这些组件的状态,系统把每个进程都划入一个“重要性层次结构”中。重要性最低的进程首先会被清除,然后是下一个最低的,依此类推,这都是恢复系统资源所必需的。 Android官方文档描述如下:
int IMPORTANCE_BACKGROUND
Constant for importance: this process process contains background code that is expendable.

int IMPORTANCE_EMPTY
Constant for importance: this process is empty of any actively running code.

int IMPORTANCE_FOREGROUND
Constant for importance: this process is running the foreground UI.

int IMPORTANCE_PERCEPTIBLE
Constant for importance: this process is running something that is considered to be actively perceptible to the user.

int IMPORTANCE_SERVICE
Constant for importance: this process is contains services that should remain running.

int IMPORTANCE_VISIBLE
Constant for importance: this process is running something that is actively visible to the user, though not in the immediate foreground.

int REASON_PROVIDER_IN_USE
Constant for importanceReasonCode: one of the application’s content providers is being used by another process.

int REASON_SERVICE_IN_USE
Constant for importanceReasonCode: one of the application’s content providers is being used by another process.

int REASON_UNKNOWN
Constant for importanceReasonCode: nothing special has been specified for the reason for this level.

有了这些了解,分析这个函数就比较简单了:
1 通过ActivityManagerService获取当前所有正在运行的进程列表infos。
调用mStaticProcessList.getOtherProcessNames获取宿主进程需要用到的进程,并把宿主进程名(就是包名添加进去)pns。
创建变量myInfos用于保存插件进程。

2 遍历当前运行的进程列表infos,判断是否是可能需要删除的代理插件进程。
info.uid == android.os.Process.myUid()
判断条件一:当前进程和宿主进程同一个uid
info.pid != android.os.Process.myPid()
判断条件二:当前进程pid和宿主进程 pid不一致
!pns.contains(info.processName)
判断条件三:当前进程不是宿主进程需要用到的进程(及不包含在pns中)
mRunningProcessList.isPlugin(info.pid)
判断条件四:当前进程是在正在运行的代理插件进程中(及通过pid能在items中找 到对应的processItems)
!mRunningProcessList.isPersistentApplication(info.pid)
判断条件五:不是持久化应用或者插件(前面已经有说明,具体代码比较简单可自行查看)
当满足这五个条件就把当前进程保存到myInfos。

3 遍历myInfos代理插件进程列表,当进程优先级为:
RunningAppProcessInfo.IMPORTANCE_GONE
RunningAppProcessInfo.IMPORTANCE_EMPTY
RunningAppProcessInfo.IMPORTANCE_BACKGROUND
RunningAppProcessInfo.IMPORTANCE_SERVICE
时调用doGc
doGc函数如下:

    private void doGc(RunningAppProcessInfo myInfo) {
        int activityCount = mRunningProcessList.getActivityCountByPid(myInfo.pid);
        int serviceCount = mRunningProcessList.getServiceCountByPid(myInfo.pid);
        int providerCount = mRunningProcessList.getProviderCountByPid(myInfo.pid);
        if (activityCount <= 0 && serviceCount <= 0 && providerCount <= 0) {
            //杀死空进程。
               android.os.Process.killProcess(myInfo.pid);
        } else if (activityCount <= 0 && serviceCount > 0) {
            List<String> names = mRunningProcessList.getStubServiceByPid(myInfo.pid);
            if (names != null && names.size() > 0) {
                for (String name : names) {
                    Intent service = new Intent();
                    service.setClassName(mHostContext.getPackageName(), name);
                    AbstractServiceStub.startKillService(mHostContext, service);
               }
            }
        }
    }

这个函数首先从RunningProcessList获取运行在该代理插件进程中的Activity个数,service个数,provider个数,
判断如果当前进程运行的三个组件个数都为零,则直接杀掉该进程。
判断如果activity组件为零,service组件大于零,则获取所有service名,调用AbstractServiceStub.startKillService,这个函数所完成的工作如下(源码会在服务篇分析):查看AbstractServiceStub的Service中是否运行其他Service,如果没有运行其他Service,这停止AbstractServiceStub,并关闭其所运行的进程。
至此DroidPlugin对插件的管理分析完了。

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
本篇介绍ListView控件,这是Android中比较重要也比较复杂的控件,这里只谈到使用ViewHolder机制优化即可。 一、ListView简介 ListView是Android系统中显示列表的控件,每个ListView都可以包含很多个列表项。 二、ListView的使用 概念不多说,直接来介绍使用方法。 ListView中比较复杂的是数据适配器,其作用是把复杂的数据(数组、链表、数据库、集合等)填充在指定视图界面,是连接数据源和视图界面的桥梁。常见的Android原生的适配器有ArrayAdapt
欢迎转载,转载请注明出处: http://blog.csdn.net/dmk877/article/details/51912104   相信不管做了多长时间开发的人都用过Tween动画,从刚开始工作到现在我也是用了N次Tween动画,但是每一次使用总感觉掌握的不够全面,所以花了点时间详细的总结了下Tween动画,其实在android中熟练掌握动画,能够帮助我们实现一些非常酷炫的效果从而使我们的app在交互或者用户体验上有一个更好的体验,鉴于此详细的学习动画还是很有必要的,相信通过本篇的学习大家会对Twe

Java(Android)回调函数详解 - 2016-07-23 17:07:29

一、前言 本周有位入行开发不久的朋友问我回调究竟是个什么概念,在网上看了很多的回调函数解释,但是越看越乱。虽然回调函数这个梗已经不新鲜了,这里还是用书面的形式记录下。 如果有了解的,就无需再看。 二、概念 概念上,这里引用百度百科的解释,如下: 回调函数就是一个通过 函数指针 调用的函数。如果你把函数的 指针 (地址)作为 参数传递 给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或

Android_聊天_表情 - 2016-07-23 17:07:26

接下来就进入聊天界面了,我的界面效果如下几个图所示: 这其中包括两个点:仿微信按住说话功能,表情管理 第一个,按住说话 按钮的功能,通过重写Button完成, /** * 控制录音Button * 1、重写onTouchEvent;(changeState方法、wantToCancel方法、reset方法); * 2、编写AudioDialogManage、并与该类AudioRecorderButton进行整合; * 3、编写AudioManage、并与该类AudioRecorderButton进行整合;

JAVA 面向对象 隐藏和封装 - 2016-07-23 17:07:20

本页面更新日期: 2016年07月22日 前言 在前面程序中,经常出现通过 某个对象直接访问其成员变量的情况. 这可能引起一些潜在问题,比如将某个 Person 的 age 成员变量直接设为 1000. 这在语法上没有任何问题, 但显然违背了当前的自然规律. 人怎么可能活到 1000岁 - - . (就现在的科学来讲) Java也考虑到了这种情况, 为你提供了 类和对象的成员变量 进行封装的方法,来保护成员变量不被恶意修改. 理解封装 封装(Encapsulation) 是面向对象的三大特征之一.(另外两
上一篇文章分析过DroidPlugin对Activity的处理过程,不得不为对DroidPlugin的工程师们钦佩不已,那么是不是Service可以像Activity的处理过程一样来处理呢?前面讲过每一个代理进程只是预定义了一个Service,如果某一个插件中有多个Service,那岂不是某一个时刻只能有一个Service运行呢?由此可以判定可能Service的处理和Activity不一样。 一方面:平时使用Activity主要是用于展示界面和用户交互,Activity的生命周期可能受用户控制,当用户操作

IntentService使用及源码分析 - 2016-07-23 14:07:58

IntentService使用及源码分析 转载请注明 原博客地址: http://blog.csdn.net/gdutxiaoxu/article/details/52000680 本篇博客主要简介一下三个问题: 什么是IntentService? 怎样使用IntentService IntentSerice()源码分析 1)什么是IntentService? 我们知道Service和Activity一样是Android的四大组件之一,Service简称为后台服务,具有较高的优先级别。我们平时在Activ

Android——ListView控件 - 2016-07-23 14:07:54

本篇介绍ListView控件,这是Android中比较重要也比较复杂的控件,这里只谈到使用ViewHolder机制优化即可。 一、ListView简介 ListView是Android系统中显示列表的控件,每个ListView都可以包含很多个列表项。 二、ListView的使用 概念不多说,直接来介绍使用方法。 ListView中比较复杂的是数据适配器,其作用是把复杂的数据(数组、链表、数据库、集合等)填充在指定视图界面,是连接数据源和视图界面的桥梁。常见的Android原生的适配器有ArrayAdapt
HTTP请求报文: 一个HTTP请求报文由四个部分组成:请求行、请求头部、空行、请求数据 1.请求行   请求行由请求 方法字段、URL字段和HTTP协议版本字段 3个字段组成,它们用空格分隔。比如 GET /data/info.html HTTP/1.1 方法字段就是HTTP使用的请求方法,比如常见的GET/POST 其中HTTP协议版本有两种:HTTP1.0/HTTP1.1 可以这样区别: HTTP1.0对于每个连接都的建立一次连接一次只能传送一个请求和响应,请求就会关闭,HTTP1.0没有Host字

gradle多渠道打包 - 2016-07-23 14:07:48

E文不好的童鞋,例如我,翻译文章的过程里没有愉悦的感受,只有2行老泪;但最终有一丝成就感也算是安慰了。所以我会去尊重那些翻译IT技术文章的大拿们,他们就是千千万万个亚里士多德和吴启明,他们是E文不好的童鞋的传教士,阿门,当然我不是大拿。 废话少说,先看一篇例子:在 http://ghui.me/post/2015/03/create-several-variants/  。 然后来看这篇翻译,扫清例子中一部分未知的知识。原文在 http://tools.android.com/tech-docs/new-b