Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> (4.6.17.6)進程保活(Android的5.0分界線):Android5.0以上版本的force close到底發生了什麼改變?

(4.6.17.6)進程保活(Android的5.0分界線):Android5.0以上版本的force close到底發生了什麼改變?

編輯:關於Android編程

在閱讀本篇之前,你首先需要大概清楚一點,無論是系統殺(android機型上長按home鍵中清除),或者是他殺(第三方管理軟件,如360,騰訊等)。其實現方法,基本都是借助ActivityManagerService的removeLruProcessLocked,改變主要也是在這裡

一、 先看代碼有啥不同

5.0以下

我們先來看看Android4.4的源碼,ActivityManagerService(源碼/frameworks/base/services/core/Java/com/Android/server/am/ActivityManagerService.java)是如何關閉在應用退出後清理內存的:

 final void removeLruProcessLocked(ProcessRecord app) {
         /.......
        if (lrui >= 0) {
            if (!app.killed) {
                Slog.wtfStack(TAG, "Removing process that hasn't been killed: " + app);
                Process.killProcessQuiet(app.pid);
            }
           /.......
         }
 }

killProcessQuiet我們暫時不向下層深究,從字面看就可以,就是kill了一個pid指向的進程

5.0以上
 final void removeLruProcessLocked(ProcessRecord app) {
         /.......
        if (lrui >= 0) {
            if (!app.killed) {
                Slog.wtfStack(TAG, "Removing process that hasn't been killed: " + app);
                Process.killProcessQuiet(app.pid);
                Process.killProcessGroup(app.info.uid, app.pid);
            }
           /.......
         }
 }

可以看到,明顯多了一句killProcessGroup,也就是把和pid同組的進程全部殺死
最大的機制改變,就是這個“同組殺”,我們接下來研究這個“同組殺”,到底是如何實現的

二、 你不信這個改變? 那測試下吧

注意,本篇的分析,都建立在你基本了解了linux的命令終端、用戶、會話、進程組、進程這些概念的基礎上,如果你這些詞都還沒聽過

2.1 fork一個子進程

pid_t pid = fork();  
if(pid <  0){  
    return;  
}else if(pid > 0){  
    return;  
}else{  
}  

我們在 adb shell中 使用命令 ps| grep test,可以看到進程信息:

USER     PID   PPID  VSIZE  RSS     WCHAN    PC        NAME  
u0_a64   16992 317   1536924 52588 ffffffff 00000000 S com.example.testndk2  
u0_a64  17011 16992 1504092 34508 ffffffff 00000000 S com.example.testndk2  

//可以看到有兩個進程,17011便是在JNI中通過fork創建出的子進程。它的父進程是16992.
//在5.0+上只要kill 16992,17011也會被kill.而在4.4上,則不會。
//打印出來他們的進程組 getpgrp()  都是317  ps:也就是主進程的父進程的組長
ps命令參數
user對應著linux的用戶,其實也能看出來uid,例如u0_a64的id就是10164 pid當前進程id號 PPID 父進程的id號 VSIZE : virtual size,進程虛擬地址空間大小; RSS : 進程正在使用的物理內存的大小; WCHAN :進程如果處於休眠狀態的話,在內核中的地址; PC : program counter, NAME: process name,進程的名稱

2.2 init領養了,還算是同組麼?

 if(pid=fork()>0) {
     //父進程,讓它活著
 }
 else if(pid< 0){ 
        perror("fail to fork1");
        exit(1);//fork失敗,退出
 }else{//第一個子進程
   if(pid=fork()>0) 
        exit(0);//【2.1】是第一子進程,結束第一子進程 
    else if(pid< 0) 
        exit(1);//fork失敗,退出 
    else{//第二個子進程
    }
 }
//可以看到,第二個子進程的ppid為1,證明它在它父進程死亡後,被init收養了
// 測試發現若kill 18602,18650也會一同被kill
//打印出來他們的進程組 getpgrp()  都是317  ps:也就是主進程的父進程的組長

USER     PID   PPID  VSIZE  RSS     WCHAN    PC        NAME  
u0_a64   18602 317   1538796 53848 ffffffff 00000000 S com.example.testndk2  
u0_a64   18650 1       1504092 34508 ffffffff 00000000 S com.example.testndk2  

2.3 守護進程實現了跨組,跨會話,怎麼樣?

