进程间保活

668 阅读10分钟

一、进程优先级概述

优先级分类:前台进程、可见进程、服务进程、后台进程、空进程

进程级别详细表如下:

ADJ 调度算法的核心方法

  • updateOomAdjLocked:更新adj,当目标进程为空,或者被杀则返回false;否则返回true;
  • computeOomAdjLocked:计算adj,返回计算后RawAdj值;
  • applyOomAdjLocked:使用adj,当需要杀掉目标进程则返回false;否则返回true。

updateOomAdjLocked

过程比较复杂,主要分为更新adj(满足条件则杀进程)和根据memFactor来调度执行TrimMemory操作;

第一部分:更新adj(满足条件则杀进程)

  • 遍历mLruProcesses进程
    • 当进程未分配adj的情况
      • 当进程procState=14或15,则设置adj=curCachedAdj(初始化=9);
        • 当curCachedAdj != nextCachedAdj,且stepCached大于cachedFactor时 则curCachedAdj = nextCachedAdj,(nextCachedAdj加2,nextCachedAdj上限为15);
      • 否则,则设置adj=curEmptyAdj(初始化=9);
        • 当curEmptyAdj != nextEmptyAdj,且stepEmpty大于EmptyFactor时 则curEmptyAdj = nextEmptyAdj,(nextEmptyAdj加2,nextEmptyAdj上限为15);
    • 根据当前进程procState状态来决策:
      • 当curProcState=14或15,且cached进程超过上限(cachedProcessLimit=16),则杀掉该进程
      • 当curProcState=16的前提下:
        • 当空进程超过上限(TRIM_EMPTY_APPS=8),且空闲时间超过30分钟,则杀掉该进程
        • 否则,当空进程超过上限(emptyProcessLimit=16),则杀掉该进程
    • 没有services运行的孤立进程,则杀掉该进程;

第二部分:根据memFactor来调度执行TrimMemory操作;

  • 根据CachedAndEmpty个数来调整内存因子memFactor(值越大,级别越高):
    • 当CachedAndEmpty < 3,则memFactor=3;
    • 当CachedAndEmpty < 5,则memFactor=2;
    • 当CachedAndEmpty >=5,且numCached<=5,numEmpty<=8,则memFactor=1;
    • 当numCached>5 或numEmpty>8,则memFactor=0;
  • 当内存因子不是普通0级别的情况下,根据memFactor来调整前台trim级别(fgTrimLevel):
    • 当memFactor=3,则fgTrimLevel=TRIM_MEMORY_RUNNING_CRITICAL;
    • 当memFactor=2,则fgTrimLevel=TRIM_MEMORY_RUNNING_LOW;
    • 否则(其实就是memFactor=1),则fgTrimLevel=TRIM_MEMORY_RUNNING_MODERATE
    • 再遍历mLruProcesses队列进程:
      • 当curProcState > 12且没有被am杀掉,则执行TrimMemory操作;
      • 否则,当curProcState = 9 且trimMemoryLevel<TRIM_MEMORY_BACKGROUND,则执行TrimMemory操作;
      • 否则,当curProcState > 7, 且pendingUiClean =true时
        • 当trimMemoryLevel<TRIM_MEMORY_UI_HIDDEN,则执行TrimMemory操作;
        • 当trimMemoryLevel<fgTrimLevel,则执行TrimMemory操作;
  • 当内存因子等于0的情况下,遍历mLruProcesses队列进程:
    • 当curProcState >=7, 且pendingUiClean =true时,
      • 当trimMemoryLevel< TRIM_MEMORY_UI_HIDDEN,则执行TrimMemory操作;

computeOomAdjLock()

Service情况

当adj>0 或 schedGroup为后台线程组 或procState>2时:

  • 当service已启动,则procState<=10;
    • 当service在30分钟内活动过,则adj=5,cached=false;
  • 获取service所绑定的connections
    • 当client与当前app同一个进程,则continue;
    • 当client进程的ProcState >=ActivityManager.PROCESS_STATE_CACHED_ACTIVITY,则设置为空进程
    • 当进程存在显示的ui,则将当前进程的adj和ProcState值赋予给client进程
    • 当不存在显示的ui,且service上次活动时间距离现在超过30分钟,则只将当前进程的adj值赋予给client进程
    • 当前进程adj > client进程adj的情况
      • 当service进程比较重要时,则设置adj >= -11
      • 当client进程adj<2,且当前进程adj>2时,则设置adj=2;
      • 当client进程adj>1时,则设置adj = clientAdj
      • 否则,设置adj <= 1;
      • 若client进程不是cache进程,则当前进程也设置为非cache进程
    • 当绑定的是前台进程的情况
      • 当client进程状态为前台时,则设置mayBeTop=true,并设置client进程procState=16
      • 当client进程状态 < 2的前提下:若绑定前台service,则clientProcState=3;否则clientProcState=6
    • 当connections并没有绑定前台service时,则clientProcState >= 7
    • 保证当前进程procState不会比client进程的procState大
  • 当进程adj >0,且activity可见 或者resumed 或 正在暂停,则设置adj = 0

