讓app 的service常駐其實是很流氓的做法,但是需求擺在那里。。。 但是要清除一點:想百分百保活service在當前是無法做到的,只能降低service被殺死的概率,曾經看了多少篇網上大神的牛逼博客,從各個層面分析如何讓service不被殺死或者被殺死后重啟,特別是從Android系統底層分析,覺得特別牛逼,但是嘗試之后沒有啥用,以前做service?;羁偸且粋€勁的鉆研android系統層的方式來?;顂ervice,實際上后來發現這是有點不對了,因為會涉及到一些android系統的兼容問題,有時候在這款手機上運行良好,但是到另一款手機就不能正常運行了,后來就去尋找從應用層來降低service被殺死的概率,個人覺得從應用層降低service被殺死的概率會比較穩定
論Service為什么會被殺死
android對于進程的殺死是有優先級的:
前臺進程,也就是前臺activity
可見進程,就是一個透明的activity下面覆蓋著的那個activity就屬于可見的,但是這個透明的activity才屬于前臺
服務進程,也就是service
后臺進程,一般情況下app按下home鍵后就變成后臺進城了
空進程 android系統給這些進程設置了oom_adj值,oom_adj值越小表示進程的優先級越高:
名稱
oom_adj
解釋
FOREGROUD_APP
0
前臺進程
VISIBLE_APP
1
可見進程
SECONDARY_SERVER
2
服務進程
HIDDEN_APP
7
后臺進程
EMPTY_APP
15
空進程
如上的oom_adj值可能會根據不同手機系統有所不同,但是只要是oom_adj越小,進程優先級越高,進程越不容易被殺死,當android系統的內存緊張的時候就會根據oom_adj來回收進程,因此app在按下home鍵后被回收是正常的,因為app按下home鍵盤后就編程后臺進程了,而后臺進程的優先級比服務進程還要低,我們項目的app在按下home鍵盤被系統回收的主要原因有以下幾種:
我們的APP進程啟動后占用內存太大了,基本是打開APP就,然后到應用程序管理里面一看,有60到70MB,現在已經飆升到80MB
有些android深度定制系統的殺死策略太嚴格,進程清理的太徹底,導致我們APP的service很容易被殺殺殺,基本是手機一鎖屏,過幾分鐘之后立刻就被殺死了
我們app的進程優先級太低,按下home鍵后沒做任何處理,占的內存又很高,所以被回收的概率也很高 綜上app進程被殺死的根本原因:系統內存出現不足時,會被Android的low memory killer殺掉
針對以上問題,
幾種有效的service?;罘桨?/p>
最近一直在做公司的推送android端sdk,我們是把tcp連接寫在了java層,然后開一個service來維護這個tcp長連接,那么問題來了,要讓提升消息的可達率就必須保證tcp長連接一直存在,這里涉及到兩點?;?,一種是service?;?,一種是tcp長連接保活。
service保活
比較古老的做法有兩種:
一種是用c在底層fork一個進程出來定時掃描,采用am命令啟動service,這種方式挺耗電的,而且有android系統的兼容問題,有些android系統是改過的,所以會導致c程序運行失效,例如我遇到過的華為手機很多機型就不兼容,而且還有很嚴重的耗電問題
一種是fork一個進程出來,然后把fork出來的進程和app主進程建立一條本地長連接來監聽兩個進程之間的狀態,一旦其中一個進程斷掉之后另一個進程就能檢測到tcp連接斷掉了,然后采用am命令拉起service,這種可能沒有第一種耗電,但是依然存在這android系統兼容性問題
上述做法在android系統是5.0以下的手機運行良好,因為就算是手機一鍵清理的時候也只是清理app主進程,而不會清理fork出來的進程,所以service會被成功拉起;而在android5.0以上的手機中,一旦系統一鍵清理,或者系統后臺自動清理,那么會殺死跟app進程有關的進程組,也包括fork出來的進程,所以android5.0以上的手機這個fork進程就貌似是一個雞肋,增加了耗電,增加了內存消耗,甚至增加了service會被系統清理的概率,而且舊的android系統在系統清理的時候據說是只會清理java進程,不會清理c進程,但是新的android系統全名提升了安全性,會徹底清理app相關的所有進程
現在守護進程已經漸漸無效了,所以一些專業做推送服務的公司已經把守護進程這個?;钍侄卧缭绲娜サ袅?,人家用的是更加高端有效的做法:TCP長連接多路復用,后面在介紹
在應用層,我嘗試過的,了解到的有效的service?;钍侄斡幸韵聨追N
在onDestroy方法里面重啟service,或者發個廣播出來觸發啟動service,這個要求service在被殺死的時候如果有調用onDestroy()方法,那么service就能被重啟,在魅族5.0系統上,我進入到應用管理,找到正在運行的應用,點擊停止,這時候service是會跑onDestroy()方法的,我親自測試過
onStartCommand中的flag設置成START_STICKY,或者直接return START_STICKY,這個有些舊版的手機在手機一鍵清理后service被殺死后會重啟起來,但是在一些國產定制機,例如小米,華為,魅族等手機就無效了,在android源碼中注釋如下:
/** * Constant to return from {@link #onStartCommand}: if this service's * process is killed while it is started
(after returning from * {@link #onStartCommand}), then leave it in the started state but * don't retain this
delivered intent. Later the system will try to * re-create the service. Because it is in the started state, it will *
guarantee to call {@link #onStartCommand} after creating the new * service instance; if there are not any
pending start commands to be * delivered to the service, it will be called with a null intent * object, so you must
take care to check for this. * * <p>This mode makes sense for things that will be explicitly started * and
stopped to run for arbitrary periods of time, such as a service * performing background music playback. */
public static final int START_STICKY = 1;
說的是如果service在已經被啟動的時候被停止,那么系統將保持service的啟動狀態,但是不會把原先的intent傳遞進來,然后系統會嘗試這重新創建這個service,因為此時service是在啟動狀態,所以它一定會在被創建后調用onStartCommand()方法,但是由于intent沒有被保存下載,所以里面的參數intent有可能為null,所以要做下判斷,這個模式可以保證service被殺死后重新啟動起來,而且避免在任意時間被停止,就如后臺音樂播放;但是有一點要生命:這個僅僅是針對android原生系統,對于定制的android系統如果有對這方面做過優化,那這個也將無效,而且如果是應用進程被直接殺死,這個方法也無效
把進程做拆分,分成app進程和push進程,其實就是把push進程做到最小,push進程盡量不做業務邏輯處理,只做數據轉發和接收,這樣push進程占用的內存就變小了,被回收的幾率自然也下降
提升app進程,push進程的優先級;一般app進程里面會有一個常駐的service,push進程也會有一個維護長連接的service,把這兩個service都設置成前臺進程,采用startForeground(id, new Notification()),但是在sdk version > 18的時候通知欄會默認顯示一個應用通知,這個是非常不友好的,有些用戶就會反饋說通知欄有哥這個,表示不爽等等。。。通過一些渠道了解到,利用android系統的一個bug可以解決上述問題:在Service里面建一個InnerService,這個InnerService必須是static類型的,否則無法啟動這個內部service,然后啟動InnerService,同時把Service和InnerService都設置成前臺service,并綁定同一個id,然后再關閉掉InnerService,這時候通知欄的顯示就沒了,記住只是stop InnerService而不是stopForeground(),此時service依然是前臺進程,親測有效,下面來展示以下測試的過程和結果: 外層service的部分代碼
public static class InnerService extends Service {
@Override
public void onCreate() {
super.onCreate();
startForeground(notificationId, new Notification());
SyncLogUtil.i("inner service onCreate...");
stopSelf();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
SyncLogUtil.i("inner service onStartCommand...");
return START_NOT_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
SyncLogUtil.i("inner service destroy!"); }
}
@Override
public void onCreate() {
super.onCreate();
SyncLogUtil.init(getApplicationContext());
startForeground(notificationId, new Notification()); startInnerService();
}
private void startInnerService() {
Intent intent = new Intent(this, InnerService.class);
startService(intent);
}
進入adb命令行: 未設置前臺service的app進程:
shell@cancro:/ $ ps | grep com.xtc.watchps | grep com.xtc.watchu0_a517 11829 245 1023556 131048 ffffffff 00000000 S com.xtc.watchu0_a517 11919 1 948 108 ffffffff 00000000 S /data/data/com.xtc.watch/app_bin/daemonu0_a517 12092 245 890632 46984 ffffffff 00000000 S com.xtc.watch:push
app界面在前臺
shell@cancro:/ $ cat proc/11829/oom_adjcat proc/11829/oom_adj0
app界面在后臺(按下home鍵)
shell@cancro:/ $ cat proc/11829/oom_adjcat proc/11829/oom_adj7
app界面在后臺(按返回鍵)
shell@cancro:/ $ cat proc/11829/oom_adjcat proc/11829/oom_adj7
設置前臺service的app進程:
shell@cancro:/ $ ps | grep com.xtc.watchps | grep com.xtc.watchu0_a518 27381 245 1015104 130352 ffffffff 00000000 R com.xtc.watchu0_a518 27481 245 890620 46544 ffffffff 00000000 S com.xtc.watch:push
app界面在前臺
shell@cancro:/ $ cat proc/27381/oom_adjcat proc/27381/oom_adj0
app界面在后臺(按下home鍵)
shell@cancro:/ $ cat proc/27381/oom_adjcat proc/27381/oom_adj2
app界面在后臺(按返回鍵)
shell@cancro:/ $ cat proc/27381/oom_adjcat proc/27381/oom_adj2
通過以上兩種對比可知,app進程設置了前臺service的app進程在退到后臺后依然保持oom_adj = 2的進程優先級,相當于app退到后臺后仍然是可視進程,而沒有設置前臺service的app進程的oom_adj = 7編程了后臺進程
設置前臺service后app的push進程
shell@cancro:/ $ ps | grep sync.pushps | grep sync.pushu0_a517 8738 245 894808 46452 ffffffff 00000000 S sync.push
app界面在前臺
shell@cancro:/ $ cat proc/8738/oom_adjcat proc/8738/oom_adj1
app界面在后臺(按下home鍵)
shell@cancro:/ $ cat proc/8738/oom_adjcat proc/8738/oom_adj2
app界面在后臺(按返回鍵)
shell@cancro:/ $ cat proc/8738/oom_adjcat proc/8738/oom_adj2
對比微信:
微信的pushshell@cancro:/ $ ps | grep com.tencent.mmps | grep com.tencent.mmu0_a337 12143 245 915440 61144 ffffffff 00000000 S com.tencent.mm:pushu0_a337 17492 245 1191424 96248 ffffffff 00000000 S com.tencent.mmapp
界面在前臺
shell@cancro:/ $ cat proc/12143/oom_adjcat proc/12143/oom_adj1app
界面在后臺(按下home鍵)
shell@cancro:/ $ cat proc/12143/oom_adjcat proc/12143/oom_adj2app
界面在后臺(按返回鍵)
shell@cancro:/ $ cat proc/12143/oom_adjcat proc/12143/oom_adj2微信appshell@cancro:/ $ ps | grep com.tencent.mmps | grep com.tencent.mmu0_a337 12143 245 915440 61144 ffffffff 00000000 S com.tencent.mm:pushu0_a337 17492 245 1191424 96436 ffffffff 00000000 S com.tencent.mmapp
界面在前臺
shell@cancro:/ $ cat proc/17492/oom_adjcat proc/17492/oom_adj0app
界面在后臺(按下home鍵)
shell@cancro:/ $ cat proc/17492/oom_adjcat proc/17492/oom_adj2app
界面在后臺(按返回鍵)
shell@cancro:/ $ cat proc/17492/oom_adjcat proc/17492/oom_adj2
可以看到我們app的進程優先級已經和微信一樣了,進程優先級提升了,其實已經在很大程度上降低了app進程或者push進程被殺死的概率了
守護Service,開一個Service單獨運行在獨立的進程中,在守護service里面定時去start app進程的service,而app進程里面的service也定時去start守護service;或者在這兩個service之間維護一條tcp連接也可以做到實時檢測
采用bind service加上start service,因為bing service之后即使stop service那service也還是存在,不會調用onDestroy,只有等service被解綁之后才用調用onDestroy,有時候你到應用程序里面去停止了進程的service,列表里面看運行中的程序是找不到service了,但是實際上service還是在運行的,過一會兒就又刷出來了
捕獲第三方推送的廣播接收,在app程序中添加一個靜態廣播專門用來接收第三方的廣播,收到廣播后就喚醒Service,例如捕獲小米推送,個推,但是有些第三方的廣播是有權限限制的,因此不是所有的第三方廣播都能被收到
監聽系統靜態廣播,開機自啟廣播,網絡變換廣播,USB接入和拔出的廣播,系統屏幕解鎖廣播,這幾個廣播都是靜態注冊的廣播;但是這種方式也是不可靠的,因為有些手機在程序停止運行之后連靜態廣播都不能收到了。。。增加廣播監聽只是在一定程度上降低service被殺后重新拉起的概率,但是系統的靜態廣播在app被殺死后是無法收到的
監聽第三方的靜態自定義廣播,靜態自定義廣播是有可能可以被接收的,只要發送方按照一定的方式發送就行,具體可以參考:如何在app被殺死的情況下仍然可接收靜態自定義廣播,這邊有個前提就是第三方推送發送廣播的時候必須要按照指定方式發送才可行,優點是無需接入第三方推送,只是監聽他們的廣播即可,很輕量級
接入第三方推送服務,但是我們不用他們的推送,我們還是用我們自己的推送,不過我們可以捕捉到他們的一些廣播,service啟動的action等來讓它們來觸發拉起我們的程序,比如說集成友盟推送,當啟動uc的時候可能會發一些廣播,通過action啟動一些service,我們就專門監聽這種廣播,監聽這些action就能成功的拉起我們的服務,這種辦法是通過第三方推送來觸發我們的推送服務,人家是專門做推送的,更專業,那我們就直接用唄,達到不使用他們的推送,但是們的app也加入了互相喚醒的app 的行列中去了
市面上的推送服務現在大多使用長連接多路復用來保證app互相喚醒,就是一臺設備商有多個app集成了一種推送,那么只會有一個宿主app會保持跟服務器之間的長連接,其他app都是共享這條長連接,宿主app就負責路由各個app的推送消息,當有一個app存活,其他app都被殺死,這是后有消息過來,那么這個活著的app就會把死掉的app喚醒,而app之間的互相喚醒是采用action來start service的方式喚醒的,我了解到友盟推送和個推都是這么做的;但是長連接多路復用的?;钚Ч墙⒃谟脩袅可厦娴?,用戶量大了,集成推送的app多了,喚醒幾率就越大,喚醒效果就越好,例如一些高頻使用的app,uc,支付寶等,用戶可能頻繁的點開這些app,那么就會經常喚醒其他app,長連接多路復用因為做到了連接共享所以更加省電,省內存;但是的但是,道高一尺,魔高一丈,嘴型android 6.0系統已經禁止app互相喚醒了。。。而且去掉了網絡切換靜態廣播
-
其實最最根本的解決方案還是把app直接加入手機白名單~,絕對百分百不會被殺死,親測有效,有些人認為為啥微信消息接收那么即時,微信不會被手機清理,因為微信已經牛逼到手機廠商自動把微信和QQ加入了手機白名單,小米系統更是直接把微信app的相關進程設置成守護進程: 小米4c手機進入adb命令行: 輸入dumpsys activity 這里寫圖片描述
app的push進程設置前臺service后sync.push的進程優先級是vis,可視進程 而微信app的push進程com.tencent.mm:push的進程優先級的prcp,protect process保護進程,可能是小米手機自定義的進程優先級別
下面幾種是不知道有沒有效果的service?;钍侄危?/p>
配置文件里面加入android:persistent=”true”提升進程優先級,但是這個貌似只有對系統應用才有效,具體原理不說了,自己百度
設置service的action優先級:
<service android:name="com.xtc.sync.connection.ConnectionService" android:process="sync.push"
android:enabled="true" android:exported="false" android:persistent="true"> <intent-filter
android:priority="0x7fffffff"> <action android:name="com.xtc.sync.connection_service" /> </intent-filter>
</service>
這種效果我感覺微乎其微,即使不設置也沒啥影響
TCP長連接保活:
心跳包保持長連接暢通,定時發心跳包是為了避免NAT,把發送心跳包的代碼寫到維護長連接的Service里面,每次發心跳包就start service,因為心跳包是用鬧鐘實現的,有可能service死了,但是系統鬧鐘還是在正常執行的,這時候就能把維護長連接的service拉起,然后只要心跳包一超時就立馬把連接斷開重連,這是為了避免無效連接,關于無效連接請看TCP長連接的一些坑冗余心跳包,手機鎖屏解鎖,網絡切換,USB接入,拔出,app帳戶登錄都發一個心跳包,長連接請求超時也補發一個心跳包斷線重連策略,采用時間間隔遞增,第一次斷開馬上連上,連接失敗隔2s再重連,重連失敗再隔4s再重連,以此類推,至于時間間隔可以自己指定比較適用的算法計算出來,這時候也會遇到要是網絡閃斷的情況會導致連接連傷后又馬上斷開,然后馬上重連成功后連接再次斷開,這樣也會導致短時間內重連多次,這時候可以對重連也做連接間隔的遞增策略,如果檢測到上次連接成功的時間在1s之內那么就采用時間間隔遞增重連,如果上次重連在比較久之前了,那么就不采用時間間隔遞增重連,直接正常重連 心跳包周期策略,當前我們的app只對wifi網絡和數據網絡指定了不同的心跳包,沒有很詳細的心跳包策略,但其實后期可以指定詳細的心跳包策略來進一步提升tcp連接的穩定性 在手機休眠的時候采用系統鬧鐘可以短暫喚醒cpu執行任務,例如利用系統鬧鐘來執行周期心跳,在網絡數據返回的時候,也會喚醒cpu,所以不需要wakelock,因為從其他渠道了解到:
a. Alarm喚醒后,有足夠的cpu時間來發包,所以不用wakelock
b. 網絡回包可喚醒機器(但是在api level >= 23就關閉了所有網絡),而且小米4c手機鎖屏后過一會兒也會自動禁止網絡,導致所有連接全部失效,小米4c手機的這種情況是我親測的,確實有這種問題跨進程通信在我們的sdk里面是采用aidl和廣播實現,但是aidl有時候會有問題,在service被殺死的時候會爆出DeadObjectException,就是表示service已經被殺死了,但是可能程序依然在執行aidl的方法,而且aidl代碼冗余,廣播的話傳輸的數據大小有限制,可以考慮采用Socket和ServerSocket來實現進程間的通信,采用TCP連接實現進程間的通信可以實現兩個進城之間互相監聽,只要有一個進程死了,另一個進程馬上就能收到反饋,這時候可以做相關操作,例如再把死掉的進程拉起來等
針對不同定制系統的問題:
小米MIUI和魅族flyme是兩個比較深android定制系統,超級坑超級坑,系統清理策略太特么嚴格了,一清全死,怎么起都起不來,秒殺一切推送,針對這種機型我們只能說,可以的話默默的加入白名單吧,親,要不你就去跟手機廠商談合作,要不你就用他們家的推送,例如MiPush
三星手機倒是還好,基本按照以上的做法在大部分5.0一下的系統都可以在很大程度上保持service常駐,因為三星的android系統是比較純凈的
華為的手機系統也是定制的,不過也不知道為啥,我們的c守護進程在其他手機上跑得好好的,在華為手機上跑超級耗電,而且無法start service,因為這個c程序是網上大牛寫的,不是我寫的,我也找不到原因所在,甚至于在華為某些機型上面會導致超級耗電,要不就是導致手機超級卡頓,要重啟才能使用,所以華為的定制機我們是把守護進城去掉了,所以我們的service變得非常脆弱
市面上的主流手機就是小米,三星,華為,魅族,vivo,oppo了,但是當我們專門針對這些進行做適配的時候,版本正式發布了,你會發現反饋問題的用戶往往都不是使用這些手機。。。都是使用什么聯想,htc等等好坑的手機,有廣播發送需要一兩分鐘才能收到
根據手機渠道來分版本發布,小米版,魅族版等等
經過測試采用上述方式后,只要不使用手機自身的一鍵清除,按下home鍵后service常駐情況后臺的機型:
小米4C——4.4Android系統——service可以常駐(但是守護進程無效,因為小米的一鍵清理太徹底)
魅族MX5——5.0Android系統——service在手機鎖屏后幾分鐘就被殺死并且無法重啟(守護進程無效,魅族一鍵清理更徹底)
華為榮耀6,6plus——5.0Android系統——service在手機鎖屏后馬上就被殺死并無法重啟(守護進程無效,守護進程導致嚴重耗電,守護進程無法正常運行)
華為很舊的機型—–4.4Android系統——service常駐后臺,就算被殺死或者手機一鍵清理也會被守護進程拉起(守護進程有效,守護進程基本不會被殺死)
華為meta7——4.4Android系統——service常駐后臺(未知守護進程是否有效,待測)
三星部分機型service可以常駐,但是部分機型service無法常駐并且守護進程無效,具體待測
vivo和oppo的手機上service基本無法常駐,死的一干二凈明明白白,絲毫沒有任何掙扎的痕跡
android5.0以上系統守護進程據說是無效,但是我沒有親測,因為5.0以下的android系統只是殺死App主進程,但是5.0以上的android系統是殺死跟App有關的所有進程組,所以game over。。。
但是最最特么坑爹的是居然能看到確實有些app可以在各種各樣的機型上保持service常駐。。。我也是醉了,雖然有些是跟廠商合作,但是我覺的應該還是有其他辦法能再次提升service常駐的概率,至少保持按下home鍵后手機鎖屏service不被殺死吧,看到這里要是有大神知道的是什么方式的請指導指導,分享分享,給小弟一個方向,多謝
不同的andorid系統版本:
android5.0開始系統做的越來越安全了,5.0以下start service可以隱式啟動,就是不設置intent的package,但是5.0開始就必須要設置了
最近剛出的android N已經把靜態廣播網絡變換廣播去掉了。。。這個對我們來說實在是巨大的打擊,編程了只能動態注冊。。。