进程保活(二)

Android进程保活主要包括两个方面:
1.提高进程优先级,降低进程被杀死的概率
2.在进程被杀死后,进行拉活。
上一篇文章进程保活(一)讲述了两种提高进程由新阿基,降低进程被杀死概率的方法,分别时1像素Activity和前台服务。本文继续讲述进程被杀死后拉活方案。

1.利用系统广播拉活

在发生特定系统事件时,系统会发出相应的广播,通过在AndroidManifest.xml中静态注册对应的广播监听器,即可在发生相应事件时拉活。
场用的用于拉活的广播事件包括:
在这里插入图片描述
这种方案使用全部Android平台,但存在如下缺点:
1.广播接收器被管理软件、系统软件通过"自启动"等功能禁用的场景无法接收到广播,从而无法自启。
2.系统广播事件不可控,只能保证发生事件时拉活进程,但无法保证进程挂掉后立即拉活。
因此,此方案主要作为备用手段。

2.监听第三方应用的静态广播

通过反编译第三方Top应用,比如:手机QQ,微信,支付宝等,以及友盟、信鸽、个推等SDK,找出它们外发的广播,在应用中进行监听,这样当这些应用发出广播时,就会将我们的应用拉活。

这种方案的局限性除了和系统广播一样的因素外,还受以下因素影响:
1.反编译分析过的第三方应用的多少
2.第三方应用的广播属于应用私有,当前版本中有效的官博,在后续版本随时就可能被移除或被改为不外发,这些因素都影响了拉活的效果。

3.利用系统Service机制拉活

Service的onStartCommand方法必须返回一个整型值,这个整形的返回值用来告诉系统在服务启动完毕后,如果被kill,系统将如何操作。这种方案虽然可以,但是在某些情况或者某些特定ROM上可能失效。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    return START_REDELIVER_INTENT;
}

关于onStartCommand方法的返回值,系统一共提供了四个:

START_STICKY
如果Service进程因为系统资源吃紧而被杀掉,则保留Service的状态为起始状态,但不保留传递过来的Intent对象,随后当系统资源不紧张时系统会尝试重新创建Service,由于服务状态为开始状态,所以创建服务后一定会掉调用onStartCommand方法,如果在此期间没有任何启动命令被传递到Service,那么参数Intent将为null。

START_STICKY_COMPATIBILITY
START_STICKY的兼容版本,不同的是其不保证服务被杀死后一定能重启。

START_NOT_STICKY
与START_STICKY恰恰相反,如果返回该值,则在执行完onStartCommand方法后如果Service被杀掉系统将不会重启该服务。

START_REDELIVER_INTENT
与START_STICKY不同的是START_STICKY重启后不会在传递之前的Intent,但如果返回该值的话系统会将上次的Intent重新传入。

一般情况下,作为一个后台常驻的Service,个人建议是尽量不要传递intent进来,避免有时候逻辑不好处理。同时需要注意的是,默认情况下Service的返回值就是START_STICKY或START_STICKY_COMPATIBILITY,如果没有什么特殊原因,我们也没必要更改。虽然Service默认情况下是可以被系统重启的,但是在某些情况或者某些定制ROM上会因为各种原因而失效,因此我们不能单靠这个返回值来达到进程重启的目的。

另外对于这种方案,如下两种情况无法拉活:
1.Service第一次被异常杀死后会在5秒内重启,第二次被杀死后会在10秒内重启,第三次会在20秒内重启,一旦在短时间内Service被杀死达到5此,则系统不再拉起。
2.进程被取得Root权限的管理工具或系统工具通过forcestop停止掉,无法重启。

4.与系统Service捆绑

Android系统提供给我们一系列Service,注意这里我我们所指的系统Service并非System Service那些,而是类似于系统广播的便于我们使用的Service,常见场用的就是IntentService,当然还有其它更多更不常用的系统Service,那么为什么要这里提到这些呢?因为某些系统Service一旦绑定就像拥有了开了挂一样的权限,这在大部分机型包括某些深度定制系统上简直就像BUG般存在,以最BUG的NotificationListenerService为例,这个是用来读取通知的,也就是说只要是通知不管是谁发的,NotificationListenerService都可以检测到,使用它也很简单,和IntentService一样定义一个类继承一下即可:

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class LiveService extends NotificationListenerService {

    public LiveService() {

    }

    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
    }

    @Override
    public void onNotificationRemoved(StatusBarNotification sbn) {
    }
}

