Android进程保活

1.概述

  据前人验证,在没有白名单的情况下,安卓系统要做一个任何情况下都不被杀死的应用是基本不可能的,但是我们可以做到应用基本不被杀死,如果杀死可以立即复活.经过上网查询,进程常驻的方案众说纷纭,但是很多的方案都是不靠谱的或不是最好的,结合很多资料,今天总结一下Android进程保活的一些可行方法.

2.问题

  系统为什么会杀掉进程,杀的为什么是我们的进程,这是根据什么规则来决定的,是一次性干掉多个进程,还是一个接着一个杀掉?保活套路一堆,如何进行进程保活才是比较恰当......

3.分析

  3.1进程的划分

    Android中的进程也是有着严格的等级,分了三流九等,Android系统把进程划为了如下几种(重要性从高到低):

    进程的优先级

  具体地,活动进程指的就是用户正在操作的程序,是前台进程,可以看到且能够操作;可见进程就是看得见摸不着的,不能直接操作的进程;服务进程是没有界面的一直在后台工作的进程,优先级不高,当系统内存不足时会被杀死,再次充裕的时候会再次开启;后台进程就是用户按了"back"或者"home"后,程序本身看不到了,但是其实还在运行的程序,比如Activity调用了onPause方法系统可能随时终止它们,回收内存.空进程:某个进程不包含任何活跃的组件时该进程就会被置为空进程,完全没用,杀了它只有好处没坏处,第一个被处理!

    3.2内存阈值

  进程是怎么被杀的呢?系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app, 这套杀进程回收内存的机制就叫 Low Memory Killer。那这个不足怎么来规定呢,那就是内存阈值,我们可以使用cat /sys/module/lowmemorykiller/parameters/minfree来查看某个手机的内存阈值。

注意这些数字的单位是page. 1 page = 4 kb.上面的六个数字对应的就是(MB): 72,90,108,126,144,180,这些数字也就是对应的内存阀值,内存阈值在不同的手机上不一样,一旦低于该值,Android便开始按顺序关闭进程. 因此Android开始结束优先级最低的空进程,即当可用内存小于180MB(46080*4/1024)。

  进程是有它的优先级的,这个优先级通过进程的adj值来反映,它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收,adj值定义在com.android.server.am.ProcessList类中,这个类路径是${android-sdk-path}\sources\android-23\com\android\server\am\ProcessList.java。oom_adj的值越小,进程的优先级越高,普通进程oom_adj值是大于等于0的,而系统进程oom_adj的值是小于0的,我们可以通过cat /proc/进程id/oom_adj可以看到当前进程的adj值。

  也就是说,oom_adj越大,占用物理内存越多会被最先kill掉,OK,那么现在对于进程如何保活这个问题就转化成,如何降低oom_adj的值,以及如何使得我们应用占的内存最少。

4.进程保活

  4.1开启一个像素的Activity

  据说这个是手Q的进程保活方案,基本思想,系统一般是不会杀死前台进程的。所以要使得进程常驻,我们只需要在锁屏的时候在本进程开启一个Activity,为了欺骗用户,让这个Activity的大小是1像素,并且透明无切换动画,在开屏幕的时候,把这个Activity关闭掉,所以这个就需要监听系统锁屏广播.

public class SinglePixelActivity extends Activity { public static final String TAG = SinglePixelActivity.class.getSimpleName(); public static void actionToSinglePixelActivity(Context pContext) { Intent intent = new Intent(pContext, SinglePixelActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); pContext.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate"); setContentView(R.layout.activity_singlepixel); Window window = getWindow(); //放在左上角 window.setGravity(Gravity.START | Gravity.TOP); WindowManager.LayoutParams attributes = window.getAttributes(); //宽高设计为1个像素 attributes.width = 1; attributes.height = 1; //起始坐标 attributes.x = 0; attributes.y = 0; window.setAttributes(attributes); ScreenManager.getInstance(this).setActivity(this); } @Override protected void onDestroy() { super.onDestroy(); } }
在屏幕关闭的时候把SinglePixelActivity启动起来,在开屏的时候把SinglePixelActivity 关闭掉,所以要监听系统锁屏广播,以接口的形式通知MainActivity启动或者关闭SinglePixActivity。
public class ScreenBroadcastListener {

