Android 進程保活方案

前言

Android 系統爲了保持系統運行流暢,在內存喫緊的情況下,會將一些進程給殺掉,以釋放一部分內存。然而,對於一些(如:QQ、微信等)比較重要的、我們希望能及時收到消息的App,需要保持進程持續活躍,那麼就需要實施一些保活措施來保證進程能夠持續存活,即 Android 進程保活。

Android 進程保活一般可以從兩個方面進行:

  1. 運行中保活:提高進程優先級,降低被系統殺掉的概率。

  2. 殺掉後拉活:被系統殺掉之後,將進程再拉活(重啓)。

一、進程

默認情況下,同一個 App 的所有組件均運行在相同的進程中,但是也可以根據需要,通過在 AndroidManifest.xml 清單文件中配置來控制某些組件所屬的進程,因此,每一個 Android 應用啓動之後至少對應一個進程,有的應用是多個進程,目前主流的應用基本上都是多個進程。

1.1 進程分類

Android 系統將盡量長時間地保持應用進程,但爲了新建進程或運行更重要的進程,最終需要移除舊進程來回收內存。 爲了確定保留或終止哪些進程,系統會根據進程中正在運行的組件以及這些組件的狀態,將每個進程放入“重要性層次結構”中。 必要時,系統會首先消除重要性最低的進程,然後是重要性略低的進程,依此類推,以回收系統資源。

重要性層次結構一共有 5 級。以下列表按照重要程度列出了各類進程(重要性從高到低):

1. 前臺進程

如果一個進程滿足以下任一條件,即視爲前臺進程:

(1) 託管用戶正在交互的 Activity (已調用 Activity 的 onResume 方法)

(2) 託管某個 Service , 並且與用戶正在交互的 Activity 綁定。

(3) 託管正在前臺運行的 Service(服務已經調用 startForeground方法)

(4) 託管正在執行一個生命週期的 Service ( onCreate 、onStart 或 onDestroy)

(5) 託管正在執行其 onReceiver 方法的 BroadcastReceiver

通常在任意給定的時間前臺進程都不是很多,一般系統是不會殺死前臺進程的,只有在內存不足以支持它們繼續運行的情況下系統纔會殺死它們。

2. 可見進程

沒有任何前臺組件、但仍會影響用戶在屏幕上所見內容的進程。 如果一個進程滿足以下任一條件,即視爲可見進程:

(1) 託管不在前臺,但對用戶可見的Activity(已調用 onPause 方法)。

(2) 託管綁定到可見(或前臺)Activity 的 Service 。

用戶正在使用,看得到,但是摸不着,沒有覆蓋到整個屏幕,只有屏幕的一部分可見進程不包含任何前臺組件,一般系統也是不會殺死可見進程的,除非要在資源喫緊的情況下,要保持某個或多個前臺進程存活。

3. 服務進程

如果一個進程滿足以下任一條件,即視爲服務進程:

(1) 在運行已使用 startService 方法啓動的服務且不屬於上述兩個更高類別進程的進程

在內存不足以維持所有前臺進程和可見進程同時運行的情況下,服務進程會被殺死

4. 後臺進程

如果一個進程滿足以下任一條件,即視爲後臺進程:

(1) 包含目前對用戶不可見的 Activity 的進程(已調用 Activity 的 onStop() 方法)。

系統可能隨時終止它們,回收內存

5. 空進程

如果一個進程滿足以下任一條件,即視爲空進程:

(1) 不含任何活動應用組件的進程 。

保留這種進程的的唯一目的是用作緩存,以縮短下次在其中運行組件所需的啓動時間。 爲使總體系統資源在進程緩存和底層內核緩存之間保持平衡,系統往往會終止這些進程。

1.2 內存閾值

