美文网首页面试题
Android进程保活有哪些实现方案

Android进程保活有哪些实现方案

作者: Eternal_life | 来源:发表于2020-04-10 15:38 被阅读0次

    说到进程保活,忍不住吐槽一下市场上除了微信这样的白名单大佬,其他的应用很难在后台保持长链接,保活只能是使用一些歪门邪道来延长进程的持续时间。总的来说也就这两种方法:
    (1)提供进程优先级,降低进程被杀死的概率;(2)在进程被杀死后,进行拉活.
    今天就先来说降低被杀死的概率方法:一像素Activity,双进程守护Service。

    1像素Activity

    1像素Activity的特点: 需要设置该activity的style设置透明,在手机锁屏时start;在屏幕解锁时finish,主要作用就是在App退到后台之后且锁屏的时候启动一个看不见的Activity,造成一个该App没有回退到后台的假象,从而降低被杀的几率。
    代码:
    在onCreate方法里设定一像素的activity

            window.setGravity(Gravity.START | Gravity.TOP);
            WindowManager.LayoutParams params = window.getAttributes();
            params.x = 0;
            params.y = 0;
            params.height = 1;
            params.width = 1;
            window.setAttributes(params);
    

    在onReceive方法里进行操作

    public void onReceive(final Context context, Intent intent) {
            Log.i("ScreenStateReceiver", "---屏幕锁屏监听---");
            if (action.equals(Intent.ACTION_SCREEN_OFF)) {
               //屏幕锁定,开启OnePixelActivity
    
            } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
               //屏幕解锁,finish OnePixelActivity
            }
        }
    

    说一下详细步骤
    1.需要注册一个监听屏幕锁屏和解屏的BroadcastReceiver
    关于这一点有一个细节需要处理,为了放置用户快速进行锁屏和解屏的切换操作,而导致OnePixelActivity频繁开关。需要用一个Handler发送一个延迟消息处理最佳:

        private Handler mHandler;
        private boolean isScreenOn = true;
        private PendingIntent  pendingIntent;
        private List<ScreenStateListener> screenStateListeners = null;
        @Override
        public void onReceive(final Context context, Intent intent) {
            String action = intent.getAction();
          
            if (action.equals(Intent.ACTION_SCREEN_OFF)) {
               //标记屏幕为锁屏状态
                isScreenOn = false;
                //开启一像素的Activity
                startOnePixelActivity(context);
    
            } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
                //标记屏幕为解屏状态
                isScreenOn = true;     
                if(pendingIntent!=null){
                    pendingIntent.cancel();
                }
         
            }
        }
    
      //开启一像素的Activity
        private void startOnePixelActivity(final Context context){
            if(mHandler==null){
                mHandler = new Handler(Looper.myLooper());
            }
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                  //如果屏幕此时已经打开,则不执行
                    if(isScreenOn){
                        return;
                    }
                    if(pendingIntent!=null){
                        pendingIntent.cancel();
                    }
                    
                    Intent startOnePixelActivity = new Intent(context, OnePixelActivity.class);
                    startOnePixelActivity.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
    
                    //启动一像素包活activity
                    pendingIntent = PendingIntent.getActivity(context, 0, startOnePixelActivity, 0);
                    try {
                        pendingIntent.send();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    notifyScreenOff();
                }
            },1000);
        }
    

    2、需要将该activity的 android:excludeFromRecents设置为true
    3、需要将该Activity设置为singleInstance启动模式

    <activity android:name=".OnePixelActivity"
                android:theme="@style/onePixelActivity"
                android:launchMode="singleInstance"
                android:excludeFromRecents="true"/>
    

    为什么需要将此Activity的启动模式设置为singleInstance呢?原因是因为如果设置成其他模式,如果按照如下步骤操作的话会出现不友好的状况:
    1、启动App(比如进入MainActivity),按home键让app返回后台
    2、锁定屏幕,此时注册好的监听广播会启动OnePixelActivity
    3、解锁屏幕,此时广播接受到此广播后会finish 掉OnePixelActivity.
    也就是说,当你的app在后台的时候,你解锁屏幕,创建销毁OnePixelActvity的同时会把整个栈的activity弹出来,然会用户就会莫名其妙的看到你的app自己打开了。

    另外需要注意的是,当屏幕解锁的时候,OnePixelActivity的onResume得到执行,所以在该Activity的onResume方法执行finish效果最好:

    //OnePixelActivity的onResume
       protected void onResume() {
            super.onResume();
            if (DeviceUtils.isScreenOn(this)) {//如果屏幕已经打开
                finish();
            }
        }
    

    双进程守护

    所谓双进程守护原理就是很简单:两个进程的Service,相互守护;当一个进程的Service挂的时候,另一个进程的Service负责重启挂掉的Service.因为系统的进程回收机制是一个个回收的,利用这个时间差来相互唤起,当一个进程被磨灭掉,另一个马上重启,缺点是现在大部分机型只要一键清理就玩完了。文中KeepAliveService是跟我们要保活的App处于一个进程的Service,RemoteService是另外一个进程的Service;将RemoteService设置成单独的进程很方便,在AndroidMainfest.xml里面配置 android:process即可。

    <service android:name=".KeepAliveService"/>
    //将RemoteService和KeepAliveServcie处于不同的进程
    <service android:name=".RemoteService" android:process=":remote"/>
    

    守护进程其实是一个双向守护的过程,比如KeepAliveService挂了,那么RemoteService负责将KeepAliveService重启;同理,如果RemoteService挂了的话,KeepAliveService负责将RemoteService重启!那么二者是怎么知道彼此是否挂了呢?因为是位于两个进程,所以我们可以通过AIDL来确保两个不同进程的Service可以通信。
    定义一个AIDL接口

    interface GuardAidl {
        void notifyAlive();
    }
    

    接着我们重写KeepAliveSerivice的onBind方法,该方法返回一个IBinder给RemoteService使用:

     //将keepAliveBinder 交给RemoteService
        public IBinder onBind(Intent intent) {
            return keepAliveBinder;
        }
     
        private GuardAidl.Stub keepAliveBinder = new GuardAidl.Stub(){
            @Override
            public void notifyAlive() throws RemoteException {
                Log.i(null,"Hello RemoteService!");
            }
        };
    

    通过KeepAliveService的onBind方法返回一个IBinder对象交给RemoteService使用,这样RemoteService就可以通过keepAliveBinder对象跟KeepAliveServcie进行通信!同样的RemoteService也是一样的代码逻辑:

    public class RemoteService extends Service {
        public IBinder onBind(Intent intent) {
            return remoteBinder;
        }
        private GuardAidl.Stub remoteBinder = new GuardAidl.Stub(){
            @Override
            public void notifyAlive() throws RemoteException {
                Log.i(null,"Hello KeepAliveService!");
            }
        };
    }
    

    两个Servcie相互绑定
    现在两个Service都重写onBind返回了自己的IBinder,但是怎么将自己创建的IBinder 交给对象使用呢?答案是双方调用bindSerice方法相互绑定对方,该方法是Service的一个方法,其签名如下:

    bindService(Intent service, ServiceConnection conn, int flags)
    

    所以我们绑定的时候除了传一个intent之外,还要传一个ServiceConnection。看看KeepAliveService绑定RemoteService的代码:

    //KeepAliveService的onStartCommand方法
     public int onStartCommand(Intent intent, int flags, int startId) {
            Log.e("KeepAliveService", "---KeepAliveService 启动---");
            //注册锁屏广播
            registerScreenStateReceiver();
            //初始化播放器
            initMediaPlayer();
            
            //开启前台Service
            startForeground(this);
            
            //start HideNotifactionService
            startHideNotificationService();
            
            //绑定守护进程
            bindRemoteService();
            
            return START_STICKY;
        }
        
       //绑定守护进程
       private void bindRemoteService(){
            Intent intent = new Intent(this,RemoteService.class);
            bindService(intent,connection,Context.BIND_ABOVE_CLIENT);
        }
    
       private ServiceConnection connection = new ServiceConnection() {
           //调用此方法说明RemoteServcie已经挂掉,需要重新启动
            public void onServiceDisconnected(ComponentName name) {
             Intent remoteService = new Intent(KeepAliveService.this,
                            RemoteService.class);
                    KeepAliveService.this.startService(remoteService);
                    
                Intent intent = new Intent(KeepAliveService.this, RemoteService.class);
                //将KeepAliveService和RemoteService进行绑定
                KeepAliveService.this.bindService(intent, connection,
                        Context.BIND_ABOVE_CLIENT);
            }
            
            @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
                try {
                    //与RemoteService绑定成功
                    GuardAidl remoteBinder = GuardAidl.Stub.asInterface(service);
                    remoteBinder.notifyAlive();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        };
    

    其中ServiceConnection有两个方法onServiceDisconnected和onServiceConnected,当RemoteService被异常销毁挂掉的时候,onServiceDisconnected会被调用。此时需要在该方法里面重新启动RemoteService;而onServiceConnected方法则是系统调用它来传送在RemoteService的onBind()中返回的IBinder!同理,RemoteService绑定KeepAlivce的代码跟上面雷同,再此就不在贴出来了,
    源码可以点击此处查阅

    相关文章

      网友评论

        本文标题:Android进程保活有哪些实现方案

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