003 APP保活

作者: 凤邪摩羯 | 来源:发表于2020-11-11 09:20 被阅读0次
    image.png

    1 进程常识

    image.png
    image.png
    image.png
    image.png

    2 如何保活

    • 1像素Activity保活
      据说这个是手Q的进程保活方案,基本思想,系统一般是不会杀死前台进程的。所以要使得进程常驻,我们只需要在锁屏的时候在本进程开启一个Activity,为了欺骗用户,让这个Activity的大小是1像素,并且透明无切换动画,在开屏幕的时候,把这个Activity关闭掉,所以这个就需要监听系统锁屏广播,我试过了,的确好使,如下。
    public class MainActivity extends AppCompatActivity {
     
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
       }
    }
    

    如果直接启动一个Activity,当我们按下back键返回桌面的时候,oom_adj的值是8,上面已经提到过,这个进程在资源不够的情况下是容易被回收的。现在造一个一个像素的Activity。

    public class LiveActivity extends Activity {
     
        public static final String TAG = LiveActivity.class.getSimpleName();
     
        public static void actionToLiveActivity(Context pContext) {
            Intent intent = new Intent(pContext, LiveActivity.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_live);
     
            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();
            Log.d(TAG, "onDestroy");
        }
    }
     
    

    为了做的更隐藏,最好设置一下这个Activity的主题,当然也无所谓了

       <style name="LiveStyle">
            <item name="android:windowIsTranslucent">true</item>
            <item name="android:windowBackground">@android:color/transparent</item>
            <item name="android:windowAnimationStyle">@null</item>
            <item name="android:windowNoTitle">true</item>
       </style>
    

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

    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() {
                LiveActivity.actionToLiveActivity(mContext);
        }
     
        public void finishActivity() {
            //结束掉LiveActivity
            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的值:

    image

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

    但是还有一个问题,内存也是一个考虑的因素,内存越多会被最先kill掉,所以把上面的业务逻辑放到Service中,而Service是在另外一个 进程中,在MainActivity开启这个服务就行了,这样这个进程就更加的轻量

    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) {
            //屏幕关闭的时候启动一个1像素的Activity,开屏的时候关闭Activity
            final ScreenManager screenManager = ScreenManager.getInstance(LiveService.this);
            ScreenBroadcastListener listener = new ScreenBroadcastListener(this);
            listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() {
                @Override
                public void onScreenOn() {
                    screenManager.finishActivity();
                }
                @Override
                public void onScreenOff() {
                    screenManager.startActivity();
                }
            });
            return START_REDELIVER_INTENT;
        }
    }
    
          <service android:name=".LiveService"
                android:process=":live_service"/>
    

    OK,通过上面的操作,我们的应用就始终和前台进程是一样的优先级了,为了省电,系统检测到锁屏事件后一段时间内会杀死后台进程,如果采取这种方案,就可以避免了这个问题。但是还是有被杀掉的可能,所以我们还需要做双进程守护,关于双进程守护,比较适合的就是aidl的那种方式,但是这个不是完全的靠谱,原理是A进程死的时候,B还在活着,B可以将A进程拉起来,反之,B进程死的时候,A还活着,A可以将B拉起来。所以双进程守护的前提是,系统杀进程只能一个个的去杀,如果一次性杀两个,这种方法也是不OK的。

    事实上
    那么我们先来看看Android5.0以下的源码,ActivityManagerService是如何关闭在应用退出后清理内存的

    Process.killProcessQuiet(pid);
    应用退出后,ActivityManagerService就把主进程给杀死了,但是,在Android5.0以后,ActivityManagerService却是这样处理的:

    Process.killProcessQuiet(app.pid);
    Process.killProcessGroup(app.info.uid, app.pid);
    在应用退出后,ActivityManagerService不仅把主进程给杀死,另外把主进程所属的进程组一并杀死,这样一来,由于子进程和主进程在同一进程组,子进程在做的事情,也就停止了。所以在Android5.0以后的手机应用在进程被杀死后,要采用其他方案。

    • 前台服务保活
      image.png
      这种大部分人都了解,据说这个微信也用过的进程保活方案,移步微信Android客户端后台保活经验分享,这方案实际利用了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可能不一样):

    image

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

    • 忽略电池优化
      1、增加权限
    <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
    

    2、代码跳转设置,Activity中操作

        /**
         * 忽略电池优化
         */
        @RequiresApi(api = Build.VERSION_CODES.M)
        public void ignoreBatteryOptimization() {
            try{
                PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
                boolean hasIgnored = powerManager.isIgnoringBatteryOptimizations(this.getPackageName());
                if(!hasIgnored) {
                    Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
                    intent.setData(Uri.parse("package:"+getPackageName()));
                    startActivity(intent);
                }
            }catch(Exception e){
                //TODO :handle exception
                Log.e("ex",e.getMessage());
            }
        }
    
    • 引导用户加入白名单
    • 引导用户配置允许后台运行,不限制

    3 如何拉活

    • 广播拉活


      image.png
    • 粘性服务&与系统服务捆绑

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

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

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

    • 账户同步拉活


      image.png
      image.png
    • JobSheduler


      image.png

      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;
        }
    }
    
    • 设置默认桌面拉活

    • 设置默认输入法拉活

    相关文章

      网友评论

        本文标题:003 APP保活

        本文链接:https://www.haomeiwen.com/subject/fmccvktx.html