Android中的进程保活

1. 概述


根据前辈们的经验,如果没有白名单,Android系统要做一个任何情况下都不被杀死的应用几乎是不可能的,但我们可以做一个最大程度不被杀死,如果被杀死可以立即让它在第一时间内复活,那也是ok的。网上的进程常驻也是众说纷纭,这篇文章就总结下Android中进程保活的一些可行性方法,当然这些东西也不是我发明的,我只是在网上查询之后将其写成自己的文章,然后分享出来。

2. 白名单概念?


首先说下什么是白名单?白名单就是把我们自己的应用直接内置到一些手机厂商的手机中,让其手机厂商自带该应用,即就是该app软件

3. 问题?


3.1>:系统为什么会杀死进程?
3.2>:为什么会杀死我们的进程?
3.3>:根据什么规则决定?
3.4>:是一次杀掉多个,还是一个一个杀死?
那么带着这几个问题,我们就继续往下边来看。

4. 分析


4.1>:进程的划分,如下图所示
进程优先级.png
分析上图可以得知,进程被分为5种,按照优先级从高到低:
1>:活动进程

优先级最高,指的是用户正在操作的程序,是前台进程,并且可以操作的;

2>:可见进程

次高优先级,指的是看的见,摸不着,不能直接操作的进程;

3>:服务进程

第三优先级,没有界面,一直运行在后台,优先级不高,当系统内存不足的时会被杀死,当内存再次充裕时会重新再次开启;

4>:后台进程

低优先级,用户按下home或者back键后,程序本身看不到了,但是其实还是在运行的程序;比如Activity调用 onPause()方法,系统可能随时终止它们,回收内存;

5>:空进程

优先级最低,某个进程不包含任何活跃的组件,该进程就会被置为空进程,完全没用,系统会第一个回收空进程。

4.2>:内存阈值

app在返回到后台时,系统并不会kill这个进程的,而是将其缓存起来。打开的进程越多,后台缓存的进程就越多,系统在内存不足时,会根据自身的一套进程回收机制来判断需要kill掉哪些进程,来腾出内存给有需要的app,这套杀死进程回收内存的机制就叫做 Low Memory Killer。怎样去规定内存不足,就是内存阈值,可以使用 cat /sys/module/lowmemorykiller/parameters/minfree 来查看手机的内存阈值;


图片.png

可以看出上边的6个数字,这些数字的单位就是 page。 1page=4kb,上边的6个数字对应的就是 MB,及对应的是 72,90,108,126,144,180,这些数字就对应的是内存阈值,内存阈值在不同手机上不一样,一旦低于该值,Android便开始顺序关闭进程,也就是说结束优先级最低的进程,即就是最小可用内存小于 180MB (46080*4/1024)。

注意:

进程是有它的优先级,该优先级是根据进程的adj值来反映,它是 Linux内核分配给每个进程的一个值,进程回收机制是根据优先级决定的。
oom_adj越大,占用的物理内存越大,会被最先kill掉,也就是说现在可以 把 进程如何保活变成 如何减小 oom_adj的值,来使应用内存占用最小。

5. 进程保活使用方法


5.1>:开启一个像素的Activity

思想就是:系统一般不会杀死前台进程,所以要使进程常驻,只需要:
打开屏幕的时候关闭 一个像素的Activity;
锁屏的时候开启一个 一个像素的Activity,透明并且没有动画;
做法就是 只需要监听系统的锁屏广播即可,代码如下:

1>:一个像素的Activity:
/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/4/13 13:57
 * Version 1.0
 * Params:
 * Description:  开启一个像素的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();
    }
}
2>:在屏幕关闭的时候启动 一个像素的SinglePixelActivity ,打开屏幕的时候 finish掉 SinglePixelActivity ,所以需要监听系统锁屏的广播,使用接口形式通知 MainActivity 来 打开或者关闭 SinglePixelActivity ,该监听器代码如下:
/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/4/13 13:58
 * Version 1.0
 * Params:
 * Description:   监听 系统锁屏的广播监听器       
*/

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);
    }
}
/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/4/13 13:57
 * Version 1.0
 * Params:
 * Description:   screen屏幕管理者
