如何让进程杀不死(2)

根据上一篇文章,我们知道了:是因为当系统资源吃紧或者说用户手动调用某些清理应用时,就会杀掉相应的进程。如果我们进程的Importance等级底与adj值高的话,我们的进程会优先被清理掉。
策略与应对:
上面说了这么多,其实我们也差不多能总结出一套规律,要想让我们的后台进程长存,我们首先要应付的就是系统的“自杀”机制,而后台进程被杀的首要原因就是我们的进程优先级太低同时系统可用资源太少,其次如果真的被系统干掉,那么我们得重新拉起进程让它重复上次的故事,因此我们的进程后台常驻策略最终可以归结为两点:
轻量化进程
所谓轻量化进程,其实就是迫使我们的进程占用尽量少的资源,但是我们知道的是一个运行中的App就算功能再少也会占用相当一部分资源,因此在这里我们是不应该去想着让我们的应用主进程在后台常驻,让一个没有看不见的界面在后台跑既没意义也没必要,因此大多数情况下我们都会使用一个新的进程去常驻在后台,而这个进程一般会持有一个Service,后台所有的龌龊事都会交由它去处理,毕竟在Android中干这种龌龊事的也只有Service了。
但是随着时间的推移进程中的一些对象可能会做缓存导致内存的使用增大,不过只要能被回收就没有什么大碍:
因此,如果你想在进程中的Service里处理更复杂的逻辑,务必尽量多地使用弱引用或软引用,或者说尽量多地去置空一些不必要的引用并在需要的时候再赋值,其次Service本身也提供了onTrimMemory方法来告诉我们系统何时需要释放掉不必要的资源,灵活使用这类方法可以最大程度的让我们的后台Service长盛不衰。还是那句话,尽量让我们的后台进程做更少的事情,及时释放资源,才是硬道理。
被杀后重启
可以这么说,没有任何一个应用进程可以做到永远不被杀死,除非系统给你开了后门,进程被杀并不可怕,可怕的是杀掉后就永远GG思密达了,所以如何使我们的进程可以在被杀后重启呢?这就需要使用到一个叫做守护进程的东西,原理很简单,多开一个进程,让这个进程轮询检查目标进程是否存活,死了的话将其拉起,同时目标进程也需要做一个轮询检查守护进程是否存活,死了的话也将其拉起,相互唤醒一起龌龊。不过即便如此有时候意外也是难免的,在Android中我们还可以通过AlarmManager和系统广播来在一定条件下唤醒逝去的进程。

后台进程常驻的实现
进程提权:我们上面曾说到adj值越小的进程越不容易被杀死,相对普通进程来说能让adj去到0显然是最完美的,可是我们如何才能让一个完全没有可见元素的后台进程拥有前台进程的状态呢?Android给了Service这样一个功能:startForeground,它的作用就像其名字一样,将我们的Service置为前台。

死后满血复活
任何一个普通的应用进程都会有被干掉的那么一天,除非你跟系统有关系有契约,说白了就是ROM是定制的且可以给你开特殊权限,不然的话,系统总会在某个时刻因为某些原因把你杀掉,被杀掉不可怕,可怕的是被杀掉后就再也活不过来了……因此,我们得制定各种策略,好让进程能在被杀后可以自启。
Service重启
Android的Service是一个非常特殊的组件,按照官方的说法是用于处理应用一些不可见的后台操作,对于Service我们经常使用,也知道通过在onStartCommand方法中返回不同的值可以告知系统让系统在Service因为资源吃紧被干掉后可以在资源不紧张时重启:

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

public int onStartCommand(Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
}

因此如果没有什么特殊原因,我们也没必要更改。
虽然Service默认情况下是可以被系统重启的,但是在某些情况or某些定制ROM上会因为各种原因而失效,因此我们不能单靠这个返回值来达到进程重启的目的。
进程守护
关于进程守护其实也不是什么高深的技术,其逻辑也很简单,AB两个进程,A进程里面轮询检查B进程是否存活,没存活的话将其拉起,同样B进程里面轮询检查A进程是否存活,没存活的话也将其拉起,而我们的后台逻辑则随便放在某个进程里执行即可,一个简单的例子是使用两个Service:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.aigestudio.daemon">

    <application>
        <service
            android:name=".services.DaemonService"
            android:process=":service" />
        <service
            android:name=".services.ProtectService"
            android:process=":remote" />
    </application>
</manifest>

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

public class DaemonService extends Service {
    private static boolean sPower = true, 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() {
                    while (sPower) {
                        if (System.currentTimeMillis() >= 123456789000000L) {
                            sPower = false;
                        }
                        Log.d("AigeStudio", "DaemonService");
                        startService(new Intent(DaemonService.this, ProtectService.class));
                        SystemClock.sleep(3000);
                    }
                }
            }).start();
        }
        return super.onStartCommand(intent, flags, startId);
    }
}
public class ProtectService extends Service {
    private static boolean sPower = true, 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() {
                    while (sPower) {
                        if (System.currentTimeMillis() >= 123456789000000L) {
                            sPower = false;
                        }
                        SystemClock.sleep(1500);
                        Log.d("AigeStudio", "ProtectService");
                        startService(new Intent(ProtectService.this, DaemonService.class));
                    }
                }
            }).start();
        }
        return super.onStartCommand(intent, flags, startId);
    }
}

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值