android組件間的交互和進程間IPC通信

在Android中窗體與窗體之間如何互相調用和交換數據?窗體(Activity)和後臺的服務(Service)如何通信?基於 Unix(Linux)的系統都有一個很優秀的傳統,就是倡導非常輕便的進程間通信(IPC)機制;倡導進程通過IPC來互相協作;倡導功能單一,小巧而強壯的進程,而不是又大又複雜的“萬金油”。同樣,在Android中我們可以將我們的Activity和Service放在不同的進程中運行,我們可以在我們的Task 中加載其他進程的Activity,這些機制都鼓勵我們“儘量利用已有的功能,利用IPC和包含這些已有功能的程序協作,來完成一個完整的應用”,例如在我們的程序中充分利用Google Map的相關窗體和服務。所有這些都建立在一套輕便好用的IPC機制上。

Android的組件和進程間通信都建立在一種基於稱爲Intent的消息基礎之上。Intent就是一種消息,它包含了兩個重要的內容:1. 消息的目的,即這個消息是發給哪個組件的?(消息的目的中不會包含“消息是發給哪個進程”這樣的信息,這裏Android有意淡化進程的概念,而只讓我們關心組件,因爲了解太多關於進程的具體信息會加大複雜度,而又如何做到進程間的消息傳遞呢?下文會說到一種Android中關於這點比較特別的設計方式,我認爲是一種簡捷有用又符合手機特點的設計);2. 消息所攜帶的數據內容,即需要傳遞給目標的數據。下面是一個簡單的利用Intent來啓動一個Service並向其傳遞數據的代碼示例:

Intent serviceIntent = new Intent(context, svrMain.class);
serviceIntent.putExtra(“Network_Report”, networkStatus);
context.startService(serviceIntent);

上面的代碼首先構造了一個Intent對象,並在構造的時候指定了這個Intent的目的地,即“svrMain.class”,表示這個Intent是要傳遞給一個類名叫svrMain的Service。然後向這個Intent中放入了一個數據,數據的key爲 “Network_Report”,value爲一個叫networkStatus的int類型變量,用來指明當前網絡的狀態。最後我們使用系統提供的上下文API,將這個Intent傳遞給指定的Service。


Intent的消息目的地分爲兩種模式,一種是顯式的,一種是隱式的。我們上面的例子中看到的就是一個顯式消息的例子。顯式消息直接指定消息目的地組件的類元信息,例如上面例子中svrMain就是我們寫的一個類名爲svrMain的Service,class操作符就是獲取其類元信息。這種模式的消息由於已經確切知道了消息目標的確切信息,所以只適用於同一進程內的不同組件之間通信,例如打開一個子窗體,和同一進程中的service通信等。對應的,隱式消息就一般用於跨進程的通信了,隱式消息沒有確定的消息目的地,除了數據外,隱式消息只是包含了一些用於表徵消息特徵的描述字段。而一些需要收到某種特定特徵消息的某個程序中的某個組件,需要通過在其所在程序的AndroidMainifest.xml中註冊一種被稱爲intent-filter的消息特徵篩選器,然後Android系統會按照一定的匹配規則來匹配發出的消息特徵和所有擁有響應這種特徵的intent-filter的組件(無論是同一進程內的組件還是不同程序中的組件),匹配到的組件就會接受到相應的消息。前面的描述多少有些拗口,我們舉個實際的例子來說明,如果我們想開啓一個子窗體(無論這個窗體來自同一進程還是不同進程),我們除了使用顯式消息外,我們還可以使用隱式消息:

Intent openSomeDiagIntent = new Intent();
openSomeDiagIntent.setAction(“edwin.demo.fooActivity”);
this.startActivity(openSomeDiagIntent);

上面的隱式消息不包含具體的目的地,而是僅包含一個名位“Action”的特徵字符串,Action就是上文所說的Intent特徵的一種。從字面上來理解,可以理解爲這個消息所代表的是完成某一個動作的含義,由action來標明動作的名字。所有能夠處理這種動作的Activity都可以收到該消息。對應的,可能在同一個程序或者另外的某個程序的AndroidMainifest.xml中聲明瞭下面這樣的一個Activity:

<activity android:name=”.fooActivity”>
<intent-filter>
<action android:name=”edwin.demo.fooActivity” />
</intent-filter>
</activity>

那麼這就表示這個Activity能夠收到並處理action爲“edwin.demo.fooActivity”的消息。所以上面的代碼串起來的效果就是,打開了這個名爲.fooActivity的窗口,無論這個窗口是在當前的進程中還是另外的一個程序中。


