Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android進程間通信(IPC)常用方式解析

Android進程間通信(IPC)常用方式解析

編輯:關於Android編程

進程間通信方式

Android開發中我們可以通過IntentContentProviders來實現進程間通信,如果不限於Android特有的話,我們還可以使用FileSocket等方式,反正只要進程間能交換信息就行了。

Intent,我們平時使用的時候好像都沒感覺出是在進程間通信。其實Android中進程間的通信是非常頻繁的,應用裡打開一個新的Activity都涉及到了進程間通信,應用裡調用打電話、調用浏覽器等等都涉及到了。

實際上IntentContentProviders都是對Binder更高級別的抽象,方便我們平時使用。

常用方式

上面說到的一些方式都是系統經過高度封裝的,而我們的業務需求可能比較特別,使用上面的方式可能不是特別適合,比如:“我們的音樂播放器希望在獨立的進程中播放音樂”。

我們至少得控制音樂的開始、暫停、顯示進程這些功能吧,那就需要進程間的通信了。這個時候使用系統經過高度封裝的方式都好像顯得不太靈活。根據官方文檔我們發現有兩種相對底層一些的方式,MessengerAIDL

在相對底層一點的進程間通信,Messenger是最簡單的方式,Messenger會在單一線程中創建包含所有請求的隊列,這樣我們就不需要處理線程安全方面的事宜。

Messenger實際上是以AIDL作為其底層結構的。

Messenger的用法

單向通信

客戶端進程相關代碼:

public class MainActivity extends AppCompatActivity {

    private Messenger mService = null;

    // 綁定遠程服務成功後相應回調方法
    private ServiceConnection mServiceConnection = new ServiceConnection() {

        // 綁定成功後會調用該方法
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            mService = new Messenger(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = new Intent(this, RemoteService.class);
        // 綁定服務
        bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
    }

    /**
     * 點擊按鈕向遠程服務發送消息
     * @param view
     */
    public void onClick(View view) {
        // 獲取一個what值為0的消息對象
        Message msg = Message.obtain(null, 0);
        try {
            // 將消息對象通過Messenger傳遞到遠程服務器
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();

        unbindService(mServiceConnection);
    }
}

布局XML代碼就不貼了,很簡單,就一個按鈕。

上面的代碼也很簡單,就得我們平常綁定服務的做法是一樣的,唯一的區別就是在綁定成功回調方法onServiceConnected()中我們根據返回的IBinder實例化了一個Messenger對象,當我們點擊按鈕的時候,通過該Messenger對象發送一個消息到遠程服務端。

服務端進程代碼:

/**
 * 遠程服務端
 */
public class RemoteService extends Service {

    // 用來處理客戶端傳過來的消息
    class ServerSideHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 0:
                    Log.e("Fiend", "我是遠程服務端,我收到客戶端傳遞過來的信息了。");
                    break;
            }
        }
    }

    // 實例化一個Messenger對象,並傳入Handler
    final Messenger mMessenger = new Messenger(new ServerSideHandler());

    /**
     * 客戶端綁定服務端的時候將調用該方法
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }

}

服務端的代碼也很簡單,創建一個Handler來處理消息,實例化一個Messenger與該Handler關聯,最後通過onBind()方法將MessengerIBinder給返回。在客戶端通過該IBinder重建一個Messenger

我們來看一下運行結果:

調用遠程結果

確實成功了,而且也確實是在兩個進程間。想要讓服務運行在別的進程只需要聲明的時候指定它的android:process屬性就可以了。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPrWrysfO0sPH1rvKx7/Nu6e2y8/yt/7O8bbLt6LLzcHL0MXPoqOsxMe3/s7xtsvI57rOz/K/zbuntsu3osvN0MXPosTYo788L3A+DQo8cD7Lq8/yzajQxTwvcD4NCjxwPr/Nu6e2y7jEtq+12Le9o7o8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> /** * 點擊按鈕向遠程服務發送消息 * @param view */ public void onClick(View view) { // 獲取一個what值為0的消息對象 Message msg = Message.obtain(null, 0); // 將客戶端的Messenger對象放到消息中傳遞到服務端 msg.replyTo = new Messenger(new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: Log.e("Fiend", "我是客戶端,收到服務端的回復了"); break; } } }); try { // 將消息對象通過Messenger傳遞到遠程服務器 mService.send(msg); } catch (RemoteException e) { e.printStackTrace(); } }

客戶端代碼只需要在發送消息之前將本地的一個Messenger對象放到消息裡一起傳遞到遠程服務端即可。

服務端改動地方:

    // 用來處理客戶端傳過來的消息
    class ServerSideHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 0:
                    Log.e("Fiend", "我是遠程服務端,我收到客戶端傳遞過來的信息了。");
                    try {
                        // 通過客戶端的Messenger回復一個what值為1的消息
                        msg.replyTo.send(Message.obtain(null, 1));
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
            }
        }
    }

