十二、Android进程保活

Android系统会尽量长时间的保持应用的进程,但是为了新建进程或运行更重要的进程,在内存不足时会清理掉旧进程来释放内存;
为了确定保留或终止哪些进程,系统将进程进行了分类;主要分为五类:

按重要性从高到低排列:
  • 1.前台进程


    十二、Android进程保活_第1张图片
    image.png
  • 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权限)


image.png

备注: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键之后的进程优先级;
前台服务需要在通知栏显示一条:


image.png

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注册

 
        

请点击具体Demo

你可能感兴趣的:(十二、Android进程保活)