Android 進程之間的通信(IPC)

序:很多都是自己的個人理解,不一定非常準確,供大家參考學習

大家應該都用過進程間的通訊,那有沒有想過一個問題,進程之間為什么要通信呢??

下面循序漸進的為大家解釋這個問題。

一、什么是進程或者線程

一個進程是一個獨立的功能模塊,像windows中的.exe, android中的一個app。

線程是一個獨立的子任務,是進程的子單元。

進程只是線程的執行場所。

二、一個進程間的多個線程需要特殊的通信機制嗎?

進程就相當于一個大房子,線程相當于房子里的一個人。房子里的人需要分享一個東西,只要每次一個人使用就可以了,用完再讓別人用。這里使用的就是“線程互斥”原理。因為一個房子里的東西,對房子里的所有人來說,都是可見的,可以直接使用的。

   對Java來說,可以簡單理解為共享引用。

   對C或者C++來說,可以理解為共享指針。

三、進程之間通信的情況又如何??

進程間的通信,就不是一個進程間的多個線程通信那么簡單了。這個操作系統的特性有關。為什么需要特別的進程通信機制。原因就是,操作系統中,進程間是相互不可見的。操作系統在邏輯上將每個進程隔離開了。

進程間的內存相互隔絕,操作系統這樣做就是防止進程間的相互干擾。假如A進程在操作一個內存時,B進程無意中程序執行出錯,改寫了A的進程的內存,A進程就很可能崩潰,從而造成了破壞。這也是保護各個進程的安全的一個手段。同時,操作系統也是有各種進程組成的。系統的進程同樣也會被其他進程進行破壞,從而讓操作系統出現嚴重問題。這樣的設計,操作系統很容易被病毒程序攻陷。所以操作系統為了安全起見,就采用隔絕的方式,既保護了用戶程序安全,更是保護了操作系統本身,使操作系統變得很健壯。

也就是因為這個原因,將進程相互隔絕,以至于進程間是相互看不到的。所以就給進程間的通信帶來了問題。相互看不見,如何通信呢?因為隔絕,才出現各種各樣的通信機制。

比如內存映射文件,就是將一個文件打開,作為通信中介,然后將這文件作為內核對象,分配一個句柄,這個是公用的文件,而這個句柄,是系統全部進程都可以看到的,并且看到的都是同一個,就好比幾個房子里的人都能看到廣場的一個雕像。然后通過向系統請求,得到訪問這個內核對象的句柄就可以操作了。操作完后,其他進程才可以操作,這個是“進程間的互斥”。而通過這種方式就可以更改公共的變量,達到通信的目的。而這個通信的過程就是內存映射文件模式。內核文件作為一個中介,讓相互看不見的進程可以相互交換數據。

而管道,郵槽則也是通過消息信件機制,通過系統投遞給進程的。進程只要接受這個信件即可,然后了解情況后再發送信件。系統成為了信使。這樣也達到通信的機制。

這些也就解決了進程間相互不能通信的問題,也保證進程間相互隔絕后的安全。如果當初沒有實現進程間的相互隔絕,也就用不上進程間的通信,就像一個進程間的多個線程之間的通信,是多么的容易。

四、為什么不用更簡單,粗暴的方式,直接分享一個引用?

為了系統的安全、進程的獨立性考慮,操作系統層把進程隔離開來。進程A把引用給進程B,進程B用不了。

五、掌握此知識點,對我們的開發工作有什么幫助?

系統framework層,到處都是使用進程通信的地方,當我們調用系統服務的時候,它就生存在其他的進程里。想深刻理解,需要掌握。

六、Android有幾種通信方式

  • 使用Bundle的方式
    我們知道在Android中三大組件(Activity,Service,Receiver)都支持在Intent中傳遞Bundle數據,由于Bundle實現了Parceable接口,所以它可以很方便的在不同的進程之間進行傳輸。當我們在一個進程中啟動另外一個進程的Activity,Service,Receiver時,我們就可以在Bundle中附加我們所需要傳輸給遠程的進程的信息,并且通過Intent發送出去。這里注意:我們傳輸的數據必須基本數據類型或者能夠被序列化。
