Android 進程常駐(3)----native保活5.0如下方案推演過程以及代碼詳述

這是一個輕量級的庫,配置幾行代碼,就能夠實如今android上實現進程常駐,也就是在系統強殺下,以及360獲取root權限下,clean master獲取root權限下都沒法殺死進程java

支持系統2.3到6.0linux

支持大部分設備,包括三星,華爲,oppo,nexus,魅族等等android

能夠簡單對開機廣播進行保護git


github地址:github

https://github.com/Marswin/MarsDaemonapi

原理分析:安全

Android 進程常駐(0)----MarsDaemon使用說明
函數

Android 進程常駐(1)----開篇
工具

Android 進程常駐(2)----細數利用android系統機制的保活手段
spa

Android 進程常駐(3)----native保活5.0如下方案推演過程以及代碼詳述

Android 進程常駐(4)----native保活5.0以上方案推演過程以及代碼詳述

Android 進程常駐(5)----開機廣播的簡單守護以及總結



正文:



今天繼續昨天,一氣呵成,爭取這個禮拜所有寫完。

上一篇文章留了一個別人的github連接,他裏面的native保活實現方案也是大多數公司採用的方案。


咱們先來說一下他的方案。

他是首先開啓一個c進程,將須要保活的service名字傳遞進去


而後定時給本身主進程發一個intent,若是主進程掛掉了,就能夠順利拉起來保證存活。



因此他只是一個沒有主動權的消息輪詢器,說是守護其實很勉強

並且,這是要創建在保證c進程不掛的基礎上,才能輪詢,可是就目前來看,只有5.0如下的非國產機纔會有這樣的漏洞。也就是說在force close的時候,系統忽略c進程的存在,5.0以上包括5.0的哪怕源生系統也會連同c進程一塊兒清理掉,國產機就更不用說了。就算是這樣,在5.0如下的非國產機上,若是安裝了獲取root權限的360\cm的話,也是能夠直接清理掉,也就是說會失效。


並且他不但不算守護,並且仍是單向的,也就是說只能a保b,b保不了a;a保b也不是在b死了馬上拉起來,要等到了時間纔會去拉。


最後,就算把剛纔說的都排除掉,在不多的一部分手機,也就是低端且沒有安裝安全軟件的手機上,他仍然沒法保證時時存活。


技術關鍵點:開啓native子進程,定時發intent
結論:單殺能夠殺死,force close 5.0以上無效,5.0如下部分手機無效,第三方軟件下無效,且沒法保證明時常駐





==========================分割線=========================






好了,那麼怎樣纔是雙向的守護進程呢?

第一,若是a守護b,則b掛掉的一瞬間,a就應該把b啓動起來
第二,a和b應該是互相守護,不管誰掛掉,對方就把他拉起來


那麼怎麼樣才能實現雙向守護呢?首先咱們想到的是fork這個函數,他會建立一個子進程,而後在父進程中調用waitpid()這個函數,這是一個阻塞函數,他會一直wait到子進程掛掉,纔會繼續向下執行,利用這個機制,咱們能夠在主進程的c層fork一個子進程,而後父進程就能夠監聽到子進程的死亡,死亡的時候再重啓子進程。


彷佛能夠用這個機制改進剛剛上面分析的那個工程,由於這樣的話:1,沒法直接殺掉子進程。二、子進程不死,他就會按時發intent給父進程。


這樣作,普通殺是沒有問題。可是force close不會按照你的要求先殺孩子,等你把孩子啓動起來,再殺父親,而後坐視子進程在那無論,三方軟件自沒必要說。那麼先殺父進程的話,子進程就沒辦法監聽到父進程的死亡嗎?


有朋友要說能夠利用linux的進程領養機制,若是父進程掛掉,那麼子進程就會被linux的init進程領養,進程所對應的父進程id也會變成1。這的確是一個很好的標示,可是要怎樣監聽這個狀態的變化呢?輪詢獲取父進程id,而後判斷是否等於1?那麼輪詢的間隔爲多少合適?1秒間隔算短算長?設爲1秒的話force close掉你的時候,根本不會等到你那每秒正時正點的輪詢點上,會被forceclose直接幹掉,我試過更短的時間,基本要小到小於10毫秒的時間間隔,纔有可能再force close的時候檢測到,併成功拉起父進程。注意個人關鍵詞,小於10毫秒!纔可能!對,一秒100次的檢查,纔有可能,只是可能!手機待機十分鐘就已經能夠開始燙手,三個小時電池發出了低電量警告。代碼:


(代碼已經不在工程裏面了,工程裏的代碼刪除了好多,作過無數試驗)

技術關鍵點:fork子進程 ,waitpid監聽子進程 ,經過linux進程領養機制監聽父進程
結論:保證單殺存活,保活效果與耗電成正比,得不償失(5.0以上無效)





==========================分割線=========================





好,咱們繼續。

waitpid是阻塞函數,因此他必定是沒有耗電問題的,即時性也沒有問題。那麼問題就集中在了子進程如何監聽到父進程的死亡上面,把那個輪詢替掉。

而後,我想到的是管道,linux中有多種ipc通訊機制,管道是最基本的一種通訊方式,且這種管道只能在父子進程間創建,因而我想是否能利用這個機制呢?在父子進程間創建管道,可是並不寫入數據,只是使用阻塞方法在另外一端去讀取管道,這樣若是對方進程掛掉,管道會被破壞,那麼另外一端的讀取方法就會執行返回,由此肯定對方掛掉而後重啓對方。

代碼:




