更多精彩請(qǐng)直接訪問SkySeraph個(gè)人站點(diǎn): www.skyseraph.com
一祈远、基礎(chǔ)知識(shí)
1.Android 進(jìn)程優(yōu)先級(jí)
1.1 進(jìn)程優(yōu)先級(jí)等級(jí)一般分法:
- Activte process
- Visible Process
- Service process
- Background process
- Empty process
1.2 進(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;
2. 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.
3. 查看某個(gè)App的進(jìn)程
步驟(手機(jī)與PC連接)
- adb shell
- ps | grep 進(jìn)程名
- cat /proc/pid/oom_adj //其中pid是上述grep得到的進(jìn)程號(hào)
4. Android賬號(hào)和同步機(jī)制
屬于Android中較偏冷的知識(shí)胸嘁,具體參考 Ref 3/4/5
二瓶摆、現(xiàn)有方法
1. 網(wǎng)絡(luò)連接保活方法
a. GCM
b. 公共的第三方push通道(信鴿等)
c. 自身跟服務(wù)器通過輪詢性宏,或者長連接
具體實(shí)現(xiàn)請(qǐng)參考 微信架構(gòu)師楊干榮的"微信Android客戶端后臺(tái)比壕活經(jīng)驗(yàn)分享" (Ref-1).
2. 雙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();
}
}
3. 守護(hù)進(jìn)程及時(shí)拉起
AlarmReceiver酵使, ConnectReceiver荐吉,BootReceiver等
三、新方法(AccountSyncAdapter)
1. 思路:
利用Android系統(tǒng)提供的賬號(hào)和同步機(jī)制實(shí)現(xiàn)
2. 效果:
- 通過adb查看,運(yùn)行在后臺(tái)的服務(wù)其進(jìn)程號(hào)變成了1(優(yōu)先級(jí)僅次于前臺(tái)進(jìn)程)口渔,能提高進(jìn)程優(yōu)先級(jí)样屠,對(duì)比如下圖
- 進(jìn)程被系統(tǒng)kill后,可以由syn拉起
3. 風(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)開啟
4. 實(shí)現(xiàn):核心代碼如下
① 建立數(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"/>
② 建立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);
③ 建立賬號(hào)系統(tǒng) (Account Authenticator)
通過建立Account賬號(hào)倦畅,并關(guān)聯(lián)SyncAdapter服務(wù)實(shí)現(xiàn)同步
- 創(chuàng)建Account服務(wù)
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 {
// 添加賬號(hào) 示例代碼
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 {
// 認(rèn)證 示例代碼
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服務(wù)
<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為:
<?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服務(wù)
同SyncAdapter遮糖,通過AccountManager使用申請(qǐng)Token主要是通過 AccountManager.getAuthToken系列方法
添加賬號(hào)則通過 AccountManager.addAccount
查看是否存在賬號(hào)通過 AccountManager.getAccountsByType
Refs
-
Android developer
By SkySeraph-2016