美文网首页Android开发程序员Android开发经验谈
Android进程保活 - 自“裁”或者耍流氓(二)

Android进程保活 - 自“裁”或者耍流氓(二)

作者: Android架构 | 来源:发表于2019-06-25 14:32 被阅读24次

    通过“流氓”手段提高oom_adj,降低被杀风险,化身流氓进程

    进程优先级的计算Android是有自己的一条准则的,某些特殊场景的需要额外处理进程的oom_adj Android也是给了参考方案的。但是,那对于流氓来说,并没有任何约束效力。 "流氓"仍然能够参照oom_adj(优先级)的计算规则,利用其漏洞,提高进程的oom_adj,以降低被杀的风险。如果单单降低被杀风险还好,就怕那种即不想死,又想占用资源的APP,累积下去就会导致系统内存不足,导致整个系统卡顿。

    优先级的计算逻辑比较复杂,这里只简述非缓存进程,因为一旦沦为缓存进程,其优先级就只能依靠LRU来计算,不可控。而流氓是不会让自己沦为缓存进程的,非缓存进程是以下进程中的一种,并且,优先级越高(数值越小),越不易被杀死:

    ADJ优先级 优先级 进程类型
    SERVICE_ADJ 5 服务进程(Service process)
    HEAVY_WEIGHT_APP_ADJ 4 后台的重量级进程,system/rootdir/init.rc文件中设置
    BACKUP_APP_ADJ 3 备份进程(这个不太了解)
    PERCEPTIBLE_APP_ADJ 2 可感知进程,比如后台音乐播放 ,通过startForeground设置的进程
    VISIBLE_APP_ADJ 1 可见进程(可见,但是没能获取焦点,比如新进程仅有一个悬浮Activity,其后面的进程就是Visible process)
    FOREGROUND_APP_ADJ 0 前台进程(正在展示是APP,存在交互界面,Foreground process)
    • 第一种提高到FOREGROUND_APP_ADJ

    我们从低到高看:如何让进程编程FOREGROUND_APP_ADJ进程,也就是前台进程,这个没有别的办法,只有TOP activity进程才能是算前台进程。正常的交互逻辑下,这个是无法实现的,锁屏的时候倒是可以启动一个Activity,但是需要屏幕点亮的时候再隐藏,容易被用户感知,得不偿失,所以基本是无解,所以之前传说的QQ通过一个像素来保活的应该不是这种方案,而通过WindowManager往主屏幕添加View的方式也并未阻止进程被杀,到底是否通过一像素实现进程包活,个人还未得到解答,希望能有人解惑。

    • 第二种,提高到VISIBLE_APP_ADJ或者PERCEPTIBLE_APP_ADJ(不同版本等级可能不同 “4.3 = PERCEPTIBLE_APP_ADJ” 而 “> 5.0 = VISIBLE_APP_ADJ”),就表现形式上看,微博、微等信都可能用到了,而且这种手段的APP一般很难杀死,就算从最近的任务列表删除,其实进程还是没有被杀死,只是杀死了Activity等组件。

    先看一下源码中对两种优先级的定义,VISIBLE_APP_ADJ是含有可见但是非交互Activity的进程,PERCEPTIBLE_APP_ADJ是用户可感知的进程,如后台音乐播放等

        // This is a process only hosting components that are perceptible to the
        // user, and we really want to avoid killing them, but they are not
        // immediately visible. An example is background music playback.
        static final int PERCEPTIBLE_APP_ADJ = 2;
    
        // This is a process only hosting activities that are visible to the
        // user, so we'd prefer they don't disappear.
        static final int VISIBLE_APP_ADJ = 1;
    
    

    这种做法是相对温和点的,Android官方曾给过类似的方案,比如音乐播放时后,通过设置前台服务的方式来保活,这里就为流氓进程提供了入口,不过显示一个常住服务会在通知栏上有个运行状态的图标,会被用户感知到。但是Android恰恰还有个漏洞可以把该图标移除,真不知道是不是Google故意的。这里可以参考微信的保活方案:双Service强制前台进程保活

    startForeground(ID, new Notification()),可以将Service变成前台服务,所在进程就算退到后台,优先级只会降到PERCEPTIBLE_APP_ADJ或者VISIBLE_APP_ADJ,一般不会被杀掉,Android的有个漏洞,如果两个Service通过同样的ID设置为前台进程,而其一通过stopForeground取消了前台显示,结果是保留一个前台服务,但不在状态栏显示通知,这样就不会被用户感知到耍流氓,这种手段是比较常用的流氓手段。优先级提高后,AMS的killBackgroundProcesses已经不能把进程杀死了,它只会杀死oom_adj大于ProcessList.SERVICE_ADJ的进程,而最近的任务列表也只会清空Activity,无法杀掉进程。 因为后台APP的优先级已经提高到了PERCEPTIBLE_APP_ADJ或ProcessList.VISIBLE_APP_ADJ,可谓流氓至极,如果再占据着内存不释放,那就是泼皮无赖了,这里有个遗留疑问:startForeground看源码只会提升到PERCEPTIBLE_APP_ADJ,但是在5.0之后的版本提升到了VISIBLE_APP_ADJ,这里看源码,没找到原因,希望有人能解惑。具体做法如下:

    public class RogueBackGroundService extends Service {
    
        private static int ROGUE_ID = 1;
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            return START_STICKY;
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            Intent intent = new Intent(this, RogueIntentService.class);
            startService(intent);
            startForeground(ROGUE_ID, new Notification());
        }
        public static class RogueIntentService extends IntentService {
    
            //流氓相互唤醒Service
            public RogueIntentService(String name) {
                super(name);
            }
    
            public RogueIntentService() {
                super("RogueIntentService");
            }
    
            @Override
            protected void onHandleIntent(Intent intent) {
    
            }
            @Override
            public void onCreate() {
                super.onCreate();
                startForeground(ROGUE_ID, new Notification());
            }   
           @Override
            public void onDestroy() {
                stopForeground(true); 
                super.onDestroy();
            }
        }
    }
    
    

    不过这个漏洞在Android7.1之后失效了,因为Google加了一个校验:如果还有Service通过setForeground绑定相同id的Notification,就不能cancelNotification,也就是说还是会显示通知(在通知列表)。

     private void cancelForegroudNotificationLocked(ServiceRecord r) {
            if (r.foregroundId != 0) {
                // First check to see if this app has any other active foreground services
                // with the same notification ID.  If so, we shouldn't actually cancel it,
                // because that would wipe away the notification that still needs to be shown
                // due the other service.
                ServiceMap sm = getServiceMap(r.userId);
                if (sm != null) {
                <!--查看是不是与该ID 通知绑定的Service取消了了前台显示-->
                    for (int i = sm.mServicesByName.size()-1; i >= 0; i--) {
                        ServiceRecord other = sm.mServicesByName.valueAt(i);
                        if (other != r && other.foregroundId == r.foregroundId
                                && other.packageName.equals(r.packageName)) {
                            // Found one!  Abort the cancel.
                            <!--如果找到还有显示的Service,直接返回-->
                            return;
                        }
                    }
                }
                r.cancelNotification();
            }
        }
    
    

    在7.1上,Google PlayStore渠道的微信似乎也放弃了这种保活手段,因为7.1的微信从最近的任务列表删除是可以杀死进程的,如果采用上述手段是杀不了的。

    双Service守护进程保活(这个也很流氓,不过如果不提高优先级(允许被杀),也算稍微良心)

    前文我们分析过Android Binder的讣告机制:如果Service Binder实体的进程挂掉,系统会向Client发送讣告,而这个讣告系统就给进程保活一个可钻的空子。可以通过两个进程中启动两个binder服务,并且互为C/S,一旦一个进程挂掉,另一个进程就会收到讣告,在收到讣告的时候,唤起被杀进程。逻辑如下下:

    首先编写两个binder实体服务PairServiceA ,PairServiceB,并且在onCreate的时候相互绑定,并在onServiceDisconnected收到讣告的时候再次绑定。

    public class PairServiceA extends Service {
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return new AliveBinder();
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            bindService(new Intent(PairServiceA.this, PairServiceB.class), mServiceConnection, BIND_AUTO_CREATE);
        }
    
        private ServiceConnection mServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
    
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                bindService(new Intent(PairServiceA.this, PairServiceB.class), mServiceConnection, BIND_AUTO_CREATE);
                ToastUtil.show("bind A");
            }
        };
    
    

    与之配对的B

    public class PairServiceB extends Service {
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return new AliveBinder();
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            bindService(new Intent(PairServiceB.this, PairServiceA.class), mServiceConnection, BIND_AUTO_CREATE);
        }
    
        private ServiceConnection mServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
    
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                bindService(new Intent(PairServiceB.this, PairServiceA.class), mServiceConnection, BIND_AUTO_CREATE);
            }
        };
    }
    
    

    之后再Manifest中注册,注意要进程分离

        <service android:name=".service.alive.PairServiceA"/>
        <service
            android:name=".service.alive.PairServiceB"
            android:process=":alive"/>
    
    

    之后再Application或者Activity中启动一个Service即可。

    startService(new Intent(MainActivity.this, PairServiceA.class));
    
    

    这个方案一般都没问题,因为Binder讣告是系统中Binder框架自带的,可能一次性全部杀死所有父子进程会不行,这个没测试过。不过这个手段也能在一定程度上提高有限级, 最近的任务列表已经不能杀死进程了。原因如下:

    此时APP内至少两个进程A\B ,并且AB相互通过bindService绑定,此时就是互为客户端,在oom_adj中有这么一种计算逻辑,如果进程A的Service被B通过bind绑定,那么A的优先级可能会受到B的影响,因为在计算A的时候需要先计算B,但是B同样是A的Service,反过来有需要计算A,如果不加额外的判断,就会出现死循环,AMS是通过一个计数来标识的:mAdjSeq == app.adjSeq。于是流程就是这样

    • 计算A:发现依赖B,去计算B
    • 计算B:发现依赖A,回头计算A
    • 计算A:发现A正在计算,直接返回已经计算到一半的A优先级

    上面的流程能保证不出现死循环,并且由于A只计算了一半,所以A的很多东西没有更新,所以B拿到的A就是之前的数值,比如 curProcState、curSchedGroup:

    private final int computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP,
            boolean doingAll, long now) {
        if (mAdjSeq == app.adjSeq) {
            // This adjustment has already been computed.
            return app.curRawAdj;
        }
        ....
          for (int is = app.services.size()-1;
                is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                        || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE
                        || procState > ActivityManager.PROCESS_STATE_TOP);
                is--) {
            ServiceRecord s = app.services.valueAt(is);
           ...
            for (int conni = s.connections.size()-1;
                    conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                            || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE
                            || procState > ActivityManager.PROCESS_STATE_TOP);
                    conni--) {
                ArrayList<ConnectionRecord> clist = s.connections.valueAt(conni);
                for (int i = 0;
                        i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ
                                || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE
                                || procState > ActivityManager.PROCESS_STATE_TOP);
                        i++) {
                    ConnectionRecord cr = clist.get(i);
    
                    if (cr.binding.client == app) {
                        // Binding to ourself is not interesting.
                        continue;
                    }
                    if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0) {
                        ProcessRecord client = cr.binding.client;
                        // 这里会不会出现死循环的问题呢? A需要B的计算、B需要A的计算,这个圆环也许就是为什么
                        // 无法左滑删除的原因 循环的,
                        <!--关键点1 -->
                        int clientAdj = computeOomAdjLocked(client, cachedAdj,
                                TOP_APP, doingAll, now);
    
                        int clientProcState = client.curProcState;
                        if (clientProcState >= ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) {
                            clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
                        }
                       <!--关键点2-->
                                ...
                        if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
                            if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) {
                                schedGroup = Process.THREAD_GROUP_DEFAULT;
                            }
                    ...        
            }
    
    

    上面的代码中:关键点1是循环计算的入口,关键点2是无法删除的原因所在,由于A没及时更新,导致schedGroup = Process.THREAD_GROUP_DEFAULT,反过来也让A保持schedGroup = Process.THREAD_GROUP_DEFAULT。A B 都无法左滑删除。

    通过START_STICKY与START_REDELIVER_INTENT实现被杀唤醒

    通过startService启动的Service,如果没用呗stopService结束掉,在进程被杀掉之后,是有可能重新启动的,实现方式:

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;//或者START_REDELIVER_INTENT
    }
    
    

    当然,前提是该进程可以被杀掉(无论被AMS还是LMDK),用户主动杀死(最近任务列表或者退出应用),都一定会通过Binder讣告机制回调:

    private final void handleAppDiedLocked(ProcessRecord app,
            boolean restarting, boolean allowRestart) {
        int pid = app.pid;
        boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1);
        ...
       }
    
    

    进而调用cleanUpApplicationRecordLocked函数进行一系列清理及通知工作,这里先看Service相关的工作:

      private final boolean cleanUpApplicationRecordLocked(ProcessRecord app,
                boolean restarting, boolean allowRestart, int index) {
            ...
             // 这里先出处理service
            mServices.killServicesLocked(app, allowRestart);
            ...
      }   
    
    

    这里传入的allowRestart==true,也就说:允许重新启动Service:

    final void killServicesLocked(ProcessRecord app, boolean allowRestart) {
    
        ...
        ServiceMap smap = getServiceMap(app.userId);
       // Now do remaining service cleanup.
        for (int i=app.services.size()-1; i>=0; i--) {
            ServiceRecord sr = app.services.valueAt(i);
            if (!app.persistent) {
                app.services.removeAt(i);
            }
            ...
            if (allowRestart && sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags
                    &ApplicationInfo.FLAG_PERSISTENT) == 0) {
                bringDownServiceLocked(sr);
            } else if (!allowRestart
                    || !mAm.mUserController.isUserRunningLocked(sr.userId, 0)) {
                bringDownServiceLocked(sr);
            } else {
               <!--关键点1 先进行判定,如果有需要将重启的消息发送到消息队列等待执行-->
                boolean canceled = scheduleServiceRestartLocked(sr, true);
               // 受时间跟次数的限制 sr.stopIfKilled  
              <!--关键点2 二次确认,如果不应该启动Service,就将重启Service的消息移除-->
               if (sr.startRequested && (sr.stopIfKilled || canceled)) {
                    if (sr.pendingStarts.size() == 0) {
                        sr.startRequested = false;
                        if (!sr.hasAutoCreateConnections()) {
                            bringDownServiceLocked(sr);
                        }
               ...
     }
    
    

    先看关键点1:如果允许重新启动,并且APP Crash的次数小于两次,就视图将为结束的Service重新唤起,其实就是调用scheduleServiceRestartLocked,发送消息,等待唤醒,关键点2是二次确认下,是不是需要被唤醒,如果不需要就将上面的消息移除,并进行一定的清理工作,这里的sr.stopIfKilled,其实主要跟onStartCommand返回值有关系:

     void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) {
            boolean inDestroying = mDestroyingServices.contains(r);
            if (r != null) {
                if (type == ActivityThread.SERVICE_DONE_EXECUTING_START) {
                    r.callStart = true;
                    switch (res) {
                        case Service.START_STICKY_COMPATIBILITY:
                        case Service.START_STICKY: {
                            r.findDeliveredStart(startId, true);
                            r.stopIfKilled = false;
                            break;
                        }
                        case Service.START_NOT_STICKY: {
                            r.findDeliveredStart(startId, true);
                            if (r.getLastStartId() == startId) {
                                r.stopIfKilled = true;
                            }
                            break;
                        }
                        case Service.START_REDELIVER_INTENT: {
                            ServiceRecord.StartItem si = r.findDeliveredStart(startId, false);
                            if (si != null) {
                                si.deliveryCount = 0;
                                si.doneExecutingCount++;
                                r.stopIfKilled = true;
                            }
                            break;
                        }
    
    

    所以,如果onStartCommand返回的是Service.START_STICKY,在被杀死后是会重新启动的,有必要的话,还会重启进程:

    private final boolean scheduleServiceRestartLocked(ServiceRecord r,
            boolean allowCancel) {
        boolean canceled = false;
         ...
         <!--关键点1-->
        mAm.mHandler.removeCallbacks(r.restarter);
        mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime);
        r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay;
        return canceled;
    }
    
    

    看关键点1,其实就是发送一个重新启动Service的消息,之后就会重新启动Service。

    private class ServiceRestarter implements Runnable {
        private ServiceRecord mService;
    
        void setService(ServiceRecord service) {
            mService = service;
        }
    
        public void run() {
            synchronized(mAm) {
                performServiceRestartLocked(mService);
            }
        }
    }
    
    

    再看下几个标志的意义:

    1、 START_STICKY

    在运行onStartCommand后service进程被kill后,那将保留在开始状态,但是不保留那些传入的intent。不久后service就会再次尝试重新创建,因为保留在开始状态,在创建 service后将保证调用onstartCommand。如果没有传递任何开始命令给service,那将获取到null的intent

    2、 START_NOT_STICKY

    在运行onStartCommand后service进程被kill后,并且没有新的intent传递给它。Service将移出开始状态,并且直到新的明显的方法(startService)调用才重新创建。因为如果没有传递任何未决定的intent那么service是不会启动,也就是期间onstartCommand不会接收到任何null的intent。

    3、 START_REDELIVER_INTENT

    在运行onStartCommand后service进程被kill后,系统将会再次启动service,并传入最后一个intent给onstartCommand。直到调用stopSelf(int)才停止传递intent。如果在被kill后还有未处理好的intent,那被kill后服务还是会自动启动。因此onstartCommand不会接收到任何null的intent。

    ProcessRecord中一些参数的意义的意义

    • int maxAdj;                 // Maximum OOM adjustment for this process
      
      
    • int curRawAdj;              // Current OOM unlimited adjustment for this process
      
      
    • int setRawAdj;              // Last set OOM unlimited adjustment for this process  
      
      
    • int curAdj;                 // Current OOM adjustment for this process
      
      
    • int setAdj;                 // Last set OOM adjustment for this process
      
      

    adj主要用来给LMKD服务,让内核曾选择性的处理后台杀死,curRawAdj是本地updateOomAdj计算出的临时值,setRawAdj是上一次计算出兵设定好的oom值,两者都是未经过二次调整的数值,curAdj与setAdj是经过调整之后的adj,这里有个小问题,为什么前台服务进程的oom_adj打印出来是1,但是在AMS登记的curAdj却是2呢?

     oom: max=16 curRaw=2 setRaw=2 cur=2 set=2
    curSchedGroup=-1 setSchedGroup=-1 systemNoUi=false trimMemoryLevel=0
    curProcState=4 repProcState=4 pssProcState=-1 setProcState=4 lastStateTime=-37s554ms
    
    

    AMS传递给LMKD服务的adj确实是2,LMKD用2计算出的oom_score_adj=117 (1000oom_adj/17) 也是准确的数值 ,那为什么proc/pid/oom_adj中的数值是1呢?应该是反向取整*导致的,高版本的内核都不在使用oom_adj,而是用oom_score_adj,oom_adj是一个向前兼容。

    static void cmd_procprio(int pid, int uid, int oomadj) {
    struct proc *procp;
    char path[80];
    char val[20];

        if (oomadj < OOM_DISABLE || oomadj > OOM_ADJUST_MAX) {
            ALOGE("Invalid PROCPRIO oomadj argument %d", oomadj);
            return;
        }
    
        // 这里只记录oom_score_adj
        snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", pid);
        snprintf(val, sizeof(val), "%d", lowmem_oom_adj_to_oom_score_adj(oomadj));
        writefilestring(path, val);
        <!--use_inkernel_interface = 1-->
         if (use_inkernel_interface)
        return;
        ....
     }
    
    

    use_inkernel_interface标识其他几个oom_adj,oom_score跟随 oom_score_adj变化。oom_adj=(oom_score_adj*17/1000),取整的话,正好小了1;看如下解释:

    The value of /proc/<pid>/oom_score_adj is added to the badness score before oom_adj;
    
    For backwards compatibility with previous kernels, /proc/<pid>/oom_adj may also
    be used to tune the badness score.  Its acceptable values range from -16
    (OOM_ADJUST_MIN) to +15 (OOM_ADJUST_MAX) and a special value of -17
    (OOM_DISABLE) to disable oom killing entirely for that task.  Its value is
    scaled linearly with /proc/<pid>/oom_score_adj.
    
    

    oom_adj的存在是为了和旧版本的内核兼容,并且随着oom_score_adj线性变化,如果更改其中一个,另一个会自动跟着变化,在内核中变化方式为:

    • 写oom_score_adj时,内核里都记录在变量 task->signal->oom_score_adj 中;
    • 读oom_score_adj时,从内核的变量 task->signal->oom_score_adj 中读取;
    • 写oom_adj时,也是记录到变量 task->signal->oom_score_adj 中,会根据oom_adj值按比例换算成oom_score_adj。
    • 读oom_adj时,也是从内核变量 task->signal->oom_score_adj 中读取,只不过显示时又按比例换成oom_adj的范围。

    所以,就会产生如下精度丢失的情况:

    # echo 9 > /proc/556/oom_adj
    # cat /proc/556/oom_score_adj
      529
    # cat /proc/556/oom_adj
      8
    
    

    这也是为什么Android中明明算出来的oom_adj=1(2),在proc/pid/oom_adj总记录的确实0(1)。

    • int curSchedGroup;          // Currently desired scheduling class
      
      
    • int setSchedGroup;          // Last set to background scheduling class
      
      

    curSchedGroup与setSchedGroup是AMS管理进程的一个参考,定义在ProcessList.java中,从名字上看与任务调度有关系,答案也确实如此,取值有如下三种,不同版本略有不同,这里是7.0,

    // Activity manager's version of Process.THREAD_GROUP_BG_NONINTERACTIVE
    static final int SCHED_GROUP_BACKGROUND = 0;
    // Activity manager's version of Process.THREAD_GROUP_DEFAULT
    static final int SCHED_GROUP_DEFAULT = 1;
    // Activity manager's version of Process.THREAD_GROUP_TOP_APP
    static final int SCHED_GROUP_TOP_APP = 2;
    
    

    AMS只能杀死后台进程,只有setSchedGroup==ProcessList.SCHED_GROUP_BACKGROUND的进程才被AMS看做后台进程,才可以被杀死,否则AMS无权杀死。

         <!--参考代码1-->
      if (app.waitingToKill != null && app.curReceivers.isEmpty()
                        && app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
                    app.kill(app.waitingToKill, true);
                    success = false;
                } 
    
       <!--参考代码2-->    
        // Kill the running processes.
        for (int i = 0; i < procsToKill.size(); i++) {
            ProcessRecord pr = procsToKill.get(i);
            if (pr.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND
                    && pr.curReceivers.isEmpty()) {
                pr.kill("remove task", true);
            } else {
                // We delay killing processes that are not in the background or running a receiver.
                pr.waitingToKill = "remove task";
            }
        }
    
    

    以上两个场景:场景一是AMS计算oomAdj并清理进程 ,场景二的代表:从最近的任务列表删除进程。

    • int curProcState = PROCESS_STATE_NONEXISTENT; // Currently computed process state
      
      
    • int repProcState = PROCESS_STATE_NONEXISTENT; // Last reported process state
      
      
    • int setProcState = PROCESS_STATE_NONEXISTENT; // Last set process state in process tracker
      
      
    • int pssProcState = PROCESS_STATE_NONEXISTENT; // Currently requesting pss for
      
      

    ProcState 主要是为AMS服务,AMS依据procState判断进程当前的状态以及重要程度,对应的值位于ActivityManager.java中,主要作用是:决定进程的缓存等级以及缓存进程的生死。

    <!--参考代码1-->
    switch (app.curProcState) {
                        case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
                        case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
                            mNumCachedHiddenProcs++;
                            numCached++;
                            if (numCached > cachedProcessLimit) {
                                app.kill("cached #" + numCached, true);
                            }
                            break;
                        case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
                            if (numEmpty > ProcessList.TRIM_EMPTY_APPS
                                    && app.lastActivityTime < oldTime) {
                                app.kill("empty for "
                                        + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
                                        / 1000) + "s", true);
                            } else {
                                numEmpty++;
                                if (numEmpty > emptyProcessLimit) {
                                    app.kill("empty #" + numEmpty, true);
                                }
                            }
                            break;
                        default:
                            mNumNonCachedProcs++;
                            break;
                }
    
    <!--参考代码2-->
    if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
                                || app.systemNoUi) && app.pendingUiClean) {
                            final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
                            if (app.trimMemoryLevel < level && app.thread != null) {
                                try {
                                    app.thread.scheduleTrimMemory(level);
                                } catch (RemoteException e) {
                                }
                            }
                            app.pendingUiClean = false;
                        }
    
    

    总结

    所有流氓手段的进程保活,都是下策,建议不要使用,本文只是分析实验用。当APP退回后台,优先级变低,就应该适时释放内存,以提高系统流畅度,依赖流氓手段提高优先级,还不释放内存,保持不死的,都是作死。

    自己是从事了七年开发的Android工程师,不少人私下问我,2019年Android进阶该怎么学,方法有没有?

    没错,年初我花了一个多月的时间整理出来的学习资料,希望能帮助那些想进阶提升Android开发,却又不知道怎么进阶学习的朋友。【包括高级UI、性能优化、架构师课程、NDK、Kotlin、混合式开发(ReactNative+Weex)、Flutter等架构技术资料】,希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

    资料获取方式:加入Android架构交流QQ群聊:513088520 ,进群即领取资料!!!

    点击链接加入群聊【Android移动架构总群】:加入群聊

    资料大全

    相关文章

      网友评论

        本文标题:Android进程保活 - 自“裁”或者耍流氓(二)

        本文链接:https://www.haomeiwen.com/subject/liynqctx.html