我們知道setsid(),會讓當前的子進程 跳到新的會話裡,並成為新的組的組長

    pid = fork();
    if(pid<0){
        LOGI(LOG_TAG, "第一次fork()失敗");
    }
    else if (pid == 0) {//第一個子進程
        LOGI(LOG_TAG, "第一個子進程: %d",getpid());
        if(setsid() == -1){ LOGI(LOG_TAG, "第一個子進程獨立到新會話:失敗");}//【2】第一子進程成為新的會話組長和進程組長
        pid = fork();
        if (pid == 0) {//第二個子進程
        LOGI(LOG_TAG, "第二個子進程ID:%d,實際用戶UID為:%d,有效用戶UID為:%d,進程組ID:%d",getpid(),getuid(),geteuid(),getpgrp());
        chdir(s_work_dir);//【3】改變工作目錄到s_work_dir
        umask(0);;//【4】重設文件創建掩模
        for(i=0;i< 5;++i)//【5】關閉打開的文件描述符  TODO  數目
            close(i);
        //將真正用來實現的子進程寫到一個二進制文件中(對應文件源碼/MarsDaemon/LibMarsdaemon/jni/daemon.c),這樣既解決了內存問題,又可以自己給新的進程命名
        //直接運行一個二進制文件,他會占用原進程,於是我們這裡僅將fork用作一個啟動binary的工具
        LOGI(LOG_TAG, "開始運行二進制文件,啟動守護進程,當前進程ID:%d,實際用戶UID為:%d,有效用戶UID為:%d,進程組ID:%d",getpid(),getuid(),geteuid(),getpgrp());
        execl(daemon, daemon, workdir, service, (char *)0);//二進制文件啟動服務
        }

        exit(0);//【2.1】是第一子進程,結束第一子進程 ,使得會話不會申請控制終端
    } else {//主進程
        // 等待第一個子進程退出,應該會立即退出,讓它繼續活著
        waitpid(pid, &status, 0);
        //一個守護進程的父進程是init進程,因為它真正的父進程在fork出子進程後就先於子進程exit退出了,所以它是一個由init繼承的孤兒進程
        //exit(EXIT_SUCCESS); //【1】父進程直接退出,從而實現子進程的脫離控制終端
    }

這次我們在一個完整項目裡實驗:
28653 是在fork後的子進程中,使用execlp()開啟了一個新進程,它會替代原進程(kill原進程,執行自己占有原進程),它是一個守護進程

u0_a315   28453 361   2179636 60772 ffffffff 00000000 S com.XXXX.pocketdog
u0_a315   28653 1     9248   568   ffffffff 00000000 S /data/data/com.XXXX.pocketdog/files/daemon

07-27 15:01:58.670 28453-28651/com.XXXX.pocketdog I/packetdog: 開啟守護進程,主進程id為:28453,實際用戶UID為:10315,有效用戶UID為:10315,進程組ID:361
..........
07-27 15:01:58.685 28652-28652/? I/packetdog: 第一個子進程: 28652
07-27 15:01:58.700 28653-28653/? I/packetdog: 開始運行二進制文件,啟動守護進程,當前進程ID:28653,實際用戶UID為:10315,有效用戶UID為:10315,進程組ID:28652

VSIZE可以看內存大小 主進程有虛擬機 多一二十兆 子進程沒有
我擦,好激動呀,真的實現了進程不同組了,子進程和守護進程不在同一個組裡
趕緊試一下,forc kill

07-27 15:03:47.227 2545-2667/? W/recents.Proxy: packageName = com.XXXX.pocketdog is other will be forceStopPackage
07-27 15:03:47.229 1197-3392/? I/ActivityManager: Force stopping com.XXXX.pocketdog appid=10315 user=0: from pid 2545
07-27 15:03:47.229 1197-3392/? I/ActivityManager: Killing 28453:com.sangfor.pocketdog/u0a315 (adj 0): stop com.XXXX.pocketdog
07-27 15:03:47.230 1197-3392/? W/ActivityManager: Scheduling restart of crashed service com.sangfor.pocketdog/.ForeverService in 1000ms
07-27 15:03:47.231 1197-1518/? I/libprocessgroup: Killing pid 28653 in uid 10315 as part of process group 28453
07-27 15:03:47.240 1197-3392/? I/ActivityManager:   Force stopping service ServiceRecord{e13adf u0 com.sangfor.pocketdog/.ForeverService}

Killing pid 28653 in uid 10315 as part of process group 28453

我去,28653 這個子守護進程已經被我搞成真的“守護進程”,並且移動到28652的新會話裡的新進程組了,已經和主進程28453脫離了呀,怎麼還是同組?

