(十六)Android 进程保活

版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

一、进程的优先级

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);
    }
}

这样,即使说能百分百保证应用一直存活,但是应用保活的成功率已经很大了。

六、附

代码链接

猜你喜欢

转载自blog.csdn.net/qq_18983205/article/details/86472614