系統出於體驗和性能上的考慮,app 在退到後臺時系統並不會真正的 kill 掉這個進程,而是將其緩存起來。打開的應用越多,後臺緩存的進程也越多。在系統內存不足的情況下,系統開始依據自身的一套進程回收機制來判斷要 kill 掉哪些進程,以騰出內存來供給需要的 app 使用, 這套殺進程回收內存的機制就叫 Low Memory Killer。那這個不足怎麼來規定呢,那就是內存閾值,當內存小於內存閾值就開始殺進程,內存閾值是存放在 /sys/module/lowmemorykiller/parameters/minfree 文件中的,這個需要root權限才能查看,因此這裏就不繼續研究內存閾值了。

讀到這裏,如果現在內存不足了,要開始殺空進程了,如果空進程不止一個,難道要一次性將空進程全部殺掉嗎?當然不是的,進程是有它的優先級的,這個優先級通過進程的 oom_adj 值來反映,它是 linux 內核分配給每個進程的一個值,代表進程的優先級,進程回收機制就是根據這個優先級來決定是否進行回收,oom_adj 的值越小,進程的優先級越高,普通進程 oom_adj 值是大於等於 0 的,而系統進程 oom_adj 的值是小於0的。

我們可以通過 cat /proc/進程id/oom_adj 可以看到當前進程的 oom_adj 值,如下圖所示:

看到 oom_adj 值爲 0, 0 就是表這個進程屬於前臺進程,我們按下 Back 鍵,將應用切換到後臺,再次查看 oom_adj 值,如下所示:

oom_adj 值是多少,每個手機的廠商可能都不一樣,具體含義也就不做分析了,只用知道 oom_adj 越大,進程的優先級就越低,就越容易被系統殺掉

二、進程保活方案

2.1 開啓一個像素的Activity

基本思想,系統一般不會殺死前臺進程,所以要使得進程常駐,我們只需要在鎖屏的時候在本進程中開啓一個 Activity ,爲了欺騙用戶,我們讓這個 Activity 的大小是 1 像素,並且透明無切換動畫,在開屏幕的時候,把這個 Activity 關閉掉,所以這個就需要監聽系統鎖屏廣播。

創建一個一像素的 AliveActivity ,在屏幕關閉的時候把 AliveActivity 啓動起來,在開屏的時候把 AliveActivity 關閉掉,所以要監聽系統鎖屏廣播,以回調的形式通知 MainActivity 啓動或者關閉 AliveActivity。

public interface ScreenStateCallback {
    /**
     * 開屏
     */
    void onScreenOn();
​
    /**
     * 鎖屏
     */
    void onScreenOff();
}public class ScreenStateManager {
    private Context mContext;
    private WeakReference<AliveActivity> mAliveActivityWrf;
    private static volatile ScreenStateManager instance;
    private ScreenStateReceiver mReceiver;
    private ScreenStateCallback mCallback;
​
    private ScreenStateManager(Context context) {
        this.mContext = context;
        mReceiver = new ScreenStateReceiver();
    }
​
    /**
     * 獲取ScreenStateManager對象
     *
     * @param context
     * @return
     */
    public static ScreenStateManager getInstance(Context context) {
        if (instance == null) {
            synchronized (ScreenStateManager.class) {
                if (instance == null) {
                    instance = new ScreenStateManager(context.getApplicationContext());
                }
            }
        }
        return instance;
    }
​
    /**
     * 設置監聽
     *
     * @param callback
     */
    public void setScreenStateCallback(ScreenStateCallback callback) {
        this.mCallback = callback;
    }
​
​
    /**
     * 註冊屏幕狀態廣播
     */
    public void registerScreenStateBroadcastReceiver() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        mContext.registerReceiver(mReceiver, filter);
    }
​
    /**
     * 解註冊廣播
     */
    public void unregisterScreenStateBroadcastReceiver() {
        mContext.unregisterReceiver(mReceiver);
    }
​
​
    /**
     * 設置AliveActivity
     *
     * @param aliveActivity
     */
    public void setAliveActivity(AliveActivity aliveActivity) {
        this.mAliveActivityWrf = new WeakReference<>(aliveActivity);
    }
​
    /**
     * 開啓AliveActivity
     */
    public void openAliveActivity() {
        AliveActivity.openAliveActivity(mContext);
    }
