編輯:關於Android編程
在Android
開發中我們可以通過Intent
、ContentProviders
來實現進程間通信,如果不限於Android
特有的話,我們還可以使用File
、Socket
等方式,反正只要進程間能交換信息就行了。
像Intent
,我們平時使用的時候好像都沒感覺出是在進程間通信。其實Android
中進程間的通信是非常頻繁的,應用裡打開一個新的Activity
都涉及到了進程間通信,應用裡調用打電話、調用浏覽器等等都涉及到了。
實際上Intent
、ContentProviders
都是對Binder
更高級別的抽象,方便我們平時使用。
上面說到的一些方式都是系統經過高度封裝的,而我們的業務需求可能比較特別,使用上面的方式可能不是特別適合,比如:“我們的音樂播放器希望在獨立的進程中播放音樂”。
我們至少得控制音樂的開始、暫停、顯示進程這些功能吧,那就需要進程間的通信了。這個時候使用系統經過高度封裝的方式都好像顯得不太靈活。根據官方文檔我們發現有兩種相對底層一些的方式,Messenger
和AIDL
。
在相對底層一點的進程間通信,Messenger
是最簡單的方式,Messenger
會在單一線程中創建包含所有請求的隊列,這樣我們就不需要處理線程安全方面的事宜。
Messenger
實際上是以AIDL
作為其底層結構的。
單向通信
客戶端進程相關代碼:
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()
方法將Messenger
的IBinder
給返回。在客戶端通過該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
,而且Messenger
比AIDL
簡單得多。如果對於服務需要執行多線程處理的,則應使用AIDL
,否則使用Messenger
就可以了。
使用AIDL
和使用Messenger
的步驟基本上是類似的。使用AIDL
需要自己定義好一個接口作為客戶端和服務端通信的規則,手工寫一個這樣的接口比較復雜,所以Android
給我們提供了一個工具來自動生成。
想要自動生成通信的接口,則需要創建一個以.aidl
結尾的文件,然後按平常我們定義接口的方式做就好了。下面以Android Studio
來講解生成過程。
Android
的形式展示:AIDL
文件:IMyAidlInterface.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);
}
}
}
看起來代碼有點多,其實並沒有什麼陌生的內容,都是我們平時非常熟悉的一些代碼。應用啟動後就綁定遠程服務端,點擊按鈕調用遠程服務端的方法,獲取到後將結果打印出來。結果如下:
成功調用另一個進程中的方法。
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秒再返回結果。我們來看一下打印結果:
從截圖可以看出,客戶端確實等服務端返回後再繼續執行的,所以是同步。因此,時刻記住客戶端調用的時候在工作線程
調用,否則有可能阻塞主線程。那想要異步該如何做?
想要以AIDL
方式異步調用,需要用到關鍵字oneway
,它可以作用在接口上也可以作用在方法上。異步方法必須返回void
。
// 所有方法都是異步的
oneway interface IAsynchronousInterface {
void method1();
void method2();
}
異步方法
interface IAsynchronousInterface {
// 這個方法是異步執行的
oneway void method1();
void method2();
}
異步已經可以了,那結果如何返回呢?通常異步都是以回調接口的方式,在這裡也是一樣的。我們修改上面的之前演示的示例,增加一個回調接口,方便服務端調用客戶端的方法,也就是所謂的反向調用。
增加一個回調AIDL
接口定義:
增加回調接口必須重新建立一個.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)
把回調方法傳過去,沒有返回值。返回結果在回調接口中處理。
我們來看一下運行結果:
通過返回時間對比,可以看到,調用完遠程服務方法就立刻返回了。而需要返回的數據是在5
秒後通過回調接口返回的。
至此,我們就實現了AIDL
方式的異步調用了。
AIDL
默認支持這麼幾種數據類型:
Java
基本數據類型,如int
、long
、boolean
等(除了short
) String
類型 CharSequence
List
類型,所有List
中的元素必須是AIDL
支持的類型,如List
Map
類型,所有Map
中的元素必須是AIDL
支持的類型,如Map
List
和Map
的接收方類型必須為ArrayList
和HashMap
。
如果默認的類型不能滿足你的需要,還可以自定義類型,自定義類型必須支持序列化,也就是實現Parcelable
接口。具體可以參考官網。
以上我們介紹的
AIDL
用法都是在同一個工程裡,只是將Service
指定運行在了不同的進程中,因此我們的.aidl
文件可以只寫一份,但是,如果我們的Service
是在另一個應用(apk
)中,那麼另一個應用中也必須有和我們項目中相同的.aidl
文件,連包名也必須一樣。
Android
中實現進程間通信在高層次抽象可以很方便的使用Intent
等方式來操作,相對底層的方式我們可以使用Messenger
和AIDL
,大多數情況下我們使用Messenger
就可以達到我們想要的效果了,而且使用也比AIDL
簡單,所以盡量用Messenger
,實在不行再考慮AIDL
,在介紹AIDL
的時候對於支持的數據類型並沒有深入的講解與演示,可以上官網看看。
實際上字母索引表的效果,可以說在現在的眾多APP中使用的非常流行,比如支付寶,微信中的聯系人,還有購物,買票的APP中選擇全國城市,切換城市的時候,這時候的城市也就是按照
廢話不多說了,直接給大家貼代碼了。java類如下: import android.content.Context; import android.content.res.
什麼是AlarmManager? AlarmManager是Android中常用的一種系統級別的提示服務,在特定的時刻為我們廣播一個指定的Intent。簡單
shape用於設定形狀,可以在selector,layout等裡面使用,有6個子標簽,各屬性如下: <?xml version=1.0 enco