但是这种方式需要权限:

  <service
            android:name=".LiveService"
            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
            <intent-filter>
                <action android:name="android.service.notification.NotificationListenerService" />
            </intent-filter>
        </service>

所以你的应用要是有消息推送的话,那么可以用这种方式去欺骗用户。

5.利用 Native进程拉活

利用Linux的fork机制创建Native进程,在Native进程中监控主进程的存活,当主进程挂掉后,在Native进程中立即对主进程进行拉活。
在Android中所有进程和系统组件的生命周期受ActivityManagerService的统一管理。而且,通过Linux的fork机制创建的进程为纯Linux进程,其生命周期不受Android的管理。
在 Native 进程中如何感知主进程死亡
要在 Native 进程中感知主进程是否存活有两种实现方式:

1.在 Native 进程中通过死循环或定时器,轮训判断主进程是否存活,档主进程不存活时进行拉活。该方案的很大缺点是不停的轮询执行判断逻辑,非常耗电。

2.在主进程中创建一个监控文件,并且在主进程中持有文件锁。在拉活进程启动后申请文件锁将会被堵塞,一旦可以成功获取到锁,说明主进程挂掉,即可进行拉活。由于 Android 中的应用都运行于虚拟机之上,Java 层的文件锁与 Linux 层的文件锁是不同的,要实现该功能需要封装 Linux 层的文件锁供上层调用。

封装 Linux 文件锁的代码如下:
在这里插入图片描述
Native 层中堵塞申请文件锁的部分代码:
在这里插入图片描述
在 Native 进程中如何拉活主进程。
通过 Native 进程拉活主进程的部分代码如下,即通过 am 命令进行拉活。通过指定“—include-stopped-packages”参数来拉活主进程处于 forestop 状态的情况。

在这里插入图片描述
如何保证 Native 进程的唯一。

从可扩展性和进程唯一等多方面考虑,将 Native 进程设计层 C/S 结构模式,主进程与 Native 进程通过 Localsocket 进行通信。在Native进程中利用 Localsocket 保证 Native 进程的唯一性,不至于出现创建多个 Native 进程以及 Native 进程变成僵尸进程等问题。

在这里插入图片描述
方案适用范围
该方案主要适用于 Android5.0 以下版本手机。

该方案不受 forcestop 影响,被强制停止的应用依然可以被拉活,在 Android5.0 以下版本拉活效果非常好。

对于 Android5.0 以上手机,系统虽然会将native进程内的所有进程都杀死,这里其实就是系统“依次”杀死进程时间与拉活逻辑执行时间赛跑的问题,如果可以跑的比系统逻辑快,依然可以有效拉起。记得网上有人做过实验,该结论是成立的,在某些 Android 5.0 以上机型有效。

6.利用 JobScheduler 机制拉活

Android5.0 以后系统对 Native 进程等加强了管理,Native 拉活方式失效。系统在 Android5.0 以上版本提供了 JobScheduler 接口,系统会定时调用该进程以使应用进行一些逻辑操作。

