前言
在我們的印象中,服務端Binder收到請求后調用onTransact處理消息,而運行的線程處於Binder管理的線程池匯中(Binder線程的創建和銷毀是在用戶空間,但是管理是由Binder驅動代為管理的)。這樣說在大部分情況下正確,但也不是所以的情況均是如此。比如一種“遠程回調”的情況,客戶端代理在調用服務端方法后,也通過參數傳遞的方式傳遞一個IBinder給服務端,服務端獲客戶端取代理后回調。這種模式在源碼中其實也常見。下面新建一個工程演示這種情況,順便回顧一下AIDL的用法。
AIDL
創建AIDL文件
為了演示雙向調用的情況,所以我們需要建立兩個AIDL文件。這兒建了一個IHelloService和IHiService。
// IHelloService.aidl
package com.android.aidl;
// Declare any non-default types here with import statements
interface IHelloService {
void Hello(String words,IBinder client);
}
// IHiService.aidl
package com.android.aidl;
// Declare any non-default types here with import statements
interface IHiService {
void Hi(String words);
}
分別定義了兩個接口,Hello和Hi,注意Hello接口的參數還要接收一個IBinder對象。這里多嘴說一下,雖然IBinder本質上是一個interface,沒有真正意義上的IBinder對象。但是實際傳遞的是一個實現了IBinder接口的Binder對象,所以就這樣簡稱了。這個對象就是讓服務端收到請求后調用asInterface將其轉為proxy后調用客戶端的Hi方法。build后分別生成對應的java文件。以IHelloService.java為例:
build生成java文件
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.android.aidl;
// Declare any non-default types here with import statements
public interface IHelloService extends android.os.IInterface
{
/** Default implementation for IHelloService. */
public static class Default implements com.android.aidl.IHelloService
{
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override public void Hello(java.lang.String words, android.os.IBinder client) throws android.os.RemoteException
{
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.android.aidl.IHelloService
{
private static final java.lang.String DESCRIPTOR = "com.android.aidl.IHelloService";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.android.aidl.IHelloService interface,
* generating a proxy if needed.
*/
public static com.android.aidl.IHelloService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.android.aidl.IHelloService))) {
return ((com.android.aidl.IHelloService)iin);
}
return new com.android.aidl.IHelloService.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
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_Hello:
{
data.enforceInterface(descriptor);
java.lang.String _arg0;
_arg0 = data.readString();
android.os.IBinder _arg1;
_arg1 = data.readStrongBinder();
this.Hello(_arg0, _arg1);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.android.aidl.IHelloService
{
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;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override public void Hello(java.lang.String words, android.os.IBinder client) 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.writeString(words);
_data.writeStrongBinder(client);
boolean _status = mRemote.transact(Stub.TRANSACTION_Hello, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().Hello(words, client);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
public static com.android.aidl.IHelloService sDefaultImpl;
}
static final int TRANSACTION_Hello = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
public static boolean setDefaultImpl(com.android.aidl.IHelloService impl) {
// Only one user of this interface can use this function
// at a time. This is a heuristic to detect if two different
// users in the same process use this function.
if (Stub.Proxy.sDefaultImpl != null) {
throw new IllegalStateException("setDefaultImpl() called twice");
}
if (impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.android.aidl.IHelloService getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public void Hello(java.lang.String words, android.os.IBinder client) throws android.os.RemoteException;
}
IHelloService.Stub是服務端需要繼承的類,通過調用asInterface可以將IBinder轉為生成IHello.Stub.Proxy客戶端對象。AIDL文件build后生成的java文件有一些標志是默認的,為了方便之后修改觀察現象,我們將AIDL刪除,然后把build后的java文件加入工程。
創建Service
為了使用Binder,我們還需要創建一個Service組件來完成IBinder對象的傳遞。
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new HelloService();
}
class HelloService extends IHelloService.Stub{
@Override
public void Hello(String words, IBinder client) throws RemoteException {
Log.d("HelloService",words +" thread:" + Thread.currentThread());
(IHiService.Stub.asInterface(client)).Hi("Hi");
}
}
}
如上所示,service的創建非常簡單,定義HelloService服務類並實現IHelloService.Stub的Hello方法,只需要在onBind方法中new 一個HelloService實體然后返回即可。我們在服務端中將收到的IBinder對象client通過IHiService.Stub.asInterface轉化為IHiService的proxy然后就可以調用客戶端提供的服務接口Hi了。
創建MainActivity
Activity的創建也非常簡單,創建HiService類,然后調用bindService去請求服務,同時把一個HiService的實例傳遞過去即可,如下所示。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService(new Intent(this, MyService.class), new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
(IHelloService.Stub.asInterface(service)).Hello("hello",new HiService());
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
},BIND_AUTO_CREATE);
}
class HiService extends IHiService.Stub{
@Override
public void Hi(String words) throws RemoteException {
Log.d("HiService",words +" thread:" + Thread.currentThread());
}
}
}
結果分析
客戶端和服務端同一進程
現在運行該程序看一下打印。
com.android.aidl D: hello , thread:Thread[main,5,main]
com.android.aidl D: Hi thread:Thread[main,5,main]
看來相互調用是成功了,但是都是在主線程執行的?這也對,他們在同一進程是不需要真正binder驅動參與的。那么將Service改到單獨進程執行看看。
不同進程,主線程請求
要讓service執行在不同的進程,只需要指定process屬性即可。
<service android:name=".MyService"
android:process=":remote"/>
下面運行看看結果:
u0_a36 27264 326 1579504 70660 0 0000000000 S com.android.aidl:remote
從ps的結果來看service的確執行在了新進程。log打印的結果如下
27264-27279/com.android.aidl D: hello , thread:Thread[Binder:27264_3,5,main]
27247-27247/com.android.aidl D: Hi thread:Thread[main,5,main]
從打印結果來看service端的比較符合預期,執行在了binder線程中,但是為什么MainActivity端還是在主線程?這就是在本文一開頭所說的特殊情況,這是Binder幫我們做的小優化,因為在MainActivity遠程調用service的過程中本來就是阻塞等調用完成,沒事兒干,那么發起遠端請求的線程就會被用來執行這次服務。因為就算Binder開一個線程去執行這次服務,由於service端在等待返回,主線程仍是阻塞的,所以根本不需要重新開個線程執行。我們可以大膽猜測如果在MainActivity的子線程發起這樣的請求,服務也應該在子線程處理,下面改一下代碼。
不同進程,子線程發起請求
修改代碼,子線程發起請求。
@Override
public void onServiceConnected(ComponentName name, final IBinder service) {
new Thread(new Runnable() {
@Override
public void run() {
try {
(IHelloService.Stub.asInterface(service)).Hello("hello",new HiService());
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
下面看一下執行結果。
29014-29030/com.android.aidl D: hello , thread:Thread[Binder:29014_3,5,main]
28995-29057/com.android.aidl D: Hi thread:Thread[Thread-3,5,main]
果然,這次Hiservice服務的執行就在創建的子線程中了,所以結論與上面分析的一致。
不同進程,修改transact發送標志
剛才說到因為阻塞所以導致HiService就在向HelloService發送請求的線程中執行,那么如果不阻塞的情況呢?因為transact是有flag參數的,其中有個flag是FLAG_ONEWAY,表示單向通信,不在乎結果。記得最開始我們刪掉AIDL文件,也是為了這一步驟,可以修改stub里面的東西。我們試試將HelloService的transact改為
@Override public void Hello(java.lang.String words, android.os.IBinder client) 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.writeString(words);
_data.writeStrongBinder(client);
boolean _status = mRemote.transact(Stub.TRANSACTION_Hello, _data, _reply, FLAG_ONEWAY);
運行查看log
29309-29321/com.android.aidl D: hello , thread:Thread[Binder:29309_1,5,main]
29293-29306/com.android.aidl D: Hi thread:Thread[Binder:29293_2,5,main]
這次兩邊都在Binder線程中執行了。同理,只修改HiService端transact的標志結果也是在Binder線程中執行。
總結
總的來說,Binder服務的執行會盡量放在Binder線程池的線程中去執行,減少對用戶本來的線程的影響,當然會有一些特殊情況,那么Binder也會靈活地找到相應線程去執行,減少不必要的開銷。
