本文參考:Android 之 IPC 進(jìn)程通信全解析
Android IPC簡(jiǎn)介
IPC是Inter-Process Communication的縮寫,含義就是進(jìn)程間通信或者跨進(jìn)程通信徘熔,是指兩個(gè)進(jìn)程之間進(jìn)行數(shù)據(jù)交換的過程审轮。
在明確其之前族展,需要先搞懂幾個(gè)概念:
線程:CPU可調(diào)度的最小單位届腐,是程序執(zhí)行流的最小單元鸵隧;線程是進(jìn)程中的一個(gè)實(shí)體粉臊,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,線程自己不擁有系統(tǒng)資源而克,只擁有一點(diǎn)兒在運(yùn)行中必不可少的資源靶壮,但它可與同屬一個(gè)進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源。
進(jìn)程: 一個(gè)執(zhí)行單元员萍,在PC 和移動(dòng)設(shè)備上一般指一個(gè)程序或者應(yīng)用腾降,一個(gè)進(jìn)程可以包含多個(gè)線程。每一個(gè)進(jìn)程都有它自己的地址空間碎绎,一般情況下螃壤,包括文本區(qū)域(text region)抗果、數(shù)據(jù)區(qū)域(data region)和堆棧(stack region)。
在Android中奸晴,為每一個(gè)應(yīng)用程序都分配了一個(gè)獨(dú)立的虛擬機(jī)冤馏,或者說每個(gè)進(jìn)程都分配一個(gè)獨(dú)立的虛擬機(jī),不同虛擬機(jī)在內(nèi)存分配上都有不同的地址空間寄啼,這就導(dǎo)致在不同的虛擬機(jī)互相訪問數(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)程的信息驾茴,并且通過Intent發(fā)送出去。這里注意:我們傳輸?shù)臄?shù)據(jù)必須基本數(shù)據(jù)類型或者能夠被序列化氢卡。
1.基本數(shù)據(jù)類型(int, long, char, boolean, double等)
2.String和CharSequence
3.List:只支持ArrayList锈至,并且里面的元素都能被AIDL支持
4.Map:只支持HashMap,里面的每個(gè)元素能被AIDL支持
5.Parcelable:Parcelable的實(shí)現(xiàn)類
下面看一個(gè)Demo例子:利用Bundle進(jìn)行進(jìn)程間通信
Intent intent = new Intent(MainActivity.this, SecondActivity.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í)有一定的局限性诀浪。
使用文件共享的方式
文件共享: 將對(duì)象序列化之后保存到文件中,在通過反序列延都,將對(duì)象從文件中讀取雷猪。
在A進(jìn)程中寫入對(duì)象(序列化):
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)程中讀取文件(反序列化):
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();
這樣,分屬不同的進(jìn)程成功的獲取到了共享的數(shù)據(jù)晰房。
通過共享文件這種方式來共享數(shù)據(jù)對(duì)文件的格式是沒有具體的要求的求摇。比如可以是文件,也可以是Xml殊者、JSON 等与境。只要讀寫雙方約定一定的格式即可。
注意:同文件共享方式也存在著很大的局限性猖吴。即并發(fā)讀/ 寫的問題摔刁。讀/寫會(huì)造成數(shù)據(jù)不是最新。讀寫很明顯會(huì)出現(xiàn)錯(cuò)誤海蔽。文件共享適合在對(duì)數(shù)據(jù)同步要求不高的進(jìn)程之間進(jìn)行通信共屈。并且要妥善處理并發(fā)讀寫的問題绑谣。
使用Messenger的方式
Messenger 可以翻譯為信使,通過該對(duì)象拗引,可以在不同的進(jìn)程中傳遞Message對(duì)象借宵。注意,兩個(gè)單詞不同矾削。
下面就通過服務(wù)端(Service)和客戶端(Activity)的方式進(jìn)行演示壤玫。
客戶端向服務(wù)端發(fā)送消息,可分為以下幾步哼凯。
服務(wù)端
1.創(chuàng)建Service
2.構(gòu)造Handler對(duì)象欲间,實(shí)現(xiàn)handlerMessage方法。
3.通過Handler對(duì)象構(gòu)造Messenger信使對(duì)象挡逼。
4.通過Service的onBind()返回信使中的Binder對(duì)象括改。
客戶端
1.創(chuàng)建Actvity
2.綁定服務(wù)
3.創(chuàng)建ServiceConnection,監(jiān)聽綁定服務(wù)的回調(diào)。
4.通過onServiceConnected()方法的參數(shù)家坎,構(gòu)造客戶端Messenger對(duì)象
5.通過Messenger向服務(wù)端發(fā)送消息嘱能。
實(shí)現(xiàn)服務(wù)端
public class MessengerService extends Service{
private Handler MessengerHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//消息處理.......
Log.i("messenger","接收到客戶端的消息--"+msgClient);
};
//創(chuàng)建服務(wù)端Messenger
private final Messenger mMessenger = new Messenger(MessengerHandler);
@Override
public IBinder onBind(Intent intent) {
//向客戶端返回Ibinder對(duì)象,客戶端利用該對(duì)象訪問服務(wù)端
return mMessenger.getBinder();
}
@Override
public void onCreate() {
super.onCreate();
}
}
注意:MessengerService需要在AndroidManifest.xml中注冊(cè)虱疏。
實(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);
//通過得到的mService 可以進(jìn)行通信
}
};
@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);
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
/**
* 布局文件中添加了一個(gè)按鈕惹骂,點(diǎn)擊該按鈕的處理方法
* @param view
*/
public void send(View view) {
try {
// 向服務(wù)端發(fā)送消息
Message message = Message.obtain();
Bundle data = new Bundle();
data.putString("msg", "lalala");
message.setData(data);
// 發(fā)送消息
mService.send(message);
Log.i("messenger","向服務(wù)端發(fā)送了消息");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy(){
unbindService(conn);
super.onDestroy();
}
}
其中有一點(diǎn)需要注意:
我們是通過Message作為媒介去攜帶數(shù)據(jù)的。但是做瞪,Message的obj 并沒有實(shí)現(xiàn)序列化(實(shí)現(xiàn)Serializable或Parcelable),也就是其不能保存數(shù)據(jù)对粪。必須使用message.setData()方法去傳入一個(gè)Bundle對(duì)象,Bundle中保存需要傳入的數(shù)據(jù)装蓬。
使用AIDL的方式
AIDL是一種IDL語言著拭,用于生成可以在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開發(fā)者來說很熟悉的語法绩衷。Android也提供了一個(gè)工具唇聘,可以自動(dòng)創(chuàng)建Stub(類架構(gòu)迟郎,類骨架)。當(dāng)我們需要在應(yīng)用間通信時(shí)表制,我們需要按以下幾步走:
1.定義一個(gè)AIDL接口么介。
2.為遠(yuǎn)程服務(wù)(Service)實(shí)現(xiàn)對(duì)應(yīng)Stub壤短。
3.將服務(wù)“暴露”給客戶程序使用久脯。
下面簡(jiǎn)單介紹一下使用AIDL的使用方法:
因?yàn)樾枰?wù)端和客戶端共用aidl文件镰吆,所以最好單獨(dú)建一個(gè)包万皿,適合拷貝到客戶端牢硅。
服務(wù)端:
添加如下包名:com.example.ipc.aidl
創(chuàng)建BookAidl.java,該對(duì)象需要作為傳輸减余。所以需要實(shí)現(xiàn)Parcelable。
public class BookAidl implements Parcelable {
public int bookId;
public String bookName;
public BookAidl() {
super();
}
public BookAidl(int bookId, String bookName) {
super();
this.bookId = bookId;
this.bookName = bookName;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
public static final Parcelable.Creator<BookAidl> CREATOR = new Creator<BookAidl>() {
@Override
public BookAidl[] newArray(int size) {
return new BookAidl[size];
}
@Override
public BookAidl createFromParcel(Parcel source) {
BookAidl book = new BookAidl();
book.bookId = source.readInt();
book.bookName = source.readString();
return book;
}
};
@Override
public String toString() {
return "BookAidl [bookId=" + bookId + ", bookName=" + bookName + "]";
}
}
創(chuàng)建BookAidl.aidl文件蛆挫,并手動(dòng)添加悴侵。
package com.example.ipc.aidl;
Parcelable BookAidl;
創(chuàng)建IBookManager.aidl文件,接口文件做粤,面向客戶端調(diào)用:
package com.example.ipc.aidl;
import com.example.ipc.aidl.BookAidl;
interface IBookManager{
List<BookAidl> getBookList();
void addBook(in BookAidl book);
}
寫完之后clean一下工程怕品,之后會(huì)在gen目錄下生成對(duì)應(yīng)的java文件肉康。
繼續(xù)編寫服務(wù)端吼和,創(chuàng)建Service類炫乓。
public class BookService extends Service {
/**
* 支持線程同步献丑,因?yàn)槠浯嬖诙鄠€(gè)客戶端同時(shí)連接的情況
*/
private CopyOnWriteArrayList<BookAidl> list = new CopyOnWriteArrayList<>();
/**
* 構(gòu)造 aidl中聲明的接口的Stub對(duì)象阳距,并實(shí)現(xiàn)所聲明的方法
*/
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<BookAidl> getBookList() throws RemoteException {
return list;
}
@Override
public void addBook(BookAidl book) throws RemoteException {
list.add(book);
Log.i("aidl", "服務(wù)端添加了一本書"+book.toString());
}
};
@Override
public void onCreate() {
super.onCreate();
//加點(diǎn)書
list.add(new BookAidl(1, "java"));
list.add(new BookAidl(2, "android"));
}
@Override
public IBinder onBind(Intent intent) {
// 返回給客戶端的Binder對(duì)象
return mBinder;
}
}
在Service中筐摘,主要干了兩件事情:
1.實(shí)現(xiàn)aidl文件中的接口的Stub對(duì)象。并實(shí)現(xiàn)方法圃酵。
2.將Binder對(duì)象通過onBinder返回給客戶端郭赐。
為了省事捌锭,在這里不在另起一個(gè)工程了观谦,直接將Service在另一個(gè)進(jìn)程中運(yùn)行豁状。
<service
android:name="com.example.ipc.BookService"
android:process=":remote" />
客戶端
因?yàn)樵谕粋€(gè)工程中,不需要拷貝aidl包中的文件夭禽。如果不在同一個(gè)工程讹躯,需要拷貝蜀撑。
public class BookActivity extends AppCompatActivity{
/**
* 接口對(duì)象
*/
private IBookManager mService;
/**
* 綁定服務(wù)的回調(diào)
*/
private ServiceConnection conn = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 獲取到書籍管理的對(duì)象
mService = IBookManager.Stub.asInterface(service);
Log.i("aidl", "連接到服務(wù)端酷麦,獲取IBookManager的對(duì)象");
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book);
// 啟動(dòng)服務(wù)
Intent intent = new Intent(this,BookService.class);
bindService(intent, conn, BIND_AUTO_CREATE);
}
/**
* 獲取服務(wù)端書籍列表
* @param view
*/
public void getBookList(View view){
try {
Log.i("aidl","客戶端查詢書籍"+mService.getBookList().toString());
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 添加書籍
*/
public void add(View view){
try {
// 調(diào)用服務(wù)端添加書籍
mService.addBook(new BookAidl(3,"iOS"));
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客戶端的代碼和之前的Messenger很類似:
1.綁定服務(wù),監(jiān)聽回調(diào)糊肤。
2.將回調(diào)中的IBinder service通過IBookManager.Stub.asInterface()轉(zhuǎn)化為借口對(duì)象馆揉。
3.調(diào)用借口對(duì)象的方法升酣。
使用ContentProvider的方式
ContentProvider是Android中的四大組件之一噩茄,為了在應(yīng)用程序之間進(jìn)行數(shù)據(jù)交換绩聘,Android提供了ContentProvider凿菩,ContentProvider是不同應(yīng)用之間進(jìn)行數(shù)據(jù)交換的API帜讲,一旦某個(gè)應(yīng)用程序通過ContentProvider暴露了自己的數(shù)據(jù)操作的接口舒帮,那么不管該應(yīng)用程序是否啟動(dòng)玩郊,其他的應(yīng)用程序都可以通過接口來操作接口內(nèi)的數(shù)據(jù)预茄,包括數(shù)據(jù)的增侦厚、刪诗宣、改想诅、查等操作篮灼。
開發(fā)一個(gè)ContentProvider的步驟很簡(jiǎn)單:
1.定義自己的ContentProvider類徘禁,該類集成ContentProvider基類娘荡;
2.在AndroidMainfest.xml中注冊(cè)這個(gè)ContentProvider它改,類似于Activity注冊(cè)央拖,注冊(cè)時(shí)要給ContentProvider綁定一個(gè)域名;
3.當(dāng)我們注冊(cè)好這個(gè)ContentProvider后鹉戚,其他應(yīng)用就可以訪問ContentProvider暴露出來的數(shù)據(jù)了鲜戒。
ContentProvider只是暴露出來可供其他應(yīng)用操作的數(shù)據(jù),其他應(yīng)用則需要通過ContentProvider來操作
使用ContentResolver操作數(shù)據(jù)的步驟也很簡(jiǎn)單:
1.調(diào)用Activity的getContentResolver()獲取ContentResolver對(duì)象抹凳;
2.根據(jù)調(diào)用的ContentResolver的insert()遏餐、delete()、update()和query()方法操作數(shù)據(jù)庫即可赢底。
使用Socket的方式
Socaket也是實(shí)現(xiàn)進(jìn)程間通信的一種方式失都,Socaket也稱為“套接字”柏蘑,網(wǎng)絡(luò)通信中的概念,通過Socket我們可以很方便的進(jìn)行網(wǎng)絡(luò)通信粹庞,都可以實(shí)現(xiàn)網(wǎng)絡(luò)通信錄,那么實(shí)現(xiàn)跨進(jìn)程通信不是也是相同的嘛,但是Socaket主要還是應(yīng)用在網(wǎng)絡(luò)通信中。
Android 進(jìn)程間通信不同方式的比較
- Bundle:四大組件間的進(jìn)程間通信方式,簡(jiǎn)單易用,但傳輸?shù)臄?shù)據(jù)類型受限典勇。
- 文件共享: 不適合高并發(fā)場(chǎng)景伤溉,并且無法做到進(jìn)程間的及時(shí)通信宫静。
- Messenger: 數(shù)據(jù)通過Message傳輸伏伯,只能傳輸Bundle支持的類型
- ContentProvider:android 系統(tǒng)提供的。簡(jiǎn)單易用。但使用受限迂猴,只能根據(jù)特定規(guī)則訪問數(shù)據(jù)携兵。
- AIDL:功能強(qiáng)大眠副,支持實(shí)時(shí)通信,但使用稍微復(fù)雜。
- Socket:網(wǎng)絡(luò)數(shù)據(jù)交換的常用方式。不推薦使用。