三、同組殺? 歷史同組殺?

上邊,我們講到,如果是fork的子進程,或者是init領養的子進程,ActivityManagerService你給我“組殺”了,我還能理解,畢竟他們的確還在一個 進程組裡
但是,守護進程我已經跳出該組了, 你還以“組殺”的名義,把我的守護進程干掉,太不講理了吧?
你這是什麼“組殺”?

3.1 組殺的實現方式

5.0+上開啟了Control Group來管理進程

它會把進程以及它的所有子進程都綁到了一個組裡面管理,這樣就能做到將所有子進程都殺了此處,買一個關子,你要知道,這個組的概念和linux的實際進程組是有不同之處的

對代碼進行分析:
在AMS中殺App時,會調用到processgroup.cpp的killProcessGroup函數,看下該函數會做什麼:

int killProcessGroup(uid_t uid, int initialPid, int signal)  
{  
     ..........  
    while ((processes = killProcessGroupOnce(uid, initialPid, signal)) > 0) {  
        if (retry > 0) {  
            usleep(sleep_us);  
            --retry;  
        } else {  
            break;  
        }  
    }  
     ..........  
    }  

可以看到在killProcessGroup中只是循環調用killProcessGroupOnce,再看看該函數又是做什麼的:

static int killProcessGroupOnce(uid_t uid, int initialPid, int signal)  
{  

     while ((pid = getOneAppProcess(uid, initialPid, &ctx) >= 0) {  
        processes++;  
         ......  
       int ret = kill(pid, signal);  
        if (ret == -1) {  
            SLOGW("failed to kill pid %d: %s", pid, strerror(errno));  
        }  
    }  

它通過getOneAppProcess循環獲取子進程id,再kill掉。
進入getOneAppProcess查看,最終發現子進程是從下面這個函數組成的文件名中讀取到的:

static int convertUidPidToPath(char *path, size_t size, uid_t uid, int pid)  
{  
    return snprintf(path, size, "%s/%s%d/%s%d",  
            PROCESSGROUP_CGROUP_PATH,  
            PROCESSGROUP_UID_PREFIX,  
            uid,  
            PROCESSGROUP_PID_PREFIX,  
            pid);  
}  

上面幾個常量的定義如下:

#define PROCESSGROUP_CGROUP_PATH "/acct"  
#define PROCESSGROUP_UID_PREFIX "uid_"  
#define PROCESSGROUP_PID_PREFIX "pid_"  
#define PROCESSGROUP_CGROUP_PROCS_FILE "/cgroup.procs"  

所以上面的函數組成的文件名(路徑)是這樣的:
/acct/uid_XX/pid_XX/cgroup.procs
該文件存儲了,同組的進程的信息

//使用shell命令去查看
//adb shell
//su   手機一定要root,不然看不了 uid_XX的目錄
//cd uid_XX   進入進程所屬的用戶目錄
//cat pid_/cgroup.procs   pid_是某個進程的目錄,cgroup存儲該進程的同組進程信息

3.2 cgroup的記錄是如何生成的

到現在你應該明白了,5.0的force kill是根據cgroup殺進程的,那麼cgroup記錄是怎麼生成的呢,是linxu的同組進程的id麼?

其實,基本是這樣子:

如果當前進程是非系統進程,非白名單進程。
那麼這個進程的所fork()或execlp出來的任何進程,都是該進程的同組進程,會在這裡記錄 那我把新創建的進程移到新的進程組呢? 對不起,只要你曾經在這個組裡,那我就會在這裡記錄。哪怕是你移到一個新組,再setsid再開一個新會話進一個新組,我都會記錄你 如果是系統進程,或者白名單進程呢?
系統進程或白名單創建的新進程,會創建一個pid_xx的文件夾,相當於根進程又啟動了幾個進程,他們不算同組進程,也不會記錄在這裡邊

所以,我們明白了 5.0 實現的是“歷史同組殺”

四、微信,qq怎麼在5.0 以上的保活的

好啦,此處我們加入講解下,為什麼微信或者qq之類的一些軟件能持久保活

看下微信的:

u0_a132   2990  362   2306772 300900 ffffffff 00000000 S com.tencent.mm
u0_a132   6550  362   1707116 43600 ffffffff 00000000 S com.tencent.mm:exdevice
u0_a132   6607  362   1715528 53760 ffffffff 00000000 S com.tencent.mm:push

注意到沒有,我們自己的應用在 魅族手機上看進程,尼瑪全是10000以上的id號,微信的呢?

2990 6550 6607 都是10000以下

我們還知道 進程號裡 0-300 是守護進程

那麼很有可能在不同定制版本的手機上,區分了 300-XX是系統進程,XX1-XX2是白名單進程

你問白名單有什麼好處?

看 3.2的第二項吧 這就是好處

我們現在看看java層啟動服務的cgroup文件(5.1中配置AndroidManifest.xml的service中配置android:process):

u0_a179   21068 490   1515144 31516 ffffffff 00000000 S com.marswin89.marsdaemon.demo
cgroup.procs--------21068
u0_a179   21098 490   1498412 20400 ffffffff 00000000 S com.yhf.demo1
cgroup.procs---------21068
u0_a179   21126 490   1498412 20420 ffffffff 00000000 S com.yhf.demo2
cgroup.procs----------21126

據此我們可以看出 android層啟動的進程 由於是系統啟動的 都會創建pid_文件夾
同時 其中的記錄文件沒有記錄同組的其他id(如上文所述,該記錄文件記錄同組歷史,哪怕你跳出去新的進程組,還是會一樣在這裡顯示)

測試記錄:\進程保護\MARS\進程保護_魅族A5.1

由此可見,如果想要解決 5.0的保護問題,勢必要從android層開始

android層解決“歷史同組”問題,native層再修復“同包名殺”和“task殺”的問題,就是整體的解決方向

五、編外:為什麼保活大部分都在native層實現?

其實這個很好理解,在android最開始的版本裡,就引入了“同包名殺”和“同task殺”

5.1 同包名殺

u0_a324   27615 362   1557944 50628 ffffffff 00000000 S com.marswin89.marsdaemon.demo
u0_a324   27647 362   1545432 38436 ffffffff 00000000 S com.marswin89.marsdaemon.demo:process1
u0_a324   27677 362   1545432 38572 ffffffff 00000000 S com.marswin89.marsdaemon.demo:process2
0

7-28 17:20:46.718 1197-1220/? I/ActivityManager: Killing 27615:com.marswin89.marsdaemon.demo/u0a324 (adj 9): remove task
07-28 17:20:46.729 2545-2667/? W/recents.Proxy: packageName = com.marswin89.marsdaemon.demo is other will be forceStopPackage
07-28 17:20:46.731 1197-3457/? I/ActivityManager: Force stopping com.marswin89.marsdaemon.demo appid=10324 user=0: from pid 2545
07-28 17:20:46.737 1197-3457/? I/ActivityManager: Killing 27647:com.marswin89.marsdaemon.demo:process1/u0a324 (adj 0): stop com.marswin89.marsdaemon.demo
07-28 17:20:46.738 1197-3457/? W/ActivityManager: Scheduling restart of crashed service com.marswin89.marsdaemon.demo/.Service1 in 1000ms
07-28 17:20:46.739 1197-3457/? I/ActivityManager: Killing 27677:com.marswin89.marsdaemon.demo:process2/u0a324 (adj 0): stop com.marswin89.marsdaemon.demo
07-28 17:20:46.740 1197-3457/? W/ActivityManager: Scheduling restart of crashed service com.marswin89.marsdaemon.demo/.Service2 in 10998ms
07-28 17:20:46.743 1197-3457/? I/ActivityManager:   Force stopping service ServiceRecord{378476d4 u0 com.marswin89.marsdaemon.demo/.Service1}
07-28 17:20:46.743 1197-3457/? I/ActivityManager:   Force stopping service ServiceRecord{1e1c2c40 u0 com.marswin89.marsdaemon.demo/.Service2}

packageName = com.marswin89.marsdaemon.demo is other will be forceStopPackage

殺進程的時候,同一個包名下的進程都會被kill掉

咦,那你說我在AndroidManifest.xml的service中配置android:process=”com.yhf.demo1”,這樣包名不一樣了吧,而且都是系統進程開啟的,跟主進程不屬“歷史同組進程”,能實現保活?

別天真了,你的確實現了避免“歷史同組進程”,但是系統還是認為你是同包名的進程的,最重要,還是“同task殺”

u0_a324   27615 362   1557944 50628 ffffffff 00000000 S com.marswin89.marsdaemon.demo
u0_a324   27647 362   1545432 38436 ffffffff 00000000 S com.yhf.demo1
u0_a324   27677 362   1545432 38572 ffffffff 00000000 S com.yhf.demo2

//還是被清除了  日志忘了記錄 哈哈  不信的自己試試哈

5.2 同task殺

task就不多講了

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved