Android进程保活(最新)带你浅析这几种可行性的保活方案

1.概述

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

2.问题

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

3.分析

  3.1进程的划分

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

3.1.1. 前台进程 —— Foreground process

用户当前操作所必需的进程。通常在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。

A. 拥有用户正在交互的 Activity(已调用onResume())

B. 拥有某个 Service,后者绑定到用户正在交互的 Activity

C. 拥有正在“前台”运行的 Service(服务已调用startForeground())

D. 拥有正执行一个生命周期回调的 Service(onCreate()、onStart()或onDestroy())

E. 拥有正执行其onReceive()方法的 BroadcastReceiver

3.1.2. 可见进程 —— Visible process

没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。

A. 拥有不在前台、但仍对用户可见的 Activity(已调用onPause())。

B. 拥有绑定到可见(或前台)Activity 的 Service

3.1.3. 服务进程 —— Service process

尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。

A. 正在运行startService()方法启动的服务,且不属于上述两个更高类别进程的进程。

3.1.4. 后台进程 —— Background process

后台进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU 列表中,以确保包含用户最近查看的Activity的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。

A. 对用户不可见的 Activity 的进程(已调用Activity的onStop()方法)

3.1.5. 空进程 —— Empty process

保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

A. 不含任何活动应用组件的进程

  具体地,活动进程指的就是用户正在操作的程序,是前台进程,可以看到且能够操作;可见进程就是看得见摸不着的,不能直接操作的进程;服务进程是没有界面的一直在后台工作的进程,优先级不高,当系统内存不足时会被杀死,再次充裕的时候会再次开启;后台进程就是用户按了"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的值,以及如何使得我们应用占的内存最少。


方案1

开启一个像素的Activity

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

publicclassSinglePixelActivityextendsActivity{

public staticfinalStringTAG=SinglePixelActivity.class.getSimpleName();

    public static void actionToSinglePixelActivity(Context pContext) {

Intent intent =newIntent(pContext, SinglePixelActivity.class);

        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        pContext.startActivity(intent);

    }

@Override

protectedvoid onCreate(BundlesavedInstanceState) {

super.onCreate(savedInstanceState);

Log.d(TAG,"onCreate");

setContentView(R.layout.activity_singlepixel);

Windowwindow = getWindow();

//放在左上角

window.setGravity(Gravity.START|Gravity.TOP);

WindowManager.LayoutParamsattributes = window.getAttributes();

//宽高设计为1个像素

attributes.width =1;

attributes.height =1;

//起始坐标

attributes.x =0;

attributes.y =0;

        window.setAttributes(attributes);

ScreenManager.getInstance(this).setActivity(this);

    }

@Override

protectedvoid onDestroy() {

super.onDestroy();

    }

}

在屏幕关闭的时候把SinglePixelActivity启动起来,在开屏的时候把SinglePixelActivity 关闭掉,所以要监听系统锁屏广播,以接口的形式通知MainActivity启动或者关闭SinglePixActivity。


publicclassScreenBroadcastListener{

privateContext mContext;

privateScreenBroadcastReceiver mScreenReceiver;

privateScreenStateListener mListener;

publicScreenBroadcastListener(Context context){

        mContext = context.getApplicationContext();

mScreenReceiver =newScreenBroadcastReceiver();

    }

interfaceScreenStateListener{

voidonScreenOn();

voidonScreenOff();

    }

/**

    * screen状态广播接收者

    */

privateclassScreenBroadcastReceiverextendsBroadcastReceiver{

privateString action =null;

        @Override

publicvoidonReceive(Context context, Intent intent){

            action = intent.getAction();

if(Intent.ACTION_SCREEN_ON.equals(action)) {// 开屏

                mListener.onScreenOn();

}elseif(Intent.ACTION_SCREEN_OFF.equals(action)) {// 锁屏

                mListener.onScreenOff();

            }

        }

    }

publicvoidregisterListener(ScreenStateListener listener){

        mListener = listener;

        registerListener();

    }

privatevoidregisterListener(){

IntentFilter filter =newIntentFilter();

        filter.addAction(Intent.ACTION_SCREEN_ON);

        filter.addAction(Intent.ACTION_SCREEN_OFF);

        mContext.registerReceiver(mScreenReceiver, filter);

    }

}


publicclassScreenManager{

privateContext mContext;

privateWeakReference mActivityWref;

publicstaticScreenManager gDefualt;

publicstaticScreenManagergetInstance(Context pContext){

if(gDefualt ==null) {

gDefualt =newScreenManager(pContext.getApplicationContext());

        }

returngDefualt;

    }

privateScreenManager(Context pContext){

this.mContext = pContext;

    }

publicvoidsetActivity(Activity pActivity){

mActivityWref =newWeakReference(pActivity);

    }

publicvoidstartActivity(){

            SinglePixelActivity.actionToSinglePixelActivity(mContext);

    }

publicvoidfinishActivity(){

//结束掉SinglePixelActivity

if(mActivityWref !=null) {

Activity activity = mActivityWref.get();

if(activity !=null) {

                activity.finish();

            }

        }

    }

}