除了Action這種消息特徵外,Intent還有category,data這兩個特徵描述屬性。Category同樣是一個字符串,從字面上理解就是 “消息的分類特徵”。從程序上看其和Action的不同在於,一個Intent只能有惟一的一個Action名稱,但是卻可以包含多個Category字符串;一個Intent-Filter可以包含多個Action節點但至少要包含一個,另一方面一個Intent-Filter可以包含零到多個 Category節點。Android在做Intent-Filter匹配的時候,Intent的Action屬性匹配到Intent-Filter中的任何一個action節點,就表明擁有這個Intent-Filter的組件能夠處理這種消息;而對於Category來講一個Intent中的所有的 Category都必須存在於Intent-Filter中的Category節點中時,才表明匹配成功。Data屬性可以描述一個Intent所要傳遞的數據類型和URI,每一個Intent只能包含一個Data屬性。其中數據的類型使用MIME類型描述方式來描述,例如video/mpeg表示編碼格式爲mpeg的視頻,這裏也可以使用通配符,例如video/*表示任意格式的視頻文件類型;數據的URI由scheme(協議),host,port,path四部分組成:scheme://host:port/path,例如http://test.com:8080 /file/file1 或者content://edwin.demo.contentProvider:100/forder/content1,其中path部分也是可以支持通配符的。Data屬性是一個很有用的描述特徵,例如下面這樣的一個包含data節點的Intent-Filter:

<activity android:name=”.actHttpVideoMan”>
<intent-filter>
<action android:name=”edwin.demo.actHttpVideoMan.Main” />
<data android:scheme=”http” android:type=”video/*” />
</intent-filter>
</activity>


它表示窗體actHttpVideoMan能夠處理來自web服務器的視頻文件。這樣的filter有什麼作用呢?最典型的情況就是配合瀏覽器工作。瀏覽器在打開一個鏈接的時候首先會嘗試顯示這個鏈接對應的html頁面,如果這個鏈接不是一個html頁面,而是一個視頻文件或者其他瀏覽器本身不能處理的格式的話,瀏覽器會使用隱式消息嘗試開啓一個能夠處理這種數據格式的Activity來處理,瀏覽器發出的隱式消息就是一個包含data屬性,其中URI scheme爲http,數據類型爲video/*的消息,如果有能夠匹配這個intent的組件,例如我們上面的那個activity,瀏覽器就會啓動這個窗體,接着這個窗體會根據data屬性指定的URI去播放在線視頻,如果沒有可以處理這個intent的Activity,瀏覽器纔會調用下載管理器下載文件。


隱式消息這個設計簡單有效,它忽略了進程的細節,讓IPC在一個更高的更接近人腦思維模式的層次工作,讓系統中的不同進程協作看起來就像是同一程序中的協作一樣,這種簡單的IPC機制在很大的程度上鼓勵我們和其他進程協作,通過協作的進程來完成一個複雜的任務,而不是把什麼功能都做到一個大而全的程序裏面。不過上文還有一些細節沒有提到,例如如果一個intent有多個可匹配的處理組件,系統如何處理?這就要分響應消息的組件類型來說了,如果是 service,那麼這些service都可以啓動並處理消息,如果是Activity,則android會彈出一個對話框讓用戶進行選擇。比如我們安裝了多個可以處理在線視頻的軟件,當我們在瀏覽器中點擊一個在線視頻的鏈接時,系統會讓用戶選擇使用哪個軟件來觀看。另外大家一定會想到安全性的問題,如果不同進程間的組件可以通過隱式消息互相通信,那我們的程序不是可以輕易調用到其他的程序或者系統中的一些敏感程序的組件,這樣會不會很不安全呢?其實 Android在安全方面有一個統一,完備和輕便的安全策略模型,Intent的安全自然是被考慮在內的,關於android的安全模型我會在後續的系列 blog中專門說明。

最後,除了Intent這種基於消息的進程內和進程間通信模型外,android中也有一種相比起來稍顯笨重一些的IPC機制,它採用類似遠程方法調用的方案,通過接口定義文件AIDL來定義一個IPC接口,然後通過接收方實現接口,調用方調用接口的本地代理實現來完成IPC。這種模型只適用於 Activity和Service間的通信,之所以需要這種稍顯重量的模式,是因爲Activity除了發送intent去啓動一個service外,可能還需要能夠在Service的運行過程中連接到service,對Service發送一些控制請求。例如音樂播放程序,其後臺的播放服務往往獨立運行,以方便我們在使用其他程序界面時也能聽到音樂。同時這個後臺播放服務也會定義一個控制接口,包含比如播放,暫停,快進之類的方法,任何時候播放程序的界面都可以通過使用bindService API連接到播放服務,獲取這個接口的包含IPC細節的實現代理,通過這組控制接口方法對其進行控制,這時這種IPC的方案就顯的更方便更直觀一些了。有關使用AIDL這種IPC的更詳細描述, google的官方文檔="">已做了詳細的講解。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章