目录
Android跨进程通信之小例子(一)
Android跨进程通信之非AIDL(二)
Android跨进程通信之Proxy与Stub(三)
Android跨进程通信之AIDL(四)
从最常用的例子谈起
这篇文章围绕的核心主题就是
IBinder
,或许你早就在Service
的onBind
方法中见过。该方法就是返回一个IBinder
对象。一般我们的做法是这样的:
service代码
public class MusicService extends Service{
public IBinder onBind(Intent intent) {
return new MyBinder();
}
public class MyBinder extends Binder {
public MyService getMyService() {
return MusicService.this;
}
}
public void play(){
Log.i("TAG", "play");
}
public void pause(){
Log.i("TAG", "pause");
}
}
获取service控制权代码
public class MainActivity extends Activity {
Intent service;
ServiceConnection conn;
MyService myService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
service = new Intent(MainActivity.this, MusicService.class);
conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
myService = ((MyBinder) binder).getMyService();
}
};
bindService(service, conn, BIND_AUTO_CREATE);
}
}
我们通过
ServiceConnection
去绑定一个Service
然后绑定成功后返回一个IBinder
对象,其中IBinder
就充当与一个中介
。是的,中介这个词没有用错。再通过MyBinder
提供的getMyService
来获取服务进而控制Service
。
一个真相引发的血案
其实上面的例子跟文章要说的内容没什么卵关系,只是让你更好的回忆一下
IBinder
接口。这篇文章主要讲一下onTransact
方法。
IBinder跨进程通信图
下面就大概简单说明一下。
- 首先
myActivity
通过调用bindService
去绑定一个远程的服务(myService
),绑定成功后返回一个IBinder
对象。这时候双方就算是建立了连接了。 - 建立连接之后,双方就可以通过持有的
IBinder
进行通信。myActivity
使用IBinder
的transact
方法去给底层的Binder Driver
(Linux层)发送消息间接调用底层的IBinder
的execTransact
方法。 - 而
execTransact
导致的结果就是调用onTransact
方法。那么这时候事件的处理就可以在该环节进行了。
其实就是一个transact->onTransact的一个过程
事实上我们看到
transact
和onTransact
的参数是一模一样的。
protected boolean onTransact(int code, Parcel data, Parcel reply,int flags);
public final boolean transact(int code, Parcel data, Parcel reply,int flags);
transact的参数
-
code
: 这是一个识别码,类似于Handler体系里面的Message的what属性。根据不同的code产生不同的响应。 -
data
: 调用transact的对象传送过去的参数。 -
reply
: 调用onTransact的对象返回的参数。 -
flags
: Java里面默认的native方法都是阻塞的,当不需要阻塞的时候设置为IBinder.FLAG_ONEWAY
,否则设置为0
一个小例子
环境:
- 一个叫做RemoteService的APP
- 一个叫做RemoteServiceDemo的APP
- RemoteServiceDemo中的Activity去控制RemoteService中的Service。
RemoteService
Service声明
别忘了加入
,否则会抛出安全异常。
Service代码
public class MusicService extends Service {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
public class MyBinder extends Binder {
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case 0:
Log.i("TAG", data.readString());
play();
reply.writeString("Service play Music at " + sdf.format(new Date()));
break;
case 1:
Log.i("TAG", data.readString());
pause();
reply.writeString("Service pause Music at " + sdf.format(new Date()));
break;
default:
break;
}
return true;
}
}
public void play() {
Log.d("TAG", "play");
}
public void pause() {
Log.d("TAG", "pause");
}
}
服务的代码很简单,也没什么需要说的。
RemoteServiceDemo
布局
代码
public class MainActivity extends Activity {
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name,
IBinder service) {
mBinder = service;
}
};
private IBinder mBinder = null;
private Parcel data = Parcel.obtain();
private Parcel reply = Parcel.obtain();
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void bind(View v) {
if (isBinded()) {
return;
}
/**
* 通过ComponentName去定位意图
* 第一个参数是应用包名
* 第二个参数是Service或者Activity所在的包名+类名
*/
bindService(
new Intent().setComponent(
new ComponentName("com.example.remoteservice", "com.example.remoteservice.MusicService")),
mServiceConnection, Context.BIND_AUTO_CREATE);
}
public void unbind(View v) {
if (!isBinded()) {
return;
}
unbindService(mServiceConnection);
mBinder = null;
}
public void play(View v) {
if (!isBinded()) {
return;
}
try {
data.writeString("Activity request to play music at " + sdf.format(new Date()));
mBinder.transact(0, data, reply, 0);
Log.i("TAG", reply.readString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void pause(View v) {
try {
data.writeString("Activity request to pause music at " + sdf.format(new Date()));
mBinder.transact(1, data, reply, 0);
Log.i("TAG", reply.readString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
private boolean isBinded() {
return mBinder != null;
}
}
因为我们需要使用
reply
,所以我们使用阻塞的transact
,否则我们调用了transact
,但是那边还未给reply写入字符串,最终导致NullPointException。
项目地址
Github地址