目錄
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 android:name="com.example.remoteservice.MusicService" >
<intent-filter>
<action android:name="top.august1996.musicservice" />
</intent-filter>
</service>
別忘了加入<action android:name="top.august1996.musicservice" />,否則會拋出安全異常。
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
布局
<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" >
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="bind"
android:text="bind service" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="unbind"
android:text="unbind service" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="play"
android:text="play" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="pause"
android:text="pause" />
</LinearLayout>
代碼
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。