​
​
    /**
     * 關閉AliveActivity
     */
    public void closeAliveActivity() {
        if (mAliveActivityWrf != null) {
            AliveActivity aliveActivity = mAliveActivityWrf.get();
            if (aliveActivity != null) {
                aliveActivity.finish();
            }
        }
    }
​
    /**
     * 屏幕狀態廣播
     */
    private class ScreenStateReceiver extends BroadcastReceiver {
​
        @Override
        public void onReceive(Context context, Intent intent) {
            if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
                // 開屏
                if (mCallback != null) {
                    mCallback.onScreenOn();
                }
            } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
                // 鎖屏
                if (mCallback != null) {
                    mCallback.onScreenOff();
                }
            }
        }
    }
public class AliveActivity extends Activity {
  private static final String TAG = AliveActivity.class.getSimpleName();
​
  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_alive);
​
      Log.d(TAG, "AliveActivity onCreate");
​
      // 設置爲1像素
      Window window = getWindow();
      // 放在左上角
      window.setGravity(Gravity.START | Gravity.TOP);
      WindowManager.LayoutParams params = window.getAttributes();
      // 寬高設置爲1個像素
      params.width = 1;
      params.height = 1;
      // 起始座標
      params.x = 0;
      params.y = 0;
      window.setAttributes(params);
       
      ScreenStateManager.getInstance(this).setAliveActivity(this);
​
  }
​
  /**
    * 打開AliveActivity
    *
    * @param context
    */
  public static void openAliveActivity(Context context) {
      Intent intent = new Intent(context, AliveActivity.class);
      intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      context.startActivity(intent);
  }
​
  @Override
  protected void onDestroy() {
      super.onDestroy();
      Log.d(TAG, "AliveActivity onDestroy");
  }
}

MainActivity 的修改如下:

public class MainActivity extends AppCompatActivity {
    private ScreenStateManager manager;
​
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
​
​
        manager = ScreenStateManager.getInstance(this);
        // 註冊廣播
        manager.registerScreenStateBroadcastReceiver();
        manager.setScreenStateCallback(new ScreenStateCallback() {
            @Override
            public void onScreenOn() {
                manager.closeAliveActivity();
            }
​
            @Override
            public void onScreenOff() {
                manager.openAliveActivity();
            }
        });
    }
​
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (manager != null) {
            manager.unregisterScreenStateBroadcastReceiver();
        }
    }
}

運行之後,先點擊home鍵,再進行鎖屏,結果如下

由以上結果可以看出,在鎖屏的時候 AliveActivity 已經啓動,進程的優先級也提高了。

然後再開屏,結果如下所示:

當我們開屏時 ,AliveActivity 也退出了。

缺點: 存在一個AliveActivity 不夠乾淨,同時需要在鎖屏時才能監聽到,如果用戶一直處於亮屏狀態,oom_adj 的值不會變小,如果系統內存不足,還是會被殺死。

2.2 前臺服務

原理是通過調用 startForeground 方法提示 Service 的優先級,讓 Services 變爲前臺服務。

對於 API level < 18:調用 startForeground(ID,new Notification()),發送空的Notification ,圖標則不會顯示。

對於 API level >= 18 並且 API leve < 26:

調用 startForeground(ID,new Notification()),發送空的Notification ,圖標卻會顯示。

因此在需要提優先級的 service A 啓動一個 InnerService,兩個服務同時 startForeground,且綁定同樣的 ID。Stop 掉 InnerService ,這樣通知欄圖標即被移除,這種方式在 API leve >=26 以上就不可以生效了,即使 Stop 調 InnerServices ,通知欄依然會有顯示。

代碼如下:

public class AliveService extends Service {
    public static final int NOTIFICATION_ID = 0x001;
    private static final String NOTIFICATION_CHANNEL_ID = "alive_notification_id";
​
    @Override
    public void onCreate() {
        super.onCreate();
        // API 18 以下,直接發送 Notification 並將其置爲前臺
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
            startForeground(NOTIFICATION_ID, new Notification());
        } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            //API 18以上,發送Notification並將其置爲前臺後,啓動InnerService
            Notification.Builder builder = new Notification.Builder(this);
            builder.setSmallIcon(R.mipmap.ic_launcher);
            startForeground(NOTIFICATION_ID, builder.build());
            startService(new Intent(this, InnerService.class));
        } 
    }