1:基本數據類型(int, long, char, boolean, double等) 
2:String和CharSequence 
3:List:只支持ArrayList,并且里面的元素都能被AIDL支持 
4:Map:只支持HashMap,里面的每個元素能被AIDL支持 
5:Parcelable:所有實現Parcelable接口的對象

下面看一個Demo例子:利用Bundle進行進程間通信

Intent intent = new Intent(MainActivity.this, TwoActivity.class);
 Bundle bundle = new Bundle();
bundle.putString("data", "測試數據"); intent.putExtras(bundle);
startActivity(intent);

注意:利用Bundle進行進程間通信是很容易的,大家應該注意到,這種方式進行進程間通信只能是單方向的簡單數據傳輸,它使用時有一定的局限性。

  • 使用文件共享的方式
    共享文件也是以后不錯的進程間通信的方式,兩個進程通過讀/寫同一個文件來交換數據,比如進程A把數據寫入到文件File中,然后進程B就可以通過讀取這個文件來獲取這個數據。通過這種方式,除了可以交換簡單的文本信息之外,我們還可以序列化一個對象到文件系統中,另一個進程可以通過反序列化恢復這個對象。
    舉個例子:
    在A進程中創建一個線程進行寫數據:
new Thread(new Runnable() { 
  @Override 
public void run() { 
  User user = new User(1, "user", false); 
  File cachedFile = new File(CACHE_FILE_PATH); 
  ObjectOutputStream objectOutputStream = null; 
  try{ objectOutputStream = new ObjectOutputStream (
  new FileOutputStream(cachedFile));
   objectOutputStream.writeObject(user); 
  }catch(IOException e){
   e.printStackTrace(); 
  }finally{ 
  objectOutputStream.close(); } 
  }
 }).start();

在B進程中創建一個線程進行讀取數據:

new Thread(new Runnable() {
 @Override public void run() { 
 User user = null;
 File cachedFile = new File(CACHE_FILE_PATH);
 if(cachedFile.exists()){ 
ObjectInputStream objectInputStream = null;
 try{ 
objectInputStream = new ObjectInputStream (new FileInputStream(cachedFile));
  user = objectInputStream.readObject(user); 
}catch(IOException e){ 
e.printStackTrace(); 
}finally{
 objectInputStream.close();
 } } try{ 
objectOutputStream = new ObjectOutputStream (new FileOutputStream(cachedFile)); objectOutputStream.writeObject(user);
 }catch(IOException e){ 
e.printStackTrace(); 
}finally{ 
objectOutputStream.close(); 
} } }).start();

通過文件共享的這種方式來共享數據對文件的格式是有具體要求的,比如可以是文本文件,也可以是XML文件,只要讀寫雙方約定數據格式即可。這種方式進行進程間通信雖然方便,可是也是有局限性的,比如并發讀/寫,這會導致比較嚴重的問題,如讀取的數據不完整或者讀取的數據不是最新的。因此通過文件共享的方式適合在數據同步要求不高的進程間通信,并且要妥善處理并發讀/寫問題。

  • 使用Messenger的方式
    我們也可以通過Messenger來進行進程間通信,在Messenger中放入我們需要傳遞的數據,就可以輕松的實現進程之間數據傳遞了。Messenger是一種輕量級的IPC方案,它的底層實現是AIDL,關于AIDL我在下面會介紹到。
    Messenger的使用方法也是比較簡單的,實現一個Messenger有以下幾步,分為服務器端和客服端:
    服務器進程:在A進程創建一個Service來處理其他進程的連接請求,同時創建一個Handler并通過它來創建一個Messenger對象,然后在Service的onBind()中返回這個Messenger對象底層的Binder即可。
