Android中進(jìn)程間通信(IPC)方式總結(jié)

不要懶惰

IPC為進(jìn)程間通信或跨進(jìn)程通信,是指兩個(gè)進(jìn)程進(jìn)行進(jìn)程間通信的過(guò)程。在PC和移動(dòng)設(shè)備上一個(gè)進(jìn)程指的是一個(gè)程序或者一個(gè)應(yīng)用,所以我們可以將進(jìn)程間通信簡(jiǎn)單的理解為:不同應(yīng)用之間的通信,當(dāng)然這種說(shuō)法并不嚴(yán)謹(jǐn)。
在Android中,為每一個(gè)應(yīng)用程序都分配了一個(gè)獨(dú)立的虛擬機(jī),或者說(shuō)每個(gè)進(jìn)程都分配一個(gè)獨(dú)立的虛擬機(jī),不同虛擬機(jī)在內(nèi)存分配上都有不同的地址空間,這就導(dǎo)致在不同的虛擬機(jī)互相訪問(wèn)數(shù)據(jù)需要借助其他手段。下面分別介紹一下在Android中實(shí)現(xiàn)IPC的方式:

  • 使用Bundle的方式
    我們知道在Android中三大組件(Activity,Service,Receiver)都支持在Intent中傳遞Bundle數(shù)據(jù),由于Bundle實(shí)現(xiàn)了Parceable接口,所以它可以很方便的在不同的進(jìn)程之間進(jìn)行傳輸。當(dāng)我們?cè)谝粋€(gè)進(jìn)程中啟動(dòng)另外一個(gè)進(jìn)程的Activity,Service,Receiver時(shí),我們就可以在Bundle中附加我們所需要傳輸給遠(yuǎn)程的進(jìn)程的信息,并且通過(guò)Intent發(fā)送出去。這里注意:我們傳輸?shù)臄?shù)據(jù)必須基本數(shù)據(jù)類(lèi)型或者能夠被序列化。
1:基本數(shù)據(jù)類(lèi)型(int, long, char, boolean, double等)
2:String和CharSequence
3:List:只支持ArrayList,并且里面的元素都能被AIDL支持
4:Map:只支持HashMap,里面的每個(gè)元素能被AIDL支持
5:Parcelable:所有實(shí)現(xiàn)Parcelable接口的對(duì)象```
下面看一個(gè)Demo例子:利用Bundle進(jìn)行進(jìn)程間通信

Intent intent = new Intent(MainActivity.this, TwoActivity.class);
Bundle bundle = new Bundle();
bundle.putString("data", "測(cè)試數(shù)據(jù)");
intent.putExtras(bundle);
startActivity(intent);```
注意:利用Bundle進(jìn)行進(jìn)程間通信是很容易的,大家應(yīng)該注意到,這種方式進(jìn)行進(jìn)程間通信只能是單方向的簡(jiǎn)單數(shù)據(jù)傳輸,它使用時(shí)有一定的局限性。

  • 使用文件共享的方式
    共享文件也是以后不錯(cuò)的進(jìn)程間通信的方式,兩個(gè)進(jìn)程通過(guò)讀/寫(xiě)同一個(gè)文件來(lái)交換數(shù)據(jù),比如進(jìn)程A把數(shù)據(jù)寫(xiě)入到文件File中,然后進(jìn)程B就可以通過(guò)讀取這個(gè)文件來(lái)獲取這個(gè)數(shù)據(jù)。通過(guò)這種方式,除了可以交換簡(jiǎn)單的文本信息之外,我們還可以序列化一個(gè)對(duì)象到文件系統(tǒng)中,另一個(gè)進(jìn)程可以通過(guò)反序列化恢復(fù)這個(gè)對(duì)象。
    舉個(gè)例子:
    在A進(jìn)程中創(chuàng)建一個(gè)線程進(jìn)行寫(xiě)數(shù)據(jù):
 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進(jìn)程中創(chuàng)建一個(gè)線程進(jìn)行讀取數(shù)據(jù):

       @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();

通過(guò)文件共享的這種方式來(lái)共享數(shù)據(jù)對(duì)文件的格式是有具體要求的,比如可以是文本文件,也可以是XML文件,只要讀寫(xiě)雙方約定數(shù)據(jù)格式即可。這種方式進(jìn)行進(jìn)程間通信雖然方便,可是也是有局限性的,比如并發(fā)讀/寫(xiě),這會(huì)導(dǎo)致比較嚴(yán)重的問(wèn)題,如讀取的數(shù)據(jù)不完整或者讀取的數(shù)據(jù)不是最新的。因此通過(guò)文件共享的方式適合在數(shù)據(jù)同步要求不高的進(jìn)程間通信,并且要妥善處理并發(fā)讀/寫(xiě)問(wèn)題。

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

