<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                #### 2.4.4 使用AIDL * [Android 接口定義語言 (AIDL)](https://developer.android.google.cn/guide/components/aidl):Android中每個應用程序都有自己的進程,當需要在不同的進程之間傳遞對象時,因為Java中是不支持跨進程內存共享,要想傳遞對象,需要把對象解析成操作系統能夠理解的數據格式,Android中采用AIDL實現。 * AIDL:一種接口定義語言,用于約束兩個進程之間的通訊規則,供編譯器生成代碼實現Android設備的IPC。 上一節我們介紹了使用Messenger來進行進程間通信的方法,可以發現,**Messenger是以串行的方式處理客戶端發來的消息,如果大量的消息同時發送到服務端,服務端仍然只能一個個處理,如果有大量的并發請求,那么用Messenger就不太合適了**。同時,**Messenger的作用主要是為了傳遞消息,很多時候我們可能需要跨進程調用服務端的方法,這種情形用Messenger就無法做到了,但是我們可以使用AIDL來實現跨進程的方法調用**。 **AIDL也是Messenger的底層實現,因此Messenger本質上也是AIDL,只不過系統為我們做了封裝從而方便上層的調用而已**。 在上一節中,我們介紹了Binder的概念,大家對Binder也有了一定的了解,在Binder的基礎上我們可以更加容易地理解AIDL。這里先介紹**使用AIDL來進行進程間通信的流程,分為服務端和客戶端兩個方面**。 **1.服務端** 服務端首先要創建一個Service用來監聽客戶端的連接請求,然后創建一個AIDL文件,**將暴露給客戶端的接口在這個AIDL文件中聲明,最后在Service中實現這個AIDL接口即可。 并非直接實現接口,而是通過繼承接口的Stub來實現,并且實現接口的方法**。 >[info]注意:這個Stub類,怎么實現的?這是編輯器自動幫助生成的,系統為IBookManager.aidl自動生成的gen目錄下的IBookManager.java類。詳細情況可以回去查看2.3.3小章節。 **2.客戶端** 客戶端所要做事情就稍微簡單一些,首先需要綁定服務端的Service,綁定成功后,將服務端返回的Binder對象轉成AIDL接口所屬的類型,接著就可以調用AIDL中的方法了。 上面描述的只是一個感性的過程,AIDL的實現過程遠不止這么簡單,接下來會對其中的細節和難點進行詳細介紹,并完善我們在Binder那一節所提供的的實例。 **3.AIDL接口的創建** 首先看AIDL接口的創建,如下所示,我們創建了一個后綴為AIDL的文件,在里面聲明了一個接口和兩個接口方法。 **IBookManager.aidl** ~~~ package com.ryg.chapter_2.aidl; import com.ryg.chapter_2.aidl.Book; import com.ryg.chapter_2.aidl.IOnNewBookArrivedListener; interface IBookManager { List<Book> getBookList(); void addBook(in Book book); void registerListener(IOnNewBookArrivedListener listener); void unregisterListener(IOnNewBookArrivedListener listener); } ~~~ 在**AIDL文件中,并不是所有的數據類型都是可以使用的**,那么到底AIDL文件支持哪些數據類型呢?如下所示。 * 基本數據類型(int、long、char、boolean、double等)除了short; * String和CharSequence; * **List:只支持ArrayList,里面每個元素都必須能夠被AIDL支持**; * 但AIDL的方法的內部可支持所有類型的List,同樣適用于Map,因為Binder會對這些其它的List和Map進行轉換 * **Map:只支持HashMap,里面的每個元素都必須被AIDL支持,包括key和value**; * Parcelable:所有實現了Parcelable接口的對象; * 比如:[Book](https://github.com/singwhatiwanna/android-art-res/blob/master/Chapter_2/src/com/ryg/chapter_2/aidl/Book.java)這個類 * AIDL:所有的AIDL接口本身也可以在AIDL文件中使用。 * 比如上面代碼中的[IOnNewBookArrivedListener](https://github.com/singwhatiwanna/android-art-res/blob/master/Chapter_2/src/com/ryg/chapter_2/aidl/IOnNewBookArrivedListener.aidl)接口 以上6種數據類型就是AIDL所支持的所有類型,其中**自定義的Parcelable對象和AIDL對象必須要顯式import進來,不管它們是否和當前的AIDL文件位于同一個包內。比如IBookManager.aidl這個文件,里面用到了Book這個類,這個類實現了Parcelable接口并且和IBookManager.aidl位于同一個包中,但是遵守AIDL的規范,我們仍然需要顯式地import進來**:`import com.ryg.chapter_2.aidl.Book`。 AIDL中會大量使用到Parcelable,至于如何使用Parcelable接口來序列化對象,在本章的前面已經介紹過,這里就不再贅述。 另外一個需要注意的地方是,**如果AIDL文件中用到了自定義的Parcelable對象,那么必須新建一個和它同名的AIDL文件,并在其中聲明它為Parcelable類型。在上面的IBookManager.aidl中,我們用到了Book這個類,所以,我們必須要創建Book.aidl,然后在里面添加如下內容**: ~~~ package com.ryg.chapter_2.aidl; parcelable Book; ~~~ 我們需要注意,**AIDL中每個實現了Parcelable接口的類都需要按照上面那種方式去創建相應的AIDL文件并聲明那個類為parcelable**。除此之外,**AIDL中除了基本數據類型,其他類型的參數必須標上方向:in、out或者inout, in表示輸入型參數,out表示輸出型參數,inout表示輸入輸出型參數,比如前面代碼中的 IBookManager.aidl中接口 IBookManager中的`addBook(in Book book);`**,至于它們具體的區別,這個就不說了。我們**要根據實際需要去指定參數類型,不能一概使用out或者inout,因為這在底層實現是有開銷的**。最后,**AIDL接口中只支持方法,不支持聲明靜態常量,這一點區別于傳統的接口**。 **為了方便AIDL的開發,建議把所有和AIDL相關的類和文件全部放入同一個包中,這樣做的好處是,當客戶端是另外一個應用時,我們可以直接把整個包復制到客戶端工程中**,對于本例來說,就是要把com.ryg.chapter_2.aidl這個包和包中的文件原封不動地復制到客戶端中。如果AIDL相關的文件位于不同的包中時,那么就需要把這些包一一復制到客戶端工程中,這樣操作起來比較麻煩而且也容易出錯。 **需要注意的是,AIDL的包結構在服務端和客戶端要保持一致,否則運行會出錯,這是因為客戶端需要反序列化服務端中和AIDL接口相關的所有類,如果類的完整路徑不一樣的話,就無法成功反序列化,程序也就無法正常運行**。 為了方便演示,本章的所有示例都是在同一個工程中進行的,但是讀者要理解,一個工程和兩個工程的多進程本質是一樣的,兩個工程的情況,除了需要復制AIDL接口所相關的包到客戶端,其他完全一樣,讀者可以自行試驗。 **4.遠程服務端Service的實現** 上面講述了如何定義AIDL接口,接下來我們就需要實現這個接口了。我們先創建一個Service,稱為BookManagerService,代碼如下: ~~~ public class BookManagerService extends Service { private static final String TAG = "BMS"; private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false); private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>(); // private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = // new CopyOnWriteArrayList<IOnNewBookArrivedListener>(); private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>(); private Binder mBinder = new IBookManager.Stub() { @Override public List<Book> getBookList() throws RemoteException { SystemClock.sleep(5000); return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"); Log.d(TAG, "check=" + check); if (check == PackageManager.PERMISSION_DENIED) { return false; } String packageName = null; String[] packages = getPackageManager().getPackagesForUid( getCallingUid()); if (packages != null && packages.length > 0) { packageName = packages[0]; } Log.d(TAG, "onTransact: " + packageName); if (!packageName.startsWith("com.ryg")) { return false; } return super.onTransact(code, data, reply, flags); } @Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException { mListenerList.register(listener); final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "registerListener, current size:" + N); } @Override public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException { boolean success = mListenerList.unregister(listener); if (success) { Log.d(TAG, "unregister success."); } else { Log.d(TAG, "not found, can not unregister."); } final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "unregisterListener, current size:" + N); }; }; @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1, "Android")); mBookList.add(new Book(2, "Ios")); new Thread(new ServiceWorker()).start(); } @Override public IBinder onBind(Intent intent) { int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"); Log.d(TAG, "onbind check=" + check); if (check == PackageManager.PERMISSION_DENIED) { return null; } return mBinder; } @Override public void onDestroy() { mIsServiceDestoryed.set(true); super.onDestroy(); } private void onNewBookArrived(Book book) throws RemoteException { mBookList.add(book); final int N = mListenerList.beginBroadcast(); for (int i = 0; i < N; i++) { IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i); if (l != null) { try { l.onNewBookArrived(book); } catch (RemoteException e) { e.printStackTrace(); } } } mListenerList.finishBroadcast(); } private class ServiceWorker implements Runnable { @Override public void run() { // do background processing here..... while (!mIsServiceDestoryed.get()) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() + 1; Book newBook = new Book(bookId, "new book#" + bookId); try { onNewBookArrived(newBook); } catch (RemoteException e) { e.printStackTrace(); } } } } } ~~~ 上面是一個服務端Service的典型實現,首先在onCreate中初始化添加了兩本圖書的信息,然后創建了一個Binder對象并在onBind中返回它,這個對象繼承自IBookManager.Stub并實現了它內部的AIDL方法,這個過程在Binder那一節已經介紹過了,這里就不多說了。 這里主要看getBookList和addBook這兩個AIDL方法的實現,實現過程也比較簡單,注意這里采用了CopyOnWriteArrayList,這個**CopyOnWriteArrayList支持并發讀/寫**。在前面我們提到,**AIDL方法是在服務端的Binder線程池中執行的,因此當多個客戶端同時連接的時候,會存在多個線程同時訪問的情形,所以我們要在AIDL方法中處理線程同步,而我們這里直接使用CopyOnWriteArrayList來進行自動的線程同步**。 前面我們提到,**AIDL中能夠使用的List只有ArrayList,但是我們這里卻使用了CopyOnWriteArrayList(注意它不是繼承自ArrayList),為什么能夠正常工作呢**?這是因為**AIDL中所支持的是抽象的List,而List只是一個接口,因此雖然服務端返回的是CopyOnWriteArrayList,但是在Binder中會按照List的規范去訪問數據并最終形成一個新的ArrayList傳遞給客戶端**。所以,我們在服務端采用CopyOnWriteArrayList是完全可以的。 和此類似的還有ConcurrentHashMap,讀者可以體會一下這種轉換情形。 然后我們**需要在XML中注冊這個Service**,如下所示。 注意:**BookManagerService是運行在獨立進程中的,它和客戶端的Activity不在同一個進程中,這樣就構成了進程間通信的場景**。 ``` <service android:name=".aidl.BookManagerService" android:process=":remote" > </service> ``` **5.客戶端的實現** 客戶端的實現就比較簡單了,**首先要綁定遠程服務,綁定成功后將服務端返回的Binder對象轉換成AIDL接口,然后就可以通過這個接口去調用服務端的遠程方法了**,代碼如下所示。 ~~~ package com.ryg.chapter_2.aidl; public class BookManagerActivity extends Activity { private static final String TAG = "BookManagerActivity"; private static final int MESSAGE_NEW_BOOK_ARRIVED = 1; private IBookManager mRemoteBookManager; @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_NEW_BOOK_ARRIVED: Log.d(TAG, "receive new book :" + msg.obj); break; default: super.handleMessage(msg); } } }; private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName()); if (mRemoteBookManager == null) { return; } mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0); mRemoteBookManager = null; // TODO:這里重新綁定遠程Service } }; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); mRemoteBookManager = bookManager; try { mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0); List<Book> list = bookManager.getBookList(); Log.i(TAG, "query book list, list type:" + list.getClass().getCanonicalName()); Log.i(TAG, "query book list:" + list.toString()); Book newBook = new Book(3, "Android進階"); bookManager.addBook(newBook); Log.i(TAG, "add book:" + newBook); List<Book> newList = bookManager.getBookList(); Log.i(TAG, "query book list:" + newList.toString()); bookManager.registerListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName className) { mRemoteBookManager = null; Log.d(TAG, "onServiceDisconnected. tname:" + Thread.currentThread().getName()); } }; private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() { @Override public void onNewBookArrived(Book newBook) throws RemoteException { mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook) .sendToTarget(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_book_manager); Intent intent = new Intent(this, BookManagerService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } public void onButton1Click(View view) { Toast.makeText(this, "click button1", Toast.LENGTH_SHORT).show(); new Thread(new Runnable() { @Override public void run() { if (mRemoteBookManager != null) { try { List<Book> newList = mRemoteBookManager.getBookList(); } catch (RemoteException e) { e.printStackTrace(); } } } }).start(); } @Override protected void onDestroy() { if (mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) { try { Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener); mRemoteBookManager .unregisterListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mConnection); super.onDestroy(); } } ~~~ 綁定成功以后,會通過bookManager去調用getBookList方法,然后打印出所獲取的圖書信息。 **需要注意的是,服務端的方法有可能需要很久才能執行完畢,這個時候下面的代碼就會導致ANR,這一點是需要注意的**,后面會再介紹這種情況,之所以先這么寫是為了讓讀者更好地了解AIDL的實現步驟。 接著**在XML中注冊此Activity,運行程序**,log如下所示。 ``` I/BookManagerActivity(3047): query book list, list type:java.util.ArrayList I/BookManagerActivity(3047): query book list:[[bookId:1, bookName:Android], [bookId:2, bokName:Ios]] ``` 可以發現,**雖然我們在服務端返回的是CopyOnWriteArrayList類型,但是客戶端收到的仍然是ArrayList類型,這也證實了我們在前面所做的分析**。第二行log表明客戶端成功地得到了服務端的圖書列表信息。 這就是一次完完整整的使用AIDL進行IPC的過程,到這里相信讀者對AIDL應該有了一個整體的認識了,但是還沒完,AIDL的復雜性遠不止這些,下面繼續介紹AIDL中常見的一些難點。 我們**接著再調用一下另外一個接口addBook,我們在客戶端給服務端添加一本書,然后再獲取一次,看程序是否能夠正常工作**。還是上面的代碼,客戶端在服務連接后,在onServiceConnected中做如下改動(下面的代碼還是上面客戶端中的代碼,只不過是挪下來了,因為上面的客戶端代碼是整個章節的代碼): ~~~ public void onServiceConnected(ComponentName className, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); mRemoteBookManager = bookManager; try { mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0); List<Book> list = bookManager.getBookList(); Log.i(TAG, "query book list, list type:" + list.getClass().getCanonicalName()); Log.i(TAG, "query book list:" + list.toString()); Book newBook = new Book(3, "Android進階"); bookManager.addBook(newBook); Log.i(TAG, "add book:" + newBook); List<Book> newList = bookManager.getBookList(); Log.i(TAG, "query book list:" + newList.toString()); bookManager.registerListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } ~~~ 運行后我們再看一下log,很顯然,我們成功地向服務端添加了一本書“Android進階”。 I/BookManagerActivity( 3148): query book list:[[bookId:1, bookName:Android], [bookId:2, bookName:Ios]] I/BookManagerActivity( 3148): add book:[bookId:3, bookName:Android進階] I/BookManagerActivity( 3148): query book list:[[bookId:1, bookName:Android], [bookId:2, bookName:Ios], [bookId:3, bookName:Android進階]] 現在我們考慮一種情況,假設有一種需求:用戶不想時不時地去查詢圖書列表了,太累了,于是,他去問圖書館,“當有新書時能不能把書的信息告訴我呢?”。大家應該明白了,這就是一種**典型的觀察者模式**,**每個感興趣的用戶都觀察新書,當新書到的時候,圖書館就通知每一個對這本書感興趣的用戶**,這種模式在實際開發中用得很多,下面我們就來模擬這種情形。 首先,我們需要提供一個AIDL接口,每個用戶都需要實現這個接口并且向圖書館申請新書的提醒功能,當然用戶也可以隨時取消這種提醒。之所以選擇AIDL接口而不是普通接口,是因為AIDL中無法使用普通接口。這里我們**創建一個IOnNewBookArrivedListener.aidl文件,我們所期望的情況是:當服務端有新書到來時,就會通知每一個已經申請提醒功能的用戶。從程序上來說就是調用所有IOnNew BookArrivedListener對象中的onNewBookArrived方法,并把新書的對象通過參數傳遞給客戶端**,內容如下所示。 package com.ryg.chapter_2.aidl; import com.ryg.chapter_2.aidl.Book; interface IOnNewBookArrivedListener { void onNewBookArrived(in Book newBook); } 除了要新加一個AIDL接口,還需要在原有的接口中添加兩個新方法,代碼如下所示,和前面的代碼一樣。 ~~~ package com.ryg.chapter_2.aidl; import com.ryg.chapter_2.aidl.Book; import com.ryg.chapter_2.aidl.IOnNewBookArrivedListener; interface IBookManager { List<Book> getBookList(); void addBook(in Book book); void registerListener(IOnNewBookArrivedListener listener); void unregisterListener(IOnNewBookArrivedListener listener); } ~~~ 接著,服務端中Service的實現也要稍微修改一下,主要是Service中IBookManager.Stub的實現,因為我們在IBookManager新加了兩個方法,所以在IBookManager.Stub中也要實現這兩個方法。同時,在BookManagerService中還開啟了一個線程,每隔5s就向書庫中增加一本新書并通知所有感興趣的用戶,整個代碼如下所示。 ~~~ package com.ryg.chapter_2.aidl; public class BookManagerService extends Service { private static final String TAG = "BMS"; private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false); private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>(); //private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = //new CopyOnWriteArrayList<IOnNewBookArrivedListener>(); private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>(); private Binder mBinder = new IBookManager.Stub() { @Override public List<Book> getBookList() throws RemoteException { SystemClock.sleep(5000); return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"); Log.d(TAG, "check=" + check); if (check == PackageManager.PERMISSION_DENIED) { return false; } String packageName = null; String[] packages = getPackageManager().getPackagesForUid( getCallingUid()); if (packages != null && packages.length > 0) { packageName = packages[0]; } Log.d(TAG, "onTransact: " + packageName); if (!packageName.startsWith("com.ryg")) { return false; } return super.onTransact(code, data, reply, flags); } @Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException { mListenerList.register(listener); final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "registerListener, current size:" + N); } @Override public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException { boolean success = mListenerList.unregister(listener); if (success) { Log.d(TAG, "unregister success."); } else { Log.d(TAG, "not found, can not unregister."); } final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "unregisterListener, current size:" + N); }; }; @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1, "Android")); mBookList.add(new Book(2, "Ios")); new Thread(new ServiceWorker()).start(); } @Override public IBinder onBind(Intent intent) { int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"); Log.d(TAG, "onbind check=" + check); if (check == PackageManager.PERMISSION_DENIED) { return null; } return mBinder; } @Override public void onDestroy() { mIsServiceDestoryed.set(true); super.onDestroy(); } private void onNewBookArrived(Book book) throws RemoteException { mBookList.add(book); final int N = mListenerList.beginBroadcast(); for (int i = 0; i < N; i++) { IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i); if (l != null) { try { l.onNewBookArrived(book); } catch (RemoteException e) { e.printStackTrace(); } } } mListenerList.finishBroadcast(); } private class ServiceWorker implements Runnable { @Override public void run() { // do background processing here..... while (!mIsServiceDestoryed.get()) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() + 1; Book newBook = new Book(bookId, "new book#" + bookId); try { onNewBookArrived(newBook); } catch (RemoteException e) { e.printStackTrace(); } } } } } ~~~ 最后,我們還需要修改一下客戶端的代碼,主要有兩方面: 1. 首先**客戶端要注冊IOnNewBookArrivedListener到遠程服務端,這樣當有新書時服務端才能通知當前客戶端,同時我們要在Activity退出時解除這個注冊**; 2. 另一方面,**當有新書時,服務端會回調客戶端的IOnNewBookArrivedListener對象中的onNewBookArrived方法,但是這個方法是在客戶端的Binder線程池中執行的,因此,為了便于進行UI操作,我們需要有一個Handler可以將其切換到客戶端的主線程中去執行**,這個原理在Binder中已經做了分析, 這里就不多說了。客戶端的代碼修改如下: ~~~ package com.ryg.chapter_2.aidl; public class BookManagerActivity extends Activity { private static final String TAG = "BookManagerActivity"; private static final int MESSAGE_NEW_BOOK_ARRIVED = 1; private IBookManager mRemoteBookManager; @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_NEW_BOOK_ARRIVED: Log.d(TAG, "receive new book :" + msg.obj); break; default: super.handleMessage(msg); } } }; private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName()); if (mRemoteBookManager == null) { return; } mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0); mRemoteBookManager = null; // TODO:這里重新綁定遠程Service } }; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); mRemoteBookManager = bookManager; try { mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0); List<Book> list = bookManager.getBookList(); Log.i(TAG, "query book list, list type:" + list.getClass().getCanonicalName()); Log.i(TAG, "query book list:" + list.toString()); Book newBook = new Book(3, "Android進階"); bookManager.addBook(newBook); Log.i(TAG, "add book:" + newBook); List<Book> newList = bookManager.getBookList(); Log.i(TAG, "query book list:" + newList.toString()); bookManager.registerListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName className) { mRemoteBookManager = null; Log.d(TAG, "onServiceDisconnected. tname:" + Thread.currentThread().getName()); } }; private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() { @Override public void onNewBookArrived(Book newBook) throws RemoteException { mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook) .sendToTarget(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_book_manager); Intent intent = new Intent(this, BookManagerService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } public void onButton1Click(View view) { Toast.makeText(this, "click button1", Toast.LENGTH_SHORT).show(); new Thread(new Runnable() { @Override public void run() { if (mRemoteBookManager != null) { try { List<Book> newList = mRemoteBookManager.getBookList(); } catch (RemoteException e) { e.printStackTrace(); } } } }).start(); } @Override protected void onDestroy() { if (mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) { try { Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener); mRemoteBookManager .unregisterListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mConnection); super.onDestroy(); } } ~~~ 運行程序,看一下log,從log中可以看出,客戶端的確收到了服務端每5s一次的新書推送,我們的功能也就實現了。 D/BMS(3414):onNewBookArrived, notify listener:com.ryg.chapter_2.aidl. IOnNewBookArrivedListener$Stub$Proxy@4052a648 D/BookManagerActivity( 3385): receive new book :[bookId:4, bookName:new book#4] D/BMS(3414):onNewBookArrived, notify listener:com.ryg.chapter_2.aidl. IOnNewBookArrivedListener$Stub$Proxy@4052a648 D/BookManagerActivity( 3385): receive new book :[bookId:5, bookName:new book#5] 如果你以為到這里AIDL的介紹就結束了,那你就錯了,之前就說過,**AIDL遠不止這么簡單,目前還有一些難點是我們還沒有涉及的**,接下來將繼續為讀者介紹。 從上面的代碼可以看出,當BookManagerActivity關閉時,我們會在onDestroy中去解除已經注冊到服務端的listener,這就相當于我們不想再接收圖書館的新書提醒了,所以我們可以隨時取消這個提醒服務。按back鍵退出BookManagerActivity,下面是打印出的log。 ``` I/BookManagerActivity(5642): unregister listener:com.ryg.chapter_2.aidl. BookManagerActivity$3@405284c8 D/BMS(5650): not found, can not unregister. D/BMS(5650): unregisterListener, current size:1 ``` 從上面的log可以看出,程序沒有像我們所預期的那樣執行。**在解注冊的過程中,服務端竟然無法找到我們之前注冊的那個listener,在客戶端我們注冊和解注冊時明明傳遞的是同一個listener啊!最終,服務端由于無法找到要解除的listener而宣告解注冊失敗**!這當然不是我們想要的結果,但是仔細想想,好像這種方式的確無法完成解注冊。其實,這是必然的,**這種解注冊的處理方式在日常開發過程中時常使用到,但是放到多進程中卻無法奏效,因為Binder會把客戶端傳遞過來的對象重新轉化并生成一個新的對象。雖然我們在注冊和解注冊過程中使用的是同一個客戶端對象,但是通過Binder傳遞到服務端后,卻會產生兩個全新的對象。別忘了對象是不能跨進程直接傳輸的,對象的跨進程傳輸本質上都是反序列化的過程,這就是為什么AIDL中的自定義對象都必須要實現Parcelable接口的原因**。 那么到底我們**該怎么做才能實現解注冊功能呢**?答案是**使用[RemoteCallbackList](https://www.androidos.net.cn/android/7.1.1_r28/xref/frameworks/base/core/java/android/os/RemoteCallbackList.java)**,這看起來很抽象,不過沒關系,請看接下來的詳細分析。 * **RemoteCallbackList是系統專門提供的用于刪除跨進程listener的接口**。 * Remote-CallbackList支持泛型,支持管理任意的AIDL接口,這點從它的聲明就可以看出,因為**所有的AIDL接口都繼承自IInterface接口,從AIDL對應的編輯器生成的對應類名的java文件也可以看出來**,讀者還有印象嗎? ``` public class RemoteCallbackList<E extends IInterface> ``` 它的工作原理很簡單,在**它的內部有一個Map結構專門用來保存所有的AIDL回調,這個Map的key是IBinder類型,value是Callback類型**,如下所示。 ``` ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>(); ``` 其中**Callback中封裝了真正的遠程listener**。 **當客戶端注冊listener的時候,它會把這個listener的信息存入mCallbacks中,其中key和value分別通過下面的方式獲得**: ``` IBinder key= listener.asBinder() Callback value = new Callback(listener, cookie) ``` 到這里,讀者應該都明白了,**雖然說多次跨進程傳輸客戶端的同一個對象會在服務端生成不同的對象,但是這些新生成的對象有一個共同點,那就是它們底層的Binder對象是同一個**,利用這個特性,就可以實現上面我們無法實現的功能。**當客戶端解注冊的時候,我們只要遍歷服務端所有的listener,找出那個和解注冊listener具有相同Binder對象的服務端listener并把它刪掉即可,這就是RemoteCallbackList為我們做的事情**。 同時**RemoteCallbackList還有一個很有用的功能,那就是當客戶端進程終止后,它能夠自動移除客戶端所注冊的listener**。 另外,**RemoteCallbackList內部自動實現了線程同步的功能,所以我們使用它來注冊和解注冊時,不需要做額外的線程同步工作。由此可見,RemoteCallbackList的確是個很有價值的類**, 下面就演示如何使用它來完成解注冊。 RemoteCallbackList使用起來很簡單,我們要對BookManagerService做一些修改,首先**要創建一個RemoteCallbackList對象來替代之前的CopyOnWriteArrayList**,如下所示。 ``` private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>(); ``` 然后修改registerListener和unregisterListener這兩個接口的實現,如下所示。 ~~~ @Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException { mListenerList.register(listener); final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "registerListener, current size:" + N); } @Override public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException { boolean success = mListenerList.unregister(listener); if (success) { Log.d(TAG, "unregister success."); } else { Log.d(TAG, "not found, can not unregister."); } final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "unregisterListener, current size:" + N); } ~~~ 怎么樣?是不是用起來很簡單,接著要**修改onNewBookArrived方法,當有新書時,我們就要通知所有已注冊的listener**,如下所示。 ~~~ private void onNewBookArrived(Book book) throws RemoteException { mBookList.add(book); final int N = mListenerList.beginBroadcast(); for (int i = 0; i < N; i++) { IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i); if (l != null) { try { l.onNewBookArrived(book); } catch (RemoteException e) { e.printStackTrace(); } } } mListenerList.finishBroadcast(); } ~~~ BookManagerService的修改已經完畢了,為了方便我們驗證程序的功能,我們還需要添加一些log,在注冊和解注冊后我們分別打印出所有listener的數量。如果程序正常工作的話,那么注冊之后listener總數量是1,解注冊之后總數量應該是0,我們再次運行一下程序,看是否如此。 從下面的log來看,很顯然,使用RemoteCallbackList的確可以完成跨進程的解注冊功能。 I/BookManagerActivity(8419): register listener:com.ryg.chapter_2.aidl. BookManagerActivity$3@40537610 D/BMS(8427): registerListener, current size:1 I/BookManagerActivity(8419): unregister listener:com.ryg.chapter_2.aidl. BookManagerActivity$3@40537610 D/BMS(8427): unregister success. D/BMS(8427): unregisterListener, current size:0 使用RemoteCallbackList,有幾點需要注意, 1. 我們無法像操作List一樣去操作它,**盡管它的名字中也帶個List,但是它并不是一個List**。 2. 遍歷RemoteCallbackList,必須要按照下面的方式進行,**其中beginBroadcast和finishBroadcast必須要配對使用**,哪怕我們僅僅是想要獲取RemoteCallbackList中的元素個數,這是必須要注意的地方。 final int N = mListenerList.beginBroadcast(); for (int i = 0; i < N; i++) { IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i); if (l ! = null) { //TODO handle l } } mListenerList.finishBroadcast(); 到這里,AIDL的基本使用方法已經介紹完了,但是有幾點還需要再次說明一下。 我們知道,**客戶端調用遠程服務的方法,被調用的方法運行在服務端的Binder線程池中,同時客戶端線程會被掛起,這個時候如果服務端方法執行比較耗時,就會導致客戶端線程長時間地阻塞在這里,而如果這個客戶端線程是UI線程的話,就會導致客戶端ANR,這當然不是我們想要看到的。因此,如果我們明確知道某個遠程方法是耗時的,那么就要避免在客戶端的UI線程中去訪問遠程方法。由于客戶端的`onServiceConnected`和`onService Disconnected`方法都運行在UI線程中,所以也不可以在它們里面直接調用服務端的耗時方法**,這點要尤其注意。 另外 ,**由于服務端的方法本身就運行在服務端的Binder線程池中,所以服務端方法本身就可以執行大量耗時操作,這個時候切記不要在服務端方法中開線程去進行異步任務,除非你明確知道自己在干什么,否則不建議這么做**。 下面我們稍微改造一下服務端的getBookList方法,我們**假定這個方法是耗時的,那么服務端可以這么實現**: ``` @Override public List<Book> getBookList() throws RemoteException { SystemClock.sleep(5000);//這里假設服務端耗時操作 return mBookList; } ``` 然后**在客戶端中放一個按鈕,單擊它的時候就會調用服務端的getBookList方法,可以預知,連續單擊幾次,客戶端就ANR了**,如圖2-7所示,感興趣讀者可以自行試一下。 :-: ![](https://img.kancloud.cn/d3/5f/d35fae5d97bf22b2e24d6ee261123928_538x795.png) 圖2-7 UI線程中調用遠程耗時方法導致的ANR 避免出現上述這種ANR其實很簡單,我們**只需要把調用放在非UI線程即可**,如下所示。 public void onButton1Click(View view) { Toast.makeText(this, "click button1", Toast.LENGTH_SHORT).show(); new Thread(new Runnable() { @Override public void run() { if (mRemoteBookManager ! = null) { try { List<Book> newList = mRemoteBookManager.getBookList(); } catch (RemoteException e) { e.printStackTrace(); } } } }).start(); } 同理,**當遠程服務端需要調用客戶端的listener中的方法時,被調用的方法也運行在Binder線程池中,只不過是客戶端的線程池**。所以,我們**同樣不可以在服務端中調用客戶端的耗時方法**。 比如**針對BookManagerService的onNewBookArrived方法,如下所示。在它內部調用了客戶端的IOnNewBookArrivedListener中的onNewBookArrived方法,如果客戶端的這個onNewBookArrived方法比較耗時的話,那么請確保BookManagerService中的onNewBookArrived運行在非UI線程中,否則將導致服務端無法響應**。 private void onNewBookArrived(Book book) throws RemoteException { mBookList.add(book); Log.d(TAG, "onNewBookArrived, notify listeners:" + mListenerList. size()); for (int i = 0; i < mListenerList.size(); i++) { IOnNewBookArrivedListener listener = mListenerList.get(i); Log.d(TAG, "onNewBookArrived, notify listener:" + listener); listener.onNewBookArrived(book); } } 另外,**由于客戶端的IOnNewBookArrivedListener中的onNewBookArrived方法運行在客戶端的Binder線程池中,所以不能在它里面去訪問UI相關的內容,如果要訪問UI,請使用Handler切換到UI線程**,這一點在前面的代碼實例中已經有所體現,這里就不再詳細描述了。 為了程序的健壯性,我們還需要做一件事。**Binder是可能意外死亡的,這往往是由于服務端進程意外停止了,這時我們需要重新連接服務**。有兩種方法, 1. 第一種方法是**給Binder設置DeathRecipient監聽,當Binder死亡時,我們會收到binderDied方法的回調,在binderDied方法中我們可以重連遠程服務**,具體方法在Binder那一節已經介紹過了,這里就不再詳細描述了。 2. 另一種方法是**在onServiceDisconnected中重連遠程服務**。 這兩種方法我們可以隨便選擇一種來使用,它們的區別在于:**onServiceDisconnected在客戶端的UI線程中被回調,而binderDied在客戶端的Binder線程池中被回調**。也就是說,**在binderDied方法中我們不能訪問UI,這就是它們的區別**。 下面驗證一下二者之間的區別,首先我們**通過DDMS殺死服務端進程,接著在這兩個方法中打印出當前線程的名稱**,如下所示。 ``` D/BookManagerActivity(13652): onServiceDisconnected. tname:main D/BookManagerActivity(13652): binder died. tname:Binder Thread #2 ``` 從上面的log和圖2-8我們可以看到,**onServiceDisconnected運行在main線程中,即UI線程,而binderDied運行在“Binder Thread #2”這個線程中,很顯然,它是Binder線程池中的一個線程**。 :-: ![](https://img.kancloud.cn/16/09/1609f7bae930981648ff2152c8cecddf_1439x700.png) 圖2-8 DDMS中的線程信息 * [ ] **如何在AIDL中使用權限驗證功能** 到此為止,我們已經對AIDL有了一個系統性的認識,但是還差最后一步:**如何在AIDL中使用權限驗證功能**。 **默認情況下,我們的遠程服務任何人都可以連接,但這應該不是我們愿意看到的,所以我們必須給服務加入權限驗證功能,權限驗證失敗則無法調用服務中的方法**。 在**AIDL中進行權限驗證,這里介紹兩種常用的方法**。 * 第一種方法,我們**可以在onBind中進行驗證,驗證不通過就直接返回null,這樣驗證失敗的客戶端直接無法綁定服務**, 至于驗證方式可以有多種,比如使用permission驗證。使用這種驗證方式,我們要先在AndroidMenifest中聲明所需的權限,比如: <permission android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" android:protectionLevel="normal" /> 關于permission的定義方式請讀者查看相關資料,這里就不詳細展開了,畢竟本節的主要內容是介紹AIDL。定義了權限以后,就可以在BookManagerService的onBind方法中做權限驗證了,如下所示。 public IBinder onBind(Intent intent) { int check = checkCallingOrSelfPermission("com.ryg.chapter_2. permission.ACCESS_BOOK_SERVICE"); if (check == PackageManager.PERMISSION_DENIED) { return null; } return mBinder; } **一個應用來綁定我們的服務時,會驗證這個應用的權限,如果它沒有使用這個權限,onBind方法就會直接返回null,最終結果是這個應用無法綁定到我們的服務,這樣就達到了權限驗證的效果,這種方法同樣適用于Messenger中**,讀者可以自行擴展。 如果我們自己內部的應用想綁定到我們的服務中,只需要在它的AndroidMenifest文件中采用如下方式使用permission即可。 ``` <uses-permission android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" /> ``` * 第二種方法,我們可以**在服務端的onTransact方法中進行權限驗證,如果驗證失敗就直接返回false,這樣服務端就不會終止執行AIDL中的方法從而達到保護服務端的效果**。 至于具體的**驗證方式有很多,可以采用permission驗證**,具體實現方式和第一種方法一樣。 **還可以采用Uid和Pid來做驗證,通過getCallingUid和getCallingPid可以拿到客戶端所屬應用的Uid和Pid,通過這兩個參數我們可以做一些驗證工作,比如驗證包名**。 在下面的代碼中,**既驗證了permission,又驗證了包名**。 **一個應用如果想遠程調用服務中的方法,首先要使用我們的自定義權限“`com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE`”,其次包名必須以“`com.ryg`”開始,否則調用服務端的方法會失敗**。 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission. ACCESS_BOOK_SERVICE"); if (check == PackageManager.PERMISSION_DENIED) { return false; } String packageName = null; String[] packages = getPackageManager().getPackagesForUid(getCalling- Uid()); if (packages ! = null && packages.length > 0) { packageName = packages[0]; } if (! packageName.startsWith("com.ryg")) { return false; } return super.onTransact(code, data, reply, flags); } 上面介紹了兩種AIDL中常用的權限驗證方法,但是**肯定還有其他方法可以做權限驗證,比如為Service指定android:permission屬性等**,這里就不一一進行介紹了。到這里為止,本節的內容就全部結束了,讀者應該對AIDL的使用過程有很深入的理解了,接下來會介紹另一個IPC方式,那就是使用ContentProvider。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看