Android程序間通訊詳解
由於android系統中應用程式之間不能共享記憶體。因此,在不同應用程式之間互動資料(跨程序通訊)就稍微麻煩一些。 |
程序間通訊(ipc)
IPC方法總是產生客戶/服務端模式的呼叫,也即是客戶端元件(Activity/Service)持有服務端Service的元件,只能是客戶端主動呼叫服務端的方法,服務端無法反過來呼叫客戶端的方法,因為IPC的另一端Service無法獲取客戶端的物件。
binder
Binder 是一種程序間通訊機制。安卓中跨程序通訊就是通過binder。當繫結服務的時候會返回一個binder物件,然後通過他進行多程序間的通訊。Binder只需要一次資料拷貝,效能上僅次於共享記憶體。
在 Android 系統中,這個執行在核心空間,負責各個使用者程序通過 Binder 實現通訊的核心模組就叫 Binder 驅動(Binder Dirver)。
Binder IPC 機制中涉及到的記憶體對映通過 mmap() 來實現,mmap() 是作業系統中一種記憶體對映的方法。記憶體對映簡單的講就是將使用者空間的一塊記憶體區域對映到核心空間。對映關係建立後,使用者對這塊記憶體區域的修改可以直接反應到核心空間;反之核心空間對這段區域的修改也能直接反應到使用者空間。
記憶體對映能減少資料拷貝次數,實現使用者空間和核心空間的高效互動。兩個空間各自的修改能直接反映在對映的記憶體區域,從而被對方空間及時感知。也正因為如此,記憶體對映能夠提供對程序間通訊的支援。
Binder IPC 正是基於記憶體對映(mmap)來實現的
Binder 通訊中的代理模式
我們已經解釋清楚 Client、Server 藉助 Binder 驅動完成跨程序通訊的實現機制了,但是還有個問題會讓我們困惑。A 程序想要 B 程序中某個物件(object)是如何實現的呢?畢竟它們分屬不同的程序,A 程序 沒法直接使用 B 程序中的 object。
前面我們介紹過跨程序通訊的過程都有 Binder 驅動的參與,因此在資料流經 Binder 驅動的時候驅動會對資料做一層轉換。當 A 程序想要獲取 B 程序中的 object 時,驅動並不會真的把 object 返回給 A,而是返回了一個跟 object 看起來一模一樣的代理物件 objectProxy,這個 objectProxy 具有和 object 一摸一樣的方法,但是這些方法並沒有 B 程序中 object 物件那些方法的能力,這些方法只需要把把請求引數交給驅動即可。對於 A 程序來說和直接呼叫 object 中的方法是一樣的。
當 Binder 驅動接收到 A 程序的訊息後,發現這是個 objectProxy 就去查詢自己維護的表單,一查發現這是 B 程序 object 的代理物件。於是就會去通知 B 程序呼叫 object 的方法,並要求 B 程序把返回結果發給自己。當驅動拿到 B 程序的返回結果後就會轉發給 A 程序,一次通訊就完成了
其實程序間通訊就是為了實現資料共享。一個程式不同元件在不同程序也叫多程序,和倆個應用沒有本質區別。使用process屬性可以實現多程序,但是會帶來很多麻煩,主要原因是共享資料會失敗,弊端有:靜態和單利失效,同步失效,sharedprefer也變的不可靠等問題。
多程序通訊的方式
1.使用intent的附加資訊extras來傳遞,通過bundle,傳遞的是bundle支援的型別,比如基本資料型別、實現pracellable或serializeable的物件
/**指定包名和帶包名的Activity的名字*/ ComponentName componentName = new ComponentName("com.example.androidaidl", "com.example.androidaidl.MainActivity"); Intent intent = new Intent(); intent.putExtra("id", 1001); intent.setComponent(componentName); startActivity(intent);
2.使用檔案共享,序列化或是sharedpre,不過不適用於讀寫併發的操作
3.廣播:Android的廣播是系統級的,只要傳遞的Action一樣,就可以接收到其他程序廣播的訊息,廣播中可以通過Intent傳遞資料。
4.scheme協議是android中的一種頁面內跳轉協議,通過定義自己的scheme協議,可以非常方便跳轉app中的各個頁面,並且傳遞資料,還是可以通過H5頁面跳轉指定頁面等。
5.ContentProvider(程序間資料共享)和message一樣,底層也是binder,除了oncreate方法其他方法(crud)都是執行在bindler執行緒裡。所以在oncerate裡不能做耗時操作。Android本身就提供了不少的ContentProvider訪問,比如聯絡人、相簿等。 訪問ContentProvider,需要通過Uri,需要以“content://”開頭。在其他應用訪問通過uri(主機名):
ContentResolver resolver = getActivity().getContentResolver(); /**com.mh.getdata/stock這個要和Provider所在程序中新增的Uri一致*/ Uri uri = Uri.parse("content://com.mh.getdata/stock"); Cursor cursor = resolver.query(uri, null, null, null, null);
常規通訊
只有允許不同應用的客戶端用 IPC 方式訪問服務,並且想要在服務中處理多執行緒(多工)時,才有必要使用 AIDL。 如果您不需要執行跨越不同應用的併發 IPC,就應該通過實現一個 Binder 建立介面;或者,如果您想執行 IPC,但根本不需要處理多執行緒,則使用 Messenger 類來實現介面。無論如何,在實現 AIDL 之前,請您務必理解繫結服務。
aidl文件
1.通過 Messenger進行傳遞(handler),在遠端服務裡建立handler(接收客戶端傳送的訊息)、 Messenger對像,在onbind裡返回( Messenger.getbinder)。在客戶端繫結服務,拿著 Messenger物件發訊息(可以用bundle)。在遠端服務的handlermessage方法就會收到。他是一個個處理的,如果大量併發請求用aidl, Messenger底層就是aidl
在客戶端中建立一個Messenger。然後,當客戶端收到 onServiceConnected() 回撥時,會向服務傳送一條 Message,並在其 send() 方法的 replyTo 引數中包含客戶端的 Messenger。
注意:Messenger和Message是倆個東西
public void sayHello(View v) { if (!mBound) return; Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0); try { mService.send(msg); } catch (RemoteException e) { e.printStackTrace(); } }
2.直接使用Binder物件:缺點是這種方式不能進行跨程序,跨應用程式的函式呼叫。只能實現在同一個程序之中,同一個應用程式之中的不同的元件之間通訊。
用法:繼承Binder,然後在service裡return
繼承Binder用它的物件返回,客戶端將bind物件強轉成自定義Bind
AIDL
Android interface definition language (android介面定義語言) , 用來跨程序的訪問方法。
aidl操作步驟:
1.在兩個專案中新建普通檔案(new ->General->File),字尾名改成(aidl),客戶端和服務端中這個檔案所在的包名要保持一致,內容也要一樣
編譯之後, 會在gen目錄下,自動產生同名的,字尾為 Java 的檔案。裡面有我們要用到的 Stub類。
public static abstract class Stub extends android.os.Binder implements com.example.aidl.AidlFunctions
2.在介面檔案AIDLFunctions.aidl中,我們定義一個方法 show
interface AidlFunctions{ void show(); }
3.AIDL的使用,需要一個Service配合,所以我們在服務端還要宣告一個Service
public class AIDLService extends Service { //stub就是系統自動產生的 AidlFunctions.Stub binder; @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub binder = new AidlFunctions.Stub() { @Override //這裡是我們在介面中宣告的方法的實現 public void show() throws RemoteException { // TODO Auto-generated method stub System.out.println("--------------------收到----------------------"); } }; return binder; } }
4.客戶端:
//繫結服務,要用到ServiceConnection private ServiceConnection serviceConnection; //自定義的介面,和服務端一樣 private AidlFunctions aidlFunctions; serviceConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { System.out.println("--------------------ServiceDisconnected----------------------"); } @Override public void onServiceConnected(ComponentName name, IBinder service) { System.out.println("--------------------ServiceConnected----------------------"); aidlFunctions = AidlFunctions.Stub.asInterface(service); } }; Intent intent = new Intent("com.example.androidaidl.AIDLService"); bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); //呼叫show方法 try { aidlFunctions.show(); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); }
使用多程序顯而易見的好處就是分擔主程序的記憶體壓力。我們的應用越做越大,記憶體越來越多,將一些獨立的元件放到不同的程序,它就不佔用主程序的記憶體空間了。當然還有其他好處,有些應用後臺是有多個程序的,啟動一個不可見的輕量級私有程序,在後臺收發訊息,或者做一些耗時的事情,或者開機啟動這個程序,然後做監聽等。還有就是防止主程序被殺守護程序,守護程序和主程序之間相互監視,有一方被殺就重新啟動它。因為它們要常駐後臺,特別是即時通訊或者社交應用。