客戶端進(jìn)程:在進(jìn)程B中首先綁定遠(yuǎn)程進(jìn)程Service,綁定成功后,根據(jù)Service返回的IBinder對(duì)象創(chuàng)建Messenger對(duì)象,并使用此對(duì)象發(fā)送消息,為了能收到Service端返回的消息,客戶端也創(chuàng)建了一個(gè)自己的Messenger發(fā)送給Service端,Service端就可以通過(guò)客戶端的Messenger向客戶端發(fā)送消息了,具體的實(shí)現(xiàn)代碼如下:

public class MessengerActivity extends Activity{  
    private ServiceConnection conn = new ServiceConnection(){  
        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {  
            //根據(jù)得到的IBinder對(duì)象創(chuàng)建Messenger  
            mService = new Messenger(service);  
            //通過(guò)得到的mService 可以進(jìn)行通信
        }  
    };  
      
    //為了收到Service的回復(fù),客戶端需要?jiǎng)?chuàng)建一個(gè)接收消息的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:

Messenger.png

Messenger內(nèi)部消息處理使用Handler實(shí)現(xiàn)的,所以它是以串行的方式處理客服端發(fā)送過(guò)來(lái)的消息的,如果有大量的消息發(fā)送給服務(wù)器端,服務(wù)器端只能一個(gè)一個(gè)處理,如果并發(fā)量大的話用Messenger就不合適了,而且Messenger的主要作用就是為了傳遞消息,很多時(shí)候我們需要跨進(jìn)程調(diào)用服務(wù)器端的方法,這種需求Messenger就無(wú)法做到了。

  • 使用AIDL的方式
    AIDL(Android Interface Definition Language)是一種IDL語(yǔ)言,用于生成可以在Android設(shè)備上兩個(gè)進(jìn)程之間進(jìn)行進(jìn)程間通信(IPC)的代碼。如果在一個(gè)進(jìn)程中(例如Activity)要調(diào)用另一個(gè)進(jìn)程中(例如Service)對(duì)象的操作,就可以使用AIDL生成可序列化的參數(shù)。
    AIDL是IPC的一個(gè)輕量級(jí)實(shí)現(xiàn),用了對(duì)于Java開(kāi)發(fā)者來(lái)說(shuō)很熟悉的語(yǔ)法。Android也提供了一個(gè)工具,可以自動(dòng)創(chuàng)建Stub(類(lèi)架構(gòu),類(lèi)骨架)。當(dāng)我們需要在應(yīng)用間通信時(shí),我們需要按以下幾步走:
    1:定義一個(gè)AIDL接口。
    2:為遠(yuǎn)程服務(wù)(Service)實(shí)現(xiàn)對(duì)應(yīng)Stub。
    3:將服務(wù)“暴露”給客戶程序使用。

官方文檔中對(duì)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.

第一句最重要,“只有當(dāng)你允許來(lái)自不同的客戶端訪問(wèn)你的服務(wù)并且需要處理多線程問(wèn)題時(shí)你才必須使用AIDL”,其他情況下你都可以選擇其他方法,如使用Messenger,也能跨進(jìn)程通信??梢?jiàn)AIDL是處理多線程、多客戶端并發(fā)訪問(wèn)的。而Messenger是單線程處理。
AIDL很大的好處就是我們直接可以調(diào)用服務(wù)端進(jìn)程所暴露出來(lái)的方法,下面簡(jiǎn)單介紹一下使用AIDL的使用方法:
服務(wù)端:
(1):創(chuàng)建aidl接口文件
AIDL使用簡(jiǎn)單的語(yǔ)法來(lái)聲明接口,描述其方法以及方法的參數(shù)和返回值。這些參數(shù)和返回值可以是任何類(lèi)型的,甚至是其他AIDL生成的接口。重要的是必須導(dǎo)入所有非內(nèi)置類(lèi)型,哪怕是這些類(lèi)型是與接口相同的包中。

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);
        }
    };
}

這樣我們的服務(wù)器端就完成了,把服務(wù)器端運(yùn)行到手機(jī)上,等一會(huì)可以看一下打印信息。重點(diǎn)看“線程名”。