    private Context mContext; private ScreenBroadcastReceiver mScreenReceiver; private ScreenStateListener mListener; public ScreenBroadcastListener(Context context) { mContext = context.getApplicationContext(); mScreenReceiver = new ScreenBroadcastReceiver(); } interface ScreenStateListener { void onScreenOn(); void onScreenOff(); } /** * screen状态广播接收者 */ private class ScreenBroadcastReceiver extends BroadcastReceiver { private String action = null; @Override public void onReceive(Context context, Intent intent) { action = intent.getAction(); if (Intent.ACTION_SCREEN_ON.equals(action)) { // 开屏 mListener.onScreenOn(); } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // 锁屏 mListener.onScreenOff(); } } } public void registerListener(ScreenStateListener listener) { mListener = listener; registerListener(); } private void registerListener() { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); mContext.registerReceiver(mScreenReceiver, filter); } }
public class ScreenManager { private Context mContext; private WeakReference<Activity> mActivityWref; public static ScreenManager gDefualt; public static ScreenManager getInstance(Context pContext) { if (gDefualt == null) { gDefualt = new ScreenManager(pContext.getApplicationContext()); } return gDefualt; } private ScreenManager(Context pContext) { this.mContext = pContext; } public void setActivity(Activity pActivity) { mActivityWref = new WeakReference<Activity>(pActivity); } public void startActivity() { SinglePixelActivity.actionToSinglePixelActivity(mContext); } public void finishActivity() { //结束掉SinglePixelActivity if (mActivityWref != null) { Activity activity = mActivityWref.get(); if (activity != null) { activity.finish(); } } } }

现在MainActivity改成如下

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final ScreenManager screenManager = ScreenManager.getInstance(MainActivity.this); ScreenBroadcastListener listener = new ScreenBroadcastListener(this); listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() { @Override public void onScreenOn() { screenManager.finishActivity(); } @Override public void onScreenOff() { screenManager.startActivity(); } }); } }

按下back之后,进行锁屏,现在测试一下oom_adj的值

果然将进程的优先级提高了。

  

据说这个微信也用过的进程保活方案,该方案实际利用了Android前台service的漏洞。
原理如下
对于 API level < 18 :调用startForeground(ID, new Notification()),发送空的Notification ,图标则不会显示。
对于 API level >= 18:在需要提优先级的service A启动一个InnerService,两个服务同时startForeground,且绑定同样的 ID。Stop 掉InnerService ,这样通知栏图标即被移除。

 

public class KeepLiveService extends Service { public static final int NOTIFICATION_ID=0x11; public KeepLiveService() { } @Override public IBinder onBind(Intent intent) { throw new UnsupportedOperationException("Not yet implemented"); } @Override public void onCreate() { super.onCreate(); //API 18以下,直接发送Notification并将其置为前台 if (Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN_MR2) { startForeground(NOTIFICATION_ID, new Notification()); } else { //API 18以上,发送Notification并将其置为前台后,启动InnerService Notification.Builder builder = new Notification.Builder(this); builder.setSmallIcon(R.mipmap.ic_launcher); startForeground(NOTIFICATION_ID, builder.build()); startService(new Intent(this, InnerService.class)); } } public static class InnerService extends Service{ @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); //发送与KeepLiveService中ID相同的Notification,然后将其取消并取消自己的前台显示 Notification.Builder builder = new Notification.Builder(this); builder.setSmallIcon(R.mipmap.ic_launcher); startForeground(NOTIFICATION_ID, builder.build()); new Handler().postDelayed(new Runnable() { @Override public void run() { stopForeground(true); NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); manager.cancel(NOTIFICATION_ID); stopSelf(); } },100); } } }

 

在没有采取前台服务之前,启动应用,oom_adj值是0,按下返回键之后,变成9(不同ROM可能不一样)

 

 

在采取前台服务之后,启动应用,oom_adj值是0,按下返回键之后,变成2(不同ROM可能不一样),确实进程的优先级有所提高。

 

  4.3进程相互唤醒

  顾名思义,就是指的不同进程,不同app之间互相唤醒,如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其他阿里系的app给唤醒了。

  4.4JobSheduler

JobSheduler是作为进程死后复活的一种手段,native进程方式最大缺点是费电, Native 进程费电的原因是感知主进程是否存活有两种实现方式,在 Native 进程中通过死循环或定时器,轮训判断主进程是否存活,当主进程不存活时进行拉活。其次5.0以上系统不支持。 但是JobSheduler可以替代在Android5.0以上native进程方式,这种方式即使用户强制关闭,也能被拉起来,亲测可行。

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

  

这个是系统自带的,onStartCommand方法必须具有一个整形的返回值,这个整形的返回值用来告诉系统在服务启动完毕后,如果被Kill,系统将如何操作,这种方案虽然可以,但是在某些情况or某些定制ROM上可能失效,认为可以多做一种保保守方案。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    return START_REDELIVER_INTENT;
}
  • START_STICKY
    如果系统在onStartCommand返回后被销毁,系统将会重新创建服务并依次调用onCreate和onStartCommand(注意:根据测试Android2.3.3以下版本只会调用onCreate根本不会调用onStartCommand,Android4.0可以办到),这种相当于服务又重新启动恢复到之前的状态了)。

  • START_NOT_STICKY
    如果系统在onStartCommand返回后被销毁,如果返回该值,则在执行完onStartCommand方法后如果Service被杀掉系统将不会重启该服务。

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

相比与粘性服务与系统服务捆绑更厉害一点,这里说的系统服务很好理解,比如NotificationListenerService,NotificationListenerService就是一个监听通知的服务,只要手机收到了通知,NotificationListenerService都能监听到,即时用户把进程杀死,也能重启,所以说要是把这个服务放到我们的进程之中,那么就可以呵呵了

@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.总结

  多种保活方式,没有说哪一种最好,只有是在什么场景下,使用哪一种最合适;当然,这些方式不是我发明或发现的,但是我觉得如果不知道的好好了解一下,对自己会有很大的帮助.掌握一些进程保活的手段,这不是耍流氓,是很多场景如果要想为用户服务,就必须有一个进程常驻,以便在特定的时候做特定的事情。诚然,但凡进程常驻内存,无论怎样优化,都会或多或少的增加一些额外的性能开支,在为用户最负责任的服务,最高品质的体现我们的价值的前提下,我们要尽可能减少内存和电量的消耗。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值