Android IPC操作步驟
一、什麼是IPC?
IPC是Inter-Process-Communication的縮寫,意思是程序間通訊或者跨程序通訊;
說起程序間通訊,我們應該首先來了解一下什麼是程序。按照作業系統的描述,執行緒是CPU排程的最小單元,而程序一般指一個執行單元,在移動裝置上指一個程式或應用;一個程序可以包含多個執行緒;
為什麼要用到多程序?
在Android系統中一個應用預設只有一個程序,每個程序都有自己獨立的資源和記憶體空間,其它程序不能任意訪問當前程序的記憶體和資源,系統給每個程序分配的記憶體會有限制。如果一個程序佔用記憶體超過了這個記憶體限制,就會報OOM的問題,很多涉及到大圖片的頻繁操作或者需要讀取一大段資料在記憶體中使用時,很容易報OOM的問題,為了徹底地解決應用記憶體的問題,Android引入了多程序的概念,它允許在同一個應用內,為了分擔主程序的壓力,將佔用記憶體的某些頁面單獨開一個程序,比如Flash、視訊播放頁面,頻繁繪製的頁面等。Android多程序使用很簡單,只需要在AndroidManifest.xml的宣告四大元件的標籤中增加”android:process”屬性即可,process分私有程序和全域性程序,以“:”號開頭的屬於私有程序,其他應用元件不可以和他跑在同一個程序中;不以“:”號開頭的屬於全域性程序,其他應用可以通過ShareUID的方式和他跑在同一個程序中;
但是多程序模式出現以下問題:
1、靜態成員和單例模式完全失效
2、執行緒同步機制完全失效
3、SharedPreferences的可靠性下降
4、Application多次建立
因此為了避免這些問題Android中有多種IPC機制,如AIDL,Messenger,Socket,ContentProvider,但是這些機制底層全部都是用了Binder機制來實現的,什麼是Binder?要了解Android系統中的IPC我們首先要了解的就是Binder;
具體詳細可閱《Android開發藝術探索》中第二章-IPC機制(上) : ofollow,noindex" target="_blank">https://blog.csdn.net/qq_26787115/article/details/52664405
二、Binder機制原理
1、Binder機制
Binder是Android系統中的一種IPC程序間通訊結構。
Binder的整個設計是C/S結構,客戶端程序通過獲取服務端程序的代理,並通過向這個代理介面方法中讀寫資料來完成程序間的資料通訊。
Android之所以選擇Binder,有2個方面的原因。
1是安全,每個程序都會被Android系統分配UID和PID,不像傳統的在資料里加入UID,這就讓那些惡意程序無法直接和其他程序通訊,程序間通訊的安全性得到提升。
2是高效,像Socket之類的IPC每次資料拷貝都需要2次,而Binder只要1次,在手機這種資源緊張的情況下很重要。
Binder機制原理圖:

