編輯:關於Android編程
IPC,即進程間通信。常見的IPC場景有兩種,一種是單個應用開啟多個進程,這些進程間需要通信;另外一種是不同應用間的進程間通信。
單個應用開啟多個進程並不復雜,只需要為四大組件聲明一個android:process屬性,這個組件便會運行在該聲明的進程上。而這個屬性的聲明方式有兩種:
以:號開頭,比如android:process=":remote",這時這個組件便運行在package_name:remote這個進程上。這種方式稱為私有進程,雖然名為私有進程,但是可以為其再聲明一個android:exported="true"屬性,然後增加intent-filter屬性(如果沒有intent-filter屬性,只能在本應用內使用),其他應用也可以關聯到這個進程。
以單個.號分割,比如android:process="com.leelit",這時這個組件便會運行在com.leelit這個進程上。這種方式稱為全局進程,假設有多個應用都有聲明了com.leelit全局進程的組件,也會產生多個名字相同但PID並不相同的進程,所以全局進程並非全局唯一的意思。全局進程的作用是,其他應用可以通過ShareUID的方式和這個全局進程跑在同一個進程上,從而共享資源。
IPC的基礎是數據結構的序列化與反序列化。Java平台簡單地實現Serializable,通過ObjectOutputStream以及ObjectInputStream即可完成對象的序列與反序列化。而Android平台上新增了一種效率更高的方式,Parcelable,其實現方式比較固定,所以也有一些插件可以直接生成代碼。如果說Serializable和Parcelable是實現IPC的“原料”,那麼Binder則可以視為“橋梁”,Binder是客戶端和服務端進行通信的媒介,當bindService時,客戶端可以得到一個可以“操縱”服務端的Binder對象。
Android平台上實現IPC的方式有很多種,比如常見的:
Intent和bundle 文件共享 Messenger AIDL ContentProvider Socket這篇文章主要關注:Messenger和AIDL
Messenger是系統為我們封裝好的一套IPC方案,它的底層是基於AIDL與Handler。如果了解Handler,Looper那一套東西就會知道,Handler每次只會處理一個Message,使用Messenger是不需要也無法考慮並發的情況的。
Messenger的構造方法有兩個:
public Messenger(Handler target) { mTarget = target.getIMessenger(); } public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }
由構造方法也能看出它基於AIDL和Handler。
使用套路:
1、Service服務端實例一個Messenger來接收客戶端Messenger發來的message,onBind方法返回這個Messenger的Binder。如果有需要還可以通過客戶端發來的message攜帶的客戶端Messenger接收對象,返回message給客戶端。
2、客戶端bind到服務端的Service後,實例一個Messenger對象,便可以通過這個Messenger對象發送message到服務端。如果有需要還可以實例另外一個Messenger對象來接收服務端返回的message。
按照這個套路一共需要實例三個Messenger,客戶端兩個,一個用於發送給服務端,一個用於接收服務端;服務端一個用於接收客戶端,而返回給客戶端的Messenger在客戶端發來的message中攜帶。
服務端進程:
public class MessengerService extends Service { private static final String Messenger_TAG = "Messenger"; private static class ServerMessengerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: // 接收到客戶端的信息 Log.i(Messenger_TAG, msg.getData().getString("client-data") + " " + Thread.currentThread().toString()); // 接收服務端返回信息的客戶端Messenger Messenger replyMessenger = msg.replyTo; // 返回信息給客戶端 Message message = Message.obtain(); message.what = 1; Bundle data = new Bundle(); data.putString("server-data", "hello client"); message.setData(data); try { replyMessenger.send(message); } catch (RemoteException e) { e.printStackTrace(); } break; default: super.handleMessage(msg); } } } // 服務端Messenger private Messenger serverMessenger = new Messenger(new ServerMessengerHandler()); @Nullable @Override public IBinder onBind(Intent intent) { // 返回兩個Messenger溝通的Binder橋梁 return serverMessenger.getBinder(); } }
客戶端進程:
public class MainActivity extends AppCompatActivity { private static final String Messenger_TAG = "Messenger"; private Messenger clientMessenger; private Button button; // 接收服務端返回信息的Messenger private Messenger replyMessenger = new Messenger(new ClientMessengerHandler()); private static class ClientMessengerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: // 接收到服務端的信息 Log.i(Messenger_TAG, msg.getData().getString("server-data") + " " + Thread.currentThread().toString()); break; default: super.handleMessage(msg); } } } private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { // 客戶端Messenger clientMessenger = new Messenger(service); } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button) findViewById(R.id.messenger); // bind服務端Service Intent intent = new Intent(this, MessengerService.class); bindService(intent, serviceConnection, BIND_AUTO_CREATE); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 發送消息到服務端 Message message = Message.obtain(); message.what = 0; Bundle data = new Bundle(); data.putString("client-data", "hello server"); message.setData(data); // 指定接收服務端返回信息的Messenger message.replyTo = replyMessenger; try { clientMessenger.send(message); } catch (RemoteException e) { e.printStackTrace(); } } }); } @Override protected void onDestroy() { super.onDestroy(); unbindService(serviceConnection); } }
點擊Button後打印的信息如下:
10-21 21:44:28.557 29650-29650/com.example.kenjxli.ipc:messenger I/Messenger: hello server Thread[main,5,main] 10-21 21:44:28.567 29615-29615/com.example.kenjxli.ipc I/Messenger: hello client Thread[main,5,main]
處理的線程是Handler實例時所在的線程,所以兩個進程的Message處理都是在主線程。
這裡補充兩點bindService相關的內容:
bindService()是異步調用,會立即返回; 系統會在與服務連接上時回調onServiceConnected()方法,並傳遞服務的onBind() 方法返回的 IBinder;與服務的連接意外中斷時(例如當服務崩潰或被終止時)回調onServiceDisconnected()方法。當客戶端主動取消綁定時,系統“絕對不會”調用該方法。根據官方文檔,可以看出,底層AIDL和經過封裝的Messenger相比,最大的不同就是,AIDL是具備多線程處理能力的。
Messenger 會在單一線程中創建包含所有客戶端請求的隊列,以便服務一次接收一個請求。不過,如果您想讓服務同時處理多個請求,則可直接使用 AIDL。 在此情況下,您的服務必須具備多線程處理能力,並采用線程安全式設計。
使用AIDL的基本步驟:
1、確定服務端提供的服務,定義AIDL接口;
2、編寫服務端Service,onBind方法返回Binder;
3、客戶端bindService,在回調處將Binder轉化為AIDL接口,後續即可使用這個接口調用服務端的方法。
可以參考之前的一篇文章,鏈接。
這裡再補充幾個內容。
AIDL傳遞對象; 服務端回調客戶端; 調用時同步異步狀態及所在線程;如果使用AIDL傳遞基本數據類型,就比較簡單,只需要服務端定義好AIDL接口文件,並在Service中實現AIDL.Stub接口,返回binder。客戶端bindService後將binder重新轉化為AIDL接口,即可IPC調用。但是要傳遞對象,就需要額外的功夫。
需要用AIDL傳遞對象時,除了實際使用的AIDL接口之外,還需要該對象的類實現Parcelable接口,在同一個包內聲明該類的AIDL文件,並且AIDL接口中必須import該類,即便處在同一個包。
比如服務端AIDL接口,需要返回一個User對象。
interface MyAidl { User getUser(); }
首先需要創建一個實現Parcelable的User類;
然後在同一個包,新建一個AIDL文件;
// User.aidl package com.example.kenjxli.ipc.aidl; parcelable User;
最後需要在AIDL接口處import該類,就算處在同一個包也必須import。
// MyAidl.aidl package com.example.kenjxli.ipc.aidl; import com.example.kenjxli.ipc.aidl.User; //必須import interface MyAidl { User getUser(); }
以上步驟缺一不可,否則編譯都將失敗。
另外,AIDL接口中的方法有一些非基本類型的參數,需要指定其“方向”。
All non-primitive parameters require a directional tag indicating which way the data goes. Either in, out, or inout (see the example below).
Primitives are in by default, and cannot be otherwise.
假設有一個AIDL接口如下:
interface MyAidl { void inUser(in User user); void outUser(out User user); void inOutUser(inout User user); }
則三個參數的含義如下:
in:客戶端輸入參數,此時服務端可以得到這個參數對象的內容,但是修改後無法同步回給客戶端。
out:客戶端輸入參數,即便這個對象是有內容的,到了服務端也會變成空值,服務端修改後能同步回給客戶端。
inout:則是上面兩者的集合,服務端既能得到客戶端對象的內容,修改後也能同步回給客戶端。
AIDL一般是客戶端進程調用服務端進程,但是有些時候是可能需要服務端回調客戶端,這時需要使用一個系統接口,RemoteCallbackList
具體使用步驟如下:
1、定義一個客戶端AIDL回調接口
2、在原有的AIDL接口增添注冊回調接口的方法
3、客戶端注冊時,將該接口添加到callbackList中;
4、回調時使用固定的代碼格式。
部分代碼如下:
1、定義一個AIDL回調接口
// OnServerCallBack.aidl package com.example.kenjxli.ipc.aidl; import com.example.kenjxli.ipc.aidl.User; // Declare any non-default types here with import statements interface OnServerCallBack { void onServerCallBack(in User user); // 此時我們的客戶端相當於是服務端的服務端了! }
2、原有AIDL接口增添注冊回調的方法
// MyAidl.aidl package com.example.kenjxli.ipc.aidl; import com.example.kenjxli.ipc.aidl.User; import com.example.kenjxli.ipc.aidl.OnServerCallBack; // Declare any non-default types here with import statements interface MyAidl { User getUser(); void inUser(in User user); void outUser(out User user); void inOutUser(inout User user); void registerCallBack(in OnServerCallBack callbcak); }
並在服務端Service中的AIDL.Stub中實現該方法
@Override public void registerCallBack(final OnServerCallBack callbcak) throws RemoteException { // 這裡可以添加一個cookie對象,方便回調時識別是哪個回調對象 callbackList.register(callbcak, "cookie1"); }
3、客戶端注冊
myAidl.registerCallBack(new OnServerCallBack.Stub() { @Override public void onServerCallBack(User user) throws RemoteException { Log.e("tag", "call back " + user.toString()); } });
4、服務端回調代碼
private void callback() { int size = callbackList.beginBroadcast(); for (int i = 0; i < size; i++) { OnServerCallBack callBack = callbackList.getBroadcastItem(i); if (callBack != null) { try { Log.e("tag", (String) callbackList.getBroadcastCookie(i)); callBack.onServerCallBack(new User("callback user", 10000)); } catch (RemoteException e) { e.printStackTrace(); } } } callbackList.finishBroadcast(); }
這樣就是一個完整的服務端回調客戶端的流程了。上面沒有涉及到反注冊,其實也是大同小異的!值得一提的是,如果客戶端進程退出後,服務端會自動移除這個回調對象。
這一小節直接說明結論:
1、ServiceConnection的回調線程始終是在主線程;
2、AIDL調用是在Binder線程池,不管是客戶端調用服務端,還是服務端回調客戶端,方法體執行的地方都是在Binder線程池中;
3、AIDL調用是同步調用,不管是客戶端調用服務端,還是服務端回調客戶端。當調用某個遠程方法後,本地進程當前的線程會掛起,直到遠程方法返回後,本地進程的線程才能喚醒。
在移動互聯網,鏈接是比較重要的傳播媒質,但很多時候我們又希望用戶能夠回到APP中,這就要求APP可以通過浏覽器或在微信中被方便地喚起。這是一個既直觀又很好的用戶體驗,但在
百度一鍵root是一款很簡單清潔的軟件,很適合剛剛接觸root使用的群眾用。root就是讓你的獲取手機權限,然後處理一些手機系統本來無法處理的軟件,讓你的手
在android開發中,經常用到去解析xml文件。我們今天來學習一下XML文件的解析,在java中我們應該知道兩種解析方式:DOM和SAX解析方式,我這裡就不講解DOM和
在做這個計算器的時候,我認為主要分為兩部分:界面設計,功能實現。(效果圖) 界面設計:其實界面設計和功能實現是相互聯系在一起的,我界面怎麼去設計,功