標(biāo)注:本文為個(gè)人學(xué)習(xí)使用,僅做自己學(xué)習(xí)參考使用公给,請(qǐng)勿轉(zhuǎn)載和轉(zhuǎn)發(fā)
2018-07-11: 初稿基协,感覺自己的Android的水平還是差了很多很多啊,參考博主coder-pig
2018-07-13: 腦殼痛剑按,就讓自己有理由休息了
0. 引言
- Android中跨進(jìn)程通信AIDL的一些概念
- 本節(jié)對(duì)應(yīng)的官方文檔:Binder
1. Bander機(jī)制
1.1 IBander和Bander
- IBinder是遠(yuǎn)程對(duì)象的基本接口疾就,是為了高性能而設(shè)計(jì)的輕量級(jí)遠(yuǎn)程調(diào)用機(jī)制的核心部分。但他不僅用于遠(yuǎn)程調(diào)用艺蝴,也用于進(jìn)程內(nèi)調(diào)用猬腰。該接口定義了與遠(yuǎn)程對(duì)象間交互的協(xié)議。但不要直接實(shí)現(xiàn)這個(gè)接口猜敢,而是繼承(extends)Binder姑荷。
- IBinder主要的API是transact(),與之對(duì)應(yīng)的API是Binder.onTransact()缩擂。通過前者鼠冕,你能 想遠(yuǎn)程IBinder對(duì)象發(fā)送發(fā)出調(diào)用,后者使你的遠(yuǎn)程對(duì)象能夠響應(yīng)接收到的調(diào)用胯盯。IBinder的API都是 Syncronous(同步)執(zhí)行的供鸠,比如transact()直到對(duì)方的Binder.onTransact()方法調(diào)用玩 后才返回。 調(diào)用發(fā)生在進(jìn)程內(nèi)時(shí)無疑是這樣的陨闹,而在進(jìn)程間時(shí)楞捂,在IPC的幫助下薄坏,也是同樣的效果。
- 通過transact()發(fā)送的數(shù)據(jù)是Parcel寨闹,Parcel是一種一般的緩沖區(qū)胶坠,除了有數(shù)據(jù)外還帶有 一些描述它內(nèi)容的元數(shù)據(jù)。元數(shù)據(jù)用于管理IBinder對(duì)象的引用繁堡,這樣就能在緩沖區(qū)從一個(gè)進(jìn)程移動(dòng) 到另一個(gè)進(jìn)程時(shí)保存這些引用沈善。這樣就保證了當(dāng)一個(gè)IBinder被寫入到Parcel并發(fā)送到另一個(gè)進(jìn)程中, 如果另一個(gè)進(jìn)程把同一個(gè)IBinder的引用回發(fā)到原來的進(jìn)程椭蹄,那么這個(gè)原來的進(jìn)程就能接收到發(fā)出的 那個(gè)IBinder的引用闻牡。這種機(jī)制使IBinder和Binder像唯一標(biāo)志符那樣在進(jìn)程間管理。
- 系統(tǒng)為每個(gè)進(jìn)程維護(hù)一個(gè)存放交互線程的線程池绳矩。這些交互線程用于派送所有從另外進(jìn)程發(fā)來的IPC 調(diào)用罩润。例如:當(dāng)一個(gè)IPC從進(jìn)程A發(fā)到進(jìn)程B,A中那個(gè)發(fā)出調(diào)用的線程(這個(gè)應(yīng)該不在線程池中)就阻塞 在transact()中了翼馆。進(jìn)程B中的交互線程池中的一個(gè)線程接收了這個(gè)調(diào)用割以,它調(diào)用 Binder.onTransact(),完成后用一個(gè)Parcel來做為結(jié)果返回应媚。然后進(jìn)程A中的那個(gè)等待的線程在 收到返回的Parcel后得以繼續(xù)執(zhí)行严沥。實(shí)際上,另一個(gè)進(jìn)程看起來就像是當(dāng)前進(jìn)程的一個(gè)線程中姜, 但不是當(dāng)前進(jìn)程創(chuàng)建的消玄。
- Binder機(jī)制還支持進(jìn)程間的遞歸調(diào)用。例如丢胚,進(jìn)程A執(zhí)行自己的IBinder的transact()調(diào)用進(jìn)程B 的Binder莱找,而進(jìn)程B在其Binder.onTransact()中又用transact()向進(jìn)程A發(fā)起調(diào)用,那么進(jìn)程A 在等待它發(fā)出的調(diào)用返回的同時(shí)嗜桌,還會(huì)用Binder.onTransact()響應(yīng)進(jìn)程B的transact()奥溺。 總之Binder造成的結(jié)果就是讓我們感覺到跨進(jìn)程的調(diào)用與進(jìn)程內(nèi)的調(diào)用沒什么區(qū)別。
- 當(dāng)操作遠(yuǎn)程對(duì)象時(shí)骨宠,你經(jīng)常需要查看它們是否有效浮定,有三種方法可以使用:
- transact()方法將在IBinder所在的進(jìn)程不存在時(shí)拋出RemoteException異常。
- 如果目標(biāo)進(jìn)程不存在层亿,那么調(diào)用pingBinder()時(shí)返回false桦卒。
- 可以用linkToDeath()方法向IBinder注冊(cè)一個(gè)IBinder.DeathRecipient, 在IBinder代表的進(jìn)程退出時(shí)被調(diào)用匿又。
嗯方灾,總體上來說
- IBinder是Android給我們提供的一個(gè)進(jìn)程間通信的一個(gè)接口,而我們一般是不直接實(shí)現(xiàn)這個(gè)接口的, 而是通過繼承Binder類來實(shí)現(xiàn)進(jìn)程間通信裕偿!是Android中實(shí)現(xiàn)IPC(進(jìn)程間通信)的一種方式洞慎!
1.2 Binder機(jī)制
- Android中的Binder機(jī)制由一系列組件構(gòu)成:Client、Server嘿棘、ServiceManager劲腿、Binder驅(qū)動(dòng)程序
-
調(diào)用流程大致如下
流程解析
- Client調(diào)用某個(gè)代理接口中的方法時(shí),代理接口的方法會(huì)將Client傳遞的參數(shù)打包成Parcel對(duì)象鸟妙;
- 然后代理接口把該P(yáng)arcel對(duì)象發(fā)送給內(nèi)核中的Binder driver焦人;;
- 然后Server會(huì)讀取Binder Driver中的請(qǐng)求數(shù)據(jù)重父,假如是發(fā)送給自己的花椭,解包Parcel對(duì)象, 處理并將結(jié)果返回房午;
- PS:代理接口中的定義的方法和Server中定義的方法是一一對(duì)應(yīng)的矿辽, 另外,整個(gè)調(diào)用過程是一個(gè)同步的歪沃,即Server在處理時(shí)嗦锐,Client會(huì)被Block(鎖)住! 而這里說的代理接口的定義就是等下要說的AIDL(Android接口描述語言)嫌松!
1.3 為何Android會(huì)使用Binder機(jī)制來實(shí)現(xiàn)進(jìn)程間的通信
- 可靠性:在移動(dòng)設(shè)備上沪曙,通常采用基于Client-Server的通信方式來實(shí)現(xiàn)互聯(lián)網(wǎng)與設(shè)備間的內(nèi)部通信。目前l(fā)inux支持IPC包括傳統(tǒng)的管道萎羔,System V IPC液走,即消息隊(duì)列/共享內(nèi)存/信號(hào)量,以及socket中只有socket支持Client-Server的通信方式贾陷。Android系統(tǒng)為開發(fā)者提供了豐富進(jìn)程間通信的功能接口缘眶,媒體播放,傳感器髓废,無線傳輸巷懈。這些功能都由不同的server來管理。開發(fā)都只關(guān)心將自己應(yīng)用程序的client與server的通信建立起來便可以使用這個(gè)服務(wù)慌洪。毫無疑問顶燕,如若在底層架設(shè)一套協(xié)議來實(shí)現(xiàn)Client-Server通信,增加了系統(tǒng)的復(fù)雜性冈爹。在資源有限的手機(jī) 上來實(shí)現(xiàn)這種復(fù)雜的環(huán)境涌攻,可靠性難以保證。
- 傳輸性能:socket主要用于跨網(wǎng)絡(luò)的進(jìn)程間通信和本機(jī)上進(jìn)程間的通信频伤,但傳輸效率低恳谎,開銷大。消息隊(duì)列和管道采用存儲(chǔ)-轉(zhuǎn)發(fā)方式憋肖,即數(shù)據(jù)先從發(fā)送方緩存區(qū)拷貝到內(nèi)核開辟的一塊緩存區(qū)中因痛,然后從內(nèi)核緩存區(qū)拷貝到接收方緩存區(qū)婚苹,其過程至少有兩次拷貝。雖然共享內(nèi)存無需拷貝婚肆,但控制復(fù)雜租副。比較各種IPC方式的數(shù)據(jù)拷貝次數(shù)。共享內(nèi)存:0次较性。Binder:1次用僧。Socket/管道/消息隊(duì)列:2次。
- 安全性:Android是一個(gè)開放式的平臺(tái)赞咙,所以確保應(yīng)用程序安全是很重要的责循。Android對(duì)每一個(gè)安裝應(yīng)用都分配了UID/PID,其中進(jìn)程的UID是可用來鑒別進(jìn)程身份。傳統(tǒng)的只能由用戶在數(shù)據(jù)包里填寫UID/PID攀操,這樣不可靠院仿,容易被惡意程序利用。而我們要求由內(nèi)核來添加可靠的UID速和。 所以歹垫,出于可靠性、傳輸性颠放、安全性排惨。android建立了一套新的進(jìn)程間通信方式。 ——摘自:Android中的Binder機(jī)制的簡(jiǎn)要理解
Binder的好處
- 我們無需關(guān)心底層如何實(shí)現(xiàn)碰凶,只需按照AIDL的規(guī)則暮芭,自定義一個(gè)接口文件, 然后調(diào)用調(diào)用接口中的方法欲低,就可以完成兩個(gè)進(jìn)程間的通信了辕宏!
2. AIDL使用解析
2.1 什么是AIDL
- IPC這個(gè)名詞,他的全名叫做:跨進(jìn)程通信(interprocess communication)
- Android系統(tǒng)中,個(gè)個(gè)應(yīng)用程序都運(yùn)行在自己的進(jìn)程中,進(jìn)程之間一般是無法直接進(jìn)行數(shù)據(jù)交換的, 而為了實(shí)現(xiàn)跨進(jìn)程砾莱,Android給我們提供了上面說的Binder機(jī)制
- 這個(gè)機(jī)制使用的接口語言就是: AIDL(Android Interface Definition Language)瑞筐,他的語法很簡(jiǎn)單,而這種接口語言并非真正的編程語言腊瑟,只是定義兩個(gè)進(jìn)程間的通信接口而已聚假。
- 而生成符合通信協(xié)議的Java代碼則是由Android SDK的 platform-tools目錄下的aidl.exe工具生成,生成對(duì)應(yīng)的接口文件在:gen目錄下扫步,一般是:Xxx.java的接口魔策!
- 而在該接口中包含一個(gè)Stub的內(nèi)部類,該類中實(shí)現(xiàn)了在該類中實(shí)現(xiàn)了IBinder接口與自定義的通信接口, 這個(gè)類將會(huì)作為遠(yuǎn)程Service的回調(diào)類——實(shí)現(xiàn)了IBinder接口,所以可作為Service的onBind( )方法的返回值河胎。
2.2 AIDL實(shí)現(xiàn)兩個(gè)進(jìn)程間通信
編寫AIDL的一些注意事項(xiàng):
- 接口名詞需要與aidl文件名相同
- 接口和方法前面不要加訪問權(quán)限修飾符:public ,private,protected等闯袒,也不能用static final!
- AIDL默認(rèn)支持的類型包括Java基本類型,String,List政敢,Map其徙,CharSequence,除此之外的其他類型都 需要import聲明喷户,對(duì)于使用自定義類型作為參數(shù)或者返回值唾那,自定義類型需要實(shí)現(xiàn)Parcelable接口, 詳情請(qǐng)看后面的傳遞復(fù)雜數(shù)據(jù)類型
- 自定義類型和AIDL生成的其它接口類型在aidl描述文件中褪尝,應(yīng)該顯式import闹获,即便在該類和定義 的包在同一個(gè)包中。自定義類型應(yīng)該可以轉(zhuǎn)化為Json串傳輸河哑。
AS中可以直接創(chuàng)建AIDL文件
- 然后就直接創(chuàng)建了AIDL的文件夾和AIDL文件了避诽,建立之前給文件取一個(gè)名,然后重新編譯下就好了璃谨。
一沙庐、服務(wù)端
- 創(chuàng)建AIDL文件
IPerson.aidl
package com.jay.aidl;
interface IPerson {
String queryPerson(int num);
}
IPerson.java
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: C:\\Code\\ASCode\\AIDLServer\\app\\src\\main\\aidl\\com\\jay\\aidl\\IPerson.aidl
*/
package com.jay.aidl;
public interface IPerson extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.jay.aidl.IPerson
{
private static final java.lang.String DESCRIPTOR = "com.jay.aidl.IPerson";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.jay.aidl.IPerson interface,
* generating a proxy if needed.
*/
public static com.jay.aidl.IPerson asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.jay.aidl.IPerson))) {
return ((com.jay.aidl.IPerson)iin);
}
return new com.jay.aidl.IPerson.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_queryPerson:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
java.lang.String _result = this.queryPerson(_arg0);
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.jay.aidl.IPerson
{
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 java.lang.String queryPerson(int num) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(num);
mRemote.transact(Stub.TRANSACTION_queryPerson, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_queryPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public java.lang.String queryPerson(int num) throws android.os.RemoteException;
}
- 上面的文件很復(fù)雜,我們只關(guān)注的是asInterface(IBinder)和我們定義的接口中的queryPerson()方法!
- 這個(gè)方法會(huì)把IBander類型的對(duì)象轉(zhuǎn)換成IPerson類型的佳吞,必要時(shí)生成一個(gè)代理對(duì)象返回結(jié)果
-
自定義我們的Service類拱雏,完成以下操作
1)繼承Service類,同時(shí)也自定義了一個(gè)PersonQueryBinder類來繼承IPerson.Stub類底扳,就是實(shí)現(xiàn)了IPerson接口和IBander接口
2)實(shí)例化自定義的Stub類铸抑,并重寫Service的onBind方法,返回一個(gè)binder對(duì)象花盐!
AIDLService.java
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import com.jay.aidl.IPerson.Stub;
/**
* Created by Jay on 2015/8/18 0018.
*/
public class AIDLService extends Service {
private IBinder binder = new PersonQueryBinder();
private String[] names = {"B神","艸神","基神","J神","翔神"};
private String query(int num)
{
if(num > 0 && num < 6){
return names[num - 1];
}
return null;
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
private final class PersonQueryBinder extends Stub{
@Override
public String queryPerson(int num) throws RemoteException {
return query(num);
}
}
}
- 在AndroidMainfest.xml中注冊(cè)Service
<service android:name=".AIDLService">
<intent-filter>
<action android:name="android.intent.action.AIDLService" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
二羡滑、客戶端
- 直接把服務(wù)端的那個(gè)aidl文件復(fù)制過來菇爪,然后我們直接在MainActivity中完成算芯,和綁定本地Service的操作
- 有點(diǎn)類似,流程如下:
1)自定義PersonConnection類實(shí)現(xiàn)ServiceConnection接口
2)以PersonConnection對(duì)象作為參數(shù),調(diào)用bindService綁定遠(yuǎn)程Service
bindService(service,conn,BIND_AUTO_CREATE);
ps:第三個(gè)參數(shù)是設(shè)置如果服務(wù)沒有啟動(dòng)的話,自動(dòng)創(chuàng)建
3)和本地Service不同凳宙,綁定遠(yuǎn)程Service的ServiceConnection并不能直接獲取Service的onBind( )方法 - 返回的IBinder對(duì)象熙揍,只能返回onBind( )方法所返回的代理對(duì)象,需要做如下處理:
iPerson = IPerson.Stub.asInterface(service);
再接著完成初始化,以及按鈕事件等就可以了
具體代碼如下:
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private EditText edit_num;
private Button btn_query;
private TextView txt_name;
private IPerson iPerson;
private PersonConnection conn = new PersonConnection();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindViews();
//綁定遠(yuǎn)程Service氏涩, android6.0之后采用顯示通信
Intent serviceIntent = new Intent();
serviceIntent.setComponent(new ComponentName("com.project.ankie.aidlservicedemo", "com.project.ankie.aidlservicedemo.AIDLService"));
bindService(service, conn, BIND_AUTO_CREATE);
btn_query.setOnClickListener(this);
}
private void bindViews() {
edit_num = (EditText) findViewById(R.id.edit_num);
btn_query = (Button) findViewById(R.id.btn_query);
txt_name = (TextView) findViewById(R.id.txt_name);
}
@Override
public void onClick(View v) {
String number = edit_num.getText().toString();
int num = Integer.valueOf(number);
try {
txt_name.setText(iPerson.queryPerson(num));
} catch (RemoteException e) {
e.printStackTrace();
}
edit_num.setText("");
}
private final class PersonConnection implements ServiceConnection {
public void onServiceConnected(ComponentName name, IBinder service) {
iPerson = IPerson.Stub.asInterface(service);
}
public void onServiceDisconnected(ComponentName name) {
iPerson = null;
}
}
}
接下來先啟動(dòng)AIDLServivce届囚,然后再啟動(dòng)AIDLClient,輸入查詢序號(hào)是尖,即可獲得對(duì)應(yīng)姓名意系! 當(dāng)然也可以直接啟動(dòng)AIDLClient,也會(huì)獲得同樣效果:
效果圖如下:
2.4 傳遞復(fù)雜數(shù)據(jù)的AIDL通信
- 上面的例子我們傳遞的只是要給int類型的參數(shù)饺汹,然后服務(wù)端返回一個(gè)String類型的參數(shù)蛔添,看似滿足 我們的基本需求,不過實(shí)際開發(fā)中,我們可能需要考慮傳遞復(fù)雜數(shù)據(jù)類型的情況迎瞧!下面我們來學(xué)習(xí)下 如何向服務(wù)端傳遞復(fù)雜數(shù)據(jù)類型的數(shù)據(jù)夸溶!開始之前我們先來了解Parcelable接口!
Parcelable接口簡(jiǎn)介:
- Parcelable主要是用于序列化凶硅,除了這個(gè)還有一個(gè)Serializable缝裁,也是用于序列化的,其中Parcelable更加輕量級(jí)足绅,速度更快捷绑,就是寫起來比較麻煩,當(dāng)然還有as的插件來完成序列化氢妈,比如Gson
- 首先胎食,需要實(shí)現(xiàn)writeToParcel和readFromParcel方法寫入方法將對(duì)象寫入到包裹parcel中,而讀取方法則從包裹中讀取對(duì)象允懂,請(qǐng)注意厕怜,寫入屬性順序與讀取順序相同
- 接著,需要在該類中添加一個(gè)名為CREATOR的static final屬性蕾总,改屬性需要實(shí)現(xiàn):android.os.Parcelable.Creator接口
- 接著粥航,需要從寫接口中的兩個(gè)方法,createFromParcel(Parcel source)方法生百,實(shí)現(xiàn)source創(chuàng)建出JavaBean實(shí)例的功能 newArray(int size)递雀,創(chuàng)建一個(gè)類型為T,長(zhǎng)度為size的數(shù)組蚀浆,只有一個(gè)簡(jiǎn)單的return new T[size]缀程,這里的T是Person類
- 最后,discribeContents():嗯市俊,這個(gè)直接返回0即可
- 另外杨凑,除了String和CharSequence以外,其余俊宇要一個(gè)方向指示符摆昧。方向指示符包括in撩满、out和inout。in表示由客戶端設(shè)置绅你,out表示由服務(wù)端設(shè)置伺帘,inout表示客戶端和服務(wù)端都設(shè)置了該值。
代碼示例
- 自定義了兩種對(duì)象:Person與Salary忌锯,Person作為調(diào)用遠(yuǎn)程的Serviec的參數(shù)伪嫁,Salary作為返回值!那么首先就要做的是創(chuàng)建Person與Salary類偶垮,同時(shí)實(shí)現(xiàn)Parcelable接口
1. 服務(wù)端
- 創(chuàng)建Person.aidl和Salary.aidl的文件张咳,因?yàn)樗麄冃枰獙?shí)現(xiàn)Parcelable接口驹吮,所以就下面一條語句:
Person.aidl: parcelable Person;
Salary.aidl: parcelable Salary;
- 分別建立Person類與Salary類,需實(shí)現(xiàn)Parcelable接口晶伦,重寫對(duì)應(yīng)的方法!
因?yàn)槲覀兒竺媸歉鶕?jù)Person對(duì)象來獲取Map集合中的數(shù)據(jù),所以Person.java中我們重寫了hashcode和equals 的方法;而Salary類則不需要!
public class Person implements Parcelable{
private Integer id;
private String name;
public Person() {}
public Person(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
//實(shí)現(xiàn)Parcelable必須實(shí)現(xiàn)的方法,不知道拿來干嘛的,直接返回0就行了
@Override
public int describeContents() {
return 0;
}
//寫入數(shù)據(jù)到Parcel中的方法
@Override
public void writeToParcel(Parcel dest, int flags) {
//把對(duì)象所包含的數(shù)據(jù)寫入到parcel中
dest.writeInt(id);
dest.writeString(name);
}
//必須提供一個(gè)名為CREATOR的static final屬性 該屬性需要實(shí)現(xiàn)
//android.os.Parcelable.Creator<T>接口
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
//從Parcel中讀取數(shù)據(jù),返回Person對(duì)象
@Override
public Person createFromParcel(Parcel source) {
return new Person(source.readInt(),source.readString());
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
//因?yàn)槲覀兗先〕鲈氐臅r(shí)候是根據(jù)Person對(duì)象來取得,所以比較麻煩,
//需要我們重寫hashCode()和equals()方法
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (name == null)
{
if (other.name != null)
return false;
}
else if (!name.equals(other.name))
return false;
return true;
}
}
Salary類
public class Salary implements Parcelable {
private String type;
private Integer salary;
public Salary() {
}
public Salary(String type, Integer salary) {
this.type = type;
this.salary = salary;
}
public String getType() {
return type;
}
public Integer getSalary() {
return salary;
}
public void setType(String type) {
this.type = type;
}
public void setSalary(Integer salary) {
this.salary = salary;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(type);
dest.writeInt(salary);
}
public static final Parcelable.Creator<Salary> CREATOR = new Parcelable.Creator<Salary>() {
//從Parcel中讀取數(shù)據(jù),返回Person對(duì)象
@Override
public Salary createFromParcel(Parcel source) {
return new Salary(source.readString(), source.readInt());
}
@Override
public Salary[] newArray(int size) {
return new Salary[size];
}
};
public String toString() {
return "工作:" + type + " 薪水: " + salary;
}
}
3碟狞、創(chuàng)建一個(gè)ISalary.aidl的文件,在里面寫一個(gè)簡(jiǎn)單的獲取工資信息的方法:
package com.jay.example.aidl;
import com.jay.example.aidl.Salary;
import com.jay.example.aidl.Person;
interface ISalary
{
//定義一個(gè)Person對(duì)象作為傳入?yún)?shù)
//接口中定義方法時(shí),需要制定新參的傳遞模式,這里是傳入,所以前面有一個(gè)in
Salary getMsg(in Person owner);
}
ps:這里可以記得如果使用的是自定義的數(shù)據(jù)類型的話,需要import哦;榕恪W逦帧!切記C诓巍4嘌汀!
Step 4:核心Service的編寫: 定義一個(gè)SalaryBinder類繼承Stub,從而實(shí)現(xiàn)ISalary和IBinder接口;定義一個(gè)存儲(chǔ)信息的Map集合! 重新onBind方法,返回SalaryBinder類的對(duì)象實(shí)例!
AidlService.java
public class AidlService extends Service {
private SalaryBinder salaryBinder;
private static Map<Person,Salary> ss = new HashMap<Person, Salary>();
//初始化Map集合,這里在靜態(tài)代碼塊中進(jìn)行初始化,當(dāng)然你可也以在構(gòu)造方法中完成初始化
static
{
ss.put(new Person(1, "Jay"), new Salary("碼農(nóng)", 2000));
ss.put(new Person(2, "GEM"), new Salary("歌手", 20000));
ss.put(new Person(3, "XM"), new Salary("學(xué)生", 20));
ss.put(new Person(4, "MrWang"), new Salary("老師", 2000));
}
@Override
public void onCreate() {
super.onCreate();
salaryBinder = new SalaryBinder();
}
@Override
public IBinder onBind(Intent intent) {
return salaryBinder;
}
//同樣是繼承Stub,即同時(shí)實(shí)現(xiàn)ISalary接口和IBinder接口
public class SalaryBinder extends Stub
{
@Override
public Salary getMsg(Person owner) throws RemoteException {
return ss.get(owner);
}
}
@Override
public void onDestroy() {
System.out.println("服務(wù)結(jié)束沽一!");
super.onDestroy();
}
}
注冊(cè)下Service:
<service android:name=".AidlService">
<intent-filter>
<action android:name="android.intent.action.AIDLService" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
2盖溺、客戶端編寫
Step 1:把服務(wù)端的AIDL文件拷貝下,拷貝后目錄如下:
Step 2:編寫簡(jiǎn)單的布局,再接著就是核心MainActvitiy的實(shí)現(xiàn)了 定義一個(gè)ServciceConnection對(duì)象,重寫對(duì)應(yīng)方法,和前面的普通數(shù)據(jù)的類似 再接著在bindService,然后再Button的點(diǎn)擊事件中獲取Salary對(duì)象并顯示出來铣缠!
MainActivity.java
public class MainActivity extends Activity {
private ISalary salaryService;
private Button btnquery;
private EditText editname;
private TextView textshow;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
salaryService = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//返回的是代理對(duì)象,要調(diào)用這個(gè)方法哦!
salaryService = ISalary.Stub.asInterface(service);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnquery = (Button) findViewById(R.id.btnquery);
editname = (EditText) findViewById(R.id.editname);
textshow = (TextView) findViewById(R.id.textshow);
Intent it = new Intent();
it.setAction("com.jay.aidl.AIDL_SERVICE");
bindService(it, conn, Service.BIND_AUTO_CREATE);
btnquery.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
try
{
String name = editname.getText().toString();
Salary salary = salaryService.getMsg(new Person(1,name));
textshow.setText(name + salary.toString());
}catch(RemoteException e){e.printStackTrace();}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
this.unbindService(conn);
}
}
運(yùn)行截圖:
2.5 注意事項(xiàng)
-
經(jīng)過測(cè)試烘嘱,上面的例子可以寫在一個(gè)Demo或者兩個(gè)Demo里面,無論寫在一個(gè)或者兩個(gè)里面蝗蛙,其中AIDL的部分必須復(fù)制
我是寫在了兩個(gè)demo中蝇庭,測(cè)試了很久都沒成功,最開始client的IPerson的AIDL的接口是存放在com.project.ankie.aidlclientdemo包下捡硅,而不是通過service端復(fù)制過來的哮内,所以會(huì)出現(xiàn)錯(cuò)誤,經(jīng)查是因?yàn)椴皇窃谕粋€(gè)包下所引發(fā)的問題壮韭!
參考了一下別的工程北发,主要是在主工程下的其他的library中添加的服務(wù),然后通過和服務(wù)綁定喷屋,然后調(diào)用AIDL接口進(jìn)行通信琳拨,此舉主要是這個(gè)AIDL只有一個(gè)就可以了啊,因?yàn)榭梢詫ntent的連接綁定bindService和AIDL接口寫在同一個(gè)library的包下逼蒙,就不會(huì)產(chǎn)生上面我遇到的錯(cuò)誤了从绘,但是兩個(gè)app之間的通信要么采用上面的復(fù)制連包和AIDL寄疏,要么采用其他的方式是牢。
Android在6.0之后必須用顯示了,所以不能Intent然后再setAction了陕截,需要直接指定包了驳棱,由于上面的例子是不同的app,所以采用的是setComponent方法
Intent serviceIntent = new Intent();
// CompontentName 的方法前面是服務(wù)的 包名农曲、后面是class的名
serviceIntent.setComponent(new ComponentName("com.project.ankie.aidlservicedemo", "com.project.ankie.aidlservicedemo.AIDLService"));
final boolean b = bindService(serviceIntent, conn, BIND_AUTO_CREATE);
// 可以通過返回值來判斷是否綁定成功社搅,也可以在Service的onBind方法那邊打log查看
Log.e(TAG, "onCreate: " + " b: " + b );
3. 總結(jié)
- 總體思想就是Activity與Service之間的通信驻债,通信依托AIDL的形式進(jìn)行;
- 進(jìn)行通信的時(shí)候形葬,在啟動(dòng)Service的方式就應(yīng)該是通過Activity綁定服務(wù)的形式了合呐,而不是StartService的形式了,因?yàn)镾ervice一旦啟動(dòng)之后笙以,可以由其他的Activity來綁定淌实;
- 綁定Service的主要方法為bindService(intent,conn, BIND_AUTO_CREATE)方法猖腕,其中intent現(xiàn)在只能是顯示綁定了拆祈,conn是一個(gè)new ServiceConnection的回調(diào),最后一個(gè)參數(shù)是自動(dòng)綁定倘感,直接寫就完了放坏;
- 在回調(diào)里由返回的IBand的類型的Service檬姥,然后通過ISalary.Stub.asInterface(service); 方法甚淡,將service轉(zhuǎn)化為AIDL的接口的實(shí)例對(duì)象,這樣就可以調(diào)用AIDL中的接口了
- ADIL模式只是支持現(xiàn)有的基本的數(shù)據(jù)模型葵擎,若復(fù)雜的數(shù)據(jù)模型需要序列化蜡豹;
- 對(duì)于復(fù)雜類型模型的傳輸互亮,也可以通過將復(fù)雜模型轉(zhuǎn)化為Json串的類型,然后傳輸余素,在接收端將該類型轉(zhuǎn)化為相同過得模型豹休,即可完成序列化和反序列化的形式。
- 上文的例子沒有驗(yàn)證桨吊,也只是對(duì)Service的不同情況的分析威根。還是為具體到Service的實(shí)現(xiàn)機(jī)制還待研究。