​
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return null;
    }
​
    @Override
    public void onDestroy() {
        super.onDestroy();
        stopForeground(true);
    }
​
    public static class InnerService extends Service {
        @Override
        public void onCreate() {
            super.onCreate();
                //發送與ALiveService中ID相同的Notification,然後將其取消並取消自己的前臺顯示
                Notification.Builder builder = new Notification.Builder(this);
                builder.setSmallIcon(R.mipmap.ic_launcher);
                startForeground(NOTIFICATION_ID, builder.build());
​
                // 100ms 銷燬
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        stopForeground(true);
                        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                        manager.cancel(NOTIFICATION_ID);
                        stopSelf();
                    }
                }, 100);
            } else {
                //發送與ALiveService中ID相同的Notification,然後將其取消並取消自己的前臺顯示
                Notification notification = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .build();
                notification.flags |= Notification.FLAG_NO_CLEAR;
                startForeground(NOTIFICATION_ID, notification);
​
                // 100ms 銷燬
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        stopForeground(true);
                        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                        manager.cancel(NOTIFICATION_ID);
                        stopSelf();
                    }
                }, 100);
            }
        }
​
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    }
}
在沒有采取前臺服務之前,啓動應用,oom_adj 值是 0,按下返回鍵之後,變成 8(不同ROM可能不一樣),如下圖所示:

在採取前臺服務之後,啓動應用,oom_adj 值是 0,按下返回鍵之後,變成 1(不同ROM可能不一樣),確實進程的優先級有所提高。

 

三、進程拉活

3.1 相互喚醒

相互喚醒的意思就是,假如你手機裏裝了支付寶、淘寶、天貓、UC等阿里系的 app,那麼你打開任意一個阿里系的 app 後,有可能就順便把其他阿里系的 app 給喚醒了。這個完全有可能的。

如果應用想保活,要是 QQ,微信願意救你也行,有多少手機上沒有 QQ,微信呢?或者像友盟、小米、華爲、信鴿這種推送 SDK,也存在喚醒 app 的功能。

3.2 廣播拉活

通過接收系統廣播去拉活進程,但是 Android 在7.0之後對廣播增加了一些限制,在8.0以後就更加嚴格了,現在接收系統廣播的拉活方式基本上已經用不了了。

3.3 JobScheduler

JobScheduler 簡單來說就是一個系統定時任務,在 app 達到一定條件時可以指定執行任務,且如果 app 被強迫終止,此前預定的任務還可執行。與普通定時器不同的是其調度由系統來完成,因此可以用來做進程保活。

JobSchedule 需要在android 5.0系統以上才能使用。

JobScheduler 是作爲進程死後復活的一種手段,native 進程方式最大缺點是費電, Native 進程費電的原因是感知主進程是否存活有兩種實現方式,在 Native 進程中通過死循環或定時器,輪訓判斷主進程是否存活,當主進程不存活時進行拉活。其次 5.0 以上系統不支持。 但是 JobScheduler 可以替代在 Android5.0 以上 native 進程方式,這種方式即使用戶強制關閉,也能被拉起來。

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class AliveJobService extends JobService {
    private static final String TAG = AliveJobService.class.getSimpleName();
​
    public static void startJobScheduler(Context context) {
        try {
            JobScheduler jobScheduler = (JobScheduler) context.getSystemService(JOB_SCHEDULER_SERVICE);
            JobInfo.Builder builder = new JobInfo.Builder(1,
                    new ComponentName(context.getPackageName(), AliveJobService.class.getName()));
​
            // 設置設備重啓依然執行
            builder.setPersisted(true);
​
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                //7.0以上延遲1s執行
                builder.setMinimumLatency(1000);
            } else {
                //每隔1s執行一次job
                builder.setPeriodic(1000);
            }
​
            jobScheduler.schedule(builder.build());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
​
    @Override
    public boolean onStartJob(JobParameters params) {
        Log.e(TAG, "開啓job");
        // 7.0 以上開啓輪詢
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            startJobScheduler(this);
        }
        return false;
    }
​
    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
​
}

