一種提高Android應(yīng)用進(jìn)程存活率新方法

更多精彩請(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連接)

  1. adb shell
  2. ps | grep 進(jìn)程名
  3. 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ì)比如下圖
正常情況
采用AccountSyncAdapter方法后
  • 進(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"
/>

Refs

  1. 微信Android客戶端后臺(tái)保活經(jīng)驗(yàn)分享

  2. Android Low Memory Killer原理

  3. stackOverflow 上介紹的雙Service方法

  4. Write your own Android Sync Adapter

  5. Write your own Android Authenticator

  6. Android developer


By SkySeraph-2016

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末叠赐,一起剝皮案震驚了整個(gè)濱河市欲账,隨后出現(xiàn)的幾起案子屡江,更是在濱河造成了極大的恐慌,老刑警劉巖赛不,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惩嘉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡踢故,警方通過查閱死者的電腦和手機(jī)文黎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來殿较,“玉大人耸峭,你說我怎么就攤上這事×芨伲” “怎么了劳闹?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長洽瞬。 經(jīng)常有香客問我本涕,道長,這世上最難降的妖魔是什么伙窃? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任菩颖,我火速辦了婚禮,結(jié)果婚禮上为障,老公的妹妹穿的比我還像新娘晦闰。我一直安慰自己,他們只是感情好产场,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布鹅髓。 她就那樣靜靜地躺著,像睡著了一般京景。 火紅的嫁衣襯著肌膚如雪窿冯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天确徙,我揣著相機(jī)與錄音醒串,去河邊找鬼。 笑死鄙皇,一個(gè)胖子當(dāng)著我的面吹牛芜赌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播伴逸,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼缠沈,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起洲愤,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤颓芭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后柬赐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肛宋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年州藕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蔚约,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布候醒,位于F島的核電站灭衷,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏严嗜。R本人自食惡果不足惜粱檀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望漫玄。 院中可真熱鬧茄蚯,春花似錦、人聲如沸睦优。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽汗盘。三九已至皱碘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間隐孽,已是汗流浹背癌椿。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留菱阵,地道東北人踢俄。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像晴及,于是被迫代替她去往敵國和親都办。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容