*/
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
        SinglePixelActivity.actionToSinglePixelActivity(mContext);
    }

    public void finishActivity() {
        //结束掉SinglePixelActivity
        if (mActivityWref != null) {
            Activity activity = mActivityWref.get();
            if (activity != null) {
                activity.finish();
            }
        }
    }

}
3>:在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() {
                // 屏幕打开时候,关闭一个像素的Activity
                screenManager.finishActivity();
            }

            @Override
            public void onScreenOff() {
                // 屏幕锁屏的时候,开启一个像素的SinglePixelActivity
                screenManager.startActivity();
            }
        });
    }
}
经过测试,按下back返回键之后,然后锁屏,测试一下 oom_adj的值,发现进程的优先级提高了。
图片.png

由于需要考虑内存的问题,因为内存占用越多的进程会被最先杀掉,所以需要把上边的 MainActivity中的业务逻辑放在Service中,然后直接在 MainActivity 中开启这个服务就ok,这样进程会更加轻量。

4>:LiveService代码如下:
/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/4/13 15:10
 * Version 1.0
 * Params:
 * Description:   把MainActivity的业务逻辑放到Service中,使得进程更加轻量
*/
public class LiveService extends Service {

    public  static void toLiveService(Context pContext){
        Intent intent=new Intent(pContext,LiveService.class);
        pContext.startService(intent);
    }

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


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 屏幕管理者的单例
        final ScreenManager screenManager = ScreenManager.getInstance(this);
        // 屏幕广播监听器
        ScreenBroadcastListener listener = new ScreenBroadcastListener(this);
        listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() {
            @Override
            public void onScreenOn() {
                // 屏幕打开时候,关闭一个像素的Activity
                screenManager.finishActivity();
            }

            @Override
            public void onScreenOff() {
                // 屏幕锁屏的时候,开启一个像素的SinglePixelActivity
                screenManager.startActivity();
            }
        });
        return START_REDELIVER_INTENT;
    }
}
5>:修改后的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() {
//                // 屏幕打开时候,关闭一个像素的Activity
//                screenManager.finishActivity();
//            }
//
//            @Override
//            public void onScreenOff() {
//                // 屏幕锁屏的时候,开启一个像素的SinglePixelActivity
//                screenManager.startActivity();
//            }
//        });

        // 不用上边的写法,把上边的逻辑放到 Service中,直接从 MainActivity中开启Service即可
        LiveService.toLiveService(this) ;

    }
}

5.2>:前台服务

微信的进程保活之前也是采用这种方式,其实就是利用 前台Service的漏洞
原理如下:
对于 API level < 18 ,调用 startForeground(ID, new Notification()),发送的是空的 Nofication,图标则不会显示;
对于 API level >= 18 ,在优先级的 KeepLiveService中启动 一个 InnerService,让两个服务同时启动 startFroeground(),且绑定同样的 Id,然后再 停掉 InnerService即可,代码如下:

/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/4/13 15:36
 * Version 1.0
 * Params:
 * Description:   方法2:前台进程
*/

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

        }
    }
}

5.3>:相互唤醒

意思就是如果你手机里边安装了 淘宝、天猫、支付宝等阿里系的 app,那么你打开任意一个 阿里的app之后,有可能就把阿里系的其他 app就给唤醒了。
所以说可以让 QQ、微信、淘宝、天猫、支付宝等app可以保活自己的应用也可以,或者像友盟、信鸽这种 推送的SDK,也有唤醒app的功能。

5.4>:JobScheduler

是一种进程死后复活的一种手段,native进程缺点是费电,原因是感知主进程是否存活有两种方式。在Native进程中通过死循环或者定时器,轮训判断主进程是否存活,如果不存活就拉活,5.0以上不支持。但是JobScheduler可以代替5.0以上 Native进程的方式。这种方式即使用户强制关闭,也能被拉起来,代码如下:

/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/4/14 8:34
 * Version 1.0
 * Params:
 * Description:   方法4:JobScheduler
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)   //API 21  5.0
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;
    }
}

5.5>:粘性服务与系统捆绑服务

捆绑服务很好理解,比如NotificationListenerService,用来监听手机通知的,只要手机收到了通知,NotificationListenerService都能监听到,即使用户把进程杀死,也能重启,所以我们可以把这个进程放到我们的服务中,代码如下:

/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/4/14 8:50
 * Version 1.0
 * Params:
 * Description:   方法5:粘性服务与捆绑进程
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class NotificationLiveService extends NotificationListenerService {

    public NotificationLiveService() {

    }

    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
    }

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

需要在清单文件中配置

<service android:name=".NotificationLiveService"
    android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">

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

</service>

代码已上传至github:
https://github.com/shuai999/ProcessDemo.git

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

推荐阅读更多精彩内容