這作樣確實解決了耗電問題,父子進程兩端的監聽都是阻塞方法,耗電基本能夠忽略不記,也基本實現雙向守護。(以上代碼不在項目工程裏)


可是問題又來了

一、用ps命令發現fork出來的進程內存佔用很大

二、fork出來的進程名字與父進程名字相同


緣由:

一、fork函數調用的時候,會複製父進程的所有內存,由於父進程必定是咱們須要保證常駐的java進程,他在初始化的時候是fork的一個zygote進程,即時在應用剛初始化的時候fork,進程裏面是有一個java虛擬機的內存在裏面的,最少一二十兆是有了。fork出來子進程多的內存最後都會算到咱們本身應用的內存中。

二、機制如此



技術關鍵點:fork子進程 ,waitpid監聽子進程, 管道pipe監聽父進程
結論:保證雙向守護,無耗電問題,fork出來的進程名字與父進程同名用戶體驗很差,並且有內存浪費(5.0以上無效)





==========================分割線=========================




咱們繼續討論內存的問題,如何不用fork也能創建管道呢。因而我想到了運行一個二進制可執行文件,這樣他是一個相對獨立的進程,可是又能夠創建父子進程之間的管道。將真正用來實現的子進程寫到一個二進制文件中(對應文件源碼/MarsDaemon/LibMarsdaemon/jni/daemon.c),這樣既解決了內存問題,又能夠本身給新的進程命名。

問題解決了嗎?沒有,直接execute一個binary文件以後

一、發現代碼再也不繼續向下執行

二、waitpid又不能用了

緣由和解決方法:

一、直接運行一個二進制文件,他會佔用原進程,因而咱們這裏僅將fork用做一個啓動binary的工具,fork終於迴歸到了Linus但願他做用的地方

二、父子進程間的管道是單向的,因而咱們能夠建兩根管道。ab兩個進程,建12兩個管道。a進程關掉管道1的寫端,堵瑟調用管道2的讀取方法;b進程關掉管道2的寫端,堵瑟調用管道1的讀取方法。這樣就能夠實現雙向監聽。任何一方監聽到對方死掉就做出相應的動做,啓動對方。至此,徹底摒棄開始的fork方案。


代碼:

此爲最終方案,代碼見下


技術關鍵點:雙管道互相監聽
結論:保證雙向守護,無耗電問題,無內存問題,進程名自定義(5.0以上無效)




==========================分割線=========================


好了,這就是我5.0如下的最終解決方案

下面講一下代碼

二進制文件存放在assets下面,程序第一次啓動的時候會將他拷貝到手機項目/data/data/...下,而後

源碼/MarsDaemon/LibMarsdaemon/src/main/java/com/marswin89/marsdaemon/strategy/DaemonStrategyUnder21.java


load對應c庫,執行代碼

源碼/MarsDaemon/LibMarsdaemon/jni/daemon_api20.c



一、將對應的packagename,servicename以及二進制可執行文件的路徑傳進來

二、清理殭屍進程,就像最開始講的,低端手機會忽略c進程,若是咱們恰巧運行在低端手機上,那麼c進程得不到釋放會愈來愈多,咱們稱他爲殭屍進程,須要清理一下

三、創建兩條管道

四、執行二進制文件,將上面的參數傳遞進去

五、而後關掉本身管道1的寫端和管道2的讀端,而後阻塞讀取管道1,若是讀取到,則表明子進程掛掉


再來看二進制可執行文件的代碼

源碼/MarsDaemon/LibMarsdaemon/jni/daemon.c




二進制文件在程序啓動起來的時候,將參數解析出來

關掉管道1的讀端和管道2的寫端,而後調用管道2的阻塞讀取方法,若是執行過去,說明父進程死掉

這裏用fork是爲了讓他的父進程id好看一些,別無他意

監聽到以後的策略看下面。


==========================分割線=========================



而後說一下監聽到對方進程死後的策略

你會說誰監聽到對方死了,就直接拉起來就行了呀。

問題:

一、從新拉起來要從新創建雙管道,子進程掛掉,父進程把他重啓起來創建雙管道還好說,若是父進程掛掉,子進程把父進程啓動起來,他們之間就沒法創建鏈接,並且若是中間出了差錯,同步起來很費勁,因而我選擇,不管誰監聽到誰死了,都重啓對方,而後自殺,從新初始化!

二、若是執行force close, 系統先殺父進程,子進程監聽到以後拉起父進程而後自殺,可是系統殺你兩個進程的間隔時間很是很是短,父進程剛起來還沒來得及初始化,系統趕過來殺父進程。有的手機強殺以後很短一段時間沒法拉起父進程。因而我選擇用第三個進程。

第三個進程和以前的父子進程都沒有任何關係,他的做用只是用作拉起常駐進程。父子進程不管誰監聽到誰死,都拉起第三個進程,第三個進程負責拉起常駐進程,而後自殺。(用戶其實是看不到他的存在的,由於他可能只存活不到一秒就自殺了)

代碼

/MarsDaemon/LibMarsdaemon/src/main/java/com/marswin89/marsdaemon/strategy/DaemonStrategyUnder21.java


在常駐進程初始化的時候,初始化一個alarm,保存在內存中,以便之後使用


監聽到子進程死了時候使用鬧鐘拉起第三個進程,二進制文件監聽到父進程死掉,直接用c代碼發intent,見上面c代碼


第三進程啓動起來,就是負責把常駐進程拉起來,而後自殺掉。






==========================分割線=========================





好了,這只是5.0如下的策略,5.0以上,以及6.0都很差用

下一篇咱們開始分析5.0+的策略

相關文章
相關標籤/搜索