在 AndroidManifest.xml 中配置 Service 。

<service
    android:name=".service.AliveJobService"
    android:enabled="true"
    android:exported="true"
    android:permission="android.permission.BIND_JOB_SERVICE" />

在 MainActivity 的 onCreate 方法中調用。

AliveJobService.startJobScheduler(this);

運行,然後在 kill ,最後 Jobschedule 依然在運行,而且進程重新啓動,結果如下所示:

 

3.4 雙進程守護

雙進程守護本質上是開啓兩個進程,一個主進程(包含一個本地服務)和一個子進程(包含一個遠程服務),當其中一個進程被殺死時,另一個進程會自動的把被殺死的那個進程拉活。原理圖如下所示:

(1) 實現一個 AIDL 文件

此處僅僅是爲了拉活,不需要遠程調用某些功能,可以不用具體實現,但是不能缺少,創建過程如下所示:

代碼如下所示:

// IAliveAidlInterface.aidl
package com.lx.keep.alive;

// Declare any non-default types here with import statements

interface IAliveAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

(2) 實現主進程的本地服務

public class LocalAliveService extends Service {
    private static final String TAG = LocalAliveService.class.getSimpleName();
    private ServiceConnection mConnection;
    private LocalAliveBinder mBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        mBinder = new LocalAliveBinder();
        mConnection = new LocalAliveServiceConnect();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //綁定本地守護Service
        bindService(new Intent(this, RemoteAliveService.class), mConnection, BIND_AUTO_CREATE);
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    class LocalAliveServiceConnect implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //服務連接後回調
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "遠程子進程可能被幹掉了,拉活");
            //連接中斷後回調,再啓動子進程所在的Service,並進行綁定,通過啓動子進程的遠程服務強行拉活
            startService(new Intent(LocalAliveService.this, RemoteAliveService.class));
            bindService(new Intent(LocalAliveService.this, RemoteAliveService.class), mConnection,
                    BIND_AUTO_CREATE);
        }
    }

    class LocalAliveBinder extends IAliveAidlInterface.Stub {

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }
    }
}

(3) 實現子進程的遠程服務

public class RemoteAliveService extends Service {
    private static final String TAG = RemoteAliveService.class.getSimpleName();
    private ServiceConnection mConnection;
    private RemoteAliveBinder mBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        mBinder = new RemoteAliveBinder();
        mConnection = new RemoteAliveServiceConnect();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //綁定本地守護Service,必須實現AIDL否則bindService在這沒有作用
        bindService(new Intent(this, LocalAliveService.class), mConnection, BIND_AUTO_CREATE);
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    class RemoteAliveServiceConnect implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //服務連接後回調
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "本地主進程可能被幹掉了,拉活");
            //連接中斷後回調,再啓動主進程所在的Service,並進行綁定,通過啓動主進程的本地服務強行拉活
            startService(new Intent(RemoteAliveService.this, LocalAliveService.class));
            bindService(new Intent(RemoteAliveService.this, LocalAliveService.class), mConnection,
                    BIND_AUTO_CREATE);
        }
    }

    class RemoteAliveBinder extends IAliveAidlInterface.Stub {

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }
    }
}

(4) AndroidManifest.xml 配置 Service 如下:

<service
    android:name=".service.RemoteAliveService"
    android:enabled="true"
    android:exported="true"
    android:process=":remote"></service>
<service
    android:name=".service.LocalAliveService"
    android:enabled="true"
    android:exported="true"/>

(5) 開啓雙進程守護

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            //雙進程Service守護
            startService(new Intent(this, LocalAliveService.class));//啓動主線程守護服務
            startService(new Intent(this, RemoteAliveService.class));//啓動主線程守護服務
        }
    }
}

