基礎知識
Android 程式優先順序
1 程式優先順序等級一般分法
- Activte process
- Visible Process
- Service process
- Background process
- Empty process
2 Service技巧
- onStartCommand返回START_STICKY
- onDestroy中startself
- Service後臺變前置,setForground(true)
- android:persistent = “true”
3 程式優先順序號
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
// 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系統記憶體不足時,系統會殺掉一部分程式以釋放空間,誰生誰死的這個生死大權就是由LMK所決定的,這就是Android系統中的Low Memory Killer,其基於Linux的OOM機制,其閾值定義如下面所示的lowmemorykiller檔案中,當然也可以通過系統的init.rc實現自定義。
lowmemorykiller.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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中通過程式的oom_adj與佔用記憶體的大小決定要殺死的程式,oom_adj值越小越不容易被殺死。其中,lowmem_minfree是殺程式的時機,誰被殺,則取決於lowmem_adj,具體值得含義參考上面 Android程式優先順序 所述.
② 在init.rc中定義了init程式(系統程式)的oom_adj為-16,其不可能會被殺死(init的PID是1),而前臺程式是0(這裡的前臺程式是指使用者正在使用的Activity所在的程式),使用者按Home鍵回到桌面時的優先順序是6,普通的Service的程式是8.
init.rc
1 2 |
# Set init and its forked children's oom_adj. write /proc/1/oom_adj -16 |
關於Low Memory Killer的具體實現原理可參考Ref-2.
檢視某個App的程式
步驟(手機與PC連線)
- adb shell
- ps | grep 程式名
- cat /proc/pid/oom_adj //其中pid是上述grep得到的程式號
Linux AM命令
am命令:在Android系統中通過adb shell 啟動某個Activity、Service、撥打電話、啟動瀏覽器等操作Android的命令.其原始碼在Am.java中,在shell環境下執行am命令實際是啟動一個執行緒執行Am.java中的主函式(main方法),am命令後跟的引數都會當做執行時引數傳遞到主函式中,主要實現在Am.java的run方法中。
撥打電話
命令:am start -a android.intent.action.CALL -d tel:電話號碼
示例:am start -a android.intent.action.CALL -d tel:10086
開啟一個網頁
命令:am start -a android.intent.action.VIEW -d 網址
示例:am start -a android.intent.action.VIEW -d http://www.skyseraph.com
啟動一個服務
命令:am startservice <服務名稱>
示例: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
用來監聽到通知的傳送以及移除和排名位置變化,如果我們註冊了這個服務,當系統任何一條通知到來或者被移除掉,我們都能通過這個service來監聽到,甚至可以做一些管理工作。
Android賬號和同步機制
屬於Android中較偏冷的知識,具體參考 Ref 3 /4 /5
Android多程式
- 實現:android:process
- 好處:一個獨立的程式可以充分利用自己的RAM預算,使其主程式擁有更多的空間處理資源。此外,作業系統對待執行在不同元件中的程式是不一樣的。這意味著,當系統執行在低可用記憶體的條件時,並不是所有的程式都會被殺死
- 大坑:每一個程式將有自己的Dalvik VM例項,意味著你不能通過這些例項共享資料,至少不是傳統意義上的。例如,靜態欄位在每個程式都有自己的值,而不是你傾向於相信的只有一個值。
- 更多詳細請參考Ref 9
現有方法
網路連線保活方法
A. GCM
B. 公共的第三方push通道(信鴿等)
C. 自身跟伺服器通過輪詢,或者長連線
具體實現請參考 微信架構師楊幹榮的”微信Android客戶端後臺保活經驗分享” (Ref-1).
雙service(通知欄) 提高程式優先順序
思路:(API level > 18 )
- 應用啟動時啟動一個假的Service(FakeService), startForeground(),傳一個空的Notification
- 啟動真正的Service(AlwaysLiveService),startForeground(),注意必須相同Notification ID
- FakeService stopForeground()
效果:通過adb檢視,執行在後臺的服務其程式號變成了1(優先順序僅次於前臺程式)
風險:Android系統前臺service的一個漏洞,可能在6.0以上系統中修復
實現:核心程式碼如下
- AlwaysLiveService 常駐記憶體服務
1 2 3 4 5 6 |
@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 臨時服務
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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及時拉起
AlarmReceiver, ConnectReceiver,BootReceiver等
- Service設定(見上面基礎部分)
- 通過監聽系統廣播,如開機,鎖屏,亮屏等重新啟動服務
- 通過alarm定時器,啟動服務
守護程式/程式互拉
在分析360手機助手app時,發現其擁有N多個程式,一個程式kill後會被其它未kill的程式拉起,這也是一種思路吧,雖然有點流氓~
守護程式一般有這樣兩種方式:
- 多個java程式守護互拉
- 底層C守護程式拉起App上層/java程式
Linux Am命令開啟後臺程式
一種底層實現讓程式不被殺死的方法,在Android4.4以上可能有相容性問題,具體參考Ref-7
NotificationListenerService通知
一種需要使用者允許特定許可權的系統拉起方式,4.3以上系統
前臺浮窗
有朋友提出一種應用退出後啟動一個不可互動的浮窗,個人覺得這種方法是無效的,讀者有興趣可以一試
新方法(AccountSync)
思路
利用Android系統提供的賬號和同步機制實現
效果
- 通過adb檢視,執行在後臺的服務其程式號變成了1(優先順序僅次於前臺程式),能提高程式優先順序,對比如下圖
正常情況
- 程式被系統kill後,可以由syn拉起
風險
- SyncAdapter時間進度不高,往往會因為手機處於休眠狀態,而時間往後調整,同步間隔最低為1分鐘
- 使用者可以單獨停止或者刪除,有些手機賬號預設是不同步的,需要手動開啟
實現 (核心程式碼)
1 建立資料同步系統(ContentProvider)
通過一個ContentProvider用來作資料同步,由於並沒有實際資料同步,所以此處就直接建立一個空的ContentProvider即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
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; } } |
然後再Manifest中宣告
1 2 3 4 5 |
<provider android:name="**.XXAccountProvider" android:authorities="@string/account_auth_provider" android:exported="false" android:syncable="true"/> |
2 建立Sync系統 (SyncAdapter)
通過實現SyncAdapter這個系統服務後, 利用系統的定時器對程式資料ContentProvider進行更新,具體步驟為:
- 建立Sync服務
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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服務
1 2 3 4 5 6 7 8 9 10 11 12 |
<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為:
1 2 3 4 5 6 7 |
<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"/> |
引數說明:
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 2 3 4 5 6 7 8 9 |
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); } |
新增賬號
1 2 3 |
Account account = AccountService.GetAccount(); AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); accountManager.addAccountExplicitly(...) |
同步週期設定
1 2 3 |
ContentResolver.setIsSyncable(account, CONTENT_AUTHORITY, 1); ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, true); ContentResolver.addPeriodicSync(account, CONTENT_AUTHORITY, new Bundle(), SYNC_FREQUENCY); |
3 建立賬號系統 (Account Authenticator)
通過建立Account賬號,並關聯SyncAdapter服務實現同步
- 建立Account服務
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
public class XXAuthService extends Service { private XXAuthenticator mAuthenticator; @Override public void onCreate() { mAuthenticator = new XXAuthenticator(this); } private XXAuthenticator getAuthenticator() { if (mAuthenticator == null) mAuthenticator = new XXAuthenticator(this); return mAuthenticator; } @Override public IBinder onBind(Intent intent) { return getAuthenticator().getIBinder(); } class XXAuthenticator extends AbstractAccountAuthenticator { private final Context context; private AccountManager accountManager; public XXAuthenticator(Context context) { super(context); this.context = context; accountManager = AccountManager.get(context); } @Override public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { // 新增賬號 示例程式碼 final Bundle bundle = new Bundle(); final Intent intent = new Intent(context, AuthActivity.class); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); bundle.putParcelable(AccountManager.KEY_INTENT, intent); return bundle; } @Override public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { // 認證 示例程式碼 String authToken = accountManager.peekAuthToken(account, getString(R.string.account_token_type)); //if not, might be expired, register again if (TextUtils.isEmpty(authToken)) { final String password = accountManager.getPassword(account); if (password != null) { //get new token authToken = account.name + password; } } //without password, need to sign again final Bundle bundle = new Bundle(); if (!TextUtils.isEmpty(authToken)) { bundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); bundle.putString(AccountManager.KEY_AUTHTOKEN, authToken); return bundle; } //no account data at all, need to do a sign final Intent intent = new Intent(context, AuthActivity.class); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); intent.putExtra(AuthActivity.ARG_ACCOUNT_NAME, account.name); bundle.putParcelable(AccountManager.KEY_INTENT, intent); return bundle; } @Override public String getAuthTokenLabel(String authTokenType) { // throw new UnsupportedOperationException(); return null; } @Override public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { return null; } @Override public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException { return null; } @Override public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { return null; } @Override public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException { return null; } } } |
- 宣告Account服務
1 2 3 4 5 6 7 8 9 10 11 12 |
<service android:name="**.XXAuthService" android:exported="true" android:process=":core"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator"/> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator"/> </service> |
其中authenticator為:
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="utf-8"?> <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" android:accountType="@string/account_auth_type" android:icon="@drawable/icon" android:smallIcon="@drawable/icon" android:label="@string/app_name" /> |
- 使用Account服務
同SyncAdapter,通過AccountManager使用- 申請Token主要是通過 AccountManager.getAuthToken)系列方法
- 新增賬號則通過 AccountManager.addAccount)
- 檢視是否存在賬號通過 AccountManager.getAccountsByType)