前言
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 值,如下所示:
imageoom_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 已经启动,进程的优先级也提高了。
然后再开屏,结果如下所示:
image当我们开屏时 ,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可能不一样),如下图所示:
image
在采取前台服务之后,启动应用,oom_adj 值是 0,按下返回键之后,变成 1(不同ROM可能不一样),确实进程的优先级有所提高。
image三、进程拉活
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 双进程守护
双进程守护本质上是开启两个进程,一个主进程(包含一个本地服务)和一个子进程(包含一个远程服务),当其中一个进程被杀死时,另一个进程会自动的把被杀死的那个进程拉活。原理图如下所示:
image(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 启动一个服务,不然会报如下错误:
image在 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 运行到手机之后,然后杀掉进程,当账户开始同步时,进程又启动了,结果如下:
以上就是利用账户同步进行拉活的主要核心思想,测试过程中发现,不同系统表现不同,至于同步周期完全是由系统进行控制的,虽然比较稳定但是周期不可控。
更多干货资料可点此处。
网友评论