說明: 在 Android O之後,當應用進入後臺時,過一段時間就會變成 idle 狀態,這時就不能通過 startService 啓動一個服務,不然會報如下錯誤:

在 Android O 之後啓動服務通過 startForegroundService() 啓動一個前臺服務,並且在系統創建 Service 後,需要在一定時間內調用startForeground( )讓 Service 爲用戶可見通知,否則則系統將停止此 Service,拋出 ANR,但是會在通知欄有提示框,上文有說明。

因此兼容方案就根據實際情況而選擇了。

運行結果如下所示:

3.5 賬戶同步拉活

手機系統設置裏會有 Account 賬戶一項功能,任何第三方 App 都可以通過此功能將我們自己的 App 註冊到這個 Account 賬戶中,並且將數據在一定時間內同步到服務器中去。系統在將 App 賬戶同步時,自動將爲開啓的 App 進程拉活

(1) 開啓賬戶服務

AuthenticationService 繼承自 Service 本質上是一個 AIDL ,提供給其他的進程使用的,主要我們實現並且聲明瞭之後,android 系統會通過 android.accounts.AccountAuthenticator 這個 Action 找到它,並通過它來把我們自己的賬號註冊到系統設置界面,其中AccountAuthenticator 是一個繼承自 AbstractAccountAuthenticator 的類,而 AbstractAccountAuthenticator 是用於實現對手機系統設置裏“賬號與同步”中 Account 的添加、刪除和驗證等一些基本功能。很明顯 AbstractAccountAuthenticator 裏面有個繼承於IAccountAuthenticator.Stub 的內部類,以用來對 AbstractAccountAuthenticator 的遠程接口調用進行包裝。所以可以通過AbstractAccountAuthenticator 的 getIBinder() 方法,返回內部類的 IBinder 形式。

public class AuthenticationService extends Service {
    private AccountAuthenticator accountAuthenticator;


    @Override
    public void onCreate() {
        super.onCreate();
        accountAuthenticator = new AccountAuthenticator(this);
    }

    @Override
    public IBinder onBind(Intent intent) {
        // 返回操作數據的Binder
        return accountAuthenticator.getIBinder();
    }

    /**
     * 賬戶操作類
     */
    static class AccountAuthenticator extends AbstractAccountAuthenticator {

        public AccountAuthenticator(Context context) {
            super(context);
        }

        @Override
        public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
            return null;
        }

        @Override
        public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
            return null;
        }

        @Override
        public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
            return null;
        }

        @Override
        public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
            return null;
        }

        @Override
        public String getAuthTokenLabel(String authTokenType) {
            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;
        }
    }
}

在 AndroidManifest.xml 中配置 service。

<service
    android:name=".service.AuthenticationService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.accounts.AccountAuthenticator"/>
    </intent-filter>
    <meta-data
        android:name="android.accounts.AccountAuthenticator"
        android:resource="@xml/authenticator"/>
</service>

在 xml 中添加 authenticator.xml 。

其中icon、label分別是Account列表中的圖標和顯示名稱,而accountType則是操作用戶所必須的參數之一

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.lx.keep.alive.account"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name" />

<!--accountType表示賬戶類型,必須唯一-->

經過以上步驟之後,安裝 Apk,再次打開設置中賬戶 -> 添加賬戶,你會發現原來的 Account 列表多了一行數據,說明我們的 App 也可以支持這個Account 系統了。

(2) 添加賬戶

public class AccountHelper {
    // 與 authenticator.xml 中  accountType 一致
    private static final String ACCOUNT_TYPE = "com.lx.keep.alive.account";

    /**
     * 添加賬戶 需要 "android.permission.GET_ACCOUNTS"權限
     * @param context
     */
    public static void addAccount(Context context){
        AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
        Account[] accounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
        if(accounts.length >0 ){
            // 賬戶已存在
            return;
        }

        Account account = new Account("test",ACCOUNT_TYPE);
        // 添加賬戶
        accountManager.addAccountExplicitly(account,"123456",new Bundle());
    }
}

