編輯:關於Android編程
通常情況下,作為一個android開發者不會直接接觸到Binder,但Binder作為ipc機制最關鍵的一個環節,我們很有必要去了解他。其實在不知不覺中,大家肯定和Binder打過交道,比如我們bindService的時候,客戶端會獲取到一個遠程服務器發送回來的Binder對象,通過操作這個對象我們可以獲取服務端的數據或者執行某些服務端的操作。再比如,我麼在獲取各種系統服務的時候,Binder是作為serviceManager連接各種manager(windowManager.....)的橋梁。接下來會為大家講解到幾個涉及到Binder通信相關的類,我們有必要先了解相關的類和接口,才能在後續解讀aidl工具為我們生成的代碼時不至於迷糊不清,這幾個類和接口分別是Parcel,Parcelable,IInterface,Binder,Ibinder。
IInterface接口是所有涉及到Binder接口的基類,通常情況下,當我們定義一個繼承了Binder類的子類的時候,該子類一定要實現一個繼承了IInterface接口的接口(有的時候也可以直接用子類去實現IIterface接口)。IInterface接口裡邊只有一個待實現方法asBinder方法,用於返回與當前接口相關聯的Binder的對象。這樣太抽象不好理解,讓我們跟著步驟一個一個來:
首先我們看看IInterface接口的源碼:
//這是任何實現Binder的子類必須實現的接口 public interface IInterface { //用來獲取與當前接口關聯的Binder對象 public IBinder asBinder(); }
public interface IBookManager extends IInterface { public static final String DESCRIPTOR = "com.hy.blog.iBookManager"; //用來辨別被遠程客戶端調用的方法,數值必須介於Binder.FIRST_CALL_TRANSACTION到Binder.LAST_CALL_TRANSACTION //一般一個方法一個常量標志,這裡不懂沒關系,後邊還會繼續詳解 public static final int REMOTE_ADD_BOOK = Binder.FIRST_CALL_TRANSACTION + 0; public static final int REMOTE_GET_BOOK = Binder.FIRST_CALL_TRANSACTION + 1; ListgetBook(); void addBook(Book book); }
最後我們需要在Binder子類去實現這個接口:比如:
public class BookManager extends Binder implements IBookManager { //這裡會有一些具體的實現,此處省略,後續內容會繼續展開。關鍵要理解實現IInterface接口目的在於實現asBinder方法將當前對象返回給客戶端 @Override public IBinder asBinder() { return this; } }
從上面可以得出我們要實現一個Binder子類就必須去實現IInterface接口。
Parcel是一種包含數據或者對象引用、可以在IBInder之間傳輸的消息容器,Parcel包含了大量的針對不同類型數據在進程間進行讀寫的方法。並且通過Parcel可以在遠程進程通信交流的時候將遠程進程的IBinder代理對象與本地的IBinder對象綁定起來。Parcel並不是一種多用途的序列化機制,它只是被設計來作為一種高性能IPC通信的傳輸機制,並且包含在Parcel裡面的數據對象除了基本類型以外必須要實現Parcelable接口。因此Parcel數據不適合用來做持久化存儲,因為對任何在Parcel裡面的數據進行改變都可能會導致之前保存在Parcel裡面的數據變得不可讀。
Parcel提供了大量的接口用來讀取不同類型的數據,它們主要可以分為六大塊,下面會講解常用的四大塊:
1、基本類型函數:
這是最基本的函數,都是用來讀取基本數據類型的,後面提到的其他類型的讀寫都是基於這些基本函數來操作的,我們需要注意的一點是,Parcel的數據讀寫是按照Cpu字節次序來讀寫的。函數如下:
public final void writeByte(byte val) public final byte readByte() public final void writeInt(int val) public final int readInt() public final void writeFloat(float val) public final float readFloat() public final void writeDouble(double val) public final double readDouble() public final void wrtieLong(long val) public final long readLong() public final void writeString (String val) public final String readString()
2、基本類型的數組函數:
相比基本類型的讀寫函數,基本類型的數組讀寫函數多了一個createxxxArray方法(xxx可以替代任意基本類型,比如int),其中createxxxArray函數用來創建一個新的數組並將讀取的內容放進新的數組裡面然後返回,readxxxArray方法是用來將讀取的數據放在一個已存在的數組裡面並返回。函數如下:
3、實現了Parcelable接口的對象:
Parcelable是用來從Parcel讀取數據的一種非常高效率的機制。你可以用readParcelable方法和readParcelableArray方法讀取數據,用writeParcelable和writeParcelableArray來將數據寫入Parcel。這兩個方法會將相應的類的類型和數據一同寫入Parcel裡面,所以在讀取數據的時候必須傳入當前的線程的類的加載器,用來尋找合適的類來重構Parcelable對象。
還有一些更高效的讀寫Parcelable對象的方法,writeTypeObject,writeTypeList,writeTypeArray,readTypeObject,createTypeList,createTypeArray......這些方法在寫入數據的時候並不需要把原始對象的類的信息寫進Parcel,相反,在讀取數據的時候只要傳遞相應的Parcelable.CREATOR對象就可以將數據從Parcel裡面還原。如果僅僅是讀寫單個Parcelable對象,使用Parcelable.writeToParcel和Parcelable.CREATOR.createFromParcle是最高效的。
4、Bundle對象:
Bundle是一種鍵值對形式的非常安全的數據容器,它在讀寫數據有非常好的表現,並且從Parcel裡面恢復數據是可以很好的避免類型轉換的錯誤,讀寫Bundle數據的方法如下:writeBundle和readBundle。
IBinder是一種遠程對象接口,是一種輕量級的並且高性能的遠程調用機制。這個接口描述了一些進行遠程交流的對象的基本守則,但是當我們進行遠程對象的交流的時候,不要直接實現IBinder接口,而應該繼承BInder類,因為這個類為遠程通信做了基本的實現,我們只需要重寫部分方法來到達我們的目的即可。對於IBinde需要知道以下幾點:
1、IBinder接口最重要的方法是transact()方法,這個方法允許你通過調用本地IBinder對象來調用遠程對象的Binde對象。注意這個方法是個同步方法,即本地調用IBinder對象的transact方法時不會立即返回結果,必須等待遠程Binder對象執行完畢後才會返回結果,因此如果在這個過程中涉及到耗時操作,我們就不要在主線程去調用transact方法,不然會造成ARN。通過IBinder,可以讓我們在調用遠程對象的時候跟執行本地對象一樣,沒有任何區別。
2、transact方法傳送的數據是通過Parcel傳送的,其中除了數據內容之外會包含一些中介數據,這些數據用來在緩沖區保存IBinder對象的引用,這樣即使在與遠程進程進行交流的時候,仍然可以保存IBinder對象的引用。這樣就確保了IBinder對象可以發送給不同的進程。
3、系統給每一個進程分配了一個事物管理的線程池,用來調度所有的遠程進程的調用。比如A進程在自己的調用線程裡調用了transact方法,然後該線程就會阻塞起來並將該事物發給B進程進行處理,然後B進程就會從自己的事務管理線程池裡面拿出可用線程來進行處理,直至B進程處理完畢將結果返回給A進程,A進程才會繼續執行。
4、Binder系統支持進程間的迭代調用,因為進程是優先執行Binder.OnTransact()方法的。比如A進程調用了B進程的Binder.onTransact()方法,然後在B進程又調用了A進程的Binder.onTransact方法,A進程會優先執行Binder.onTransact方法。
5、當我們進行遠程對象調用的時候,因為需要阻塞自己的線程,所以在調用的時候必須判斷遠程進程的BInder對象是否存在,我們可以通過以下三個方法來判斷:
①、調用transact方法時,如果拋出了RemoteException異常就說明遠程對象不存在。
②、調用pingBinder方法,如果返回false也表示遠程對象不存在。
③、通過linkToDeath方法注冊一個DeathRecipient對象,如果遠程對象不存在,那麼DeathRecipient對象會被回調。
這個類是遠程對象的基類,它實現了IBinder的接口並提供了一些標准的實現。所以如果我們要實現一個遠程對象必須繼承Binder而不是去實現IBinder接口。通常來說,作為一個開發者並不會直接去和Binder打交道,因為通過aidl工具,系統可以為我們生成我們所期望的Binder子類。但是,如果你想,你仍然可以自己實現Binder並定義遠程通信的協議,然後實例化它並傳給遠程對象調用。
Binder對象進程鍵通信的基礎,通常來說他並不會影響application的生命周期,但是Binder對象只有在進程運行的時候才能存在,所以我們必須保持進程是運行狀態。一般來說進程可優先級可分為五個等級:
第一級:前台進程:即該進程存在正在與用戶交互的組件。
第二級:可見進程:即該進程擁有可見狀態的組件。
第三級:服務進程:即該進程擁有service組件。
第四級:後台進程:即該進程的組件正在執行onStop方法,處於不可見狀態。
第五級:空進程:即進程什麼都沒有。
當系統因為內存不足決定回收哪個線程時,第五級是最容易被回收的,第一級是最難的(除非達到了使用虛擬內存的情況,否則不會回收前台進程),因此我們使用Binder對象,最好是基於四大組件運行,這樣才會是進程處於可運行狀態,Binder對象才不會被殺死。
通過上面的介紹,可以知道要實現一個可以遠程調用的對象,必須繼承Binder類(不要直接使用IBinder接口,因為Binder類為遠程通信做了最基本的實現),同時要實現IInterface接口,在遠程調用過程中,數據是通過Parcel來傳遞的所以要求傳遞的對象必須實現Parcelable接口。下面會通過兩個例子來引導大家開發一個可供遠程調用的對象。
首先,不用aidl工具,我們自己來寫一個可供遠程調用的遠程對象。涉及到一個繼承了Parcelable接口的Book類,因為要在進程間傳遞的對象必須要實現Parcelable接口。代碼如下:
package com.example.myy.blog.binder; import android.os.Parcel; import android.os.Parcelable; /** * Created by Myy on 2016/7/12. */ public class Book implements Parcelable { private String name; private int id; public Book(String name, int id) { this.name = name; this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } private Book(Parcel in) { this.name = in.readString(); this.id = in.readInt(); } /** * 必須實現且名稱必須是CREATOR */ public static final Creator其中每個方法的用途和特殊的地方都有注釋,讀者請認真閱讀。然後我們需要寫一個繼承了IInterface接口的接口,寫這個接口的用意在於將一些基本的常量標志(一般一個方法一個常量標志)和基本的方法寫出來。IBookManager代碼如下:CREATOR = new Creator () { /** * 將數據反序列化 * @param in */ @Override public Book createFromParcel(Parcel in) { return new Book(in); } @Override public Book[] newArray(int size) { return new Book[size]; } }; /** * 通常情況下返回0,當對象數據含有文件描述符的時候返回1 * * @return */ @Override public int describeContents() { return 0; } /** * 將數據序列化,flags通常為0,為1的時候表示當前對象需要做為返回值返回不能立即釋放資源 * * @param dest * @param flags */ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(id); } }
package com.example.myy.blog.binder; import android.os.Binder; import android.os.IInterface; import java.util.List; /** * 任何binder接口都必須繼承IInterface接口 * Created by Myy on 2016/7/12. */ public interface IBookManager extends IInterface { public static final String DESCRIPTOR = "com.hy.blog.iBookManager"; //用來辨別遠程客戶端調用的方法,數值必須介於Binder.FIRST_CALL_TRANSACTION到Binder.LAST_CALL_TRANSACTION //一般一個方法一個常量標志 public static final int REMOTE_ADD_BOOK = Binder.FIRST_CALL_TRANSACTION + 0; public static final int REMOTE_GET_BOOK = Binder.FIRST_CALL_TRANSACTION + 1; List其中DESCRIPTOR用來標識當前的Binder接口,通常作為進程間獲取目標BInder對象的標志,所以使用我們的包名(可自定義任意字符串)即可。最後我們需要實現IBookManager方法,實現一些用於遠程交流的基本方法。代碼如下:getBook(); void addBook(Book book); }
package com.example.myy.blog.binder; import android.os.Binder; import android.os.IBinder; import android.os.IInterface; import android.os.Parcel; import android.os.RemoteException; import java.util.ArrayList; import java.util.List; /** * Created by Myy on 2016/7/12. */ public class BookManager extends Binder implements IBookManager { private Listbooks = new ArrayList<>(); /** * 給binder接口綁定token */ public BookManager() { this.attachInterface(this, DESCRIPTOR); } @Override public List getBook() { return books; } @Override public void addBook(Book book) { books.add(book); } @Override public IBinder asBinder() { return this; } public static IBookManager asInterface(IBinder binder) { if (binder == null) return null; IInterface iInterface = binder.queryLocalInterface(DESCRIPTOR); if ((iInterface != null) && (iInterface instanceof IBookManager)) return (IBookManager) iInterface; return new Proxy(binder); } @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { switch (code) { case REMOTE_ADD_BOOK: data.enforceInterface(DESCRIPTOR); Book book = null; if (data.readInt() != 0) { book = Book.CREATOR.createFromParcel(data); this.addBook(book); reply.writeNoException(); return true; } else { reply.writeException(new NullPointerException("參數為空")); return false; } case REMOTE_GET_BOOK: data.enforceInterface(DESCRIPTOR); List list = null; list = this.getBook(); reply.writeNoException(); reply.writeTypedList(list); return true; } return super.onTransact(code, data, reply, flags); } public static class Proxy implements IBookManager { private IBinder remote; public Proxy(IBinder binder) { remote = binder; } @Override public List getBook() { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); List list = null; data.writeInterfaceToken(DESCRIPTOR); try { remote.transact(REMOTE_GET_BOOK, data, reply, 0); reply.readException(); list = reply.createTypedArrayList(Book.CREATOR); } catch (RemoteException e) { e.printStackTrace(); } finally { data.recycle(); reply.recycle(); } return list; } @Override public void addBook(Book book) { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(DESCRIPTOR); book.writeToParcel(data, 0); try { remote.transact(REMOTE_ADD_BOOK, data, reply, 0); reply.readException(); } catch (RemoteException e) { e.printStackTrace(); } finally { data.recycle(); reply.recycle(); } } @Override public IBinder asBinder() { return remote; } public static String getInterfaceDescriptor() { return DESCRIPTOR; } } }
1、
public BookManager() { this.attachInterface(this, DESCRIPTOR); }這個方法用於給當前的Binder對象添加標簽,用於後續系統查找目標Binder對象。
2、
public IBinder asBinder() { return this; }這是IInterface接口的方法,目的是將當前對象返回。
3、
public static IBookManager asInterface(IBinder binder) { if (binder == null) return null; IInterface iInterface = binder.queryLocalInterface(DESCRIPTOR); if ((iInterface != null) && (iInterface instanceof IBookManager)) return (IBookManager) iInterface; return new Proxy(binder); }用來獲取Binder對象的接口,當當前Binder對象在本地進程查不到指定標記的Binder接口時,就說明這個binder對象是一個遠程對象,所以應該將本地的一個代理Binder對象返回給遠程客戶端。
4、
@Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { switch (code) { case REMOTE_ADD_BOOK: data.enforceInterface(DESCRIPTOR); Book book = null; if (data.readInt() != 0) { book = Book.CREATOR.createFromParcel(data); this.addBook(book); reply.writeNoException(); return true; } else { reply.writeException(new NullPointerException("參數為空")); return false; } case REMOTE_GET_BOOK: data.enforceInterface(DESCRIPTOR); List這是Binder中最重要的回調方法,當客戶端執行了transact方法時,就會回調對應的遠程Binder對象的onTransact方法。其中code是用來區分當前客戶端所調用的方法,data是輸入參數,這個參數不能為NULL,即使你不需要傳遞任何參數你也應該傳遞一個空的Parcel對象。reply是含有遠程客戶端執行接口的Parcel對象,flags是附加的執行遠程操作的標志,0表示正常的遠程調用,1表示不需要返回值的one-way調用。list = null; list = this.getBook(); reply.writeNoException(); reply.writeTypedList(list); return true; } return super.onTransact(code, data, reply, flags); }
5、data.enforceInterface(DESCRIPTOR)用於說明當前的parcel對象是和制指定了DESCRIPTOR接口相關聯的。writeNoException說明當前操作沒有出現異常。
6、代理對象Proxy
public static class Proxy implements IBookManager { private IBinder remote; public Proxy(IBinder binder) { remote = binder; } @Override public List這是BookManager的遠程代理對象,當客戶端是遠程調用時,返回的就是當前的代理Binder對象。data.writeInterfaceToken用於說明給當前的數據加上中介數據用來標識data是給含有DESCRIPTOR標志的Binder接口的參數。remote.transact方法的參數和onTransact方法是一樣的,當執行到transact方法,當前線程會阻塞,並調用遠程對象的onTransact方法。注意parcel對象不再使用時必須調用recycle方法進行釋放資源,以免造成內存洩漏。getBook() { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); List list = null; data.writeInterfaceToken(DESCRIPTOR); try { remote.transact(REMOTE_GET_BOOK, data, reply, 0); reply.readException(); list = reply.createTypedArrayList(Book.CREATOR); } catch (RemoteException e) { e.printStackTrace(); } finally { data.recycle(); reply.recycle(); } return list; } @Override public void addBook(Book book) { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(DESCRIPTOR); book.writeToParcel(data, 0); try { remote.transact(REMOTE_ADD_BOOK, data, reply, 0); reply.readException(); } catch (RemoteException e) { e.printStackTrace(); } finally { data.recycle(); reply.recycle(); } } @Override public IBinder asBinder() { return remote; } public static String getInterfaceDescriptor() { return DESCRIPTOR; } }
package com.example.myy.blog.AIDL; import android.os.Parcel; import android.os.Parcelable; /** * Created by Myy on 2016/7/13. */ public class User implements Parcelable { private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } 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; } protected User(Parcel in) { this.name = in.readString(); this.age = in.readInt(); } public static final Creator為了便於大家參考,筆者的文件結構如下圖所示: 其中User.aidl用於聲明User類是一個Parcelable類,注意任何parcelable類都需要在aidl文件裡面聲明。內容如下:CREATOR = new Creator () { @Override public User createFromParcel(Parcel in) { return new User(in); } @Override public User[] newArray(int size) { return new User[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(age); } }
// User.aidl package com.example.myy.blog.AIDL; parcelable User;UserManager.adil用於聲明遠程接口應該有的基本方法,在此文件中,任何類別都要使用完整的包名路徑引入,即使在同一個包裡面。在方法中in表示當前的是輸入參數,out表示的當前參數是輸出參數,除了基本類型之外,其它Parcelable對象都必須指明in,out。代碼如下:
// UserManager.aidl package com.example.myy.blog.AIDL; import com.example.myy.blog.AIDL.User; import java.util.List; interface UserManager { List接著我們clean一下項目,之後在如下所示目錄就可以找到系統為我們生成的遠程對象:getUser(); void addUser(in User user); }
/* * This file is auto-generated. DO NOT MODIFY. * Original file: E:\\ascode\\Blog\\app\\src\\main\\aidl\\com\\example\\myy\\blog\\AIDL\\UserManager.aidl */ package com.example.myy.blog.AIDL; public interface UserManager extends android.os.IInterface { /** * Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.example.myy.blog.AIDL.UserManager { private static final java.lang.String DESCRIPTOR = "com.example.myy.blog.AIDL.UserManager"; /** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.example.myy.blog.AIDL.UserManager interface, * generating a proxy if needed. */ public static com.example.myy.blog.AIDL.UserManager asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.example.myy.blog.AIDL.UserManager))) { return ((com.example.myy.blog.AIDL.UserManager) iin); } return new com.example.myy.blog.AIDL.UserManager.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_getUser: { data.enforceInterface(DESCRIPTOR); java.util.List我們可以發現,系統生成的對象在結構上並沒有我們自己寫的那麼清晰。但根本思路都是一樣的。都是實現IInterface接口,繼承Binder類,通過parcel傳遞Parcelable對象。_result = this.getUser(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addUser: { data.enforceInterface(DESCRIPTOR); com.example.myy.blog.AIDL.User _arg0; if ((0 != data.readInt())) { _arg0 = com.example.myy.blog.AIDL.User.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addUser(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.example.myy.blog.AIDL.UserManager { 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.util.List getUser() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getUser, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(com.example.myy.blog.AIDL.User.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void addUser(com.example.myy.blog.AIDL.User user) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); if ((user != null)) { _data.writeInt(1); user.writeToParcel(_data, 0); } else { _data.writeInt(0); } mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } static final int TRANSACTION_getUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } public java.util.List getUser() throws android.os.RemoteException; public void addUser(com.example.myy.blog.AIDL.User user) throws android.os.RemoteException; }
手機qq如何上傳照片,具體步驟如下,一起來試試吧!1.首先要打開手機qq,登錄到自己的qq帳號,登錄qq帳號以後點擊下面的動態。2.點擊動態以後,進入到動態
Auticompelete TextView動態匹配輸入的內容:目的,動態匹配輸入的內容,如百度搜索引擎當輸入文本時可以根據內容顯示匹配的熱門信息。一.目的效果圖:實驗效
今天完成一個畫畫板。首先來個布局: 可見,要分紅綠色,而且還要保存最後畫的圖片。 看一下主活動代碼: package com.it
根據書上教程運行代碼報錯,2.2的黑屏無效果,4.1的閃退。後研究發現,問題出在在一個物體同時啟用了顏色數組和紋理,注釋掉 gl.glEnableClientState