1.客戶端獲取服務端的代理物件(proxy)。我們需要明確的是客戶端程序並不能直接操作服務端中的方法,如果要操作服務端中的方法,那麼有一個可行的解決方法就是在客戶端建立一個服務端程序的代理物件,這個代理物件具備和服務端程序一樣的功能,要訪問服務端程序中的某個方法,只需要訪問代理物件中對應的方法即可;
2.客戶端通過呼叫代理物件向服務端傳送請求。
3.代理物件將使用者請求通過Binder驅動傳送到伺服器程序;
4.服務端程序處理客戶端發過來的請求,處理完之後通過Binder驅動返回處理結果給客戶端的服務端代理物件;
5.代理物件將請求結果進一步返回給客戶端程序。
通過以上5個步驟,就完成了一次Binder通訊。
2、Binder機制的組成
Binder機制由三部分組成,即:
1.Client;
2.Server;
3.ServiceManager。
三部分元件之間的關係:
1.Client、Server、ServiceManager均在使用者空間中實現,而Binder驅動程式則是在核心空間中實現的;
2.在Binder通訊中,Server程序先註冊一些Service到ServiceManager中,ServiceManager負責管理這些Service並向Client提供相關的介面;
3.Client程序要和某一個具體的Service通訊,必須先從ServiceManager中獲取該Service的相關資訊,Client根據得到的Service資訊與Service所在的Server程序建立通訊,之後Clent就可以與Service進行互動了;
4.Binder驅動程式提供裝置檔案/dev/binder與使用者空間進行互動,Client、Server和ServiceManager通過open和ioctl檔案操作函式與Binder驅動程式進行通訊;
5.Client、Server、ServiceManager三者之間的互動都是基於Binder通訊的,所以通過任意兩者這件的關係,都可以解釋Binder的機制。
三、AIDL的實現
在Android中有多種實現IPC的方式,各有各的優缺點,我們拿其中一種最常用方式來更深入的瞭解一下Android中IPC的實現方式,從而徹底理解Binder機制的工作方式;
伺服器程式碼:
(1)定義Book類,包含ID, Name 屬性,實現 Parcelable 介面
package com.uiuno.myapplication; import android.os.Parcel; import android.os.Parcelable; public class Book implements Parcelable { public int bookId; public String bookName = ""; public Book(int bookId, String bookName) { this.bookId = bookId; this.bookName = bookName; } public void setBookName(String bookName){ this.bookName = bookName; } public String getBookName(){ return this.bookName; } public void setBookId(int bookId){ this.bookId = bookId; } public int getBookId(){ return this.bookId; } public Book(Parcel in) { bookId = in.readInt(); bookName = in.readString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(bookId); dest.writeString(bookName); } public static final Creator<Book> CREATOR = new Creator<Book>() { @Override public Book createFromParcel(Parcel in) { return new Book(in); } @Override public Book[] newArray(int size) { return new Book[size]; } }; @Override public String toString() { return String.format("編號:%d, 書名:%s", bookId, bookName); } }
(2).申明Book的AIDL檔案(如果建立的時候出現與Book.java同名提示,請先建立好aidl申明檔案。):
// Book.aidl package com.uiuno.myapplication; parcelable Book;
(3).定義AIDL檔案
// IBookManager.aidl package com.uiuno.myapplication; // Declare any non-default types here with import statements //aidl中如果使用非基本型別,需要import import com.uiuno.myapplication.Book; interface IBookManager { List<Book> getBookList(); void addBook(in Book book); }
建立或修改過AIDL檔案後需要clean下工程,使系統及時生成我們需要的檔案
(4).建立一個 Service 供客戶端遠端綁定了,這裡命名為 AIDLService
package com.uiuno.myapplication; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import java.util.ArrayList; import java.util.List; public class AIDLServices extends Service { private final String TAG = "Server"; private List<Book> bookList; public AIDLServices() { } @Override public void onCreate() { super.onCreate(); bookList = new ArrayList<Book>(); initData(); } private void initData() { Book book1 = new Book(1,"活著"); Book book2 = new Book(2,"或者"); Book book3 = new Book(3,"葉應是葉"); bookList.add(book1); bookList.add(book2); bookList.add(book3); Log.e(TAG, "初始化書籍"); } private final IBookManager.Stub stub = new IBookManager.Stub() { @Override public List<Book> getBookList() throws RemoteException { return bookList; } @Override public void addBook(Book book) throws RemoteException { if (book != null) { bookList.add(book); Log.e(TAG, "伺服器接收到了一個新書物件:" + book.toString()); } else { Log.e(TAG, "伺服器接收到了一個空物件"); } } }; @Override public IBinder onBind(Intent intent) { return stub; } }
可以看到, onBind 方法返回的就是 BookController.Stub 物件,實現當中定義的兩個方法
最後,服務端還有一個地方需要注意,因為服務端的Service需要被客戶端來遠端繫結,所以客戶端要能夠找到這個Service,可以通過先指定包名,之後再配置Action值或者直接指定Service類名的方式來繫結Service
如果是通過指定Action值的方式來繫結Service,那還需要將Service的宣告改為如下所示:
<service android:name=".AIDLServices" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="com.uiuno.myapplication.bookservice.action" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service>
客戶端程式碼:
需要把服務端的AIDL檔案以及Book類複製過來,將 aidl 資料夾整個複製到和Java資料夾同個層級下,不需要改動任何程式碼
此處需要複製三個檔案,並保持和伺服器相同的包名:1.Book.java 2.Book.aidl 3.IBookManager.aidl
伺服器只需要操作獲取書籍列表和新增書籍操作:
1.佈局檔案:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <Button android:id="@+id/btn_getBookList" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="獲取書籍列表" /> <Button android:id="@+id/btn_addBook_inOut" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="新增書籍" /> </LinearLayout>
2.MainActivity.java
package com.uiuno.myapplication; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import com.uiuno.myapplication2.R; import java.util.List; public class MainActivity extends AppCompatActivity { private final String TAG = "Client"; private IBookManager bookController; private boolean connected; private List<Book> bookList; private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bookController = IBookManager.Stub.asInterface(service); connected = true; } @Override public void onServiceDisconnected(ComponentName name) { connected = false; } }; private View.OnClickListener clickListener = new View.OnClickListener() { @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_getBookList: if (connected) { try { bookList = bookController.getBookList(); log(); } catch (RemoteException e) { e.printStackTrace(); } } break; case R.id.btn_addBook_inOut: if (connected) { Book book = new Book(10, "這是一本新書 InOut"); try { bookController.addBook(book); Log.e(TAG, "向伺服器以InOut方式添加了一本新書"); Log.e(TAG, "新書名:" + book.getBookName()); } catch (RemoteException e) { e.printStackTrace(); } } break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.btn_getBookList).setOnClickListener(clickListener); findViewById(R.id.btn_addBook_inOut).setOnClickListener(clickListener); bindService(); } @Override protected void onDestroy() { super.onDestroy(); if (connected) { unbindService(serviceConnection); } } private void bindService() { Intent intent = new Intent(); intent.setPackage("com.uiuno.myapplication"); intent.setAction("com.uiuno.myapplication.bookservice.action"); bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); } private void log() { if(bookList.size() == 0){ return; } for (Book book : bookList) { if(book != null) { Log.e(TAG, book.toString()); } } } }
兩個按鈕分別用於獲取服務端的書籍列表和新增書籍,在新增書籍時,服務端還改變了Book物件的Name屬性,據此觀察客戶端和服務端資料的變化情況
接下來我們來分析一下整個AIDL的執行過程:
1、首先服務端的MyAddService在自己的程序中向Binder驅動申請建立了一個MyAddService的Binder實體;Binder驅動為MyAddService建立了位於核心中的Binder實體節點以及Binder的引用,並將名字和新建的引用打包傳遞給SM(實體沒有傳給SM),通知SM註冊一個MyAddService;
2、SM收到資料包後,從中取出MyAddService名字和引用,填入一張查詢表中。在啟動服務的時候,SM就會從這張查詢表中查詢相應的服務;
3、客戶端Client申請訪問MyAddService,SM就會從請求資料包中獲得MyAddService的名字,在查詢表中找到該名字對應的條目,取出Binder的引用打包回覆給client。之 後,Client就可以利用MyAddService的引用使用MyAddService的服務了。
AIDL注意事項:
1.AIDL編譯錯誤: ‘aidl.exe” finished with non-zero exit value 1
解決方法:
1.包名問題
2.AIDL不支援同名不同參函式
3.使用自定義型別時需要在AIDL中宣告
4.AIDL中如果需要使用非基礎型別,需要定義Parcelable類,然後在申明一個AIDL檔案,在需要AIDL的實現檔案(實現檔案中即便aidl和java檔案屬於同級報名,也需要import使用的java檔案,不然編譯錯誤。)