applyOomAdjLocked

  • curRawAdj != setRawAdj

  • 进程当前OOM的校准 != 进程最后的OOM校准(app.curAdj != app.setAdj)

    • 将adj值 发送给lmkd守护进程
  • 最后设置的调度组 != 当前所需的调度组(app.setSchedGroup != app.curSchedGroup)

    • 等待被杀
      • 杀进程,并设置success = false
    • else
      • 设置进程组信息
      • 调整进程的swappiness值
  • 最近的前台Activity != 正在运行的前台活动(app.repForegroundActivities != app.foregroundActivities)

  • 最近的进程状态 != 当前的进程状态(app.repProcState != app.curProcState)

    • 设置进程状态
  • 当setProcState = -1或者curProcState与setProcState值不同时

    • 计算pss下次时间
  • else

    • 当前时间超过pss下次时间,则请求统计pss,并计算pss下次时间
  • 进程跟踪器最后的状态 != 当前的状态

二、进程间保活方法分类

1、一像素保活

原理:手机息屏时,创建一个Activity,让应用成为前台进程;打开屏幕时,关闭该Activity

        //Activit中的代码
        
        Window window = getWindow();
        //放在左上角
        window.setGravity(Gravity.START | Gravity.TOP);

        WindowManager.LayoutParams params = window.getAttributes();
        //设置宽高
        params.width = 1;
        params.height = 1;
        //设置起始坐标
        params.x = 0;
        params.y = 0;
        window.setAttributes(params);]()

注:activity的主题要设置透明主题,如下:

 <style name="KeepTheme">
        <!--背景-->
        <item name="android:windowBackground">@null</item>
        <!--是否透明-->
        <item name="android:windowIsTranslucent">true</item>
    </style>
        /**
     * 注册 开屏 关屏 广播
     *
     * @param context
     */
    public void registerKeep(Context context) {
        IntentFilter filter = new IntentFilter();

        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);

        mKeepReceiver = new KeepReceiver();
        context.registerReceiver(mKeepReceiver, filter);
    }
        /**
     * 开启1像素Activity
     *
     * @param context
     */
    public void startKeep(Context context) {
        Intent intent = new Intent(context, KeepActivity.class);
        // 结合 taskAffinity 一起使用 在指定栈中创建这个activity
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }
        /**
     * 关闭1像素Activity
     */
    public void finishKeep() {
        if (mKeepActivity != null) {
            Activity activity = mKeepActivity.get();
            if (activity != null) {
                activity.finish();
            }
            mKeepActivity = null;
        }
    }

2、前台service 保活

原理:启动一个前台服务,从而拉高整个应用的优先级。

缺点:API >=26 后暂时没有方式能够隐藏通知

需要适配: API < 18 通知图标不会显示 API >= 18 && API < 26 可以启动双服务,绑定同样的ID,然后stop一个服务,通知图标将不显示

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {//4.3以下
            //将service设置成前台服务,并且不显示通知栏消息
            startForeground(SERVICE_ID, new Notification());
        } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { //Android4.3-->Android7.0
            //将service设置成前台服务
            startForeground(SERVICE_ID, new Notification());
            //删除通知栏消息
            startService(new Intent(this, InnerService.class));
              } else { // 8.0 及以上
            //通知栏消息需要设置channel
            NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            //NotificationManager.IMPORTANCE_MIN 通知栏消息的重要级别  最低,不让弹出
            //IMPORTANCE_MIN 前台时,在阴影区能看到,后台时 阴影区不消失,增加显示 IMPORTANCE_NONE时 一样的提示
            //IMPORTANCE_NONE app在前台没有通知显示,后台时有
            NotificationChannel channel = new NotificationChannel("channel", "xx", NotificationManager.IMPORTANCE_NONE);
            if (manager != null) {
                manager.createNotificationChannel(channel);
                Notification notification = new NotificationCompat.Builder(this, "channel").build();
                //将service设置成前台服务,8.x退到后台会显示通知栏消息,9.0会立刻显示通知栏消息
                startForeground(SERVICE_ID, notification);
            }
        }
    public static class InnerService extends Service {

        @Override
        public void onCreate() {
            super.onCreate();
            Log.e(TAG, "InnerService 服务创建了");
            // 让服务变成前台服务
            startForeground(SERVICE_ID, new Notification());
            // 关闭自己
            stopSelf();
        }

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

3、广播拉活

在发生特定系统事件时,系统会发出广播,通过在 AndroidManifest 中静态注册对应的广播监听器,即可在发生响应事件时拉活。 但是从android 7.0开始,对广播进行了限制,而且在8.0更加严格https://developer.android.google.cn/about/versions/oreo/background.html#broadcasts 可静态注册广播列表: developer.android.google.cn/guide/compo…

“全家桶”拉活 有多个app在用户设备上安装,只要开启其中一个就可以将其他的app也拉活。比如手机里装了手Q、QQ空间、兴趣部落等等,那么打开任意一个app后,其他的app也都会被唤醒。

4、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次,则系统不再拉起。

public class StickyService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    //START_STICKY
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }
}

