Android 双进程守护

前言

  最近有在项目中用到高德的定位SDK,功能是每隔一定的时间获取一次用户的地理位置,采取的方案是在后台开启一个 Service,监听高德地图的位置变化。
  该功能在用户手机屏幕亮时完美实现,但是当屏幕被关闭的时候,位置信息却无法被获取了,经过原因的排查,发现是由于在用户手机息屏后,后台的 Service 被系统清除,所以功能无法起作用,也就是所谓的进程被杀了。

 

  杀进程,一方面是因为手机内存不足,另一方面其实是 Google 从用户的方面考虑,把一些常驻后台的程序通过一定的算法进行管理,将那些过度消耗系统资源的流氓软件杀除,保证手机的性能和续航。但是有的软件,像定位这类的必须要保持后台的运行,如何才能避免被系统杀掉呢。其实避免被杀进程很难做到,除非是像微信、QQ、支付宝这类系统厂商认可的软件被官方加入白名单可以避免被杀进程。那其他的小软件怎么办,我们可以另辟蹊径,无法避免被杀进程,那就让我们的软件在被杀进程后,能自动重启。
  我这里介绍一下双进程守护的方法,来实现进程被杀后的拉起。

双进程守护

  双进程守护的思想就是,两个进程共同运行,如果有其中一个进程被杀,那么另一个进程就会将被杀的进程重新拉起,相互保护,在一定的意义上,维持进程的不断运行。
  双进程守护的两个进程,一个进程用于我们所需的后台操作,且叫它本地进程,另一个进程只负责监听着本地进程的状态,在本地进程被杀的时候拉起,于此同时本地进程也在监听着这个进程,准备在它被杀时拉起,我们将这个进程称为远端进程。
  由于在 Android 中,两个进程之间无法直接交互,所以我们这里还要用到 AIDL (Android interface definition Language ),进行两个进程间的交互。

代码实现

  先来看一下demo代码结构,结构很简单,我这里创建了一个 Activity 作为界面,以及两个 Service ,一个是后台操作的 本地Service,另一个是守护进程的 远端Service,还有一个 AIDL文件用作进程间交互用。

 

项目结构

 

  Activity 的定义很简单,就几个按钮,控制 Service 的状态,我这边定义了三个按钮,一个是开启后台服务,另外两个分别是关闭本地Service和远端的Service。

 

/**
 * @author chaochaowu
 */
public class GuardActivity extends AppCompatActivity {

    @BindView(R.id.button)
    Button button;
    @BindView(R.id.button2)
    Button button2;
    @BindView(R.id.button3)
    Button button3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().hide();
        setContentView(R.layout.activity_guard);
        ButterKnife.bind(this);
    }


    @OnClick({R.id.button, R.id.button2, R.id.button3})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.button:
                startService(new Intent(this, LocalService.class));
                break;
            case R.id.button2:
                stopService(new Intent(this, LocalService.class));
                break;
            case R.id.button3:
                stopService(new Intent(this, RemoteService.class));
                break;
            default:
                break;
        }
    }
}

  可以看一下界面。

 

主界面

  AIDL文件可以根据业务需要添加接口。

 

/**
 * @author chaochaowu
 */
interface IMyAidlInterface {
    String getServiceName();
}

  重点是在两个 Service 上。
  在定义Service时,需要在 AndroidManifest 中声明一下 远端Service 的 process 属性,保证 本地Service 和 远端Service 两者跑在不同的进程上,如果跑在同一个进程上,该进程被杀,那就什么都没了,就没有了双进程守护的说法了。

 

<service
    android:name=".guard.LocalService"
    android:enabled="true"
    android:exported="true" />
<service
    android:name=".guard.RemoteService"
    android:enabled="true"
    android:exported="true"
    android:process=":RemoteProcess"/>

  先来看 LocalService 的代码,重点关注 onStartCommand 方法 和 ServiceConnection 中重写的方法。onStartCommand 方法是在 Service 启动后被调用,在 LocalService 被启动后,我们将 RemoteService 进行了启动,并将 LocalService 和 RemoteService 两者绑定了起来(因为远端Service 对于用户来说是不可见的,相对于我们实际工作的进程也是独立的,它的作用仅仅是守护线程,所以说 RemoteService 仅与 LocalService 有关系,应该只能由 LocalService 将它启动)。
  启动并绑定之后,我们需要重写 ServiceConnection 中的方法,监听两者之间的绑定关系,关键的是对两者绑定关系断开时的监听。
  当其中一个进程被杀掉时,两者的绑定关系就会被断开,触发方法 onServiceDisconnected ,所以,我们要在断开时,进行进程拉起的操作,重写 onServiceDisconnected 方法,在方法中将另外一个 Service 重新启动,并将两者重新绑定。

 

/**
 * @author chaochaowu
 */
public class LocalService extends Service {

