Android 双进程守护的实现代码

所属分类: 软件编程 / Android 阅读数: 37
收藏 0 赞 0 分享

前言

最近有在项目中用到高德的定位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 守护线程也被启动。此时,两者已经绑定在了一起。

开启服务

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

关闭本地服务

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

关闭远端服务

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

总结

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

更多精彩内容其他人还在看

Android实现信号强度监听的方法

这篇文章主要介绍了Android实现信号强度监听的方法,是Android手机中很常见的一个实用功能,需要的朋友可以参考下
收藏 0 赞 0 分享

Android实现Activity界面切换添加动画特效的方法

这篇文章主要介绍了Android实现Activity界面切换添加动画特效的方法,非常实用的技巧,需要的朋友可以参考下
收藏 0 赞 0 分享

Android中Dialog去黑边的方法

这篇文章主要介绍了Android中Dialog去黑边的方法,需要的朋友可以参考下
收藏 0 赞 0 分享

Qt for Android开发实例教程

这篇文章主要介绍了Qt for Android开发的方法,具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

Android开发之时间日期操作实例

这篇文章主要介绍了Android开发之时间日期操作,是Android程序开发中常见的一个功能,需要的朋友可以参考下
收藏 0 赞 0 分享

Android开发之时间日期组件用法实例

这篇文章主要介绍了Android开发之时间日期组件用法,主要介绍了TimePicker和DatePicker组件,对于Android程序开发有不错的借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

Android开发之获取网络链接状态

这篇文章主要介绍了Android获取网络链接状态的方法,主要是通过ConnectivityManager类来完成的,需要的朋友可以参考下
收藏 0 赞 0 分享

Android开发之广播机制浅析

这篇文章主要介绍了Android开发之广播机制浅析,主要包括了发布、接收及配置广播的实例,需要的朋友可以参考下
收藏 0 赞 0 分享

Android开发之登录验证实例教程

这篇文章主要介绍了Android开发之登录验证实现方法,包括发送数据、服务器端验证、配置文件等,需要的朋友可以参考下
收藏 0 赞 0 分享

Android开发之注册登录方法示例

这篇文章主要介绍了Android开发的注册登录方法,是针对Android程序设计中版本兼容性的进一步完善,需要的朋友可以参考下
收藏 0 赞 0 分享
查看更多