zoukankan      html  css  js  c++  java
  • 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}sourcesandroid-23comandroidserveramProcessList.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.总结

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

  • 相关阅读:
    题目:输入一个链表,从尾到头打印链表每个节点的值
    【转】 文档与笔记利器 reStructuredText 和 Sphinx
    自动化selenium开发
    Sublime 3 打造成 Python/Django IDE开发利器
    python中的StringIO模块
    python检查IP地址正确性
    python2.7 使用super关键词 报错 TypeError: must be type, not&n
    【转】python time模块详解
    [黑群晖经典教程] 一步一步建立自己的黑群晖
    【转】NAS群晖DSM 5.0-4458安装教程
  • 原文地址:https://www.cnblogs.com/fuyaozhishang/p/6667301.html
Copyright © 2011-2022 走看看