服務端改動的代碼也非常簡單,只是在收到客戶端消息的時候, 通過客戶端的Messenger回復一個消息。這樣就實現了本地客戶端與遠程服務端的通信了。

對於大多數的應用來說,Messenger就能滿足IPC的需求了,完全沒必要使用AIDL,而且MessengerAIDL簡單得多。如果對於服務需要執行多線程處理的,則應使用AIDL,否則使用Messenger就可以了。

AIDL的用法

使用AIDL和使用Messenger的步驟基本上是類似的。使用AIDL需要自己定義好一個接口作為客戶端和服務端通信的規則,手工寫一個這樣的接口比較復雜,所以Android給我們提供了一個工具來自動生成。

想要自動生成通信的接口,則需要創建一個以.aidl結尾的文件,然後按平常我們定義接口的方式做就好了。下面以Android Studio來講解生成過程。

新建一個項目,名字隨便取:AIDLExample 將工程目錄結構以Android的形式展示:
切換視圖 點擊項目,右鍵,新建一個AIDL文件:
newaidl 打開新建的AIDL文件IMyAidlInterface.aidl,編寫通信規則:
AIDL內容 編寫完IMyAidlInterface.aidl後,需要重新Build一下項目,然將工程目錄結構以Project的形式展示,就可以找到生成的真正接口:
這裡寫圖片描述

至此AIDL接口就定義好了,剩下的步驟比較簡單,和之前講過的類似。

我們先來編寫服務端,直接新建一個Service並在配置文件中將其配置為android:process=":remote",確保它運行在另一個進程中。

// 服務端
public class MyService extends Service {

    IMyAidlInterface.Stub iBinder = new IMyAidlInterface.Stub() {
        // 我們在aidl文件中定義的通信規則
        @Override
        public String getMsg() throws RemoteException {
            return "我來自MyService";
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return iBinder;
    }
}

代碼很簡單,在綁定的時候將帶有我們自己定義的規則的IBinder返回給客戶端。XXX.Stub iBinder = new XXX.Stub() {···}這樣的寫法是固定的,記住就好了,將XXX替換成你的AIDL接口名稱就可以了。

我們來看一下客戶端代碼:

// 客戶端
public class MainActivity extends AppCompatActivity {