public class MessengerService extends Service{ 
private Handler MessengerHandler = new Handler(){ 
@Override 
public void handleMessage(Message msg) {
 //消息處理.......            
}; //創建服務端Messenger 
 private final Messenger mMessenger = new Messenger(MessengerHandler);
 @Override public IBinder onBind(Intent intent) {
 //向客戶端返回Ibinder對象,客戶端利用該對象訪問服務端
  return mMessenger.getBinder();
 }
 @Override
 public void onCreate() {
 super.onCreate(); }
 }

客戶端進程:在進程B中首先綁定遠程進程Service,綁定成功后,根據Service返回的IBinder對象創建Messenger對象,并使用此對象發送消息,為了能收到Service端返回的消息,客戶端也創建了一個自己的Messenger發送給Service端,Service端就可以通過客戶端的Messenger向客戶端發送消息了,具體的實現代碼如下:

public class MessengerActivity extends Activity{ 
private ServiceConnection conn = new ServiceConnection(){ 
@Override
 public void onServiceConnected(ComponentName name, IBinder service) {
 //根據得到的IBinder對象創建
Messenger  mService = new Messenger(service);
 //通過得到的mService 可以進行通信 }
 }; 
//為了收到Service的回復,客戶端需要創建一個接收消息的Messenger和Handler 
 private Handler MessengerHander = new Handler(){ 
@Override
 public void handleMessage(Message msg) { 
//消息處理
 } }; 
private Messenger mGetMessenger = new Messenger(MessengerHander);
 @Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState); 
setContentView(R.layout.activity_messenger); 
init(); 
} 
private void init() { 
intent = new Intent(MessengerActivity.this, MessengerService.class);
 indService(intent, conn, Context.BIND_AUTO_CREATE); 
}
 @Override
 protected void onDestroy(){ 
unbindService(conn); 
super.onDestroy(); 
} 
}

下面給出一張Messenger的工作原理圖,以便于更好的理解Messenger:


3416944-96de340a332104cc.png