5、账户同步拉活

优点:系统唤醒,比较稳定

缺点:时间不能把控

public class SyncService 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) {
            Log.e(TAG, "同步账户");
            //与互联网 或者 本地数据库同步账户
        }
    }
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;
    }
}
public class AccountHelper {

    private static final String TAG = "AccountHelper";

    private static final String ACCOUNT_TYPE = "com.enjoy.daemon.account";

    /**
     * 添加账号
     *
     * @param context
     */
    public static void addAccount(Context context) {
        AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);

        // 获得此类型的账户
        // 需要增加权限  GET_ACCOUNTS
        Account[] accounts = accountManager.getAccountsByType(ACCOUNT_TYPE);

        if (accounts.length > 0) {
            Log.e(TAG, "账户已存在");
            return;
        }
        Account account = new Account("enjoy", ACCOUNT_TYPE);
        // 给这个账户类型添加一个账户
        // 需要增加权限  AUTHENTICATE_ACCOUNTS
        accountManager.addAccountExplicitly(account, "xx", new Bundle());
    }
  /**
     * 设置账户自动同步
     */
    public static void autoSync() {
        Account account = new Account("enjoy", ACCOUNT_TYPE);

        // 下面三个都需要同一个权限  WRITE_SYNC_SETTINGS

        // 设置同步
        ContentResolver.setIsSyncable(account, "com.enjoy.daemon.provider", 1);

        // 自动同步
        ContentResolver.setSyncAutomatically(account, "com.enjoy.daemon.provider", true);

        // 设置同步周期
        ContentResolver.addPeriodicSync(account, "com.enjoy.daemon.provider", new Bundle(), 1);
    }

}
/**
 * 创建 可添加用户
 */
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);
    }
    
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;
        }
    }
}

manifest中注册

        <service android:name=".account.AuthenticationService">
            <!--<action android:name="android.accounts.AccountAuthenticator" /> 让系统能够找到这个账户服务-->
            <intent-filter>
                <action android:name="android.accounts.AccountAuthenticator" />
            </intent-filter>
            <meta-data
                android:name="android.accounts.AccountAuthenticator"
                android:resource="@xml/account_authenticator" />
        </service>

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

account_authenticator.xml

<?xml version="1.0" encoding="utf-8"?>

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

    <!--android:label="Daemon"  不能这么写-->

sync_adapter.xml

<?xml version="1.0" encoding="utf-8"?>

<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.enjoy.daemon.account"
    android:allowParallelSyncs="false"
    android:contentAuthority="com.enjoy.daemon.provider"
    android:isAlwaysSyncable="true"
    android:userVisible="true" />

    <!-- allowParallelSyncs 是否支持多账号同时同步-->

    <!--contentAuthority 指定要同步的 ContentProvider-->

    <!--android:userVisible 显示开关按钮 给用户控制-->

6.JobScheduler 拉活

原理:JobScheduler允许在特定状态与特定时间间隔周期执行任务,即开启一个定时器,与普通定时器不同的是其调度由系统完成

@SuppressLint("NewApi")
public class MyJobService extends JobService {

    private static final String TAG = "MyJobService";

    public static void startJob(Context context) {
        JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);

        //setPersisted 在设备重启依然执行
        // 需要增加权限 RECEIVE_BOOT_COMPLETED
        JobInfo.Builder builder = new JobInfo.Builder(8, new ComponentName(context.getPackageName(),
                MyJobService.class.getName())).setPersisted(true);

        // 小于7.0
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            // 每隔 1s 执行一次 job
            // 版本23 开始 进行了改进,最小周期为 5s
            builder.setPeriodic(1000);
        } else {
            // 延迟执行任务
            builder.setMinimumLatency(1000);
        }

        jobScheduler.schedule(builder.build());
    }
     @Override
    public boolean onStartJob(JobParameters params) {
        Log.e(TAG, "onStartJob");

        // 如果7.0以上 轮询
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            startJob(this);
        }

        return false;
    }

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