一種提高Android應(yīng)用進(jìn)程存活率新方法(上)
基礎(chǔ)知識(shí)
Android 進(jìn)程優(yōu)先級(jí)
1 進(jìn)程優(yōu)先級(jí)等級(jí)一般分法
- Activte process
- Visible Process
- Service process
- Background process
- Empty process
2 Service技巧
- onStartCommand返回START_STICKY
- onDestroy中startself
- Service后臺(tái)變前置,setForground(true)
- android:persistent = “true”
3 進(jìn)程優(yōu)先級(jí)號(hào)
ProcessList.java
- // Adjustment used in certain places where we don't know it yet.
- // (Generally this is something that is going to be cached, but we
- // don't know the exact value in the cached range to assign yet.)
- static final int UNKNOWN_ADJ = 16;
- // This is a process only hosting activities that are not visible,
- // so it can be killed without any disruption.
- static final int CACHED_APP_MAX_ADJ = 15;
- static final int CACHED_APP_MIN_ADJ = 9;
- // The B list of SERVICE_ADJ -- these are the old and decrepit
- // services that aren't as shiny and interesting as the ones in the A list.
- static final int SERVICE_B_ADJ = 8;
- // This is the process of the previous application that the user was in.
- // This process is kept above other things, because it is very common to
- // switch back to the previous app. This is important both for recent
- // task switch (toggling between the two top recent apps) as well as normal
- // UI flow such as clicking on a URI in the e-mail app to view in the browser,
- // and then pressing back to return to e-mail.
- static final int PREVIOUS_APP_ADJ = 7;
- // This is a process holding the home application -- we want to try
- // avoiding killing it, even if it would normally be in the background,
- // because the user interacts with it so much.
- static final int HOME_APP_ADJ = 6;
- // This is a process holding an application service -- killing it will not
- // have much of an impact as far as the user is concerned.
- static final int SERVICE_ADJ = 5;
- // This is a process with a heavy-weight application. It is in the
- // background, but we want to try to avoid killing it. Value set in
- // system/rootdir/init.rc on startup.
- static final int HEAVY_WEIGHT_APP_ADJ = 4;
- // This is a process currently hosting a backup operation. Killing it
- // is not entirely fatal but is generally a bad idea.
- static final int BACKUP_APP_ADJ = 3;
- // This is a process only hosting components that are perceptible to the
- // user, and we really want to avoid killing them, but they are not
- // immediately visible. An example is background music playback.
- static final int PERCEPTIBLE_APP_ADJ = 2;
- // This is a process only hosting activities that are visible to the
- // user, so we'd prefer they don't disappear.
- static final int VISIBLE_APP_ADJ = 1;
- // This is the process running the current foreground app. We'd really
- // rather not kill it!
- static final int FOREGROUND_APP_ADJ = 0;
- // This is a process that the system or a persistent process has bound to,
- // and indicated it is important.
- static final int PERSISTENT_SERVICE_ADJ = -11;
- // This is a system persistent process, such as telephony. Definitely
- // don't want to kill it, but doing so is not completely fatal.
- static final int PERSISTENT_PROC_ADJ = -12;
- // The system process runs at the default adjustment.
- static final int SYSTEM_ADJ = -16;
- // Special code for native processes that are not being managed by the system (so
- // don't have an oom adj assigned by the system).
- static final int NATIVE_ADJ = -17;
Android Low Memory Killer
Android系統(tǒng)內(nèi)存不足時(shí),系統(tǒng)會(huì)殺掉一部分進(jìn)程以釋放空間,誰生誰死的這個(gè)生死大權(quán)就是由LMK所決定的,這就是Android系統(tǒng)中的Low Memory Killer,其基于Linux的OOM機(jī)制,其閾值定義如下面所示的lowmemorykiller文件中,當(dāng)然也可以通過系統(tǒng)的init.rc實(shí)現(xiàn)自定義。
lowmemorykiller.c
- static uint32_t lowmem_debug_level = 1;
- static int lowmem_adj[6] = {
- 0,
- 1,
- 6,
- 12,
- };
- static int lowmem_adj_size = 4;
- static int lowmem_minfree[6] = {
- 3 * 512, /* 6MB */
- 2 * 1024, /* 8MB */
- 4 * 1024, /* 16MB */
- 16 * 1024, /* 64MB */
- };
- static int lowmem_minfree_size = 4;
① 在Low Memory Killer中通過進(jìn)程的oom_adj與占用內(nèi)存的大小決定要?dú)⑺赖倪M(jìn)程,oom_adj值越小越不容易被殺死。其中,lowmem_minfree是殺進(jìn)程的時(shí)機(jī),誰被殺,則取決于lowmem_adj,具體值得含義參考上面 Android進(jìn)程優(yōu)先級(jí) 所述.
② 在init.rc中定義了init進(jìn)程(系統(tǒng)進(jìn)程)的oom_adj為-16,其不可能會(huì)被殺死(init的PID是1),而前臺(tái)進(jìn)程是0(這里的前臺(tái)進(jìn)程是指用戶正在使用的Activity所在的進(jìn)程),用戶按Home鍵回到桌面時(shí)的優(yōu)先級(jí)是6,普通的Service的進(jìn)程是8.
init.rc
- # Set init and its forked children's oom_adj.
- write /proc/1/oom_adj -16
關(guān)于Low Memory Killer的具體實(shí)現(xiàn)原理可參考Ref-2.
查看某個(gè)App的進(jìn)程
步驟(手機(jī)與PC連接)
- adb shell
- ps | grep 進(jìn)程名
- cat /proc/pid/oom_adj //其中pid是上述grep得到的進(jìn)程號(hào)
Linux AM命令
am命令:在Android系統(tǒng)中通過adb shell 啟動(dòng)某個(gè)Activity、Service、撥打電話、啟動(dòng)瀏覽器等操作Android的命令.其源碼在Am.java中,在shell環(huán)境下執(zhí)行am命令實(shí)際是啟動(dòng)一個(gè)線程執(zhí)行Am.java中的主函數(shù)(main方法),am命令后跟的參數(shù)都會(huì)當(dāng)做運(yùn)行時(shí)參數(shù)傳遞到主函數(shù)中,主要實(shí)現(xiàn)在Am.java的run方法中。
撥打電話
命令:am start -a android.intent.action.CALL -d tel:電話號(hào)碼
示例:am start -a android.intent.action.CALL -d tel:10086
打開一個(gè)網(wǎng)頁
命令:am start -a android.intent.action.VIEW -d 網(wǎng)址
示例:am start -a android.intent.action.VIEW -d http://www.skyseraph.com
啟動(dòng)一個(gè)服務(wù)
命令:am startservice <服務(wù)名稱>
示例:am startservice -n com.android.music/ com.android.music.MediaPlaybackService
NotificationListenerService
“A service that receives calls from the system when new notifications are posted or removed, or their ranking changed.” From Google
用來監(jiān)聽到通知的發(fā)送以及移除和排名位置變化,如果我們注冊(cè)了這個(gè)服務(wù),當(dāng)系統(tǒng)任何一條通知到來或者被移除掉,我們都能通過這個(gè)service來監(jiān)聽到,甚至可以做一些管理工作。
Android賬號(hào)和同步機(jī)制
屬于Android中較偏冷的知識(shí),具體參考 Ref 3 /4 /5
Android多進(jìn)程
- 實(shí)現(xiàn):android:process
- 好處:一個(gè)獨(dú)立的進(jìn)程可以充分利用自己的RAM預(yù)算,使其主進(jìn)程擁有更多的空間處理資源。此外,操作系統(tǒng)對(duì)待運(yùn)行在不同組件中的進(jìn)程是不一樣的。這意味著,當(dāng)系統(tǒng)運(yùn)行在低可用內(nèi)存的條件時(shí),并不是所有的進(jìn)程都會(huì)被殺死
- 大坑:每一個(gè)進(jìn)程將有自己的Dalvik VM實(shí)例,意味著你不能通過這些實(shí)例共享數(shù)據(jù),至少不是傳統(tǒng)意義上的。例如,靜態(tài)字段在每個(gè)進(jìn)程都有自己的值,而不是你傾向于相信的只有一個(gè)值。
- 更多詳細(xì)請(qǐng)參考Ref 9
現(xiàn)有方法
網(wǎng)絡(luò)連接保活方法
A. GCM
B. 公共的第三方push通道(信鴿等)
C. 自身跟服務(wù)器通過輪詢,或者長(zhǎng)連接
具體實(shí)現(xiàn)請(qǐng)參考 微信架構(gòu)師楊干榮的”微信Android客戶端后臺(tái)保活經(jīng)驗(yàn)分享” (Ref-1).
雙service(通知欄) 提高進(jìn)程優(yōu)先級(jí)
思路:(API level > 18 )
- 應(yīng)用啟動(dòng)時(shí)啟動(dòng)一個(gè)假的Service(FakeService), startForeground(),傳一個(gè)空的Notification
- 啟動(dòng)真正的Service(AlwaysLiveService),startForeground(),注意必須相同Notification ID
- FakeService stopForeground()
效果:通過adb查看,運(yùn)行在后臺(tái)的服務(wù)其進(jìn)程號(hào)變成了1(優(yōu)先級(jí)僅次于前臺(tái)進(jìn)程)
風(fēng)險(xiǎn):Android系統(tǒng)前臺(tái)service的一個(gè)漏洞,可能在6.0以上系統(tǒng)中修復(fù)
實(shí)現(xiàn):核心代碼如下
- AlwaysLiveService 常駐內(nèi)存服務(wù)
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- startForeground(R.id.notify, new Notification());
- startService(new Intent(this, FakeService.class));
- return super.onStartCommand(intent, flags, startId);
- }
- FakeService 臨時(shí)服務(wù)
- public class FakeService extends Service {
- @Nullable
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- startForeground(R.id.notify, new Notification());
- stopSelf();
- return super.onStartCommand(intent, flags, startId);
- }
- @Override
- public void onDestroy() {
- stopForeground(true);
- super.onDestroy();
- }
- }
Service及時(shí)拉起
AlarmReceiver, ConnectReceiver,BootReceiver等
- Service設(shè)置(見上面基礎(chǔ)部分)
- 通過監(jiān)聽系統(tǒng)廣播,如開機(jī),鎖屏,亮屏等重新啟動(dòng)服務(wù)
- 通過alarm定時(shí)器,啟動(dòng)服務(wù)
守護(hù)進(jìn)程/進(jìn)程互拉
在分析360手機(jī)助手app時(shí),發(fā)現(xiàn)其擁有N多個(gè)進(jìn)程,一個(gè)進(jìn)程kill后會(huì)被其它未kill的進(jìn)程拉起,這也是一種思路吧,雖然有點(diǎn)流氓~
守護(hù)進(jìn)程一般有這樣兩種方式:
- 多個(gè)java進(jìn)程守護(hù)互拉
- 底層C守護(hù)進(jìn)程拉起App上層/java進(jìn)程
Linux Am命令開啟后臺(tái)進(jìn)程
一種底層實(shí)現(xiàn)讓進(jìn)程不被殺死的方法,在Android4.4以上可能有兼容性問題,具體參考Ref-7
NotificationListenerService通知
一種需要用戶允許特定權(quán)限的系統(tǒng)拉起方式,4.3以上系統(tǒng)
前臺(tái)浮窗
有朋友提出一種應(yīng)用退出后啟動(dòng)一個(gè)不可交互的浮窗,個(gè)人覺得這種方法是無效的,讀者有興趣可以一試
新方法(AccountSync)
思路
利用Android系統(tǒng)提供的賬號(hào)和同步機(jī)制實(shí)現(xiàn)
效果
- 通過adb查看,運(yùn)行在后臺(tái)的服務(wù)其進(jìn)程號(hào)變成了1(優(yōu)先級(jí)僅次于前臺(tái)進(jìn)程),能提高進(jìn)程優(yōu)先級(jí),對(duì)比如下圖
正常情況
采用AccountSyncAdapter方法后
- 進(jìn)程被系統(tǒng)kill后,可以由syn拉起
風(fēng)險(xiǎn)
- SyncAdapter時(shí)間進(jìn)度不高,往往會(huì)因?yàn)槭謾C(jī)處于休眠狀態(tài),而時(shí)間往后調(diào)整,同步間隔最低為1分鐘
- 用戶可以單獨(dú)停止或者刪除,有些手機(jī)賬號(hào)默認(rèn)是不同步的,需要手動(dòng)開啟
實(shí)現(xiàn) (核心代碼)
1 建立數(shù)據(jù)同步系統(tǒng)(ContentProvider)
通過一個(gè)ContentProvider用來作數(shù)據(jù)同步,由于并沒有實(shí)際數(shù)據(jù)同步,所以此處就直接建立一個(gè)空的ContentProvider即可
- public class XXAccountProvider extends ContentProvider {
- public static final String AUTHORITY = "包名.provider";
- public static final String CONTENT_URI_BASE = "content://" + AUTHORITY;
- public static final String TABLE_NAME = "data";
- public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_BASE + "/" + TABLE_NAME);
- @Override
- public boolean onCreate() {
- return true;
- }
- @Nullable
- @Override
- public Cursor query(Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
- return null;
- }
- @Nullable
- @Override
- public String getType(Uri uri) {
- return new String();
- }
- @Nullable
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- return null;
- }
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- return 0;
- }
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- return 0;
- }
- }
然后再M(fèi)anifest中聲明
- <provider
- android:name="**.XXAccountProvider"
- android:authorities="@string/account_auth_provider"
- android:exported="false"
- android:syncable="true"/>
2 建立Sync系統(tǒng) (SyncAdapter)
通過實(shí)現(xiàn)SyncAdapter這個(gè)系統(tǒng)服務(wù)后, 利用系統(tǒng)的定時(shí)器對(duì)程序數(shù)據(jù)ContentProvider進(jìn)行更新,具體步驟為:
- 創(chuàng)建Sync服務(wù)
- public class XXSyncService extends Service {
- private static final Object sSyncAdapterLock = new Object();
- private static XXSyncAdapter sSyncAdapter = null;
- @Override
- public void onCreate() {
- synchronized (sSyncAdapterLock) {
- if (sSyncAdapter == null) {
- sSyncAdapter = new XXSyncAdapter(getApplicationContext(), true);
- }
- }
- }
- @Override
- public IBinder onBind(Intent intent) {
- return sSyncAdapter.getSyncAdapterBinder();
- }
- static class XXSyncAdapter extends AbstractThreadedSyncAdapter {
- public XXSyncAdapter(Context context, boolean autoInitialize) {
- super(context, autoInitialize);
- }
- @Override
- public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
- getContext().getContentResolver().notifyChange(XXAccountProvider.CONTENT_URI, null, false);
- }
- }
- }
- 聲明Sync服務(wù)
- <service
- android:name="**.XXSyncService"
- android:exported="true"
- android:process=":core">
- <intent-filter>
- <action
- android:name="android.content.SyncAdapter"/>
- </intent-filter>
- <meta-data
- android:name="android.content.SyncAdapter"
- android:resource="@xml/sync_adapter"/>
- </service>
其中sync_adapter為:
- <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
- android:accountType="@string/account_auth_type"
- android:allowParallelSyncs="false"
- android:contentAuthority="@string/account_auth_provide"
- android:isAlwaysSyncable="true"
- android:supportsUploading="false"
- android:userVisible="true"/>
參數(shù)說明:
android:contentAuthority 指定要同步的ContentProvider在其AndroidManifest.xml文件中有個(gè)android:authorities屬性。
android:accountType 表示進(jìn)行同步的賬號(hào)的類型。
android:userVisible 設(shè)置是否在“設(shè)置”中顯示
android:supportsUploading 設(shè)置是否必須notifyChange通知才能同步
android:allowParallelSyncs 是否支持多賬號(hào)同時(shí)同步
android:isAlwaysSyncable 設(shè)置所有賬號(hào)的isSyncable為1
android:syncAdapterSettingsAction 指定一個(gè)可以設(shè)置同步的activity的Action。
- 賬戶調(diào)用Sync服務(wù)
首先配置好Account(第三步),然后再通過ContentProvider實(shí)現(xiàn)
手動(dòng)更新
- public void triggerRefresh() {
- Bundle b = new Bundle();
- b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
- b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
- ContentResolver.requestSync(
- account,
- CONTENT_AUTHORITY,
- b);
- }
添加賬號(hào)
- Account account = AccountService.GetAccount();
- AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
- accountManager.addAccountExplicitly(...)
同步周期設(shè)置
- ContentResolver.setIsSyncable(account, CONTENT_AUTHORITY, 1);
- ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, true);
- ContentResolver.addPeriodicSync(account, CONTENT_AUTHORITY, new Bundle(), SYNC_FREQUENCY);
3 建立賬號(hào)系統(tǒng) (Account Authenticator)
通過建立Account賬號(hào),并關(guān)聯(lián)SyncAdapter服務(wù)實(shí)現(xiàn)同步
接下文