现在MainActivity改成如下

publicclassMainActivityextendsAppCompatActivity{

@Override

protectedvoid onCreate(BundlesavedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

finalScreenManagerscreenManager =ScreenManager.getInstance(MainActivity.this);

ScreenBroadcastListenerlistener =newScreenBroadcastListener(this);

listener.registerListener(newScreenBroadcastListener.ScreenStateListener() {

@Override

            public void onScreenOn() {

                screenManager.finishActivity();

            }

@Override

            public void onScreenOff() {

                screenManager.startActivity();

            }

        });

    }

}


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

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


方案2

据说这个微信也用过的进程保活方案,该方案实际利用了Android前台service的漏洞。

原理如下

对于 API level < 18 :调用startForeground(ID, new Notification()),发送空的Notification ,图标则不会显示。

对于 API level >= 18:在需要提优先级的service A启动一个InnerService,两个服务同时startForeground,且绑定同样的 ID。Stop 掉InnerService ,这样通知栏图标即被移除。


publicclassKeepLiveServiceextendsService{

publicstaticfinalintNOTIFICATION_ID=0x11;

publicKeepLiveService(){

    }

@Override

publicIBinderonBind(Intent intent){

thrownewUnsupportedOperationException("Not yet implemented");

    }

@Override

publicvoidonCreate(){

super.onCreate();

//API 18以下,直接发送Notification并将其置为前台

if(Build.VERSION.SDK_INT

startForeground(NOTIFICATION_ID,newNotification());

}else{

//API 18以上,发送Notification并将其置为前台后,启动InnerService

Notification.Builder builder =newNotification.Builder(this);

            builder.setSmallIcon(R.mipmap.ic_launcher);

            startForeground(NOTIFICATION_ID, builder.build());

startService(newIntent(this, InnerService.class));

        }

    }

publicstaticclass  InnerService extends Service{

@Override

publicIBinderonBind(Intent intent){

returnnull;

        }

@Override

publicvoidonCreate(){

super.onCreate();

//发送与KeepLiveService中ID相同的Notification,然后将其取消并取消自己的前台显示

Notification.Builder builder =newNotification.Builder(this);

            builder.setSmallIcon(R.mipmap.ic_launcher);

            startForeground(NOTIFICATION_ID, builder.build());

newHandler().postDelayed(newRunnable() {

@Override

publicvoidrun(){

stopForeground(true);

                    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

                    manager.cancel(NOTIFICATION_ID);

                    stopSelf();

                }

},100);

        }

    }

}


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



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



方案3

进程相互唤醒

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


方案4

JobSheduler

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

JobSheduler@TargetApi(Build.VERSION_CODES.LOLLIPOP)

publicclassMyJobServiceextendsJobService{

@Override

publicvoidonCreate(){

super.onCreate();

        startJobSheduler();

    }

publicvoidstartJobSheduler(){

try{

JobInfo.Builder builder =newJobInfo.Builder(1,newComponentName(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

publicbooleanonStartJob(JobParameters jobParameters){

returnfalse;

    }

@Override

publicbooleanonStopJob(JobParameters jobParameters){

returnfalse;

    }

}

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

@Override

publicintonStartCommand(Intent intent,intflags,intstartId){

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

publicLiveService() {

    }

@Override

    public void onNotificationPosted(StatusBarNotification sbn) {

    }

@Override

    public void onNotificationRemoved(StatusBarNotification sbn) {

    }

}

但是这种方式需要权限

android:name=".LiveService"

android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">

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

okhttp执行流出:

流程简述:

(1)、当我们通过OkhttpClient创立一个Call,并发起同步或者异步请求时;

(2)、okhttp会通过Dispatcher对我们所有的RealCall(Call的具体实现类)进行统一管理,并通过execute()及enqueue()方法对同步或者异步请求进行解决;

(3)、execute()及enqueue()这两个方法会最终调用RealCall中的getResponseWithInterceptorChain()方法,从阻拦器链中获取返回结果;

(4)、阻拦器链中,依次通过RetryAndFollowUpInterceptor(重定向阻拦器)、BridgeInterceptor(桥接阻拦器)、CacheInterceptor(缓存阻拦器)、ConnectInterceptor(连接阻拦器)、CallServerInterceptor(网络阻拦器)对请求依次解决,与服务的建立连接后,获取返回数据,再经过上述阻拦器依次解决后,最后将结果返回给调用方。

————————————————

版权声明:本文为CSDN博主「沐潼爱吃鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/aiyoufang/article/details/103173087

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342