Messenger內部消息處理使用Handler實現的,所以它是以串行的方式處理客服端發送過來的消息的,如果有大量的消息發送給服務器端,服務器端只能一個一個處理,如果并發量大的話用Messenger就不合適了,而且Messenger的主要作用就是為了傳遞消息,很多時候我們需要跨進程調用服務器端的方法,這種需求Messenger就無法做到了。

  • **使用ContentProvider的方式 **
    ContentProvider(內容提供者)是Android中的四大組件之一,為了在應用程序之間進行數據交換,Android提供了ContentProvider,ContentProvider是不同應用之間進行數據交換的API,一旦某個應用程序通過ContentProvider暴露了自己的數據操作的接口,那么不管該應用程序是否啟動,其他的應用程序都可以通過接口來操作接口內的數據,包括數據的增、刪、改、查等操作。ContentProvider分為系統的和自定義的,系統的(例如:聯系人,圖片等數據)。
    開發一個ContentProvider的步驟很簡單:
    (1):定義自己的ContentProvider類,該類集成ContentProvider基類;
    (2):在AndroidMainfest.xml中注冊這個ContentProvider,類似于Activity注冊,注冊時要給ContentProvider綁定一個域名;
    (3):當我們注冊好這個ContentProvider后,其他應用就可以訪問ContentProvider暴露出來的數據了。
    ContentProvider只是暴露出來可供其他應用操作的數據,其他應用則需要通過ContentProvider來操作ContentProvider所暴露出來的數據。Content提供了getContentResolver()方法來獲取ContentProvider對象,獲取之后皆可以對暴露出來的數據進行增、刪、改、查操作了。
    使用ContentResolver操作數據的步驟也很簡單:
    (1)調用Activity的getContentResolver()獲取ContentResolver對象;
    (2)根據調用的ContentResolver的insert()、delete()、update()和query()方法操作數據庫即可。

  • 使用廣播接收者(Broadcast)的方式
    廣播是一種被動跨進程通信方式。當某個程序向系統發送廣播時,其他的應用程序只能被動地接收廣播數據。這就像電臺進行廣播一樣,聽眾只能被動地收聽,而不能主動與電臺進行溝通。
    BroadcastReceiver本質上是一個系統級的監聽器,它專門監聽各個程序發出的Broadcast,因此它擁有自己的進程,只要存在與之匹配的Intent被廣播出來,BroadcastReceivert總會被激發。我們知道,只要注冊了某個廣播之后,廣播接收者才能收到該廣播。廣播注冊的一個行為是將自己感興趣的IntentFilter注冊到Android系統的AMS(ActivityManagerService)中,里面保存了一個IntentFilter列表。廣播發送者將IntentFilter的action行為發送到AMS中,然后遍歷AMS中的IntentFilter列表,看誰訂閱了該廣播,然后將消息遍歷發送到注冊了相應的IntentFilter或者Service中---也就是說:會調用抽象方法onReceive()方法。其中AMS起到了中間橋梁的作用。
    程序啟動BroadcastReceiver只需要兩步:
    (1):創建需要啟動的BroadcastReceivert的intent;
    (2):調用Context的sendBroadcast()或者sendOrderBroadcast()方法來啟動指定的BroadcastReceivert。
    每當Broadcast事件發生后,系統會創建對應的BroadcastReceiver實例,并自動觸發onReceiver()方法,onReceiver()方法執行完后,BroadcastReceiver實例就會被銷毀。
    注意:onReceiver()方法中盡量不要做耗時操作,如果onReceiver()方法不能再10秒之內完成事件的處理,Android會認為該進程無響應,也就彈出我們熟悉的ANR對話框。如果我們需要在接收到廣播消息后進行耗時的操作,我們可以考慮通過Intent啟動一個Server來完成操作,不應該啟動一個新線程來完成操作,因為BroadcastReceiver生命周期很短,可能新建線程還沒有執行完,BroadcastReceivert已經銷毀了,而如果BroadcastReceivert結束了,它所在的進程中雖然還有啟動的新線程執行任務,可是由于該進程中已經沒有任何組件,因此系統會在內存緊張的情況下回收該進程,這就導致BroadcastReceivert啟動的子線程不能執行完成。

  • 使用Socket的方式
    Socaket也是實現進程間通信的一種方式,Socaket也稱為“套接字”,網絡通信中的概念,通過Socket我們可以很方便的進行網絡通信,都可以實現網絡通信錄,那么實現跨進程通信不是也是相同的嘛,但是Socaket主要還是應用在網絡通信中。

  • **使用AIDL的方式 **
    AIDL(Android Interface Definition Language)是一種IDL語言,用于生成可以在Android設備上兩個進程之間進行進程間通信(IPC)的代碼。如果在一個進程中(例如Activity)要調用另一個進程中(例如Service)對象的操作,就可以使用AIDL生成可序列化的參數。
    AIDL是IPC的一個輕量級實現,用了對于Java開發者來說很熟悉的語法。Android也提供了一個工具,可以自動創建Stub(類架構,類骨架)。當我們需要在應用間通信時,我們需要按以下幾步走:
    1:定義一個AIDL接口。
    2:為遠程服務(Service)實現對應Stub。
    3:將服務“暴露”給客戶程序使用。

官方文檔中對AIDL有這樣一段介紹:
Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.
第一句最重要,“只有當你允許來自不同的客戶端訪問你的服務并且需要處理多線程問題時你才必須使用AIDL”,其他情況下你都可以選擇其他方法,如使用Messenger,也能跨進程通信。可見AIDL是處理多線程、多客戶端并發訪問的。而Messenger是單線程處理。
AIDL很大的好處就是我們直接可以調用服務端進程所暴露出來的方法,下面簡單介紹一下使用AIDL的使用方法:
服務端:
(1):創建aidl接口文件
AIDL使用簡單的語法來聲明接口,描述其方法以及方法的參數和返回值。這些參數和返回值可以是任何類型的,甚至是其他AIDL生成的接口。重要的是必須導入所有非內置類型,哪怕是這些類型是與接口相同的包中。

package com.example.android; 
interface IRemoteService { 
int getPid(); 
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString); 
}

