Android系统会尽量长时间的保持应用的进程,但是为了新建进程或运行更重要的进程,在内存不足时会清理掉旧进程来释放内存;
为了确定保留或终止哪些进程,系统将进程进行了分类;主要分为五类:
按重要性从高到低排列:
-
1.前台进程
2.可见进程
3.服务进程
4.后台进程
5.空进程
在需要时,系统会清除重要性低的进程,以此类推,来回收系统资源;
系统出于体验和性能上的考虑,app退到后台时,系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也也越多,系统开始依据自身的一套进程回收机制来判断需要kill掉哪些进程,以腾出内存供给需要的app,Android系统这套杀进程回收内存的机制位:Low Memory Killer;
cat /sys/module/lowmemorykiller/parameters/minfree
这个命令会展示出一行数字,这些数字的单位是page,1page==4kb
内存阈值在不同的手机上不一样,一旦低于该值,Android系统便开始按顺序关闭进程,因此Android开始关闭优先级最低的空进程,即当内存小于xxxMB(46080page);
其实,进程的优先级可以通过一个oom_adj的值来反应出来,它是linux内核分配给每个系统进程的一个值,越小代表进程优先级越高;
可以通过命令:
cat /proc/进程id/oom_adj
看到当前进程的oom_adj的值;(这条命令需要room权限)
备注:adj越大,占用内存越多会被最先kill掉,所以保活就成了降低oom_adj的值,以及如何使得我们应用占的内存最少。
一、进程优先级提高方法一:Activity提权
就是监控手机锁屏和开屏广播,在手机锁屏时,启动一个1像素的透明的Activity;在用户解锁时,销毁这个Activity;从而达到提高进程优先级的作用;
弊端:存在一个无用的Activity,并且只有在锁屏的时候才能提权;
具体步骤:
1.创建一个activity
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.Gravity;
import android.view.Window;
import android.view.WindowManager;
/**
* Created by serenitynanian on 2018/10/23.
*/
public class KeepActivity extends AppCompatActivity {
private static final String TAG = "KeepActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Window window = getWindow();
//放在左上角
window.setGravity(Gravity.TOP|Gravity.LEFT);
WindowManager.LayoutParams attributes = window.getAttributes();
//宽高设置为1像素
attributes.width = 1 ;
attributes.height = 1 ;
//起始坐标
attributes.x = 0 ;
attributes.y = 0 ;
window.setAttributes(attributes);
}
public void registerKeep(){
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
//注册广播
BroadcastReceiver receiver = null;//声明成员属性
registerReceiver(receiver, intentFilter);
}
public void unRegisterKeep(){
//从成员属性中获得
BroadcastReceiver receiver = null;
if (null != receiver) {
unregisterReceiver(receiver);
}
}
}
2.在AndroidManifest.xml中注册
//注册activity时设置的style样式
二、进程优先级提高方法二:Service提权
使用startForeground(ID,Notification):使Service成为前台Service。 前台服务也是前台进程的一种形式:可以创建一个前台服务用于提高app在按下home键之后的进程优先级;
前台服务需要在通知栏显示一条:
API level < 18 :参数2 设置 new Notification()即可,图标不会显示;在大于18,参数1设置0后,图标不会显示,但是提权失败。
API level >= 18:在需要提优先级的service A启动一个InnerService。两个服务都startForeground,且绑定同样的 ID。Stop 掉InnerService ,通知栏图标被移除。也就是在第一个Service的onCreate中启动第二个InnerService,在InnerService也startForeground(id, notification),同时调用stopSelf()结束自己;
具体使用步骤:
1.创建两个service ,其中一个是内部service;
package com.serenity.daemon.daemon.service;
import android.app.Notification;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.Nullable;
/**
* Created by serenitynanian on 2018/10/24.
* 前台服务
*/
public class ForegroundService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
//startForeground让服务变成前台服务
//两个参数的意思是:设置一个id为1的通知
startForeground(1,new Notification());
/**
* 如果 18 以上的设备 启动一个service startForeground给相同的id
* 然后结束这个服务 就不会显示通知栏了
*/
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.JELLY_BEAN_MR2){
startService(new Intent(this, InnerSerivice.class));
}
}
public static class InnerSerivice extends Service{
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
startForeground(1,new Notification());
//结束自己
stopSelf();
}
}
}
2.在AndroidManifest.xml中注册这两个service
三、 拉活进程:广播拉活
在系统发出特定事件时,会发出系统广播,在AndroidManifest.xml中静态注册对应的广播监听器,即可在发生响应事件时拉活;
但是从android 7.0开始,对广播进行了限制,而且在8.0更加严格https://developer.android.google.cn/about/versions/oreo/background.html#broadcasts
可静态注册广播列表:
https://developer.android.google.cn/guide/components/broadcast-exceptions.html
现在可监听的系统广播没有合适的实现进程拉活,这种情况暂时不在阐述;
三、 拉活进程:系统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后一定能重启。
只要 targetSdkVersion 不小于5,就默认是 START_STICKY。
但是某些ROM 系统不会拉活。并且经过测试,Service 第一次被异常杀死后很快被重启,第二次会比第一次慢,第三次又会比前一次慢,一旦在短时间内 Service 被杀死4-5次,则系统不再拉起。
四、 拉活进程:账户同步拉活
手机系统设置里面都会有“账户”功能,任何第三方的app都可以通过此功能添加一个账户,然后触发同步,将数据在一定的时间内同步到服务器中去;系统在将app账户同步时,会将未启动app的进程拉活;
https://github.com/googlesamples/android-BasicSyncAdapter
优点:由系统主动拉起进程,生态稳定
缺点:同步账户周期过长;由系统自己计算时间,来调用,不能人为控制;
- 1.第一步,创建一个APP账户service,通过binder告知系统存在这样一个APP账户
package com.serenity.daemon.daemon.account;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.NetworkErrorException;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.Nullable;
/**
* Created by serenitynanian on 2018/10/24.
* 这个service通过binder来告知系统,存在这样一个APP账户
* 这个APP的账号信息在authenticator.xml中配置的
* 这个服务不需要我们来启动,是由系统自动检测 自动启动
*/
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);
}
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;
}
}
}
- 2.在AndroidManifest.xml中注册
- 3.需要添加一个meta-data resource文件,来指定这个app账户信息
- 4.通过以上三步骤,系统已经知道存在这样一个APP账户,下面是添加一个这样的账户
public static final String ACCOUNT_TYPE = "com.serenity.daemon.account";
/**
* 添加一个系统中存在账户类型的账户
* @param context
*/
public static void addAccount(Context context){
AccountManager am = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
//获得此类型的账户
Account[] accountsByType = am.getAccountsByType(ACCOUNT_TYPE);
if (accountsByType.length > 0) {
Log.e(TAG, "账户已存在" );
return;
}
//给这个账户类型添加一个账户
Account serenity = new Account("serenity", ACCOUNT_TYPE);
//明确指定一个账户和密码,及所传参数
am.addAccountExplicitly(serenity, "123", new Bundle());
}
- 5.添加账户后,需要系统进行同步账户信息,需要创建一个同步数据的service
package com.serenity.daemon.daemon.account;
import android.accounts.Account;
import android.app.Service;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
/**
* Created by serenitynanian on 2018/10/24.
* 系统通过调用此服务 用于同步账户数据的service
* 并且在onBind返回AbstractThreadedSyncAdapter的getSyncAdapterBinder
* 当系统通过binder同步数据时,会调用到SyncAdapter的onPerformSync方法,执行同步
*
* 这个服务也是由系统bind此service调用 通过binder来同步数据
*/
public class SyncService extends Service {
private static final String TAG = "SyncService";
private SyncAdapter syncAdapter;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return syncAdapter.getSyncAdapterBinder();
}
@Override
public void onCreate() {
super.onCreate();
syncAdapter = new SyncAdapter(this, 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, "onPerformSync: 同步账户");
//与互联网 或者 本地数据库同步账户
}
}
}
- 6.在AndroidManifest.xml中注册
上面注册同步数据service时,需要添加一个同步数据账户配置meta-data resource
- 7.配置同步参数----告知系统应该怎样进行同步
/**
* 配置同步参数----告知系统应该怎样进行同步
* 设置账户自动同步
*
* 这个方法要在添加完账户后,主动调用
*/
public static void autoAccount(){
Account serenity = new Account("serenity", ACCOUNT_TYPE);
//设置同步
//第二个参数就是在sync_adapter.xml设置的contentAuthority的值
ContentResolver.setIsSyncable(serenity,"com.serenity.daemon.provider",1);
//设置自动同步
ContentResolver.setSyncAutomatically(serenity,"com.serenity.daemon.provider",true);
//设置同步周期
ContentResolver.addPeriodicSync(serenity,"com.serenity.daemon.provider",new Bundle(),1);
//设置立即同步,但是我们需要的是由系统主动拉活进程
// ContentResolver.requestSync();
}
在上面的配置同步账户时,需要指定一个contentProvider,
package com.serenity.daemon.daemon.account;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
/**
* Created by serenitynanian on 2018/10/24.
*/
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中配置
其中authorities是一个标识,必须与同步设置的authorities一致
五、 拉活进程:JobScheduler
JobScheduler允许在特定状态和特定时间间隔周期执行任务;可以利用它的这个特点来保活进程,其效果就是开启一个定时器;但是与普通定时器不同的是:其调度是由系统来完成的;
缺点:在某些ROM手机上无效果(小米)
package com.serenity.daemon.daemon.account.jobscheduler;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.os.Build;
import android.util.Log;
/**
* Created by serenitynanian on 2018/10/24.
* 使用jobService来进行拉活
*/
public class MyJobService extends JobService {
private static final String TAG = "MyJobService";
public static void startJobServcie(Context context){
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(JOB_SCHEDULER_SERVICE);
// setPersisted 用来设置 在设备重启依然执行 设置这个属性需要重启权限
//
JobInfo.Builder builder = new JobInfo.Builder(10, new ComponentName(context.getPackageName(), MyJobService.class.getName()))
.setPersisted(true);
//小于7.0
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
//设置没隔1秒执行一次job
//7.0以上设置无效
builder.setPeriodic(1000);
}else{
//7.0以上 设置最小等待时间(时延) 延迟执行
builder.setMinimumLatency(1000);
}
JobInfo jobInfo = builder.build();
jobScheduler.schedule(jobInfo);
}
@Override
public boolean onStartJob(JobParameters params) {
Log.e(TAG, "onStartJob:-----开启jobService");
//如果大于7.0 进行 轮循执行
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
startJobServcie(this);
}
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
Log.e(TAG, "onStopJob:-----停止jobService");
return false;
}
}
在AndroidManifest.xml注册