Android App 保活服务的配置与禁用

Android应用的保活一般会从两个方面着手:一是如何提高应用的存活周期;二是如何唤醒关闭的应用。一般情况下会通过Android账户的自动同步机制和开机广播唤醒已关闭的应用;然后通过定时任务、前台服务、在屏幕上显示控件等方式提高应用的存活周期。在账户同步的服务和开机广播接收器中为应用开启一个前台Service就实现了应用保活的基本策略。下面分别介绍各个方式的实现。

Android应用自启与白名单

通过静态注册开机广播可以在系统启动时唤醒应用,应用被唤醒后可以检查并初始化前台服务等保活策略。

1. 首先我们需要实现BroadcastReceiver的子类作为开机广播的接收器,并在onReceive方法中处理业务逻辑。

public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//检查并初始化前台服务等保活策略
}
}

2. 然后我们将开机广播接收器注册到AndroidManifest.xml中,并增加开机动作过滤器。

<receiver
android:name=".receiver.BootReceiver"
android:directBootAware="true"
android:enabled="true"
android:exported="true">


<intent-filter android:priority="2147483647">
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
</intent-filter>
</receiver>

账户同步机制

Android应用可以在运行时注册系统账户,并通过service与系统账户进行关联,当系统运行时会在特定时期同步账户,同步账户的时候会启动所关联的service,在关联service中可以检查保活方案,通过账户同步机制可以唤醒被关闭的应用。

在开始之前首先定义两常量,在文中通过{常量名}的方式方式指代:
accountType=“xxxxxx”
contentAuthority=“xxxx”

1. 在项目res/xml中添加账户配置文件,指定文件名为account_sync_adapter.xml


2. 在项目res/xml中配置账户显示信息,命名为account_authenticator.xml

 <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"     android:accountType="com.qihoo.qa.ticker.account"     android:icon="@mipmap/ic_launcher"      android:label="@string/app_name" />

3. 实现ContentProvider的子类,并在AndroidManifest.xml中注册

public class AccountSyncProvider 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;
}
}

注册服务

<provider
android:name=".account.AccountSyncProvider"
android:authorities="{contentAuthority}"
android:enabled="true"
android:exported="true" />

4. 实现账户的认证service,系统会调用该service认证账户,由于是用于保活的空账户,所以不需要关注具体业务

public class AuthenticationService extends Service {

private AccountAuthenticator accountAuthenticator;

@Nullable
@Override
public IBinder onBind(Intent intent) {
return accountAuthenticator.getIBinder();//返回binder对象供系统使用
}

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

public 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 android:name=".account.AuthenticationService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/account_authenticator" />

</service>

5. 编写并配置账户同步服务,系统自动同步账户时回调此服务

public class AccountSyncService extends Service {

private SyncAdapter mSyncAdapter;

private static final String TAG = "SyncService";

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

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

;
public 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) {
//账户同步时回调此方法,在此处检测保活业务
}
}
}

在AndroidManifest.xml中注册账户同步服务

<service
android:name=".account.AccountSyncService"<!--指定service文件-->

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/account_sync_adapter" />

</service>

6. 添加账户并设置自动同步

accountName="test"
accountPwd="pwd"
//添加账户
AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
Account account = new Account(accountName, {accountType});
accountManager.addAccountExplicitly(account, accountPwd, new Bundle());
//设置账户同步
Account account = new Account(accountName, {accountType});
// 下面三个都需要同一个权限 WRITE_SYNC_SETTINGS
// 设置同步
ContentResolver.setIsSyncable(account, {contentAuthority}, 1);
// 自动同步
ContentResolver.setSyncAutomatically(account, {contentAuthority}, true);
// 设置同步周期
ContentResolver.addPeriodicSync(account, {contentAuthority}, new Bundle(), 1);

Schedule定时任务

1. 实现JobService的子类,用于执行任务时回调

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class LiveJobService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
//执行任务时回调
return false;
}

@Override
public boolean onStopJob(JobParameters params) {
return false;
}
}

2. 在AndroidManifest中配置任务service

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

3. 设置定时任务

JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
//setPersisted 在设备重启依然执行
JobInfo.Builder builder = new JobInfo.Builder(lastJobId+i, new ComponentName(context.getPackageName(),
LiveJobService.class.getName())).setPersisted(true);
// 50s后执行任务
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
builder.setPeriodic(50000);
} else {
// 延迟执行任务
builder.setMinimumLatency(50000);
}
jobScheduler.schedule(builder.build());

前台服务

保活服务一般在Service中后台运行,而Android系统对后台服务有一些列的运行限制,所以把服务绑定为前台服务会提高服务的优先级,在系统资源紧张时可以更好的运行。

1. 实现Service子类NotificationService,并在在onStartCommand方法中开启常驻通知

/**
* @author walker
* @date 2020/12/25.
* @description 在应用后台处理数据
*/

public class NotificationService extends Service {

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

@Override
public void onCreate() {
super.onCreate();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 如果Service被终止
// 当资源允许情况下,重启service
//绑定前台通知
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setForegroundService();
}
return START_STICKY;
}

/**
* 通过通知启动服务
*/

@androidx.annotation.RequiresApi(api = Build.VERSION_CODES.O)
public void setForegroundService() {
//设定的通知渠道名称
String channelName = "slient_name";
String CHANNEL_ID = "slient_id";
//设置通知的重要程度
int importance = NotificationManager.IMPORTANCE_LOW;
//构建通知渠道
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, channelName, importance);
channel.setDescription("test");
//在创建的通知渠道上发送通知
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID);
builder.setSmallIcon(R.drawable.ic_launcher) //设置通知图标
.setContentTitle("通知")//设置通知标题
.setContentText("前台服务")//设置通知内容
.setAutoCancel(true) //用户触摸时,自动关闭
.setOngoing(true);//设置处于运行状态
//向系统注册通知渠道,注册后不能改变重要性以及其他通知行为
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
//将服务置于启动状态 NOTIFICATION_ID指的是创建的通知的ID
startForeground(111, builder.build());
}

@Override
public void onDestroy() {
super.onDestroy();
}
}

2. 在AndroidManifest中配置服务


<service
android:name=".service.DataService"
android:enabled="true"
android:exported="false"
android:process=":sync" />

3. 在应用启动时开启服务

startService(new Intent(context,DataService.class));