在本项目中,我对 JobScheduler 进行了进一步封装,兼容 Android5.0 以下版本。封装后 JobScheduler 接口的使用如下:

  JobSheduler@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService {
    @Override
    public void onCreate() {
        super.onCreate();
        startJobSheduler();
    }

    public void startJobSheduler() {
        try {
            JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(getPackageName(), MyJobService.class.getName()));
            builder.setPeriodic(5);
            builder.setPersisted(true);
            JobScheduler jobScheduler = (JobScheduler) this.getSystemService(Context.JOB_SCHEDULER_SERVICE);
            jobScheduler.schedule(builder.build());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        return false;
    }

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

其中的builder.setPersisted(true); 方法是设备重启后,是否重新执行任务,在这测过是可以重新启动任务的。

该方案主要适用于 Android5.0 以上版本手机。

该方案在 Android5.0 以上版本中不受 forcestop 影响,被强制停止的应用依然可以被拉活,在 Android5.0 以上版本拉活效果非常好。

仅在小米手机可能会出现有时无法拉活的问题。

在6.0以上引入了Doze模式,当6.0以上的手机进入这个模式后,便会使JobScheduler停止工作。

6.0以上Doze模式的处理
为了让JobScheduler可以在6.0以上进入Doze模式工作,这里针对6.0以上的Doze模式做特殊的处理-忽略电池的优化。

1.在Manifest.xml中加入权限。

<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>

2.在设置闹钟的时候,判断系统是否是6.0以上,如果是,则判断是否忽略电池的优化。判断是否忽略电池优化代码如下:

@TargetApi(Build.VERSION_CODES.M)
public static boolean isIgnoringBatteryOptimizations(Activity activity){
    String packageName = activity.getPackageName();
    PowerManager pm = (PowerManager) activity
            .getSystemService(Context.POWER_SERVICE);
    if (pm.isIgnoringBatteryOptimizations(packageName)) {
        return true;
    }else {
        return false;
    }
}

3.如果没有忽略电池优化的时候,弹出提醒对话框,提示用户进行忽略电池优化操作。代码如下:

/**
 * 针对N以上的Doze模式
 *
 * @param activity
 */
public static void isIgnoreBatteryOption(Activity activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        try {
            Intent intent = new Intent();
            String packageName = activity.getPackageName();
            PowerManager pm = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
            if (!pm.isIgnoringBatteryOptimizations(packageName)) {
//               intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
                intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
                intent.setData(Uri.parse("package:" + packageName));
                activity.startActivityForResult(intent, REQUEST_IGNORE_BATTERY_CODE);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在界面重写onActivityResult方法来捕获用户的选择。如,代码如下:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (resultCode == RESULT_OK) {
        if (requestCode == BatteryUtils.REQUEST_IGNORE_BATTERY_CODE){
            //TODO something
        }
    }else if (resultCode == RESULT_CANCELED){
        if (requestCode == BatteryUtils.REQUEST_IGNORE_BATTERY_CODE){
            ToastUtils.show(getActivity(), "请开启忽略电池优化~");
        }
    }
}

7.双进程守护

关于进程守护其逻辑就是,AB两个进程,A进程里面轮询检查B进程是否存活,没存活的话就将其拉起,同样B进程里面轮询检查A进程是否存活,没存活的话也将其拉起,而我们的后台逻辑则随便放在某个进程里执行即可,一个简单的例子是使用两个Service:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="teach.focus.notification">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".DaemonService"
            android:enabled="true"
            android:exported="false"
            android:process=":service" />
        <service
            android:name=".ProtectService"
            android:enabled="true"
            android:exported="false"
            android:process=":remote"/>
    </application>

</manifest>

使用两个进程分别装载两个Service,在两个Service中开轮询,互相唤醒:

public class DaemonService extends Service {
    private static boolean isRunning;
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
         if (!isRunning) {
            isRunning = true;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    SystemClock.sleep(3000);
                    isRunning = false;
                    startService(new Intent(DaemonService.this, ProtectService.class));
                    System.out.println("DaemonService");
                }
            }).start();
        }
        return super.onStartCommand(intent, flags, startId);
    }
}
public class ProtectService extends Service {
     private static boolean isRunning;

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

    @Override
    public void onCreate() {
        super.onCreate();
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (!isRunning) {
            isRunning = true;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    SystemClock.sleep(1500);
                    System.out.println("ProtectService");
                    isRunning = false;
                    startService(new Intent(ProtectService.this, DaemonService.class));
                }
            }).start();
        }
        return START_STICKY;
    }
}

在原生系统及相当一部分的ROM下上述方法就已经很有用了,即便应用主进程被用户在Recent Task中被清理也无妨上述进程的进行,该方法直至Android 6.0也相当有效,但是对于一些深度定制的ROM就显得很鸡肋,比如魅族、小米。

8.利用账号同步机制拉活

Android 系统的账号同步机制会定期同步账号进行,该方案目的在于利用同步机制进行进程的拉活。

该方案适用于所有的 Android 版本,包括被 forestop 掉的进程也可以进行拉活。

最新 Android 版本(Android N)中系统好像对账户同步这里做了变动,该方法不再有效。

此外,还有一些技术之外的措施,比如说应用内 Push 通道的选择:

1.国外版应用:接入 Google 的 GCM。

2.国内版应用:根据终端不同,在小米手机(包括 MIUI)接入小米推送、华为手机接入华为推送;其他手机可以考虑接入腾讯信鸽或极光推送与小米推送。

参考链接:
http://www.360doc.com/content/16/1116/17/1073512_607042512.shtml
Android 进程保活招式大全

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值