調用 addAccount 這個方法之後就會在系統設置的 Account 界面多了一個 Account 。如下圖所示:

(3) 同步服務

創建一個 Service 作爲同步 Service,並且在 onBind 返回 AbstractThreadedSyncAdapter 的 getSyncAdapterBinder。

public class SyncAccountService extends Service {
    private static final String TAG = SyncAccountService.class.getSimpleName();
    private SyncAdapter syncAdapter;

    @Override
    public void onCreate() {
        super.onCreate();
        syncAdapter = new SyncAdapter(this, true);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return syncAdapter.getSyncAdapterBinder();
    }

    static class SyncAdapter extends AbstractThreadedSyncAdapter {

        public SyncAdapter(Context context, boolean autoInitialize) {
            super(context, autoInitialize);
        }

        @Override
        public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
            Log.d(TAG, "賬戶同步了");
        }
    }
}

在 AndroidManifest.xml 中配置 service。

<service
    android:name=".service.SyncAccountService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.content.SyncAdapter" />
    </intent-filter>
    <meta-data
        android:name="android.content.SyncAdapter"
        android:resource="@xml/sync_account_adapter" />
</service>

在 xml 中添加 sync_account_adapter.xml 。

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.lx.keep.alive.account"
    android:allowParallelSyncs="true"
    android:contentAuthority="com.lx.keep.alive.provider"
    android:isAlwaysSyncable="true"
    android:supportsUploading="false"
    android:userVisible="false" />

<!--contentAuthority 系統在進行賬戶同步的時候會查找 此auth的ContentProvider-->
<!--accountType表示賬戶類型,與authenticator.xml裏要一致-->
<!-- userVisible 是否在“設置”中顯示-->
<!-- supportsUploading 是否必須notifyChange通知才能同步-->
<!-- allowParallelSyncs 允許多個賬戶同時同步-->
<!--isAlwaysSyncable 設置所有賬號的isSyncable爲1-->

(4) 創建 ContentProvider

public class AccountProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
}

在 AndroidManifest.xml 中配置 ContentProvider 。

<provider
    android:name=".helper.AccountProvider"
    android:authorities="com.lx.keep.alive.provider"
    android:exported="false" />

(5) 開啓同步

爲了達到進程保活的效果,可以開啓自動同步。時間間隔雖然設置了1s,但是 Android 本身爲了考慮同步所帶來的消耗和減少喚醒設備的次數,1s只是一個參考時間

public class AccountHelper {
    // 與 authenticator.xml 中  accountType 一致
    private static final String ACCOUNT_TYPE = "com.lx.keep.alive.account";
    private static final String CONTENT_AUTHORITY = "com.lx.keep.alive.provider";

    // ...

    /**
     * 設置賬戶同步,即告知系統我們需要系統爲我們來進行賬戶同步,只有設置了之後系統纔會自動去
     * 觸發SyncAccountAdapter#onPerformSync方法
     */
    public static void autoSyncAccount(){
        Account account = new Account("test",ACCOUNT_TYPE);
        // 設置同步
        ContentResolver.setIsSyncable(account,CONTENT_AUTHORITY,1);
        // 設置自動同步
        ContentResolver.setSyncAutomatically(account,CONTENT_AUTHORITY,true);
        // 設置同步週期
        ContentResolver.addPeriodicSync(account,CONTENT_AUTHORITY,new Bundle(),1);
    }

}

最後在 MainActivity 的 onCreate 中調用

AccountHelper.addAccount(this);
AccountHelper.autoSyncAccount();

測試, 將 App 運行到手機之後,然後殺掉進程,當賬戶開始同步時,進程又啓動了,結果如下:

以上就是利用賬戶同步進行拉活的主要核心思想,測試過程中發現,不同系統表現不同,至於同步週期完全是由系統進行控制的,雖然比較穩定但是週期不可控。

 

掃描下方二維碼關注公衆號,獲取更多技術乾貨。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章