2017年開始上班的第一天,老不想工作了,假期感覺還沒開始就已經結束了,唉,時間就是這樣,新的一年開始了,雖然很不想幹正事,沒辦法,必須幹起來,因爲後邊的路還很長,距離六十歲還很遠。剛上班也沒什麼事,複習一下之前的東西,看了一下Aidl相關的知識,仔細瞅了瞅Aidl的調用流程,這裏寫篇文章整理一下,幫助大家的同時,自己也加深一下印象。對Aidl不太瞭解的童鞋可以先看一下我之前的一篇文章,
鏈接如下:http://blog.csdn.net/liuyonglei1314/article/details/54317902
案例下載鏈接:http://download.csdn.net/detail/liuyonglei1314/9734165 。
在上篇文章中我們已經說過了Android中Aidl的簡單應用及對象的傳遞方式,還包含了在AS中進行Aidl開發會遇到的一些問題及決解方法,本篇文章針對使用方法我們不在多說,我們將以傳遞對象爲例深入的剖析Aidl的詳細調用流程,繼續以上文中傳遞Person對象爲例展開,通過本片文章的學習你會學到以下相關知識:1.Aidl工作調用流程;2.Aidl傳遞對象時修飾符in、out、inout的深入理解。3.對實現Parcelable接口對象中需要實現方法的深入理解。
首先看一下我們要傳遞對象的代碼:
public class Person implements Parcelable {
private String name;
private int age;
public Person() {
}
protected Person(Parcel in) {
name = in.readString();
age = in.readInt();
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
public void readFromParcel(Parcel dest) {
name = dest.readString();
age = dest.readInt();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Person類實現了parcelable接口,有兩個構造方法,有name、age兩個成員變量,編譯器可以幫我們生成一個static、final的CREATOR跟writeToParcel()方法,我們自己寫readFromParcel()方法,這裏切記順序要與writeToParcel()方法一直。
下邊再看一下我們的.aidl文件代碼:
import com.jason.aidl.aidldemo.Person;
interface IMyAidlInterface {
String inPerson(in Person p);
String outPerson(out Person p);
String inOutPerson(inout Person p);
}
這裏需要注意我們的Person也需要寫對應的Person.aidl文件,並在build.gradle中配置,詳細信息就不介紹了,上篇文章中進行了詳細講解,在這裏看到了修飾符in、out、inout,後文會做詳細講解。
我們繼續看Aidl文件生成對應的.java文件的一個整體架構。縮略代碼如下:
public interface IMyAidlInterface extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.jason.aidl.aidldemo.IMyAidlInterface {
private static final java.lang.String DESCRIPTOR = "com.jason.aidl.aidldemo.IMyAidlInterface";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.jason.aidl.aidldemo.IMyAidlInterface interface,
* generating a proxy if needed.
*/
public static com.jason.aidl.aidldemo.IMyAidlInterface asInterface(android.os.IBinder 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 {
//...
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.jason.aidl.aidldemo.IMyAidlInterface {
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 inPerson(com.jason.aidl.aidldemo.Person p) throws android.os.RemoteException {
//...
return _result;
}
@Override
public java.lang.String outPerson(com.jason.aidl.aidldemo.Person p) throws android.os.RemoteException {
//...
return _result;
}
@Override
public java.lang.String inOutPerson(com.jason.aidl.aidldemo.Person p) throws android.os.RemoteException {
//...
return _result;
}
}
static final int TRANSACTION_inPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_outPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_inOutPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
}
public java.lang.String inPerson(com.jason.aidl.aidldemo.Person p) throws android.os.RemoteException;
public java.lang.String outPerson(com.jason.aidl.aidldemo.Person p) throws android.os.RemoteException;
public java.lang.String inOutPerson(com.jason.aidl.aidldemo.Person p) throws android.os.RemoteException;
}
大致分爲三部分,一個interface(最外層的類)兩個內部類(Stub、Proxy),先看interface,它跟我們定義的.aidl文件名字相同,繼承了IInterface,它的內容分爲兩部分:一個內部類Stub及我們定義的Person抽象操作方法,每一個都拋出RemoteException。接下來看他的內部類Stub,它是一個抽象類,繼承了Binder類,實現了外層的Interface,他比較重要的是一個asInterface()方法和onTransact()方法,它還有一個static的內部類Proxy,它也實現了最外層的Interface,另外有一個需要傳遞Ibinder的構造函數,還有就是與我們在Aidl類中定義的方法名稱相同的方法。這就是我們生成的.java文件的一個大致的架構。由上邊可知最外層Interface是IInterface的子類,而Stub與Proxy是最外層Interface的實現類,Stub繼承了Binder,他們之間存在這樣的關係。下面我們就對代碼的調用流程進行詳細講解。
首先我們在應用Aidl時通過客戶端綁定的方式來獲取我們的IMyAidlInterface,並通過它來調用服務端的方法進行通信,如下代碼:
private IMyAidlInterface mService;
Intent intent = new Intent();
intent.setAction("com.lyl.aidl");
Intent intent1 = new Intent(createExplicitFromImplicitIntent(this, intent));//兼容5.0以後版本
bindService(intent1, mServiceC, Context.BIND_AUTO_CREATE);
ServiceConnection mServiceC = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
public void do(View v) {
try {
Person p1 = new Person();
p1.setName("劉大");
p1.setAge(3);
Person p2 = new Person();
p2.setName("趙二");
p2.setAge(3);
Person p3 = new Person();
p3.setName("張三");
p3.setAge(3);
tv.setText("" + mService.inPerson(p1) + "\n" + mService.outPerson(p2) + "\n" + mService.inOutPerson(p3));
} catch (RemoteException e) {
e.printStackTrace();
}
}
上邊代碼通過IMyAidlInterface.Stub.asInterface(service)獲取了我們的IMyAidlInterface,do()方法中通過它才調用了我們服務端的代碼,那我們先看一下這個asInterface方法中的參數是什麼,我們知道當通過綁定的方式獲取的binder是我們在服務中的onBind()方法中返回的,看一下我們服務端的代碼:
public class MyAidlService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("Log_LYL", "Onstart");
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return stub;
}
IMyAidlInterface.Stub stub = new IMyAidlInterface.Stub() {
@Override
public String inPerson(Person p) throws RemoteException {
...
}
@Override
public String outPerson(Person p) throws RemoteException {
...
}
@Override
public String inOutPerson(Person p) throws RemoteException {
...
}
};
}
通過以上服務端代碼可以知道我們返回的是一個stub 事例,而它是我們的.aidl文件自動生成的.java文件中Stub的一個實例對象。現在我們知道IMyAidlInterface.Stub.asInterface(service)中的service是編譯器爲我們生成的.java文件的一個實例。我們接着看IMyAidlInterface中Stub.asInterface(service)方法,代碼如下:
public static com.jason.aidl.aidldemo.IMyAidlInterface asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.jason.aidl.aidldemo.IMyAidlInterface))) {
return ((com.jason.aidl.aidldemo.IMyAidlInterface) iin);
}
return new com.jason.aidl.aidldemo.IMyAidlInterface.Stub.Proxy(obj);
}
因爲我們的Stub類繼承了Binder,Binder實現了Ibinder接口,所以傳遞Stub的實例在這裏沒問題。上述代碼首先判斷了obj是否爲空,不爲空往下走,第二個if判斷是在判斷通過obj獲取的iin是否屬於當前程序運行的進程,如果是,直接返回,這裏也就是說我們要調用的服務與我們當前程序在同一進程,不需要遠程通信,直接調用就行。如果不是可以看見系統新生成了一個Proxy對象實例,並把我們的stub實例對象傳遞進去,看一下Proxy的構造函數,代碼如下:
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
可以看到通過構造函數將Stub實例又一次傳到了Proxy中並賦值給mRemote,mRemote也變成了Stub實例。
到這裏綁定服務獲取ImyAidlInterface對象就介紹完了,做一下總結:通過綁定的方式獲取到了在服務端的系統生成的.aidl文件對應的.java文件中子類Stub的一個實例,調用Stub類中的asInterface()方法判斷Stub實例所在進程與當前進程是否爲同一個,是直接可以操作,不是生成一個Proxy實例並將Stub傳入。
下面我們看一下調用代碼實現:
public void do(View v) {
try {
Person p1 = new Person();
p1.setName("劉大");
p1.setAge(3);
Person p2 = new Person();
p2.setName("趙二");
p2.setAge(3);
Person p3 = new Person();
p3.setName("張三");
p3.setAge(3);
tv.setText("" + mService.inPerson(p1) + "\n" + mService.outPerson(p2) + "\n" + mService.inOutPerson(p3));
} catch (RemoteException e) {
e.printStackTrace();
}
}
我們首先分析第一個inPerson(p1);這個方法我們在定義時的修飾符爲in,及String inPerson(in Person p);下邊我們看一下它的實現過程,通過上邊的介紹可以知道mService如果不是在同一個進程中是返回的Proxy對象實例,那麼我們就進入Proxy中查找對應的方法,代碼如下:
@Override
public java.lang.String inPerson(com.jason.aidl.aidldemo.Person p) 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);
if ((p != null)) {
_data.writeInt(1);
p.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_inPerson, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
可以看到系統生成了_data、_reply兩個Parcel類型的對象,然後操作_data,寫入Token,判斷如果傳入的Person對象不爲空寫入了一個1,接下來調用了person類的writeToParcel()方法,傳入了_data,咦,注意這裏,我們就知道了實現Parcelable接口時的writeToParcel()方法在這裏會用到。好的,我們跟進看一下,代碼如下:
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
這一步看來就是給_data賦值,將我們從開始調用inPerson(com.jason.aidl.aidldemo.Person p) p實例中的值以writeString、writeInt的方式保存到_data中,如果傳入的p爲null則寫入一個0。繼續往下走,我標紅的那句是重點,也是這句進行了binder通信,關於binder通訊機制有很多很多東西,由於本人能力有限在這裏就不多講了,感興趣的可以去了解一下。通過前邊可以知道mRemote爲asInterface方法傳入的service,及Stub實例,而Stub中根本沒有transact()方法,而它是Binder的子類,好,我們去Binder中找,還真有:
public final boolean transact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
可以看到,在這裏調用了onTransact(code, data, reply, flags);我們知道在Stub類中我們實現了這個方法,我們看一下Stub類中的這個方法:
@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_inPerson: {
data.enforceInterface(DESCRIPTOR);
com.jason.aidl.aidldemo.Person _arg0;
if ((0 != data.readInt())) {
_arg0 = com.jason.aidl.aidldemo.Person.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
java.lang.String _result = this.inPerson(_arg0);
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
在這裏我們只摘取了inPerson()方法相關內容,先了解一下傳入的四個參數:1.code 每一個方法對應一個code,我們定義了多少方法就會有多少code,通過它來做不同處理;2.data、reply爲在proxy對應方法中生成的兩個Parcel對象;3.flags 一般爲1,這個不用管先。 首先通過code找到對應方法的操作,判斷data中寫入的int值是否爲0,不爲0說明之前的p不爲null,並且已經存入到data中,調用Person中的CREATOR的createFromParcel()方法,咦,這裏又用到了Parcelable的東西。看一下這個方法:
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
protected Person(Parcel in) {
name = in.readString();
age = in.readInt();
}
可以看到,系統在這裏創建了一個Person實例,傳入了我們之前的data(保存有person信息),並將data中的信息賦值給新的person對象。回到Stub類的onTransace()方法中繼續往下看:
java.lang.String _result = this.inPerson(_arg0);
reply.writeNoException();
reply.writeString(_result);
return true;
接着系統調用了this.inPerson(_arg0)方法,_arg0爲上邊通過createFromParcel()方法新生成的person(包含傳遞過來的person值)對象,我們知道Stub爲抽象類,他實現了我們外層的Interface類,但是沒有實現inPerson()方法,那麼我們就需要它的子類實現,而我們的mRemote.transace()中的mRemote就是服務端返回的Stub的一個實現子類,也就是說這裏調用的this.inPerson(_arg0)方法實際是調用了服務端的那個Stub實現類裏邊的inPerson()方法,看一下我們之前服務端的inPerson()方法的具體實現:
@Override
public String inPerson(Person p) throws RemoteException {
String old = "name:" + p.getName() + " age:" + p.getAge();
Log.d("Log_LYL:iPerson_", old);
p.setName("李四");
p.setAge(13);
return "name:" + p.getName() + " age:" + p.getAge();
}
通過以上分析,大家就知道了這裏的 Log.d("Log_LYL:iPerson_", old); 是爲什麼有值了,因爲我們一開始就將傳入的person值附到了Parcel類型的data中了,然後又通過createFromParcel()方法將data中的值付給了新的person對象。 然後回到Stub類的onTransace()方法,會看到調用了reply.writeNoException();reply.writeString(_result);兩個方法,將服務端處理完後返回的信息寫入了reply中。最後return。再回到Proxy類中的inPerson()方法中:
mRemote.transact(Stub.TRANSACTION_inPerson, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
將服務端返回的信息讀出來並付給_result,最後返回,這樣我們在activity中就可以得到服務端處理後的結果了,到現在爲止,我們就應該明白了,通過in修飾符傳遞person後服務端生成了新的person對象,並對新person對象進行處理,這樣的話無論服務端如何修改person對象我們客戶端的person對象是不會受任何影響的,爲什麼,因爲不是一個對象。這就是in操作符。
下面我們分析一下out操作符,直接看代碼:
String outPerson(out Person p);//.aidl文件中定義;
mService.outPerson(p2)//客戶端調用;
我們在客戶端直接通過綁定時返回的Stub實例調用outPerson()方法,通過上邊對inPerson()方法的介紹我們知道不是同一個進程中的會最終調用到Proxy中相應的方法中,那我們直接看Proxy中代碼:
@Override
public java.lang.String outPerson(com.jason.aidl.aidldemo.Person p) 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);
mRemote.transact(Stub.TRANSACTION_outPerson, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
if ((0 != _reply.readInt())) {
p.readFromParcel(_reply);
}
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
有了上邊對inPerson()方法的瞭解這個相對就簡單多了,同樣生成兩個Parcel對象,作用相同,不同的是用out修飾後沒有對傳入的p做null判斷,也沒有取值保存到data中,而是直接調用了mRemote.transact(Stub.TRANSACTION_outPerson, _data, _reply, 0)方法,那好,通過前邊我們直接去Stub中找onTransact()方法吧:
case TRANSACTION_outPerson: {
data.enforceInterface(DESCRIPTOR);
com.jason.aidl.aidldemo.Person _arg0;
_arg0 = new com.jason.aidl.aidldemo.Person();
java.lang.String _result = this.outPerson(_arg0);
reply.writeNoException();
reply.writeString(_result);
if ((_arg0 != null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
我們只要out方法相關信息,這裏系統通過new Person()的方式創建了一個person對象實例賦值個_arg0,這個對象與我們調用時傳進來的對象沒有一點關係,然後直接調用了this.outPerson()方法,通過inPerson()的介紹我們就知道這裏直接調用了服務端outPerson()實現:
@Override
public String outPerson(Person p) throws RemoteException {
//這裏的p是空的,因爲在IMyAidlInterface的Stub類中onTransact()方法中沒有寫入;
// _arg0 = new com.jason.aidl.aidldemo.Person();
//java.lang.String _result = this.outPerson(_arg0);
String old = "name:" + p.getName() + " age:" + p.getAge();
Log.d("Log_LYL:outPerson_", old);
p.setName("週六");
p.setAge(20);
return "name:" + p.getName() + " age:" + p.getAge();
}
這裏可以知道old是沒有賦值的,下面賦值return,回到Stub中:
java.lang.String _result = this.outPerson(_arg0);
reply.writeNoException();
reply.writeString(_result);
if ((_arg0 != null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
將處理結果寫入reply中,判斷_arg0是否爲null,不是,寫入1,這裏又一次用到了writeToParcel()方法,通過上邊inPerson()方法介紹可知這裏是將_arg0的值付給reply;如果爲null,reply中賦值0。回到Proxy中:
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_outPerson, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
if ((0 != _reply.readInt())) {
p.readFromParcel(_reply);
}
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
執行完transact()方法後從_reply中讀出服務端操作返回結果,判斷寫入的int值,如果不爲0,說明_reply中寫入了服務端產生的person信息,則將通過readFromParcel()方法將新的_reply中保存的服務端的person信息寫入到客戶端傳入的p中,這樣客戶端就收到了服務端的person信息。總結一下out修飾符,即使我們在服務端將p的值寫好,服務端也不會接收我們的客戶端信息,可以說服務端根本不關係客戶端傳入的信息,服務端通過new Person()方式產生新對象,並返回給客戶端,客戶端的p通過readFromParcel()方法獲得服務端值,並沒有做對象的交換。
下邊看一下inout修飾符代碼:
String inOutPerson(inout Person p);//.aidl文件中;
mService.inOutPerson(p3);//客戶端調用;
同樣看一下Proxy中相應方法:
@Override
public java.lang.String inOutPerson(com.jason.aidl.aidldemo.Person p) 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);
if ((p != null)) {
_data.writeInt(1);
p.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_inOutPerson, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
if ((0 != _reply.readInt())) {
p.readFromParcel(_reply);
}
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
case TRANSACTION_inOutPerson: {
data.enforceInterface(DESCRIPTOR);
com.jason.aidl.aidldemo.Person _arg0;
if ((0 != data.readInt())) {
_arg0 = com.jason.aidl.aidldemo.Person.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
java.lang.String _result = this.inOutPerson(_arg0);
reply.writeNoException();
reply.writeString(_result);
if ((_arg0 != null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
可以看到在inout修飾符時判斷了傳入的p對象,也就是說服務端接收了保存了客戶端的p對象,操作類似與in修飾符,也是通過Person.CREATOR.createFromParcel(data)創建新對象,在Proxy中同樣判斷了_reply中寫入的int值,也就是說,客戶端也能收到服務端對對象的修改,也就是說,inout操作符是int、out操作符的合體。到這裏相信大家已經明白in、out、inout操作符的區別了,並且連實現都明白了。
到現在今天要跟大家分享的東西就寫完了,相信您看完這篇文章會對Android中的Aidl有一個新的瞭解,對in、out、inout修飾也明白了,對實現Parcelable接口有了新的認識,如果是這樣,那我這篇文章就沒白寫,謝謝大家。