編輯:關於Android編程
在閱讀本篇之前,你首先需要大概清楚一點,無論是系統殺(android機型上長按home鍵中清除),或者是他殺(第三方管理軟件,如360,騰訊等)。其實現方法,基本都是借助ActivityManagerService的removeLruProcessLocked,改變主要也是在這裡
我們先來看看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的命令終端、用戶、會話、進程組、進程這些概念的基礎上,如果你這些詞都還沒聽過
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命令參數
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
我們知道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你給我“組殺”了,我還能理解,畢竟他們的確還在一個 進程組裡
但是,守護進程我已經跳出該組了, 你還以“組殺”的名義,把我的守護進程干掉,太不講理了吧?
你這是什麼“組殺”?
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存儲該進程的同組進程信息
到現在你應該明白了,5.0的force kill是根據cgroup殺進程的,那麼cgroup記錄是怎麼生成的呢,是linxu的同組進程的id麼?
其實,基本是這樣子:
如果當前進程是非系統進程,非白名單進程。所以,我們明白了 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殺”的問題,就是整體的解決方向
其實這個很好理解,在android最開始的版本裡,就引入了“同包名殺”和“同task殺”
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 //還是被清除了 日志忘了記錄 哈哈 不信的自己試試哈
task就不多講了
一、按鍵燈的簡介最近調試一下按鍵燈,今天抽空順便把的流程分析了一下。按鍵燈也是一種led,它的使用規則如命名一樣,當按鍵按下亮燈,如果一定時間不操作的話,一會會滅燈。其實
對計算器的一些說明: 此計算器比較簡陋,可以實現加減乘除這些運算,並能實現連續運算。對小數運算進行了優化了,避免了小數在計算時出現誤差。 主界面: calculato
導語這裡展示的View估計項目中多半是用不到的,只是用來加深理解的。文章末尾會有全部的代碼,如果想研究可以復制過去直接運行,不需要額外的資源。先看效果:這裡指針是通過手指
MPAndroidChart 是 Android 系統上一款開源的圖表庫。目前提供線圖和餅圖,支持選擇、縮放和拖放。android開源圖表庫MPAndroidChar的g