    private MyBinder mBinder;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            try {
                Log.i("LocalService", "connected with " + iMyAidlInterface.getServiceName());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Toast.makeText(LocalService.this,"链接断开,重新启动 RemoteService",Toast.LENGTH_LONG).show();
            startService(new Intent(LocalService.this,RemoteService.class));
            bindService(new Intent(LocalService.this,RemoteService.class),connection, Context.BIND_IMPORTANT);
        }
    };

    public LocalService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this,"LocalService 启动",Toast.LENGTH_LONG).show();
        startService(new Intent(LocalService.this,RemoteService.class));
        bindService(new Intent(this,RemoteService.class),connection, Context.BIND_IMPORTANT);
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        mBinder = new MyBinder();
        return mBinder;
    }

    private class MyBinder extends IMyAidlInterface.Stub{

        @Override
        public String getServiceName() throws RemoteException {
            return LocalService.class.getName();
        }

    }

}

  在另外一个 RemoteService 中也一样,在与 LocalService 断开链接的时候,由于监听到绑定的断开,说明 RemoteService 还存活着,LocalService 被杀进程,所以要将 LocalService 进行拉起,并重新绑定。方法写在 onServiceDisconnected 中。

 

/**
 * @author chaochaowu
 */
public class RemoteService extends Service {

    private MyBinder mBinder;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            try {
                Log.i("RemoteService", "connected with " + iMyAidlInterface.getServiceName());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Toast.makeText(RemoteService.this,"链接断开,重新启动 LocalService",Toast.LENGTH_LONG).show();
            startService(new Intent(RemoteService.this,LocalService.class));
            bindService(new Intent(RemoteService.this,LocalService.class),connection, Context.BIND_IMPORTANT);
        }
    };

    public RemoteService() {
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this,"RemoteService 启动",Toast.LENGTH_LONG).show();
        bindService(new Intent(this,LocalService.class),connection,Context.BIND_IMPORTANT);
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        mBinder = new MyBinder();
        return mBinder;
    }

    private class MyBinder extends IMyAidlInterface.Stub{

        @Override
        public String getServiceName() throws RemoteException {
            return RemoteService.class.getName();
        }

    }
}

运行效果

  启动 Activity 点击开启 LocalService 启动本地服务,提示中可以看到, LocalService 启动后 RemotService 守护线程也被启动。此时,两者已经绑定在了一起。

 

webpuploading.4e448015.gif转存失败重新上传取消

开启服务

 

  点击关闭 LocalService 模拟本地进程被杀,Toast 提示链接断开,并尝试重新启动 LocalService,第二个Toast 提示 LocalService 被重新拉起。

 

关闭本地服务


  点击关闭 RemoteService 模拟远端进程被杀,Toast 提示链接断开,并尝试重新启动 RemoteService ,第二个Toast 提示 RemoteService 被重新拉起。

关闭远端服务

 

  可以发现,无论我们怎么杀进程,进程都会被重新拉起,这就达到了 Service 保活,双进程相互守护的目的。

总结

  在开发的过程中总是有些无法避免的麻烦,但是方法总比困难多,耐心研究研究就行了。关于进程的保活,其实是没有办法的办法,我们应该尽量避免将进程常驻后台,如果真的需要,在完成后台工作后,也要及时将他们销毁。否则后台进程无端地消耗系统资源,用户又不知道,咱们的软件就也就成了流氓软件。开发人员应该有自己的良心,嗯。

以上。


链接:https://www.jianshu.com/p/83ea5e03bd61
 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android进程守护是指在Android系统中通过一些机制来保证关键进程的稳定运行,防止其被异常终止或被系统回收。下面是一些常见的Android进程守护方式: 1. 前台服务(Foreground Service):通过将服务设置为前台服务,可以提高其优先级,使其更不容易被系统回收。前台服务通常会在状态栏显示一个持续运行的通知,以提醒用户该服务正在后台运行。 2. JobScheduler:JobScheduler是Android提供的一种度任务的机制,可以用于执行一些延迟执行或者周期性执行的任务。通过JobScheduler,可以在设备空闲时执行关键任务,从而提高任务的执行成功率。 3. AlarmManager:AlarmManager是Android提供的一种定时任务度机制,可以在指定的时间点唤醒设备并执行任务。通过设置定时任务,可以保证关键进程在指定时间内得到唤醒和执行。 4. 后台服务(Background Service):后台服务是指在后台运行的服务,不会显示通知栏,但仍然可以保持运行。后台服务通常会使用startService()方法启动,并在服务内部使用startForeground()方法将其提升为前台服务。 5. 进程优先级设置:Android系统会根据进程的重要性和优先级来进行资源分配和回收。通过设置进程的优先级,可以提高关键进程的稳定性。可以通过设置android:priority属性或使用setThreadPriority()方法来设置进程或线程的优先级。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值