客戶端:
客戶端所做的事情就要簡(jiǎn)單很多了,首先需要綁定服務(wù)器端Service,綁定成功后將服務(wù)器端返回的Binder對(duì)象轉(zhuǎn)成AIDL接口所屬的類(lèi)型,接著皆可以調(diào)用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, 
                                   "我們的愛(ài),我明白");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            System.out.println("bind success! " 
                           + remoteService.toString());
        }
    };
         
    /**
     * 監(jiān)聽(tīng)按鈕點(diǎn)擊
     * @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);
    }
}

這樣就實(shí)現(xiàn)了AIDL進(jìn)行進(jìn)程間通信了,是不是也很簡(jiǎn)單,不過(guò)這個(gè)看似簡(jiǎn)單,其實(shí)底層Android為我們做了很多的事情,核心就是Binder,感興趣的讀者可以學(xué)習(xí)一下Binder原理。

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

  • 使用廣播接收者(Broadcast)的方式
    廣播是一種被動(dòng)跨進(jìn)程通信方式。當(dāng)某個(gè)程序向系統(tǒng)發(fā)送廣播時(shí),其他的應(yīng)用程序只能被動(dòng)地接收廣播數(shù)據(jù)。這就像電臺(tái)進(jìn)行廣播一樣,聽(tīng)眾只能被動(dòng)地收聽(tīng),而不能主動(dòng)與電臺(tái)進(jìn)行溝通。
    BroadcastReceiver本質(zhì)上是一個(gè)系統(tǒng)級(jí)的監(jiān)聽(tīng)器,它專(zhuān)門(mén)監(jiān)聽(tīng)各個(gè)程序發(fā)出的Broadcast,因此它擁有自己的進(jìn)程,只要存在與之匹配的Intent被廣播出來(lái),BroadcastReceivert總會(huì)被激發(fā)。我們知道,只要注冊(cè)了某個(gè)廣播之后,廣播接收者才能收到該廣播。廣播注冊(cè)的一個(gè)行為是將自己感興趣的IntentFilter注冊(cè)到Android系統(tǒng)的AMS(ActivityManagerService)中,里面保存了一個(gè)IntentFilter列表。廣播發(fā)送者將IntentFilter的action行為發(fā)送到AMS中,然后遍歷AMS中的IntentFilter列表,看誰(shuí)訂閱了該廣播,然后將消息遍歷發(fā)送到注冊(cè)了相應(yīng)的IntentFilter或者Service中---也就是說(shuō):會(huì)調(diào)用抽象方法onReceive()方法。其中AMS起到了中間橋梁的作用。
    程序啟動(dòng)BroadcastReceiver只需要兩步:
    (1):創(chuàng)建需要啟動(dòng)的BroadcastReceivert的intent;
    (2):調(diào)用Context的sendBroadcast()或者sendOrderBroadcast()方法來(lái)啟動(dòng)指定的BroadcastReceivert。
    每當(dāng)Broadcast事件發(fā)生后,系統(tǒng)會(huì)創(chuàng)建對(duì)應(yīng)的BroadcastReceiver實(shí)例,并自動(dòng)觸發(fā)onReceiver()方法,onReceiver()方法執(zhí)行完后,BroadcastReceiver實(shí)例就會(huì)被銷(xiāo)毀。

注意:onReceiver()方法中盡量不要做耗時(shí)操作,如果onReceiver()方法不能再10秒之內(nèi)完成事件的處理,Android會(huì)認(rèn)為該進(jìn)程無(wú)響應(yīng),也就彈出我們熟悉的ANR對(duì)話框。如果我們需要在接收到廣播消息后進(jìn)行耗時(shí)的操作,我們可以考慮通過(guò)Intent啟動(dòng)一個(gè)Server來(lái)完成操作,不應(yīng)該啟動(dòng)一個(gè)新線程來(lái)完成操作,因?yàn)锽roadcastReceiver生命周期很短,可能新建線程還沒(méi)有執(zhí)行完,BroadcastReceivert已經(jīng)銷(xiāo)毀了,而如果BroadcastReceivert結(jié)束了,它所在的進(jìn)程中雖然還有啟動(dòng)的新線程執(zhí)行任務(wù),可是由于該進(jìn)程中已經(jīng)沒(méi)有任何組件,因此系統(tǒng)會(huì)在內(nèi)存緊張的情況下回收該進(jìn)程,這就導(dǎo)致BroadcastReceivert啟動(dòng)的子線程不能執(zhí)行完成。

  • 使用Socket的方式
    Socaket也是實(shí)現(xiàn)進(jìn)程間通信的一種方式,Socaket也稱(chēng)為“套接字”,網(wǎng)絡(luò)通信中的概念,通過(guò)Socket我們可以很方便的進(jìn)行網(wǎng)絡(luò)通信,都可以實(shí)現(xiàn)網(wǎng)絡(luò)通信錄,那么實(shí)現(xiàn)跨進(jìn)程通信不是也是相同的嘛,但是Socaket主要還是應(yīng)用在網(wǎng)絡(luò)通信中。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容