版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
一、进程的优先级
Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,需要清除旧进程来回收内存。 为了确定保留或终止哪些进程,系统会对进程进行分类。 需要时,系统会首先消除重要性最低的进程,然后是清除重要性稍低一级的进程,依此类推,以回收系统资源。
进程的重要性层次结构一共有 6 级:(官方文档只写 5 级)
1.前台进程(foreground)
用户当前操作所必需的进程。如果一个进程满足以下任一条件,即视为前台进程:
- 托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)
- 托管某个 Service,后者绑定到用户正在交互的 Activity
- 托管正在“前台”运行的 Service(服务已调用 startForeground())
- 托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
- 托管正执行其 onReceive() 方法的 BroadcastReceiver
通常,在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。
2.可见进程(visible)
没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件,即视为可见进程:
- 托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。
- 托管绑定到可见(或前台)Activity 的 Service。
可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。
3.服务进程(secondary server)
正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。
4.后台进程(hidden)
包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。 有关保存和恢复状态的信息,请参阅 Activity文档。
5.内容供应节点(content provider)
没有程序实体,进提供内容供别的程序去用的,比如日历供应节点,邮件供应节点等。在终止进程时,这类程序应该有较高的优先权。
6.空进程(empty)
不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。
具体可以查看安卓官方中文网相关内容。
二、进程释放
1.Low Memory Killer
系统出于体验和性能上的考虑,app 在退到后台时系统并不会真正的 kill 掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。
在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要 kill 掉哪些进程,以腾出内存来供给需要的 app, 这套杀进程回收内存的机制就叫 Low Memory Killer。
查看阈值指令:
cat /sys/module/lowmemorykiller/parameters/minfree
这个结果是6个数字,以逗号隔开,如:
这 6 个值分别对应上面 6 级进程的的阈值。单位是 page, 1 page 为 4 个 KB,如最后一个为 46080 * 4 = 184320 KB,为 180 MB。当内存少于 180 MB 的时候,会进行回收空进程所占用的内存。
内存阈值在不同的手机上不一样,一旦低于该值,Android 便开始按顺序关闭进程。
2.oom_adj
进程的优先级通过进程的 adj 值来反映,它是 linux 内核分配给每个系统进程的一个值,进程回收机制根据这个值来决定是否进行回收。adj 的值越小,进程的优先级越高。
查看当前进程的 adj 值(需要 root 权限):
cat /proc/进程 id/oom_adj
应用正在运行:
按 Home 键隐藏后(不同的ROM可能不一样):
进程 id 可以直接通过开发工具查看:
adj 值的解释:
adj 越大,占用内存越多会被先被 kill 掉,所以保活就成了降低 oom_adj 的值,以及如何使得我们应用占的内存最少。
三、提权
由上面分析可以知道,在内存不足的时候,安卓系统会根据应用程序的 adj 来判断释放程序的先后顺序,为了使我们的应用更久的运行在安卓系统中,我们可以通过一些方法使应用的 adj 尽可能的变小,这样就可以尽可能的晚一点被释放,甚至不被释放。
1.Activity 提权
监控手机锁屏解锁事件,在屏幕锁屏时启动 1 个像素透明的 Activity,在用户解锁时将 Activity 销毁掉。从而达到在锁屏时候提高进程优先级的作用。
我们新建一个大小只有 1 个像素的 Activity。
KeepActivity:
public class KeepActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Window window = getWindow();
//设置这个 Activity 在左上角
window.setGravity(Gravity.START | Gravity.TOP);
WindowManager.LayoutParams attributes = window.getAttributes();
attributes.width = 1;
attributes.height = 1;
attributes.x = 0;
attributes.y = 0;
window.setAttributes(attributes);
//保存当前 activity,释放的时候需要使用
KeepManager.getInstance().setKeep(this);
}
}
在 AndroidManifest.xml 中配置这个 activity 在最近列表不显示,以及新建堆栈堆栈保存该 Activity。设置样式窗口背景为空并且透明。
AndroidManifest.xml
<!-- android:excludeFromRecents 设置在最近列表中不显示 -->
<!-- android:taskAffinity 设置新建堆栈保存该 Activity,
不指定新的堆栈,在息屏亮屏后,改应用会自动显示出来 -->
<activity android:name=".activity.KeepActivity"
android:excludeFromRecents="true"
android:taskAffinity="com.xiaoyue.myapplication.activity.KeepActivity"
android:theme="@style/KeepTheme">
</activity>
styles.xml:
<!-- 提权 Activity Style -->
<style name="KeepTheme">
<!-- 添加窗口背景为空 -->
<item name="android:windowBackground">@null</item>
<!-- 添加窗口为透明的 -->
<item name="android:windowIsTranslucent">true</item>
</style>
新建 KeepActivity 的管理类 KeepManager,管理 Activity 提权时的广播注册与反注册,Activity 的启动与释放。
KeepManager:
public class KeepManager {
private WeakReference<Activity> mKeepAct;
private KeepReceiver mKeepReceiver;
private static final KeepManager ourInstance = new KeepManager();
public static KeepManager getInstance() {
return ourInstance;
}
private KeepManager() {
}
/**
* 注册息屏亮屏广播
* @param context
*/
public void registerKeep(Context context) {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
mKeepReceiver = new KeepReceiver();
context.registerReceiver(mKeepReceiver, filter);
}
/**
* 反注册广播接收者
* @param context
*/
public void unregisterKeep(Context context) {
if (null != mKeepReceiver) {
context.unregisterReceiver(mKeepReceiver);
}
}
/**
* 开启 Activity
* @param context
*/
public void startKeep(Context context) {
Intent intent = new Intent(context, KeepActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
/**
* 结束 Activity
*/
public void finishKeep() {
if (null != mKeepAct) {
Activity activity = mKeepAct.get();
if (null != activity) {
activity.finish();
}
mKeepAct = null;
}
}
public void setKeep(KeepActivity keep) {
mKeepAct = new WeakReference<Activity>(keep);
}
}
新建息屏亮屏的广播接收者,在息屏的时候启动 KeepActivity,在亮屏的时候结束 KeepActivity。
BroadcastReceiver:
public class KeepReceiver extends BroadcastReceiver {
private static final String TAG = "KeepReceiver";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.e(TAG, "receive:" + action);
if (TextUtils.equals(action, Intent.ACTION_SCREEN_OFF)) {
//息屏的时候,开启 1 像素 Activity
KeepManager.getInstance().startKeep(context);
} else if (TextUtils.equals(action, Intent.ACTION_SCREEN_ON)) {
//亮屏的时候,关闭 1 像素的 Activity
KeepManager.getInstance().finishKeep();
}
}
}
最后,在 MainActivity 中,onCreate 时候注册广播,onDestroy 时候反注册广播。
MainActivity:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//通过Activity 提权
KeepManager.getInstance().registerKeep(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
KeepManager.getInstance().unregisterKeep(this);
}
}
运行结果:
运行程序,查看 adj,在程序启动的时候,adj 为 0;按 home 键返回桌面的时候,adj 为 6(不同手机有区别);点击息屏,这时候 adj 为 0。说明在息屏的时候,应用提权了。
缺点: Activity 提权局限性比较大,只能在息屏后才可以,而且存在一个 Activity ,不够干净。
2.Service 提权
在前台进程中,有一个是“前台”运行的 Service,即服务已经调用了 startForeground,我们可以利用这个特性对应用进行提权。
创建一个提权的“前台” Service。
ForgroundService:
public class ForgroundService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
//让服务变成前台服务
startForeground(10, new Notification());
}
}
在 AndroidManifest.xml 中进行配置。
AndroidManifest.xml:
<service android:name=".service.ForgroundService" />
最后,在 MainActivity 中,onCreate 时候启动服务,onDestroy 时候停止服务。
MainActivity:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//通过Service 提权
startService(new Intent(this, ForgroundService.class));
}
@Override
protected void onDestroy() {
super.onDestroy();
stopService(new Intent(this, ForgroundService.class));
}
}
结果:
程序运行的时候 adj 为0;按 home 键返回桌面的时候 adj 为 1;在未启动“前台” Service 的时候,返回桌面 adj 为 6.(不同手机有差异)
使用这个 Service 提权有一个问题,API level >= 18 的时候,通知栏会有“正在运行”的通知。
这边提供一个方法解决这个问题。
当 API level < 18 :参数 2 设置 new Notification(),不会有图标显示。
当 API level >= 18:在需要提优先级的 Service 中启动一个 InnerService。两个服务都 startForeground,且绑定同样的 ID。Stop 掉 InnerService ,通知栏图标被移除。
修改后的 ForgroundService:
public class ForgroundService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
//让服务变成前台服务
startForeground(10, new Notification());
//如果 API 18 以上的设备 启动一个相同的 id 的 Service startForeground
//然后结束这个 Service
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
startService(new Intent(this, InnnerService.class));
}
}
public static class InnnerService extends Service {
@Override
public void onCreate() {
super.onCreate();
startForeground(10, new Notification());
stopSelf();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
}
在 AndroidManifest.xml 中添加配置。
AndroidManifest.xml:
<service android:name=".service.ForgroundService$InnnerService" />
结果:
运行的 adj 结果与上面一直,但是在通知栏不会有“正在运行”的通知。
四、拉活
我们在上面已经对应用进行提权,让应用在安卓系统中尽可能长的运行。但是,终究还是有可能被释放掉,这时候就需要有个拉活机制,把应用重新启动起来。
拉活机制有很多种,但是没有一种是百分百保证能拉活,而且使用拉活机制或多或少都会占用一点资源,所以使用的时候需要慎重。
1.广播拉活
在发生特定系统事件时,系统会发出广播,通过在 AndroidManifest.xml 中静态注册对应的广播监听器,即可在发生响应事件时拉活。比如时间广播,屏幕广播等。
如果应用注册为接收广播,则在每次发送广播时,应用的接收器都会消耗资源。 如果多个应用注册为接收基于系统事件的广播,这会引发问题;触发广播的系统事件会导致所有应用快速地连续消耗资源,从而降低用户体验。
为了缓解这一问题,Android 7.0(API 级别 25)对广播施加了一些限制。Android 8.0 中这些限制更为严格。后台执行限制官网介绍
Android 8.0 中可静态注册的广播,在这里面可以发现没有适合做拉活的广播。
2.全家桶拉活
有多个 app 在用户设备上安装,只要开启其中一个就可以将其他的 app 也拉活。比如手机里装了手 Q、QQ 空间、兴趣部落等等,那么打开任意一个 app 后,其他的 app 也都会被唤醒。
3.系统 Service 机制拉活
将 Service 设置为 START_STICKY,利用系统机制在 Service 挂掉后自动拉活:
START_STICKY:
“粘性”。如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。
START_NOT_STICKY:
“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。
START_REDELIVER_INTENT:
重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。
START_STICKY_COMPATIBILITY:
START_STICKY 的兼容版本,但不保证服务被kill后一定能重启。
Service 的 onStartCommand 方法默认判断 mStartCompatibility 这个值,mStartCompatibility 初始化是在 Service 的 attach 中进行初始化,如果 targetSdkVersion 小于 5 就返回 START_STICKY_COMPATIBILITY,否则就返回 START_STICKY。
Service 的 onStartCommand:
public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
onStart(intent, startId);
return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
}
Service 的 attach:
public final void attach(
Context context,
ActivityThread thread, String className, IBinder token,
Application application, Object activityManager) {
attachBaseContext(context);
mThread = thread; // NOTE: unused - remove?
mClassName = className;
mToken = token;
mApplication = application;
mActivityManager = (IActivityManager)activityManager;
mStartCompatibility = getApplicationInfo().targetSdkVersion
< Build.VERSION_CODES.ECLAIR;
}
现在 targetSdkVersion 基本没有小于 5 的,只要 targetSdkVersion 不小于 5,就默认是 START_STICKY。这样只要启动了服务,系统就会自动重启。
创建 Service。
StickService:
public class StickService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
}
在 AndroidManifest.xml 中进行配置。
AndroidManifest.xml:
<service android:name=".StickService" />
启动 Service。
MainActivity:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Stick 拉活
startService(new Intent(this,StickService.class));
}
@Override
protected void onDestroy() {
super.onDestroy();
stopService(new Intent(this, StickService.class));
}
}
效果:
但是某些 ROM 系统不会拉活。并且经过测试,Service 第一次被异常杀死后很快被重启,第二次会比第一次慢,第三次又会比前一次慢,一旦在短时间内 Service 被杀死4-5次,则系统不再拉起,也有些手机会一直拉活。米 2 真机一次也没拉起来。
4.账户同步拉活
手机系统设置里会有“帐户”一项功能,任何第三方 APP 都可以通过此功能将数据在一定时间内同步到服务器中去。系统在将 APP 帐户同步时,会将未启动的 APP 进程拉活。谷歌官方 账户同步 demo。
首先,我们需要创建一个 Serviceandroid,供系统通过 “android.accounts.AccountAuthenticator” 这个 Action 进行调用,并通过它来把我们自己的账号注册到“设置”中。
AuthenticationService :
public class AuthenticationService extends Service {
private AccountAuthenticator accountAuthenticator;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return accountAuthenticator.getIBinder();
}
@Override
public void onCreate() {
super.onCreate();
accountAuthenticator = new AccountAuthenticator(this);
}
private 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/accountauthenticator" />
</service>
accountauthenticator 是在 res 下的 xml 文件中的一个 xml 配置文件,记录一些参数配置。
accountauthenticator.xml
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.xiaoyue.myapplication.account"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" />
运行结果:
在设置的 “账户” --》“添加账户” 下出现了我们自己应用的配置(图中第二个),其中图片与文字显示为我们在 accountauthenticator.xml 中配置的。
接下来我们需要为我们的应用添加一个账户。
AccountHelper :
public class AccountHelper {
private static final String TAG = "AccountHelper";
/** 在 xml 中配置的 accountType */
public static final String ACCOUNT_TYPE = "com.xiaoyue.myapplication.account";
/**
* 添加账户
* @param context
*/
public static void addAccount(Context context) {
AccountManager am = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
//获得此类型的账户
Account[] accounts = am.getAccountsByType(ACCOUNT_TYPE);
if (accounts.length > 0) {
Log.e(TAG, "账户已存在");
return;
}
//给这个账户类型添加一个账户
Account xiaoyue = new Account("xiaoyue", ACCOUNT_TYPE);
am.addAccountExplicitly(xiaoyue, "xiaoyue", new Bundle());
}
}
这个需要两个权限,配置在 AndroidManifest.xml,这边没有进行动态权限申请,有需要自行添加。
AndroidManifest.xml:
<uses-permission
android:name="android.permission.AUTHENTICATE_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission
android:name="android.permission.GET_ACCOUNTS"
android:maxSdkVersion="22" />
在 MainActivity 中进行调用添加账户操作。
MainActivity :
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//账户拉活
AccountHelper.addAccount(this);
}
}
结果:
在账户界面,成功添加对应应用的账户。
创建一个账户同步的服务,这个服务由系统调用,所以在应用未启动的时候,会被系统自动调用起来。
SyncService :
public class SyncService extends Service {
private SyncAdapter syncAdapter;
private static final String TAG = "SyncService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
return syncAdapter.getSyncAdapterBinder();
}
@Override
public void onCreate() {
super.onCreate();
syncAdapter = new SyncAdapter(getApplicationContext(), true);
}
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.e(TAG,"同步账户");
//与互联网 或者 本地数据库同步账户
}
}
}
AndroidManifest.xml 中的配置:
<service android:name=".account.SyncService">
<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 也是保存在 res 下的 xml 文件夹下。
sync_adapter.xml
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.xiaoyue.myapplication.account"
android:contentAuthority="com.xiaoyue.myapplication.provider"
android:allowParallelSyncs="false"
android:isAlwaysSyncable="true"
android:userVisible="false"/>
<!-- accountType 与 accountauthenticator.xml 中的 accountType 一样 -->
<!-- contentAuthority 系统在进行账户同步的时候会查找 此 auth 的 ContentProvider -->
<!-- allowParallelSyncs 允许多个同步 -->
<!-- isAlwaysSyncable 是否总是同步 -->
<!-- userVisible 是否在账户列表中展示一个开关 -->
contentAuthority 配置的是一个 ContentProvider ,所以我们还需要一个 ContentProvider,创建一个空的 ContentProvider。
SyncProvider:
public class SyncProvider 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 中的配置:
<provider
android:name=".account.SyncProvider"
android:authorities="com.xiaoyue.myapplication.provider" />
在 AccountHelper 中添加账户自动同步,并在 MainActivity 中进行调用,这样系统就会自动调用同步。从而启动服务 SyncService。
AccountHelper:
public class AccountHelper {
/** 在 xml 中配置的 accountType */
public static final String ACCOUNT_TYPE = "com.xiaoyue.myapplication.account";
public static final String CONTENT_AUTHORITY = "com.xiaoyue.myapplication.provider";
/**
* 设置账户自动同步
*/
public static void autoSync() {
Account xiaoyue = new Account("xiaoyue", ACCOUNT_TYPE);
//设置同步
ContentResolver.setIsSyncable(xiaoyue, CONTENT_AUTHORITY, 1);
//自动同步
ContentResolver.setSyncAutomatically(xiaoyue, CONTENT_AUTHORITY, true);
//设置同步周期,这个周期只是一个参考值
ContentResolver.addPeriodicSync(xiaoyue, CONTENT_AUTHORITY, new Bundle(), 1);
}
}
这个同步周期,时间间隔虽然设置了 1s,但是 Android 本身为了考虑同步所带来的消耗和减少唤醒设备的次数,1s 只是一个参考时间,具体时间由系统决定。
MainActivity:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//账户拉活
AccountHelper.addAccount(this);
AccountHelper.autoSync();
}
}
账户拉活的时间充满不确定性,只能看人品决定。
结果:
这是其中一台手机的测试结果,实际为每分钟同步一次,不同手机各不相同。
5.JobScheduler 拉活
JobScheduler 允许在特定状态与特定时间间隔周期执行任务。可以利用它的这个特点完成保活的功能,即开启一个定时器,与普通定时器不同的是其调度由系统完成。
同样在某些 ROM 可能并不能达到需要的效果。
编写一个定时任务 MyJobService,每个 1 s 执行一次。
MyJobService :
public class MyJobService extends JobService {
public static void StartJob(Context context) {
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context
.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(10, new ComponentName(context
.getPackageName(), MyJobService.class
.getName()));
// setPersisted 在设备重启依然执行
builder.setPersisted(true);
//小于7.0
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
// 每隔1s 执行一次 job
builder.setPeriodic(1_000);
} else {
//延迟执行任务
builder.setMinimumLatency(1_000);
}
jobScheduler.schedule(builder.build());
}
private static final String TAG = "MyJobService";
@Override
public boolean onStartJob(JobParameters params) {
Log.e(TAG, "开启job");
//如果7.0以上,则轮训
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
StartJob(this);
}
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
}
setPeriodic 这个方法是设置间隔时间,但是对于 7.0 以上的设备不能生效,所以对于 7.0 以上的安卓设备,手动调用进行轮询。
在 AndroidManifest.xml 中添加权限以及 Service 配置。
AndroidManifest.xml:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<service
android:name=".jobschuduler.MyJobService"
android:permission="android.permission.BIND_JOB_SERVICE" />
在 MainActivity 中进行调用。
MainActivity :
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//jobscheduler拉活
MyJobService.StartJob(this);
}
}
结果:
6.其他拉活
推送拉活
根据终端不同,在小米手机(包括 MIUI)接入小米推送、华为手机接入华为推送。
Native拉活
Native fork子进程用于观察当前 app 主进程的存亡状态。对于 5.0以上成功率极低。
五、混合保活
上面讲述了多种报货机制,包括了提权、拉活等,但是各个方法都不能保证其百分百保活。为了提高应用保活的成功率,可以使用多个方案混合保活。
1.使用 Service 提权
使用 Service 提权的时候在 targetSdkVersion 不小于 5 的时候,其实也使用了 Service 拉活机制。
2.双进程拉活
我们使用了 Service 提权,并且使用了 Service 拉活。从上面 Service 拉活中可以知道,每一次 Service 拉活等待的时间越来越久,甚至可能不拉活。
为了让每次 Service 被杀死后都会重新拉活,使用双进程互相拉活。在主线程和子线程中各自创建一个 Service ,并进行提权,两个 Service 互相绑定,并在断开连接的时候(可能另一个线程被杀死),重新调用另一个服务,并绑定,从而实现双进程拉活。
新建 AIDL 接口,并且创建实现类,作为 Service 绑定时候返回的 IBinder,如果仍然像 Service 提权中在绑定时候返回 null,则无法回调到连接断开的方法 onServiceDisconnected。
AIDL 接口 IMyAidlInterface 。
IMyAidlInterface :
interface IMyAidlInterface {
/**
* 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);
}
AIDL 的实现,只是为了只是为了让服务绑定返回不为空,这边接口是随便定义的,没有作用。有业务需求可自行定义接口。
MyBinder :
public class MyBinder extends IMyAidlInterface.Stub {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
}
创建运行在主线程的 Service LocalService,使用 Service 提权,当该服务断开的时候,判定为子线程被杀死,启动子线程的服务 RemoteService。
LocalService :
public class LocalService extends Service {
private static final String TAG = "Service";
private MyBinder myBinder;
private ServiceConnection serviceConnection;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return myBinder;
}
@Override
public void onCreate() {
super.onCreate();
myBinder = new MyBinder();
serviceConnection = new ServiceConnection();
//让服务变成前台服务
startForeground(10, new Notification());
//如果 API 18 以上的设备 启动一个相同的 id 的 Service startForeground
//然后结束这个 Service
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
startService(new Intent(this, InnnerService.class));
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
bindService(new Intent(this, RemoteService.class), serviceConnection,
BIND_AUTO_CREATE);
return super.onStartCommand(intent, flags, startId);
}
public static class InnnerService extends Service {
@Override
public void onCreate() {
super.onCreate();
startForeground(10, new Notification());
stopSelf();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
class ServiceConnection implements android.content.ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//服务连接后回调
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG,"子进程可能被干掉了,拉活");
//连接中断后回调
startService(new Intent(LocalService.this, RemoteService.class));
bindService(new Intent(LocalService.this, RemoteService.class),serviceConnection,
BIND_AUTO_CREATE);
}
}
}
AndroidManifest.xml 的配置。
AndroidManifest.xml:
<service android:name=".LocalService"/>
<service android:name=".LocalService$InnnerService" />
同样,创建运行在子线程的 Service RemoteService,与 LocalService 一样,在该服务断开的时候,判定为主线程被杀死,启动主线程的服务 LocalService 。
AndroidManifest.xml 的配置。
AndroidManifest.xml:
<service android:name=".RemoteService"
android:process=":remote" />
<service android:name=".RemoteService$InnnerService"
android:process=":remote" />
在 MainActivity 中开始这两个服务。
MainActivity :
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService(new Intent(this, LocalService.class));
startService(new Intent(this, RemoteService.class));
}
}
效果:
可以发现,当前应用有两个线程,主线程和 remote 子线程。杀死主线程,在 remote 子线程中发现服务被断开连接,则调起了主线程的服务,从而调起了主线程。同理,杀死 remote 子线程,主线程会把 remote 子线程调起来。
3.添加 JobService
使用双进程也有可能应用被杀死,我们在添加一个 JobService,定时判断两个服务是否被杀死,是的话就重新调用起来。
MyJobService :
public class MyJobService extends JobService {
public static void StartJob(Context context) {
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context
.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(10, new ComponentName(context
.getPackageName(), MyJobService.class
.getName()));
// setPersisted 在设备重启依然执行
builder.setPersisted(true);
//小于7.0
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
// 每隔1s 执行一次 job
builder.setPeriodic(1_000);
} else {
//延迟执行任务
builder.setMinimumLatency(1_000);
}
jobScheduler.schedule(builder.build());
}
private static final String TAG = "MyJobService";
@Override
public boolean onStartJob(JobParameters params) {
Log.e(TAG, "开启job");
//如果7.0以上,则轮训
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
StartJob(this);
}
//判断服务是否正在运行,没有的话,则调起服务
boolean isLocalRun = isRunningService(this, LocalService.class.getName());
boolean isRemoteRun = isRunningService(this, RemoteService.class.getName());
if (!isLocalRun || !isRemoteRun) {
startService(new Intent(this, LocalService.class));
startService(new Intent(this, RemoteService.class));
}
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
public boolean isRunningService(Context context, String name) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> runningServices = am.getRunningServices(100);
for (ActivityManager.RunningServiceInfo info : runningServices
) {
if (TextUtils.equals(info.service.getClassName(), name)) {
return true;
}
}
return false;
}
}
AndroidManifest.xml 的配置。
AndroidManifest.xml:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<service
android:name=".MyJobService"
android:permission="android.permission.BIND_JOB_SERVICE" />
同时,在 MainActivity 中调用 MyJobService。
MainActivity :
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService(new Intent(this, LocalService.class));
startService(new Intent(this, RemoteService.class));
MyJobService.StartJob(this);
}
}
这样,即使说能百分百保证应用一直存活,但是应用保活的成功率已经很大了。