(2):向客戶端暴露接口

public class DDService extends Service {
 @Override public void onCreate() { 
super.onCreate(); 
System.out.println("DDService onCreate........"
 + "Thread: " + Thread.currentThread().getName()); } 
@Override
 public IBinder onBind(Intent arg0) { 
 System.out.println("DDService onBind");
 return mBinder; 
} 
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
 public int getPid(){
 System.out.println("Thread: " + Thread.currentThread() .getName()); 
 System.out.println("DDService getPid "); 
 return Process.myPid();
 } 

public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) { 
System.out.println("Thread: " + Thread.currentThread().getName()); 
System.out.println("basicTypes aDouble: " + aDouble +" anInt: "
 + anInt+" aBoolean " + aBoolean +" aString " + aString); 
} 
}; 
}

這樣我們的服務器端就完成了,把服務器端運行到手機上,等一會可以看一下打印信息。重點看“線程名”。

客戶端:

客戶端所做的事情就要簡單很多了,首先需要綁定服務器端Service,綁定成功后將服務器端返回的Binder對象轉成AIDL接口所屬的類型,接著皆可以調用AIDL中的方法了。

public class MainActivity extends Activity { 
private IRemoteService remoteService; 
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
 } 
ServiceConnection conn = new ServiceConnection() { 
@Override
 public void onServiceDisconnected(ComponentName name) { 
} 
@Override
 public void onServiceConnected(ComponentName name, IBinder service) {
 remoteService = IRemoteService.Stub.asInterface(service);
 try { int pid = remoteService.getPid();
 int currentPid = Process.myPid(); 
System.out.println("currentPID: " + currentPid +"  remotePID: " + pid); remoteService.basicTypes(12, 1223, true, 12.2f, 12.3, "我們的愛,我明白"); 
} catch (RemoteException e) { 
e.printStackTrace();
} System.out.println("bind success! " + remoteService.toString()); } };
/**
     * 監聽按鈕點擊
     * @param view
     */
public void buttonClick(View view) { 
System.out.println("begin bindService"); 
Intent intent = new Intent("duanqing.test.aidl"); 
bindService(intent, conn, Context.BIND_AUTO_CREATE); 
} 
@Override 
protected void onDestroy() { 
super.onDestroy(); unbindService(conn); 
}
}

這樣就實現了AIDL進行進程間通信了,是不是也很簡單,不過這個看似簡單,其實底層Android為我們做了很多的事情,核心就是Binder,感興趣的讀者可以學習一下Binder原理。

//重點記錄

1. 系統生成****.java 文件的位置:

如果是Eclips 的工程,會在gen目錄下生成該文件,

如果是系統工程里的,會在out/target/common/obj/JAVA_LIBRARIES/...生成該文件

備注:有些自動化的東西,提高了編程的速度,同時也帶來了負面影響:給深入理解帶來障礙。

2.接口繼承:

是不需要重寫父親接口的函數的

public interface TestInterface extendsandroid.os.IInterface {

}

//接口可以繼承接口,類可以繼承類或者實現接口
//java中抽象類當實現某個接口類時,可以不用實現接口中的方法??因為它是抽象的,實現類去實現這些接口就好。

3.關于stub:

stub 意思是存根,stubclass (存根類)
句子:To do this , you need a stub class of the remoteobject.
要做到這點,你需要一個遠程類的存根類。

4.返回同一對象or拷貝??

A:如果綁定的是同一個app中的Service,返回的是同一個引用
Activity 和Service 中是一個存根對象 afb6f16,一個app 內,內存是可以共享的,符合預期估算

B:如果綁定的是另一個app中的service,返回的就不是同一個對象了,是拷貝對象b89ac31(拷貝并不準確,其實是一個代理)
BinderProxy you see.

5. Binder 和 BinderProxy

都位于Binder.java 中 ,是并行類。

6. 代理是一個位于內核空間的對象嗎??

7. 內核可以訪問進程A和進程B的所有數據

     這樣內核才有了做為中轉的前提條件。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容