【Android】一種提升Android應用進程存活率新方法html
SkySeraph Jun. 19st 2016java
Email:skyseraph00@163.comandroid
更多精彩請直接訪問SkySeraph我的站點:www.skyseraph.com shell
1.1 進程優先級等級通常分法:
- Activte process
- Visible Process
- Service process
- Background process
- Empty process服務器
1.2 進程優先級號微信
1 // Adjustment used in certain places where we don't know it yet. 2 // (Generally this is something that is going to be cached, but we 3 // don't know the exact value in the cached range to assign yet.) 4 static final int UNKNOWN_ADJ = 16; 5 6 // This is a process only hosting activities that are not visible, 7 // so it can be killed without any disruption. 8 static final int CACHED_APP_MAX_ADJ = 15; 9 static final int CACHED_APP_MIN_ADJ = 9; 10 11 // The B list of SERVICE_ADJ -- these are the old and decrepit 12 // services that aren't as shiny and interesting as the ones in the A list. 13 static final int SERVICE_B_ADJ = 8; 14 15 // This is the process of the previous application that the user was in. 16 // This process is kept above other things, because it is very common to 17 // switch back to the previous app. This is important both for recent 18 // task switch (toggling between the two top recent apps) as well as normal 19 // UI flow such as clicking on a URI in the e-mail app to view in the browser, 20 // and then pressing back to return to e-mail. 21 static final int PREVIOUS_APP_ADJ = 7; 22 23 // This is a process holding the home application -- we want to try 24 // avoiding killing it, even if it would normally be in the background, 25 // because the user interacts with it so much. 26 static final int HOME_APP_ADJ = 6; 27 28 // This is a process holding an application service -- killing it will not 29 // have much of an impact as far as the user is concerned. 30 static final int SERVICE_ADJ = 5; 31 32 // This is a process with a heavy-weight application. It is in the 33 // background, but we want to try to avoid killing it. Value set in 34 // system/rootdir/init.rc on startup. 35 static final int HEAVY_WEIGHT_APP_ADJ = 4; 36 37 // This is a process currently hosting a backup operation. Killing it 38 // is not entirely fatal but is generally a bad idea. 39 static final int BACKUP_APP_ADJ = 3; 40 41 // This is a process only hosting components that are perceptible to the 42 // user, and we really want to avoid killing them, but they are not 43 // immediately visible. An example is background music playback. 44 static final int PERCEPTIBLE_APP_ADJ = 2; 45 46 // This is a process only hosting activities that are visible to the 47 // user, so we'd prefer they don't disappear. 48 static final int VISIBLE_APP_ADJ = 1; 49 50 // This is the process running the current foreground app. We'd really 51 // rather not kill it! 52 static final int FOREGROUND_APP_ADJ = 0; 53 54 // This is a process that the system or a persistent process has bound to, 55 // and indicated it is important. 56 static final int PERSISTENT_SERVICE_ADJ = -11; 57 58 // This is a system persistent process, such as telephony. Definitely 59 // don't want to kill it, but doing so is not completely fatal. 60 static final int PERSISTENT_PROC_ADJ = -12; 61 62 // The system process runs at the default adjustment. 63 static final int SYSTEM_ADJ = -16; 64 65 // Special code for native processes that are not being managed by the system (so 66 // don't have an oom adj assigned by the system). 67 static final int NATIVE_ADJ = -17;
Android系統內存不足時,系統會殺掉一部分進程以釋放空間,誰生誰死的這個生死大權就是由LMK所決定的,這就是Android系統中的Low Memory Killer,其基於Linux的OOM機制,其閾值定義以下面所示的lowmemorykiller文件中,固然也能夠經過系統的init.rc實現自定義。架構
lowmemorykiller.capp
1 static uint32_t lowmem_debug_level = 1; 2 static int lowmem_adj[6] = { 3 0, 4 1, 5 6, 6 12, 7 }; 8 static int lowmem_adj_size = 4; 9 static int lowmem_minfree[6] = { 10 3 * 512, /* 6MB */ 11 2 * 1024, /* 8MB */ 12 4 * 1024, /* 16MB */ 13 16 * 1024, /* 64MB */ 14 }; 15 static int lowmem_minfree_size = 4;
① 在Low Memory Killer中經過進程的oom_adj與佔用內存的大小決定要殺死的進程,oom_adj值越小越不容易被殺死。其中,lowmem_minfree是殺進程的時機,誰被殺,則取決於lowmem_adj,具體值得含義參考上面 Android進程優先級 所述.ide
② 在init.rc中定義了init進程(系統進程)的oom_adj爲-16,其不可能會被殺死(init的PID是1),而前臺進程是0(這裏的前臺進程是指用戶正在使用的Activity所在的進程),用戶按Home鍵回到桌面時的優先級是6,普通的Service的進程是8.
init.rc
1 # Set init and its forked children's oom_adj. 2 write /proc/1/oom_adj -16
關於Low Memory Killer的具體實現原理可參考Ref-2.
步驟(手機與PC鏈接)
1. adb shell
2. ps | grep 進程名
3. cat /proc/pid/oom_adj //其中pid是上述grep獲得的進程號
屬於Android中較偏冷的知識,具體參考 Ref 3/4/5
a. GCM
b. 公共的第三方push通道(信鴿等)
c. 自身跟服務器經過輪詢,或者長鏈接
具體實現請參考 微信架構師楊幹榮的"微信Android客戶端後臺保活經驗分享" (Ref-1).
思路:(API level > 18 )
① 應用啓動時啓動一個假的Service(FakeService), startForeground(),傳一個空的Notification
② 啓動真正的Service(AlwaysLiveService),startForeground(),注意必須相同Notification ID
③ FakeService stopForeground()
效果:經過adb查看,運行在後臺的服務其進程號變成了1(優先級僅次於前臺進程)
風險:Android系統前臺service的一個漏洞,可能在6.0以上系統中修復
實現:核心代碼以下
AlwaysLiveService 常駐內存服務
1 @Override 2 public int onStartCommand(Intent intent, int flags, int startId) { 3 startForeground(R.id.notify, new Notification()); 4 startService(new Intent(this, FakeService.class)); 5 return super.onStartCommand(intent, flags, startId); 6 }
FakeService 臨時服務
1 public class FakeService extends Service { 2 @Nullable 3 @Override 4 public IBinder onBind(Intent intent) { 5 return null; 6 } 7 8 @Override 9 public int onStartCommand(Intent intent, int flags, int startId) { 10 startForeground(R.id.notify, new Notification()); 11 stopSelf(); 12 return super.onStartCommand(intent, flags, startId); 13 } 14 15 @Override 16 public void onDestroy() { 17 stopForeground(true); 18 super.onDestroy(); 19 } 20 }
AlarmReceiver, ConnectReceiver,BootReceiver等
利用Android系統提供的帳號和同步機制實現
① 經過adb查看,運行在後臺的服務其進程號變成了1(優先級僅次於前臺進程),能提升進程優先級,對好比下圖
正常狀況
採用AccountSyncAdapter方法後
② 進程被系統kill後,能夠由syn拉起
① SyncAdapter時間進度不高,每每會由於手機處於休眠狀態,而時間日後調整,同步間隔最低爲1分鐘
② 用戶能夠單獨中止或者刪除,有些手機帳號默認是不一樣步的,須要手動開啓
① 創建數據同步系統(ContentProvider)
經過一個ContentProvider用來做數據同步,因爲並無實際數據同步,因此此處就直接創建一個空的ContentProvider便可
1 public class XXAccountProvider extends ContentProvider { 2 public static final String AUTHORITY = "包名.provider"; 3 public static final String CONTENT_URI_BASE = "content://" + AUTHORITY; 4 public static final String TABLE_NAME = "data"; 5 public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_BASE + "/" + TABLE_NAME); 6 7 @Override 8 public boolean onCreate() { 9 return true; 10 } 11 12 @Nullable 13 @Override 14 public Cursor query(Uri uri, String[] projection, String selection, 15 String[] selectionArgs, String sortOrder) { 16 return null; 17 } 18 19 @Nullable 20 @Override 21 public String getType(Uri uri) { 22 return new String(); 23 } 24 25 @Nullable 26 @Override 27 public Uri insert(Uri uri, ContentValues values) { 28 return null; 29 } 30 31 @Override 32 public int delete(Uri uri, String selection, String[] selectionArgs) { 33 return 0; 34 } 35 36 @Override 37 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 38 return 0; 39 } 40 }
而後再Manifest中聲明
<provider android:name="**.XXAccountProvider" android:authorities="@string/account_auth_provider" android:exported="false" android:syncable="true"/>
② 創建Sync系統 (SyncAdapter)
經過實現SyncAdapter這個系統服務後, 利用系統的定時器對程序數據ContentProvider進行更新,具體步驟爲:
- 建立Sync服務
1 public class XXSyncService extends Service { 2 private static final Object sSyncAdapterLock = new Object(); 3 private static XXSyncAdapter sSyncAdapter = null; 4 @Override 5 public void onCreate() { 6 synchronized (sSyncAdapterLock) { 7 if (sSyncAdapter == null) { 8 sSyncAdapter = new XXSyncAdapter(getApplicationContext(), true); 9 } 10 } 11 } 12 13 @Override 14 public IBinder onBind(Intent intent) { 15 return sSyncAdapter.getSyncAdapterBinder(); 16 } 17 18 static class XXSyncAdapter extends AbstractThreadedSyncAdapter { 19 public XXSyncAdapter(Context context, boolean autoInitialize) { 20 super(context, autoInitialize); 21 } 22 @Override 23 public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { 24 getContext().getContentResolver().notifyChange(XXAccountProvider.CONTENT_URI, null, false); 25 } 26 } 27 }
- 聲明Sync服務
1 <service 2 android:name="**.XXSyncService" 3 android:exported="true" 4 android:process=":core"> 5 <intent-filter> 6 <action 7 android:name="android.content.SyncAdapter"/> 8 </intent-filter> 9 <meta-data 10 android:name="android.content.SyncAdapter" 11 android:resource="@xml/sync_adapter"/> 12 </service>
其中sync_adapter爲:
1 <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" 2 android:accountType="@string/account_auth_type" 3 android:allowParallelSyncs="false" 4 android:contentAuthority="@string/account_auth_provide" 5 android:isAlwaysSyncable="true" 6 android:supportsUploading="false" 7 android:userVisible="true"/>
參數說明:
android:contentAuthority 指定要同步的ContentProvider在其AndroidManifest.xml文件中有個android:authorities屬性。
android:accountType 表示進行同步的帳號的類型。
android:userVisible 設置是否在「設置」中顯示
android:supportsUploading 設置是否必須notifyChange通知才能同步
android:allowParallelSyncs 是否支持多帳號同時同步
android:isAlwaysSyncable 設置全部帳號的isSyncable爲1
android:syncAdapterSettingsAction 指定一個能夠設置同步的activity的Action。
- 帳戶調用Sync服務
首先配置好Account(第三步),而後再經過ContentProvider實現
手動更新
1 public void triggerRefresh() { 2 Bundle b = new Bundle(); 3 b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); 4 b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); 5 ContentResolver.requestSync( 6 account, 7 CONTENT_AUTHORITY, 8 b); 9 }
添加帳號
1 Account account = AccountService.GetAccount(); 2 AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); 3 accountManager.addAccountExplicitly(...)
同步週期設置
1 ContentResolver.setIsSyncable(account, CONTENT_AUTHORITY, 1); 2 ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, true); 3 ContentResolver.addPeriodicSync(account, CONTENT_AUTHORITY, new Bundle(), SYNC_FREQUENCY);
③ 創建帳號系統 (Account Authenticator)
經過創建Account帳號,並關聯SyncAdapter服務實現同步
- 建立Account服務
1 public class XXAuthService extends Service { 2 private XXAuthenticator mAuthenticator; 3 4 @Override 5 public void onCreate() { 6 mAuthenticator = new XXAuthenticator(this); 7 } 8 9 private XXAuthenticator getAuthenticator() { 10 if (mAuthenticator == null) 11 mAuthenticator = new XXAuthenticator(this); 12 return mAuthenticator; 13 } 14 15 @Override 16 public IBinder onBind(Intent intent) { 17 return getAuthenticator().getIBinder(); 18 } 19 20 class XXAuthenticator extends AbstractAccountAuthenticator { 21 private final Context context; 22 private AccountManager accountManager; 23 public XXAuthenticator(Context context) { 24 super(context); 25 this.context = context; 26 accountManager = AccountManager.get(context); 27 } 28 29 @Override 30 public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) 31 throws NetworkErrorException { 32 // 添加帳號 示例代碼 33 final Bundle bundle = new Bundle(); 34 final Intent intent = new Intent(context, AuthActivity.class); 35 intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); 36 bundle.putParcelable(AccountManager.KEY_INTENT, intent); 37 return bundle; 38 } 39 40 @Override 41 public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) 42 throws NetworkErrorException { 43 // 認證 示例代碼 44 String authToken = accountManager.peekAuthToken(account, getString(R.string.account_token_type)); 45 //if not, might be expired, register again 46 if (TextUtils.isEmpty(authToken)) { 47 final String password = accountManager.getPassword(account); 48 if (password != null) { 49 //get new token 50 authToken = account.name + password; 51 } 52 } 53 //without password, need to sign again 54 final Bundle bundle = new Bundle(); 55 if (!TextUtils.isEmpty(authToken)) { 56 bundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); 57 bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); 58 bundle.putString(AccountManager.KEY_AUTHTOKEN, authToken); 59 return bundle; 60 } 61 62 //no account data at all, need to do a sign 63 final Intent intent = new Intent(context, AuthActivity.class); 64 intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); 65 intent.putExtra(AuthActivity.ARG_ACCOUNT_NAME, account.name); 66 bundle.putParcelable(AccountManager.KEY_INTENT, intent); 67 return bundle; 68 } 69 70 @Override 71 public String getAuthTokenLabel(String authTokenType) { 72 // throw new UnsupportedOperationException(); 73 return null; 74 } 75 76 @Override 77 public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { 78 return null; 79 } 80 81 @Override 82 public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) 83 throws NetworkErrorException { 84 return null; 85 } 86 87 @Override 88 public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) 89 throws NetworkErrorException { 90 return null; 91 } 92 93 @Override 94 public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) 95 throws NetworkErrorException { 96 return null; 97 } 98 } 99 }
- 聲明Account服務
1 <service 2 android:name="**.XXAuthService" 3 android:exported="true" 4 android:process=":core"> 5 <intent-filter> 6 <action 7 android:name="android.accounts.AccountAuthenticator"/> 8 </intent-filter> 9 <meta-data 10 android:name="android.accounts.AccountAuthenticator" 11 android:resource="@xml/authenticator"/> 12 </service>
其中authenticator爲:
1 <?xml version="1.0" encoding="utf-8"?> 2 <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" 3 android:accountType="@string/account_auth_type" 4 android:icon="@drawable/icon" 5 android:smallIcon="@drawable/icon" 6 android:label="@string/app_name" 7 />
- 使用Account服務
同SyncAdapter,經過AccountManager使用
- 申請Token主要是經過 [AccountManager.getAuthToken]系列方法
- 添加帳號則經過 [AccountManager.addAccount]
- 查看是否存在帳號經過 [AccountManager.getAccountsByType]
1. [微信Android客戶端後臺保活經驗分享]
2. [Android Low Memory Killer原理]
3. [stackOverflow 上介紹的雙Service方法]
4. [Write your own Android Sync Adapter]
5. [Write your own Android Authenticator]
6. Android developer
- [android.accounts]
- [AccountManager]
- [AbstractAccountAuthenticator]
- [AccountAuthenticatorActivity]
- [Creating a Sync Adapter]
========
By SkySeraph-2016 www.skyseraph.com