跨进程开启、停止、绑定、解绑Service
在上一节(Android Service)的学习中我们知道在同一个进程中,不同组件(例如Activity和Service)之间通过IBinder接口进行通信,但是不同进程里的组件是不能通过IBinder进行通信的,为了让不同进程里的组件能够通信我们需要使用到AIDL。
在学习AIDL之前,我们先了解一下如何跨进程开启、停止、绑定、解绑Service。在上一节的基础上,我们新建一个Module取名为AIDLDemo,在其中也放上四个垂直排列的按钮,分别为:远程开启服务、远程停止服务、远程绑定服务、远程解绑服务,我们的目的就是在这个新的Module中通过点击这四个按钮与ServiceDemo(上一节创建的Module)中的Service进行交互。
首先回到ServiceDemo的Manifest文件中,为了完成在不同进程中与ServiceDemo中的Service进行交互,我们需要为MyService(ServiceDemo中的Service)添加一个IntentFilter,在其中增加一个action取名为com.imooc.myservice,之后在AIDLDemo中我们需要通过这个action与MyService进行交互。
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.imooc.myservice"/>
</intent-filter>
</service>
接着在AIDLDemo模块里完成布局文件的编写:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.aidldemo.MainActivity">
<Button
android:id="@+id/start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="远程启动服务"
android:onClick="operate"/>
<Button
android:id="@+id/stop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="远程停止服务"
android:onClick="operate"/>
<Button
android:id="@+id/bind"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="远程绑定服务"
android:onClick="operate"/>
<Button
android:id="@+id/unbind"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="远程解绑服务"
android:onClick="operate"/>
</LinearLayout>
然后为四个按钮完成点击事件处理函数operate()
,我们先尝试远程启动、停止服务。
public void operate (View view) {
switch (view.getId()) {
case R.id.start: {
//远程启动服务
Intent intent = new Intent();
intent.setAction("com.imooc.myservice");
intent.setPackage("com.example.xwx.servicedemo");
startService(intent);
break;
}
case R.id.stop: {
//远程停止服务
Intent intent = new Intent();
intent.setAction("com.imooc.myservice");
intent.setPackage("com.example.xwx.servicedemo");
stopService(intent);
break;
}
case R.id.bind: {
break;
}
case R.id.unbind: {
break;
}
}
}
分析上述代码,和之前一样,要想启动一个服务,我们首先得创建一个Intent对象,不同的是在跨进程通信里我们需要用Action和Package来指明我们要的是哪一个应用里的哪一个Service,通过intent.setAction
指定Service的Action,通过intent.setPackage()
指定这个Service位于哪个应用里,在这里的意思就是Intent意图对象指向ServiceDemo应用下的Action为com.imooc.myservice的Service,也就是MyService。之后依然是调用startService(intent)
和stopService(intent)
来启动和停止Service。
同时经过尝试发现,要想实现跨进程与Service进行交互,必须得保证两个Module(应用)同时处于运行状态才行。
远程绑定和解绑也很简单,步骤和上面一样,唯一不同的就是多了一个ServiceConnection对象:
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
public void operate (View view) {
switch (view.getId()) {
......
case R.id.bind: {
//远程绑定服务
Intent intent = new Intent();
intent.setAction("com.imooc.myservice");
intent.setPackage("com.example.xwx.servicedemo");
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
break;
}
case R.id.unbind: {
//远程解绑服务
unbindService(serviceConnection);
break;
}
}
}
到这里我们点击绑定按钮,查看Log输出:
03-11 14:08:26.173 30343-30343/com.example.xwx.servicedemo D/TAG: Service:onCreate()
03-11 14:08:26.174 30343-30343/com.example.xwx.servicedemo D/TAG: Service:onBind()
发现与我们之前点击绑定按钮有什么不同没有?没错,在之前同一个进程中,我们通过IBinder成功让Activity和Service进行了通信,在每次Activity和Service进行绑定的时候,都会调用ServiceConnection下的onServiceConnected()
在Log中输出一个后台耗时任务的进度,但是这次却无法输出了,这就是跨进程通信导致的问题,为了解决这个问题,我们将要学习AIDL的使用。
AIDL的使用
首先在ServiceDemo的java文件夹的包com.example.xwx.servicedemo下新建一个aidl file,之后我们会发现IDE自动为我们在与java文件夹同级的位置创建了一个名为aidl的文件夹,在这个文件夹下面有一个包名为com.example.xwx.servicedemo,在这个包下面有一个aidl文件。
我们打开这个aidl文件看下其中的内容:
package com.example.xwx.servicedemo;
interface IMyAidlInterface {
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString);
}
可以看到aidl文件实际上是一个以Java代码为内容的很简单的接口类文件,basicTypes()
方法是自动生成的一个方法,我们可以选择删除,在这里就不删除了,接着需要在这里增加一个我们自己业务需要的方法,也就是显示服务完成进度的方法:
package com.example.xwx.servicedemo;
interface IMyAidlInterface {
......
//定义自己需要的方法
//显示当前服务的进度
void showProcess();
}
Rebuild这个Project,将目录显示结构从Android切换成Project,打开build->generated->source->aidl->debug,可以看到最底层的debug文件夹下生成了一个名为IMyAidlInterface的java文件,这个文件就是实现我们跨进程通信的关键。
让我们打开这个文件看看内部:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: G:\\AndroidProject\\ServiceDemo\\app\\src\\main\\aidl\\com\\example\\xwx\\servicedemo\\IMyAidlInterface.aidl
*/
package com.example.xwx.servicedemo;
public interface IMyAidlInterface extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.xwx.servicedemo.IMyAidlInterface
{
private static final java.lang.String DESCRIPTOR = "com.example.xwx.servicedemo.IMyAidlInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.xwx.servicedemo.IMyAidlInterface interface,
* generating a proxy if needed.
*/
public static com.example.xwx.servicedemo.IMyAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.xwx.servicedemo.IMyAidlInterface))) {
return ((com.example.xwx.servicedemo.IMyAidlInterface)iin);
}
return new com.example.xwx.servicedemo.IMyAidlInterface.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_basicTypes:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
long _arg1;
_arg1 = data.readLong();
boolean _arg2;
_arg2 = (0!=data.readInt());
float _arg3;
_arg3 = data.readFloat();
double _arg4;
_arg4 = data.readDouble();
java.lang.String _arg5;
_arg5 = data.readString();
this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
reply.writeNoException();
return true;
}
case TRANSACTION_showProcess:
{
data.enforceInterface(DESCRIPTOR);
this.showProcess();
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.xwx.servicedemo.IMyAidlInterface
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(anInt);
_data.writeLong(aLong);
_data.writeInt(((aBoolean)?(1):(0)));
_data.writeFloat(aFloat);
_data.writeDouble(aDouble);
_data.writeString(aString);
mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
//定义自己需要的方法
//显示当前服务的进度
@Override public void showProcess() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_showProcess, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_showProcess = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
//定义自己需要的方法
//显示当前服务的进度
public void showProcess() throws android.os.RemoteException;
}
这个文件中代码是在是太繁杂了,没有高深的网络和Java知识根本无法看懂这些代码,但我们不需要完全看懂这其中的代码,我们要做的是找到关键部分。
首先这个Java文件是一个名为IMyAidlInterface的接口,我们翻到代码最下方可以看到这个接口下面有着我们刚才声明的方法showProcess()
以及我们自己写的注释,接着再回到文件上方,我们关注这样一个内部抽象类Stub:
public static abstract class Stub extends android.os.Binder implements com.example.xwx.servicedemo.IMyAidlInterface
这个抽象类继承了Binder类,同时实现了这个IMyAidlInterface接口,因此毫不意外的可以在这个内部类中找到另一个showProcess()
方法,只不过这个showProcess()
方法被一些Java代码填充了,我们只需要知道这些代码完成了跨进程通信的功能即可。
@Override public void showProcess() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_showProcess, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
接着我们来到MyService类中,找到onBind()
方法,由于onBind
返回的是一个IBinder类型,正好我们之前的IMyAidlInterface接口下的Stub内部类继承了Binder类,而Binder实现了IBinder接口,因此Stub也是一个实现了IBinder接口的类;同时Stub又实现了IMyAidlInterface接口,实现了我们需要的showProcess()
方法,那么我们这里就不再返回MyBinder类,而是返回一个Stub类并重写其中的showProcess()
。
@Override
public IBinder onBind(Intent intent) {
Log.d("TAG", "Service:onBind()");
return new IMyAidlInterface.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public void showProcess() throws RemoteException {
//显示当前进度
Log.d("MyService", "当前进度:" + process);
}
};
}
此时回到AIDLDemo,在main文件夹下新建一个名为aidl的文件夹,在这个文件夹下新建一个名为com.example.xwx.servicedemo的包,在这个包下将ServiceDemo中的aidl文件(不是Build后生成的Java文件,而是我们一开始创建的aidl file)复制过来。注意文件夹名、包名、aidl file文件名都必须和ServiceDemo中的一致。
Rebuild整个项目,可以看到build目录下生成了对应的java文件:
打开AIDLDemo的Activity的Java文件,修改ServiceConnection内的onServiceConnected()
方法:
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder iBinder) {
IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
try {
iMyAidlInterface.showProcess();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
分析上述代码,我们首先调用了Stub类下的asInterface()
方法,传入一个IBinder类型的对象iBinder,这个方法会根据IBinder返回一个对应的IMyAidlInterface对象,而这个iBinder正是ServiceDemo中的MyService里的onBind()
方法返回的Stub类对象!我们将ServiceDemo中MyService里的onBind()
方法返回的Stub类对象(之前说过,Stub继承了Binder,因此也实现了IBinder接口)传入asInterface()
方法获取到了对应的 IMyAidlInterface对象,接着调用IMyAidlInterface下的ShowProcess()就实现了跨进程通信,在AIDLDemo中获取ServiceDemo中Service的数据了。
查看最终效果:
03-11 14:51:19.205 32708-32708/com.example.xwx.servicedemo D/TAG: Service:onCreate()
03-11 14:51:19.212 32708-32708/com.example.xwx.servicedemo D/TAG: Service:onStartCommand()
03-11 14:51:21.640 32708-32708/com.example.xwx.servicedemo D/TAG: Service:onBind()
03-11 14:51:21.649 32708-32721/com.example.xwx.servicedemo D/MyService: 当前进度:3