Binder與AIDL服務
服務(Service)是Android系統中4個應用程序組件之一。服務主要用於兩個目的:后台運行和跨進程訪問。通過啟動一個服務,可以在不顯示界面的前提下在后台運行指定的任務,這樣可以不影響用戶做其他事情。通過AIDL服務可以實現不同進程之間的通信,這也是服務的重要用途之一。
跨進程訪問(AIDL服務)
Android系統中的進程之間不能共享內存,因此,需要提供一些機制在不同進程之間進行數據通信。Activity和Broadcast都可以跨進程通信,除此之外,還可以使用Content Provider進行跨進程通信。現在我們已經了解了4個Android應用程序組件中的3個(Activity、Broadcast和Content Provider)都可以進行跨進程訪問,另外一個Android應用程序組件Service同樣可以。這就是本節要介紹的AIDL服務。
什么是AIDL服務
本章前面的部分介紹了開發人員如何定制自己的服務(startService,bindService),但這些服務並不能被其他的應用程序訪問。為了使其他的應用程序也可以訪問本應用程序提供的服務,Android系統采用了遠程過程調用(Remote Procedure Call,RPC)方式來實現。與很多其他的基於RPC的解決方案一樣,Android使用一種接口定義語言(Interface Definition Language,IDL)來公開服務的接口。因此,可以將這種可以跨進程訪問的服務稱為AIDL(Android Interface Definition Language)服務。
建立AIDL服務的步驟:
建立AIDL服務要比建立普通的服務復雜一些,具體步驟如下:
(1)在Eclipse Android工程的Java包目錄中建立一個擴展名為aidl的文件。該文件的語法類似於Java代碼,但會稍有不同。詳細介紹見后面實例的內容。
(2)如果aidl文件的內容是正確的,ADT會自動生成一個Java接口文件(*.java)。
(3)建立一個服務類(Service的子類)。
(4)實現由aidl文件生成的Java接口。
(5)在AndroidManifest.xml文件中配置AIDL服務,尤其要注意的是,<action>標簽中android:name的屬性值就是客戶端要引用該服務的ID,也就是Intent類的參數值。
本例中將建立一個簡單的AIDL服務。這個AIDL服務只有一個getValue方法,該方法返回一個String類型的值。在安裝完服務后,會在客戶端調用這個getValue方法,並將返回值在TextView組件中輸出。建立這個AIDL服務的步驟如下:
(1)建立一個aidl文件。在Java包目錄中建立一個IMyService.aidl文件。IMyService.aidl文件的位置如圖所示。
IMyService.aidl文件的內容如下:
package com.anjoyo.aidl.remote;
interface IMyService { String getValue(); }
(2)編寫一個MyService類。MyService是Service的子類,在MyService類中定義了一個內嵌類(MyServiceImpl),該類是IMyService.Stub的子類。MyService類的代碼如下:
public class MyService extends Service { public class MyServiceImpl extends IMyService.Stub{ @Override public String getValue() throws RemoteException{ return "AIDL遠程訪問"; } } @Override public IBinder onBind(Intent intent) { return new MyServiceImpl(); } }
在編寫上面代碼時要注意如下兩點:
IMyService.Stub是根據IMyService.aidl文件自動生成的,一般並不需要管這個類的內容,只需要編寫一個繼承於IMyService.Stub類的子類(MyServiceImpl類)即可。
onBind方法必須返回MyServiceImpl類的對象實例,否則客戶端無法獲得服務對象。
(3)在AndroidManifest.xml文件中配置MyService類,代碼如下:
<service android:name=".MyService" > <intent-filter> <action android:name="com.anjoyo.aidl.IMyService" /> </intent-filter> </service>
其中"com.anjoyo.aidl.IMyService"是客戶端用於訪問AIDL服務的ID。
服務端寫完!!!
下面來編寫客戶端的調用代碼。首先新建一個Eclipse Android工程,並將自動生成的IMyService.java文件連同包目錄一起復制到工程的src目錄中,如圖所示。
調用AIDL服務首先要綁定服務,然后才能獲得服務對象,代碼如下:
傳遞復雜數據的AIDL服務
AIDL服務只支持有限的數據類型,因此,如果用AIDL服務傳遞一些復雜的數據就需要做更一步處理。AIDL服務支持的數據類型如下:
Java的簡單類型(int、char、boolean等)。不需要導入(import)。
String和CharSequence。不需要導入(import)。
List和Map。但要注意,List和Map對象的元素類型必須是AIDL服務支持的數據類型。不需要導入(import)。
AIDL自動生成的接口。需要導入(import)。
實現android.os.Parcelable接口的類。需要導入(import)。
其中后兩種數據類型需要使用import進行導入,將在本章的后面詳細介紹。
傳遞不需要import的數據類型的值的方式相同。傳遞一個需要import的數據類型的值(例如,實現android.os.Parcelable接口的類)的步驟略顯復雜。除了要建立一個實現android.os.Parcelable接口的類外,還需要為這個類單獨建立一個aidl文件,並使用parcelable關鍵字進行定義。具體的實現步驟如下:
(1)建立一個IMyService.aidl文件,並輸入如下代碼:
package com.example.day0108_aidl; import com.example.day0108_aidl.Product; interface IMyService { String getValue(in Product p); Product getProduct(); }
在編寫上面代碼時要注意如下兩點:
Product是一個實現android.os.Parcelable接口的類,需要使用import導入這個類。
如果方法的類型是非簡單類型,例如,String、List或自定義的類,需要使用in、out或inout修飾。其中in表示這個值被客戶端設置;out表示這個值被服務端設置;inout表示這個值既被客戶端設置,又被服務端設置。
(2)編寫Product類。該類是用於傳遞的數據類型,代碼如下:
package com.example.day0108_aidl; import android.os.Parcel; import android.os.Parcelable; public class Product implements Parcelable { private int id; private String name; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(id); dest.writeString(name); } public Product(){ } public Product(Parcel in){ this.id=in.readInt(); this.name=in.readString(); } public static final Parcelable.Creator<Product> CREATOR=new Creator<Product>() { @Override public Product[] newArray(int size) { return new Product[size]; } @Override public Product createFromParcel(Parcel source) { return new Product(source); } }; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Product [id=" + id + ", name=" + name + "]"; } }
在編寫Product類時應注意如下3點:
Product類必須實現android.os.Parcelable接口。該接口用於序列化對象。在Android中之所以使用Pacelable接口序列化,而不java.io.Serializable接口,是因為Google在開發Android時發現Serializable序列化的效率並不高,因此,特意提供了一個Parcelable接口來序列化對象。
在Product類中必須有一個靜態常量,常量名必須是CREATOR,而且CREATOR常量的數據類型必須是Parcelable.Creator。
在writeToParcel方法中需要將要序列化的值寫入Parcel對象。
(3)建立一個Product.aidl文件,並輸入如下內容:
parcelable Product;
建立AIDL服務的步驟(3)
(4)編寫一個MyService類,代碼如下:
package com.anjoyo.remote.aidl;
// AIDL服務類
package com.example.day0108_aidl; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; public class MyService extends Service { class MyBinder extends IMyService.Stub{ @Override public Product getProduct() throws RemoteException { Product p=new Product(); p.setId(1); p.setName("測試"); return p; } @Override public String getValue(Product p) throws RemoteException { p.setName(p.getName()+"我又回來了"); return p.toString(); } } @Override public IBinder onBind(Intent intent) { return new MyBinder(); } }
(5)在AndroidManifest.xml文件中配置MyService類,代碼如下:
<service android:name=".MyService" > <intent-filter> <action android:name="com.anjoyo.remote.aidl.IMyService" /> </intent-filter> </service>
在客戶端調用AIDL服務的方法與第一個實例介紹的方法相同,首先將IMyService.java和Product.java文件復制到客戶端工程,然后綁定AIDL服務,並獲得AIDL服務對象,最后調用AIDL服務中的方法。完整的客戶端代碼如下:
package com.example.day0108_test; import com.example.day0108_aidl.IMyService; import com.example.day0108_aidl.Product; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class MainActivity extends Activity { private TextView textView; private Button button; IMyService myService; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { } @Override public void onServiceConnected(ComponentName name, IBinder service) { myService=IMyService.Stub.asInterface(service); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView=((TextView) findViewById(R.id.textView1)); button=(Button) findViewById(R.id.button1); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { try { Product p=new Product(); p.setId(2); p.setName("測試"); textView.setText(myService.getProduct().toString()+myService.getValue(p).toString()); } catch (RemoteException e) { e.printStackTrace(); } } }); bindService(new Intent("com.yjy.service"), conn, Context.BIND_AUTO_CREATE); } }

