前言
Android 系統為了保持系統運行流暢,在記憶體吃緊的情況下,會將一些行程給殺掉,以釋放一部分記憶體,然而,對于一些(如:QQ、微信等)比較重要的、我們希望能及時收到訊息的App,需要保持行程持續活躍,那么就需要實施一些保活措施來保證行程能夠持續存活,即 Android 行程保活,
Android 行程保活一般可以從兩個方面進行:
-
運行中保活:提高行程優先級,降低被系統殺掉的概率,
-
殺掉后拉活:被系統殺掉之后,將行程再拉活(重啟),
一、行程
默認情況下,同一個 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 運行到手機之后,然后殺掉行程,當賬戶開始同步時,行程又啟動了,結果如下:

以上就是利用賬戶同步進行拉活的主要核心思想,測驗程序中發現,不同系統表現不同,至于同步周期完全是由系統進行控制的,雖然比較穩定但是周期不可控,
掃描下方二維碼關注公眾號,獲取更多技術干貨,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/240083.html
標籤:其他