    private IMyAidlInterface mService;
    private boolean isBound;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 綁定服務端
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, mServiceConnection, BIND_AUTO_CREATE);

    }

    // 綁定回調
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 獲取AIDL接口對象,這樣就可以用來通信了
            mService = IMyAidlInterface.Stub.asInterface(service);
            isBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
            isBound = false;
        }
    };

    // 按鈕點擊回調方法
    public void btnClick(View view) {
        if (isBound) {
            try {
                // 調用服務端方法
                String result = mService.getMsg();
                Log.e("Fiend", "客戶端:" + result);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        } else {
            Log.e("Fiend", "還沒有綁定成功");
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (isBound) {
            unbindService(mServiceConnection);
        }
    }
}

看起來代碼有點多,其實並沒有什麼陌生的內容,都是我們平時非常熟悉的一些代碼。應用啟動後就綁定遠程服務端,點擊按鈕調用遠程服務端的方法,獲取到後將結果打印出來。結果如下:

result

成功調用另一個進程中的方法。

onServiceConnected()方法裡的這句代碼mService = IMyAidlInterface.Stub.asInterface(service);,屬於固定寫法,和之前的服務端寫法一樣,記住就好了。

上面這種IPC方式是屬於同步的,所謂同步是指,客戶端調用後會等待服務端返回後才會繼續向下執行。我們來修改一下客戶端代碼:

    public void btnClick(View view) {
        if (isBound) {
            try {
                Log.e("Fiend", "開始調用服務端方法");
                // 調用服務端方法
                String result = mService.getMsg();
                Log.e("Fiend", "客戶端:" + result);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        } else {
            Log.e("Fiend", "還沒有綁定成功");
        }
    }

沒有改什麼實質性的,只是在調用服務端方法之前打印了一個Log,方便我們之前對比時間用。

改一下服務端的代碼:

    IMyAidlInterface.Stub iBinder = new IMyAidlInterface.Stub() {
        // 我們在aidl文件中定義的通信規則
        @Override
        public String getMsg() throws RemoteException {
            // 5秒後再返回結果
            SystemClock.sleep(5 * 1000);
            return "我來自MyService";
        }
    };

同樣沒有修改多少,只是延遲5秒再返回結果。我們來看一下打印結果:

time

從截圖可以看出,客戶端確實等服務端返回後再繼續執行的,所以是同步。因此,時刻記住客戶端調用的時候在工作線程調用,否則有可能阻塞主線程。那想要異步該如何做?

異步調用

想要以AIDL方式異步調用,需要用到關鍵字oneway,它可以作用在接口上也可以作用在方法上。異步方法必須返回void

異步接口
// 所有方法都是異步的
oneway interface IAsynchronousInterface {
    void method1();
    void method2();
}
異步方法
interface IAsynchronousInterface {
    // 這個方法是異步執行的
    oneway void method1();
    void method2();
}

異步已經可以了,那結果如何返回呢?通常異步都是以回調接口的方式,在這裡也是一樣的。我們修改上面的之前演示的示例,增加一個回調接口,方便服務端調用客戶端的方法,也就是所謂的反向調用。

增加一個回調AIDL接口定義:

callback

增加回調接口必須重新建立一個.aidl結尾的文件,IMyAidlInterfaceCallback.aidl具體內容如下:

// 用於服務端回調
interface IMyAidlInterfaceCallback {

    // 結果處理
    void handleResult(String result);
}

修改IMyAidlInterface.aidl的內容:

import com.fiend.aidlexample.IMyAidlInterfaceCallback;

// 和我們平常定義一個接口語法一樣
oneway interface IMyAidlInterface {

    // 定義了一個方法(所謂的通信規則)
    void getMsg(IMyAidlInterfaceCallback callback);
}

getMsg()方法的返回改為void,並將新定義的回調接口作為參數。這裡必須顯示import接口,否則編譯會報錯。

修改服務端部分代碼:

    IMyAidlInterface.Stub iBinder = new IMyAidlInterface.Stub() {
        @Override
        public void getMsg(IMyAidlInterfaceCallback callback) throws RemoteException {
            // 5秒後再返回結果
            SystemClock.sleep(5 * 1000);
            // 通過回調接口返回結果
            callback.handleResult("我是異步返回,我來自MyService");
        }
//        // 我們在aidl文件中定義的通信規則
//        @Override
//        public String getMsg() throws RemoteException {
//            // 5秒後再返回結果
//            SystemClock.sleep(5 * 1000);
//            return "我來自MyService";
//        }
    };

注釋掉的部分是我們之前的做法,現在是通過回調接口返回結果。

修改客戶端部分代碼:

    /**
     * 回調接口
     */
    private IMyAidlInterfaceCallback.Stub mCallback = new IMyAidlInterfaceCallback.Stub() {
        @Override
        public void handleResult(String result) throws RemoteException {
            Log.e("Fiend", "客戶端:" + result);
        }
    };

    public void btnClick(View view) {
        if (isBound) {
            try {
                Log.e("Fiend", "開始調用服務端方法");
//                String result = mService.getMsg();
//                Log.e("Fiend", "客戶端:" + result);
                // 調用服務端方法,將回調接口傳過去
                mService.getMsg(mCallback);
                Log.e("Fiend", "結束調用服務端方法");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        } else {
            Log.e("Fiend", "還沒有綁定成功");
        }
    }

增加了一個回調接口,修改了調用服務端方法,之前是調用getMsg()並返回結果,現在是調用getMsg(mCallback)把回調方法傳過去,沒有返回值。返回結果在回調接口中處理。

我們來看一下運行結果:

result

通過返回時間對比,可以看到,調用完遠程服務方法就立刻返回了。而需要返回的數據是在5秒後通過回調接口返回的。

至此,我們就實現了AIDL方式的異步調用了。

AIDL支持的數據類型

AIDL默認支持這麼幾種數據類型:

Java基本數據類型,如intlongboolean等(除了short) String類型 CharSequence List類型,所有List中的元素必須是AIDL支持的類型,如List Map類型,所有Map中的元素必須是AIDL支持的類型,如Map

ListMap的接收方類型必須為ArrayListHashMap

如果默認的類型不能滿足你的需要,還可以自定義類型,自定義類型必須支持序列化,也就是實現Parcelable接口。具體可以參考官網。

以上我們介紹的AIDL用法都是在同一個工程裡,只是將Service指定運行在了不同的進程中,因此我們的.aidl文件可以只寫一份,但是,如果我們的Service是在另一個應用(apk)中,那麼另一個應用中也必須有和我們項目中相同的.aidl文件,連包名也必須一樣。

總結

Android中實現進程間通信在高層次抽象可以很方便的使用Intent等方式來操作,相對底層的方式我們可以使用MessengerAIDL,大多數情況下我們使用Messenger就可以達到我們想要的效果了,而且使用也比AIDL簡單,所以盡量用Messenger,實在不行再考慮AIDL,在介紹AIDL的時候對於支持的數據類型並沒有深入的講解與演示,可以上官網看看。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved