android系統進程
❶ Android 進程管理篇(四)-cpu限制
梳理Process進程相關知識點,再繼續補充點內容。
linux系統中對進程的管理無非是從調度策略、優先順序以及CPU限制三個角度進行配置與管理,那麼Android中主要是通過AMS來管理應用程序進程的,是不是也是從這三個方面進行管理的呢?答案是肯定的,那麼本篇文章先來看看cpuset負載均衡在AMS中是如何應用的。
cpuset是Linux cgroup子系統,它為cgroup任務分配單獨的CPU和內存。單獨分配CPU即表明進程可調度cpu范圍。cpu按不同的晶元,大小核數目和頻率都有差別,大核頻率高處理速度相對比小核快,而Android系統實際上還是響應優先於吞吐的交互型系統,因此Android AMS對進程管理於不同優先順序的進程在調度cpu限制上會做有一些策略,以保證更好的交互響應。
還是回到AMS中與adj相關的有三個方法,這三個方法值得看一萬遍,每一遍都會有新收獲:
聚焦到computeOomAdjLocked方法,該方法主要是根據進程的四大組件狀態決定當前進程的adj優先順序。
以TOP_APP為例,這里ProcessRecord 的curSchedGroup屬性對應的是cup調度組,而在後續applyOomAdjLocked中會執行Process的setProcessGroup方法。
調用Process的setProcessGroup方法
setProcessGroup是個native方法,並且這里分了若干類型的group,這里看top app優先順序是最高的。接著jni到native
這里直接調用sched_policy.cpp的set_cpuset_policy,並傳入對應的pid和SchedPolicy
這里主要就是通過policy對應具體的fd句柄,然後通過add_tid_to_cgroup()寫cpuset對應節點。這里要注意,如果cpusets_enabled為false的話,會走set_sched_policy,這部分下篇會講到。
看看對應的fd是什麼:
那我們來看看對應節點是什麼內容:
然後看看對應的cpuset配置:
顯然,top app 滿核隨便跑,foreground跑在除了3這個核以外的所有核上, 而background只能跑在小核上。
不同晶元平台配置會有差別。
❷ Android 進程管理篇(五)-調度策略與優先順序
接上篇cpuset,這篇來看看進程優先順序與調度策略管理。
Linux中,優先順序號一共有0-139,其中0-99的是RT(實時)進程,100-139的是非實時進程。
數字越低優先順序越高。
SCHED_IDLE idle狀態低優先順序進程調度
先看Process中調度策略的劃分,與上面介紹的一樣。
首先在AMS中封裝了FIFO和NORMAL的兩個策略,NORMAL好說,看看FIFO在哪用到
這里Process.setThreadScheler並沒有太多的應用,我們直接來看優先順序設置吧。else中將top app的UI線程與render線程都設置為TOP_APP_PRIORITY_BOOST優先順序,nice值為-10,非常高。
這里主要調用androidSetThreadPriority方法
這里通過set_sched_policy來調整調度策略,並通過setpriority設置進程優先順序。這里不特意區分進程與線程了,反正在linux中都是進程。
這里與前面的cpuset非常相似,依然是寫節點,節點前面也提了就是:
那麼這里又引入了一個schedtune子系統,簡單介紹下:
schedtune是ARM/Linaro為了EAS新增的一個子系統,主要用來控制進程調度選擇CPU以及boost觸發。通過權重來分配CPU負載能力來實現快速運行。高權重意味著會享受到更好的cpu負載來處理對應的任務,換句話說你能享受相對更好的cpu運行性能。
簡單梳理下schedtune和不同類型SchedPolicy之間的對應關系:
看下具體文件夾內容:
系統配置:
這里/dev/stune相關配置只做了這么一個
❸ Android 守護進程的實現方式
在我們進行應用開發時,會遇到上級的各種需求,其中有一條 剛需: 後台保活 ,更有甚者:
我要我們的應用永遠活在用戶的手機後台不被殺死 —— 這都 TM 的扯淡
除了系統級別的應用能持續運行,所有三方程序都有被殺死的那一天!當然 QQ/微信/陌陌 等會好一些,因為他們已經深入設備的 心 ;
我們能做的只是通過各種手段盡量讓我們的程序在後台運行的時間長一些,或者在被幹掉的時候,能夠重新站起來,而且這個也不是每次都有效的,也是不能在所有的設備的上都有效的;要做到後台進程保活,我們需要做到兩方便:
要實現實現上邊所說,通過下邊幾點來實現,首先我們需要了解下進程的優先順序劃分:
Process Importance 記錄在 ActivityManager.java 類中:
了解進程優先順序之後,我們還需要知道一個進程回收機制的東西;這里參考 AngelDevil 在博客園上的一篇文章:
Android 的 Low Memory Killer 基於 Linux 的 OOM 機制,在 Linux 中,內存是以頁面為單位分配的,當申請頁面分配時如果內存不足會通過以下流程選擇bad進程來殺掉從而釋放內存:
在 Low Memory Killer 中通過進程的 oom_adj 與佔用內存的大小決定要殺死的進程, oom_adj 越小越不容易被殺死;
Low Memory Killer Driver 在用戶空間指定了一組內存臨界值及與之一一對應的一組 oom_adj 值,當系統剩餘內存位於內存臨界值中的一個范圍內時,如果一個進程的 oom_adj 值大於或等於這個臨界值對應的 oom_adj 值就會被殺掉。
下邊是表示 Process State (即老版本里的 OOM_ADJ )數值對照表,數值越大,重要性越低,在新版SDK中已經在 android 層去除了小於0的進程狀態
Process State (即老版本的 OOM_ADJ )與 Process Importance 對應關系,這個方法也是在 ActivityManager.java 類中,有了這個關系,就知道可以知道我們的應用處於哪個級別,對於我們後邊優化有個很好地參考
一般情況下,設備端進程被幹掉有一下幾種情況
由以上分析,我們可以可以總結出,如果想提高我們應用後台運行時間,就需要提高當前應用進程優先順序,來減少被殺死的概率
分析了那麼多,現在對Android自身後台進程管理,以及進程的回收也有了一個大致的了解,後邊我們要做的就是想盡一切辦法去提高應用進程優先順序,降低進程被殺的概率;或者是在被殺死後能夠重新啟動後台守護進程
第一種方式就是利用系統漏洞,使用 startForeground() 將當前進程偽裝成前台進程,將進程優先順序提高到最高(這里所說的最高是服務所能達到的最高,即1);
這種方式在 7.x 之前都是很好用的,QQ、微信、IReader、Keep 等好多應用都是用的這種方式實現;因為在7.x 以後的設備上,這種偽裝前台進程的方式也會顯示出來通知欄提醒,這個是取消不掉的,雖然 Google 現在還沒有對這種方式加以限制,不過這個已經能夠被用戶感知到了,這種方式估計也用不了多久了
下邊看下實現方式,這邊這個 VMDaemonService 就是一個守護進程服務,其中在服務的 onStartCommand() 方法中調用 startForeground() 將服務進程設置為前台進程,當運行在 API18 以下的設備是可以直接設置,API18 以上需要實現一個內部的 Service ,這個內部類實現和外部類同樣的操作,然後結束自己;當這個服務啟動後就會創建一個定時器去發送廣播,當我們的核心服務被幹掉後,就由另外的廣播接收器去接收我們守護進程發出的廣播,然後喚醒我們的核心服務;
當我們啟動這個守護進程的時候,就可以使用以下 adb 命令查看當前程序的進程情況(需要 adb shell 進去設備),
為了等下區分進程優先順序,我啟動了一個普通的後台進程,兩外兩個一個是我們啟動的守護進程,一個是當前程序的核心進程,可以看到除了後台進程外,另外兩個進程都帶有 isForeground=true 的屬性:
然後我們可以用下邊的命令查看 ProcessID
有了 ProcessID 之後,我們可以根據這個 ProcessID 獲取到當前進程的優先順序狀態 Process State ,對應 Linux 層的 oom_adj
可以看到當前核心進程的級別為 0 ,因為這個表示當前程序運行在前台 UI 界面,守護進程級別為 1 ,因為我們利用漏洞設置成了前台進程,雖然不可見,但是他的級別也是比較高的,僅次於前台 UI 進程,然後普通後台進程級別為 4 ;當我們退到後台時,可以看到核心進程的級別變為 1 了,這就是因為我們利用 startForeground() 將進程設置成前台進程的原因,這樣就降低了進程被系統回收的概率了;
可以看到這種方式確實能夠提高進程優先順序,但是在一些國產的設備上還是會被殺死的,比我我測試的時候小米點擊清空最近運行的應用進程就別幹掉了;當把應用加入到設備白名單里就不會被殺死了,微信就是這樣,人家直接裝上之後就已經在白名單里了,我們要做的就是在用戶使用中引導他們將我們的程序設置進白名單,將守護進程和白名單結合起來,這樣才能保證我們的應用持續或者
Android系統在5.x以上版本提供了一個 JobSchele 介面,系統會根據自己實現定時去調用改介面傳遞的進程去實現一些操作,而且這個介面在被強制停止後依然能夠正常的啟動;不過在一些國產設備上可能無效,比如小米;
下邊是 JobServcie 的實現:
我們要做的就是在需要的時候調用 JobSchele 的 schele 來啟動任務;剩下的就不需要關心了, JobSchele 會幫我們做好,下邊就是我這邊實現的啟動任務的方法:
在實現 Service 類時,將 onStartCommand() 返回值設置為 START_STICKY ,利用系統機制在 Service 掛掉後自動拉活;不過這種方式只適合比較原生一些的系統,像小米,華為等這些定製化比較高的第三方廠商,他們都已經把這些給限制掉了;
這種方式在以下兩種情況無效:
事事沒有絕對,萬物總有一些漏洞,就算上邊的那些方式不可用了,後邊肯定還會出現其他的方式;我們不能保證我們的應用不死,但我們可以提高存活率;
其實最好的方式還是把程序做好,讓程序本身深入人心,別人喜歡你了,就算你被幹掉了,他們也會主動的把你拉起來,然後把你加入他們的白名單,然後我們的目的就實現了不是 😁 ~
❹ Android 如何進行進程保活
每一個 Android 應用啟動後至少對應一個進程,有的是多個進程,而且主流應用中多個
進程的應用比例較大
對於任何一個進程,我們都可以通過 adb shell ps|grep <package_name>的方式來查看
它的基本信息
Android 中的進程跟封建社會一樣,分了三流九等,Android 系統把進程的劃為了如下
幾種(重要性從高到低),網上多位大神都詳細總結過(備註:嚴格來說是劃分了 6 種)。
場景:
1.某個進程持有一個正在與用戶交互的 Activity 並且該 Activity 正處於 resume 的
狀態。
2.某個進程持有一個 Service,並且該 Service 與用戶正在交互的 Activity 綁定。
3.某個進程持有一個 Service,並且該 Service 調用 startForeground()方法使之位於前台運行。
4.某個進程持有一個 Service,並且該 Service 正在執行它的某個生命周期回調方法,比如 onCreate()、 onStart()或 onDestroy()。
5.某個進程持有一個 BroadcastReceiver,並且該 BroadcastReceiver 正在執行其onReceive()方法。用戶正在使用的程序,一般系統是不會殺死前台進程的,除非用戶強制停止應用或者系統內存不足等極端情況會殺死。
場景:
1.擁有不在前台、但仍對用戶可見的 Activity(已調用 onPause())。
2.擁有綁定到可見(或前台)Activity 的 Service
用戶正在使用,看得到,但是摸不著,沒有覆蓋到整個屏幕,只有屏幕的一部分可見進程
不包含任何前台組件,一般系統也是不會殺死可見進程的,除非要在資源吃緊的情況下,
要保持某個或多個前台進程存活
場景
1.某個進程中運行著一個 Service 且該 Service 是通過 startService()啟動的,與用戶看見的界面沒有直接關聯。
在內存不足以維持所有前台進程和可見進程同時運行的情況下,服務進程會被殺死
場景:
在用戶按了"back"或者"home"後,程序本身看不到了,但是其實還在運行的程序,
比如 Activity 調用了 onPause 方法系統可能隨時終止它們,回收內存
場景:
某個進程不包含任何活躍的組件時該進程就會被置為空進程,完全沒用,殺了它只有好處沒壞處,第一個干它!
上面是進程的分類,進程是怎麼被殺的呢?系統出於體驗和性能上的考慮,app 在退到
後台時系統並不會真正的 kill 掉這個進程,而是將其緩存起來。打開的應用越多,後台緩存的進程也越多。在系統內存不足的情況下,系統開始依據自身的一套進程回收機制
來判斷要 kill 掉哪些進程,以騰出內存來供給需要的 app, 這套殺進程回收內存的機制
就叫 Low Memory Killer。那這個不足怎麼來規定呢,那就是內存閾值,我們可以使用
cat /sys/mole/lowmemorykiller/parameters/minfree 來查看某個手機的內存閾值。
其實系統在進程回收跟內存回收類似也是有一套嚴格的策略,可以
自己去了解,大概是這個樣子的,oom_adj 越大,佔用物理內存越多會被最先 kill 掉,OK,那麼現在對於進程如何保活這個問題就轉化成,如何降低 oom_adj 的值,以及如
何使得我們應用占的內存最少。
據說這個是手 Q 的進程保活方案,基本思想,系統一般是不會殺死前台進程的。所以要
使得進程常駐,我們只需要在鎖屏的時候在本進程開啟一個 Activity,為了欺騙用戶,
讓這個 Activity 的大小是 1 像素,並且透明無切換動畫,在開屏幕的時候,把這個 Activity
關閉掉,所以這個就需要監聽系統鎖屏廣播,我試過了,的確好使,如下。
如果直接啟動一個 Activity,當我們按下 back 鍵返回桌面的時候,oom_adj 的值是 8,
上面已經提到過,這個進程在資源不夠的情況下是容易被回收的。現在造一個一個像素
的 Activity。
為了做的更隱藏,最好設置一下這個 Activity 的主題,當然也無所謂了
在屏幕關閉的時候把 LiveActivity 啟動起來,在開屏的時候把 LiveActivity 關閉掉,所以
要監聽系統鎖屏廣播,以介面的形式通知 MainActivity 啟動或者關閉 LiveActivity。
現在 MainActivity 改成如下
按下 back 之後,進行鎖屏,現在測試一下 oom_adj 的值
果然將進程的優先順序提高了。
但是還有一個問題,內存也是一個考慮的因素,內存越多會被最先 kill 掉,所以把上面
的業務邏輯放到 Service 中,而 Service 是在另外一個 進程中,在 MainActivity 開啟這
個服務就行了,這樣這個進程就更加的輕量,
OK,通過上面的操作,我們的應用就始終和前台進程是一樣的優先順序了,為了省電,
系統檢測到鎖屏事件後一段時間內會殺死後台進程,如果採取這種方案,就可以避免了
這個問題。但是還是有被殺掉的可能,所以我們還需要做雙進程守護,關於雙進程守護,
比較適合的就是 aidl 的那種方式,但是這個不是完全的靠譜,原理是 A 進程死的時候,
B 還在活著,B 可以將 A 進程拉起來,反之,B 進程死的時候,A 還活著,A 可以將 B
拉起來。所以雙進程守護的前提是,系統殺進程只能一個個的去殺,如果一次性殺兩個,
這種方法也是不 OK 的。
事實上
那麼我們先來看看 Android5.0 以下的源碼,ActivityManagerService 是如何關閉在應用
退出後清理內存的
在應用退出後,ActivityManagerService 不僅把主進程給殺死,另外把主進程所屬的進
程組一並殺死,這樣一來,由於子進程和主進程在同一進程組,子進程在做的事情,也
就停止了。所以在 Android5.0 以後的手機應用在進程被殺死後,要採用其他方案。
這種大部分人都了解,據說這個微信也用過的進程保活方案,移步微信 Android 客戶端
後台保活經驗分享,這方案實際利用了 Android 前台 service 的漏洞。
原理如下
對於 API level < 18 :調用 startForeground(ID, new Notification()),發送空的
Notification ,圖標則不會顯示。
對於 API level >= 18:在需要提優先順序的 service A 啟動一個 InnerService,兩個服務
同時 startForeground,且綁定同樣的 ID。Stop 掉 InnerService ,這樣通知欄圖標即
被移除。
public class KeepLiveService extends Service{
public static final int NOTIFICATION_ID=0x11;
public KeepLiveService() {
}
@Override
public IBinder onBind(Intent intent){
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate(); //API 18 以下,直 接發 送 Notification 並 將 其 置 為 前 台
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.JELLY_BEAN_MR2){
startForeground(NOTIFICATION_ID,new Notification());
} else { //API 18 以上,發送 Notification 並將其置為前台後,啟動 InnerService
Notification.Builder builder=new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
startForeground(NOTIFICATION_ID, builder.build());
startService(new Intent(this, InnerService.class));
}
}
public class InnerService extends Service{
@Override public IBinder onBind(Intent intent) {
return null;
}
@Override public void onCreate() {
super.onCreate(); //發送與 KeepLiveService中 ID 相同的 Notification,然後將其取消並取消自己的前台顯示
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);startForeground(NOTIFICATION_ID,
builder.build());
new Handler().postDelayed(new Runnable() {
@Override public void run() {
stopForeground(true);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(NOTIFICATION_ID);
stopSelf();
}
},
100);
}
}
}
在沒有採取前台服務之前,啟動應用,oom_adj 值是 0,按下返回鍵之後,變成 9(不
同 ROM 可能不一樣)
相互喚醒的意思就是,假如你手機里裝了支付寶、淘寶、天貓、UC 等阿里系的 app,
那麼你打開任意一個阿里系的 app 後,有可能就順便把其他阿里系的 app 給喚醒了。
這個完全有可能的。此外,開機,網路切換、拍照、拍視頻時候,利用系統產生的廣播
也能喚醒 app,不過 Android N 已經將這三種廣播取消了。
如果應用想保活,要是 QQ,微信願意救你也行,有多少手機上沒有 QQ,微信呢?或
者像友盟,信鴿這種推送 SDK,也存在喚醒 app 的功能。
拉活方法
JobSheler是作為進程死後復活的一種手段,
native進程方式最大缺點是費電,Native
進程費電的原因是感知主進程是否存活有兩種實現方式,在 Native 進程中通過死循環
或定時器,輪訓判斷主進程是否存活,當主進程不存活時進行拉活。其次 5.0 以上系統
不支持。 但是 JobSheler 可以替代在 Android5.0 以上 native 進程方式,這種方式即
使用戶強制關閉,也能被拉起來,親測可行。
JobSheler@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService {
@Override
public void onCreate() {
super.onCreate();
startJobSheler();
}
public void startJobSheler() {
try {
JobInfo.Builder builder = new JobInfo.Builder(1,new ComponentName(getPackageName(), MyJobService.class.getName()));
builder.setPeriodic(5); builder.setPersisted(true); JobScheler jobScheler =(JobScheler)
this.getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheler.schele(builder.build());
}
catch
(Exception ex)
{ ex.printStackTrace(); } }
@Override
public boolean onStartJob(JobParameters jobParameters) {
return false;
} @Override public boolean onStopJob(JobParameters jobParameters) {
return false;
}
}
這個是系統自帶的,onStartCommand 方法必須具有一個整形的返回值,這個整形的返
回值用來告訴系統在服務啟動完畢後,如果被 Kill,系統將如何操作,這種方案雖然可
以,但是在某些情況 or 某些定製 ROM 上可能失效,我認為可以多做一種保保守方案。
1.START_STICKY
如果系統在 onStartCommand 返回後被銷毀,系統將會重新創建服務並依次調用
onCreate 和 onStartCommand(注意:根據測試 Android2.3.3 以下版本只會調用
onCreate 根本不會調用 onStartCommand,Android4.0 可以辦到),這種相當於服務
又重新啟動恢復到之前的狀態了)。
2.START_NOT_STICKY
如果系統在 onStartCommand 返回後被銷毀,如果返回該值,則在執行完
onStartCommand 方法後如果 Service 被殺掉系統將不會重啟該服務
3.START_REDELIVER_INTENT
START_STICKY 的兼容版本,不同的是其不保證服務被殺後一定能重啟。
4.相比與粘性服務與系統服務捆綁更厲害一點,這個來自愛哥的研究,這里說的系統服務
很好理解,比如 NotificationListenerService,NotificationListenerService 就是一個監聽
通知的服務,只要手機收到了通知,NotificationListenerService 都能監聽到,即時用戶
把進程殺死,也能重啟,所以說要是把這個服務放到我們的進程之中,那麼就可以呵呵
了
所以你的應用要是有消息推送的話,那麼可以用這種方式去欺騙用戶。
❺ 033 Android多進程-共享內存
要使用一塊共享內存
還是先看共享內存的使用方法,我主要介紹兩個函數:
通過 shmget() 函數申請共享內存,它的入參如下
通過 shmat() 函數將我們申請到的共享內存映射到自己的用戶空間,映射成功會返回地址,有了這個地址,我們就可以隨意的讀寫數據了,我們繼續看一下這個函數的入參
共享內存的原理是在內存中單獨開辟的一段內存空間,這段內存空間其實就是一個tempfs(臨時虛擬文件),tempfs是VFS的一種文件系統,掛載在/dev/shm上,前面提到的管道pipefs也是VFS的一種文件系統。
由於共享的內存空間對使用和接收進程來講,完全無感知,就像是在自己的內存上讀寫數據一樣,所以也是 效率最高 的一種IPC方式。
上面提到的IPC的方式都是 在內核空擾灶間中開辟內存來存儲數據 ,寫數據時,需要將數據從用戶空間拷貝到內核空間,讀數據時,需要從內核空間拷貝到自己的用戶空間,
共享內存就只需要一次拷貝 ,而且共享內存不是在內核開辟空間,所以可以 傳輸的數據量大 。
但是 共享內存最大的缺點就是沒有並發的控制,我們一般通過信號量配合共享內存使用,進行同步和並發的控制 。
共享內存在Android系統中主要的使用場景是 用來傳輸大數據 ,並且 Android並沒有直接使用Linux原生的共享內存方式,而是設計了Ashmem匿名共享內存 。
之前說到有名管道和匿名管道的區別在於有名管道可以在vfs目錄樹中查看到這個管道的文件,但是匿名管道不行, 所以匿名共享內存同樣也是無法在vfs目錄中查看到 的, Android之所以要設計匿名共享內存 ,我覺得主要是為了安全性的考慮吧。
我們來看看共享內存的一個使用場景,在Android中,如果蠢卜我們想要將當前的界面顯示出來,需要將當前界面的圖元數據傳遞Surfaceflinger去做圖層混合,圖層混合之後的數據會直接送入幀緩存,送入幀緩存後,顯卡就會直接取出幀緩存里的圖元數據顯示了。
那麼我們如何將應用的Activity的圖元數據傳遞給SurfaceFlinger呢?想要將圖像數據這樣比較大的數據跨進程傳輸,靠binder是不行的,所以這兒便用到匿名共享內存。
從谷歌官方提供的架構圖可以看到,圖元數據是通過BufferQueue傳遞到SurfaceFlinger去的,當我們想要繪制圖像的時候, 需要從BufferQueue中申請一個Buffer,Buffer會調用Gralloc模塊來分配共享內存 當作圖元緩沖區存放我們的圖元數據。
可以看到Android的匿名共享內存是通過 ashmem_create_region() 函數來申請共享內存的,它會在/dev/ashmem下創建一個虛擬文件,Linux原生共享內存是通過shmget()函數,並會在/dev/shm下創建虛擬文件。
匿名共享內存是通過 mmap() 函數將申請到的內存映射到自己的進程空間,而Linux是通過*shmat()函數。
雖然函數不一樣,但是帶李穗Android的匿名共享內存和Linux的共享內存在本質上是大同小異的。
。