上一篇Android深入理解IPC機(jī)制(一)基礎(chǔ)知識(shí)概要中搜立,首先介紹了Android中的多進(jìn)程概念以及使用多進(jìn)程模式需要注意的事項(xiàng)申尤,然后介紹了Android中的序列化機(jī)制,最后詳細(xì)分析了Binder的使用以及上層原理。介紹完IPC的基礎(chǔ)知識(shí)躁愿,接下來我們看看如何將IPC機(jī)制應(yīng)用到實(shí)際的開發(fā)中去嫁盲。
- Android IPC 簡(jiǎn)介
- Android中的多進(jìn)程模式
- IPC基礎(chǔ)概念介紹
- Android中的幾種IPC方式
- Binder連接池
- 選擇合適的IPC方式
Android中的幾種IPC方式
1篓叶、使用Bundle
在Android開發(fā)中,我們通常會(huì)使用Bundle在不同的組件中傳遞一些數(shù)據(jù)羞秤,由于Bundle 本身已經(jīng)實(shí)現(xiàn)了Parcelable 接口缸托,所以它可以很方便地在進(jìn)程間傳輸。當(dāng)我們?cè)谝粋€(gè)進(jìn)程中啟動(dòng)了另一個(gè)進(jìn)程的Activity瘾蛋、Service和Receiver俐镐,我們可以將需要傳輸?shù)臄?shù)據(jù)放入Bundle中并通過Intent傳遞出去。使用示例:
private void startMain() {
Intent intent = new Intent(FirstActivity.this, MainActivity.class);
Bundle bundle = new Bundle();
bundle.putString("key", "value");
……
intent.putExtra("bundle", bundle);
}
我們必須要知道瘦黑,我們傳輸?shù)臄?shù)據(jù)必須是可序列化的京革,比如基本類型、實(shí)現(xiàn)了Serializable 或Parcelable接口的對(duì)象以及一些Android支持的特殊對(duì)象幸斥,具體可以查看Bundle 這類中的一系列put 方法匹摇。Bundle 不支持的數(shù)據(jù)類型我們無法通過它在進(jìn)程間傳遞。
2甲葬、使用文件共享
兩個(gè)進(jìn)程可以通過讀/寫同一個(gè)文件來交換數(shù)據(jù)廊勃,也就是說A進(jìn)程把數(shù)據(jù)寫入到共享文件中,B進(jìn)程通過讀取共享文件獲取A進(jìn)程共享的數(shù)據(jù)经窖,這在Android 中也是一個(gè)常見的數(shù)據(jù)共享方式坡垫。
由于Android系統(tǒng)是基于Linux的,使得并發(fā)讀寫可以沒有限制地進(jìn)行画侣,甚至兩個(gè)線程對(duì)同一個(gè)文件進(jìn)行寫操作都是可以的冰悠,盡管這樣可能會(huì)使數(shù)據(jù)變得雜亂。除了交換一些文本信息配乱,我們還可以序列化一個(gè)對(duì)象到文件系統(tǒng)中溉卓,之后在另一個(gè)進(jìn)程中恢復(fù)這個(gè)對(duì)象,雖然兩個(gè)對(duì)象內(nèi)容完全相同搬泥,但是得到的對(duì)象和之前的對(duì)象本質(zhì)上是兩個(gè)不同的對(duì)象桑寨。
private static final String FILE_PATH = "對(duì)象序列化路徑";
/**
* 序列化對(duì)象到文件系統(tǒng)
*/
public static void presistToFile(final Serializable serializable) {
new Thread(new Runnable() {
@Override
public void run() {
File file = new File(FILE_PATH);
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
objectOutputStream.writeObject(serializable);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (objectOutputStream != null) {
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
/**
* 從文件系統(tǒng)反序列化一個(gè)對(duì)象
*/
public static void recoverFromFile() {
new Thread(new Runnable() {
@Override
public void run() {
Serializable serializable = null;
File file = new File(FILE_PATH);
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(new FileInputStream(file));
serializable = (Serializable) objectInputStream.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (objectInputStream != null) {
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
通過文件共享實(shí)現(xiàn)進(jìn)程間通信的局限性也是比較明顯的:當(dāng)多個(gè)線程并發(fā)讀寫文件,那么我們得到的數(shù)據(jù)就可能不是正確的忿檩。所以在使用這項(xiàng)技術(shù)的時(shí)候尉尾,我們應(yīng)該盡量避免并發(fā)讀寫這種情況的發(fā)生,或者只在對(duì)數(shù)據(jù)同步要求不高的情況下使用文件共享來實(shí)現(xiàn)進(jìn)程間通信燥透,并且妥善處理并發(fā)讀寫問題沙咏。
3辨图、使用Messenger
Messenger是一種輕量級(jí)的IPC方案,它的底層實(shí)現(xiàn)是AIDL芭碍,通過它可以在不同進(jìn)程中傳遞Message對(duì)象徒役,在Message中放入我們需要傳遞的數(shù)據(jù),就能輕松地實(shí)現(xiàn)數(shù)據(jù)在進(jìn)程間的傳遞窖壕。
Messenger的使用方法很簡(jiǎn)單忧勿,它對(duì)AIDL做了封裝,使得我們可以很方便地進(jìn)行進(jìn)程間通信瞻讽。同時(shí)由于它一次處理一個(gè)請(qǐng)求鸳吸,也就是說服務(wù)端中不存在并發(fā)的情形,所以我們不用考慮線程同步的問題速勇。
接下來分別從服務(wù)端和客戶端介紹Messenger 的使用步驟:
- 服務(wù)端創(chuàng)建一個(gè)Service 來處理客戶端的連接請(qǐng)求:
創(chuàng)建一個(gè)靜態(tài)內(nèi)部類MessengerHandler 晌砾,接收并處理客戶端消息,利用MessengerHandler 創(chuàng)建一個(gè)Messenger烦磁, 并在onBind 方法里返回Messenger里面的Binder對(duì)象养匈;
同時(shí)使用msg.replyTo 的Messenger 給客戶端回復(fù)一個(gè)Message,告知客戶端已接收到消息都伪。
public class MyService extends Service {
//處理客戶端發(fā)來的消息
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constant.MSG_FROMCLIENT:
//接受客戶端消息
Bundle bundle = msg.getData();
LogUtils.d("qianwei", bundle.getString("msg"));
//通知客戶端消息已收到(發(fā)送消息給客戶端)
Messenger replyTo = msg.replyTo;
Message replyMessage = Message.obtain(null, Constant.MSG_FROMSERVICE);
Bundle data = new Bundle();
data.putString("reply", "Hello client, message received! Thank you.");
replyMessage.setData(data);
try {
replyTo.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
}
super.handleMessage(msg);
}
}
private Messenger messenger = new Messenger(new MessengerHandler());
@Override
public IBinder onBind(Intent intent) {
//messenger將消息傳遞給MessengerHandler處理
return messenger.getBinder();
}
}
注冊(cè)Service并運(yùn)行在單獨(dú)進(jìn)程中:
<service
android:name=".service.MyService"
android:process=":remoteservice" />
- 接下來看看客戶端的實(shí)現(xiàn):
客戶端綁定服務(wù)成功會(huì)調(diào)用ServiceConnection 接口的onServiceConnected方法呕乎,根據(jù)服務(wù)端返回的Binder 創(chuàng)建一個(gè)Messenger,通過這個(gè)Messenger 把Message 消息傳輸給服務(wù)端陨晶。
同時(shí)設(shè)置"message.replyTo = replyMessenger" 接收并處理服務(wù)端回復(fù)的消息猬仁,實(shí)現(xiàn)方法與服務(wù)端處理消息基本相同。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindServiceAction();
}
/**
* 監(jiān)控Service連接狀態(tài)
*/
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LogUtils.d("onServiceConnected");
//傳給服務(wù)端的Message
Message message = Message.obtain(null, Constant.MSG_FROMCLIENT);
Bundle bundle = new Bundle();
bundle.putString("msg", "Hello service, I'm client!");
message.setData(bundle);
//接收服務(wù)端回復(fù)的消息
message.replyTo = replyMessenger;
try {
Messenger messenger = new Messenger(service);
messenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
LogUtils.d("onServiceDisconnected");
}
};
/**
* 綁定服務(wù)端Service
*/
private void bindServiceAction(){
Intent intent = new Intent(MainActivity.this, MyService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
//處理服務(wù)端回復(fù)的消息***************************
private static class ServiceReplyMessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constant.MSG_FROMSERVICE:
//接受客戶端回復(fù)
Bundle bundle = msg.getData();
LogUtils.d("qianwei", bundle.getString("reply"));
}
super.handleMessage(msg);
}
}
private Messenger replyMessenger = new Messenger(new ServiceReplyMessengerHandler());
//*********************************************
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
}
到此先誉,準(zhǔn)備工作已經(jīng)就緒湿刽,啟動(dòng)MainActivity我們會(huì)看到消息內(nèi)容在兩個(gè)不同的進(jìn)程間完成了傳輸。
在Messenger中傳遞數(shù)據(jù)必須將數(shù)據(jù)放入Message中褐耳,Messenger 和Message都實(shí)現(xiàn)了Parcelable 接口诈闺,因此可以進(jìn)行跨進(jìn)程通信。也就是說,Message 支持的數(shù)據(jù)類型就是Messenger 支持的傳輸類型。
在Android 2.2以前犁跪,msg.obj字段不支持跨進(jìn)程傳輸摄乒,Android 2.2之后,也只是系統(tǒng)提供的實(shí)現(xiàn)了Parcelable 接口的對(duì)象才能通過它跨進(jìn)程傳輸嗤军,我們自己定義的Parcelable 對(duì)象是無法通過object字段傳輸?shù)淖⒂@導(dǎo)致object 字段的實(shí)用性大大降低,所幸我們還有Bundle 字段叙赚。
最后老客,獻(xiàn)上Messenger工作原理圖僚饭。
4、使用AIDL
上一節(jié)我們介紹了使用Messenger 實(shí)現(xiàn)進(jìn)程間通信胧砰,我們能看到Messenger 是以串行的方式處理客戶端發(fā)來的請(qǐng)求鳍鸵,如果客戶端同時(shí)發(fā)送大量并發(fā)請(qǐng)求,使用Messenger就不太合適了尉间。同時(shí)偿乖,Messenger主要是為了跨進(jìn)程傳輸數(shù)據(jù),如果想實(shí)現(xiàn)跨進(jìn)程調(diào)用服務(wù)端方法哲嘲,Messenger 無法做到贪薪,我們可以使用AIDL來實(shí)現(xiàn)跨進(jìn)程的方法調(diào)用。同樣眠副,我們從服務(wù)端和客戶端兩個(gè)方面介紹AIDL的使用画切。
服務(wù)端
創(chuàng)建Service監(jiān)聽連接請(qǐng)求,創(chuàng)建AIDL文件囱怕,在文件中聲明要暴露給客戶端的接口霍弹,在Service中實(shí)現(xiàn)這些接口。客戶端
首先綁定服務(wù)端的Service娃弓,成功后將服務(wù)端返回的Binder轉(zhuǎn)化為AIDL接口所屬類型典格,然后就可以調(diào)用AIDL中的方法了。
上一篇文章Android深入理解IPC機(jī)制(二)淺談Binder中我們已經(jīng)介紹了如何使用AIDL生成Binder忘闻,感興趣的小伙伴可以先了解了解钝计,有助于理解接下來要介紹的內(nèi)容。上面只是簡(jiǎn)單地介紹了使用AIDL進(jìn)行進(jìn)程間通信的流程齐佳,接下來對(duì)其中的難點(diǎn)和實(shí)現(xiàn)細(xì)節(jié)進(jìn)行詳細(xì)介紹私恬。
首先,創(chuàng)建AIDL接口炼吴,接口里面聲明兩個(gè)接口方法:
// IBookManager.aidl
package com.example.qianwei.myapplication.aidl;
//Book必須要顯示地import進(jìn)來本鸣,哪怕是在同一個(gè)package下
import com.example.qianwei.myapplication.aidl.Book;
interface IBookManager {
/**
* 獲取圖書列表
*/
List<Book> getBookList();
/**
* 添加圖書
*/
void addBook(in Book book);
}
在AIDL文件中并不是所有數(shù)據(jù)類型都可以使用的,我們來看看AIDL到底支持哪些類型硅蹦。
- Java基本數(shù)據(jù)類型
- String和 CharSequence
- List:只支持ArrayList荣德,里面的每個(gè)元素必須能夠被AIDL支持
- Map:只支持HashMap,里面的每個(gè)元素必須能夠被AIDL支持童芹,包括key和value
- 所有實(shí)現(xiàn)了Parcelable 接口的對(duì)象
- 所有的AIDL接口本身也可以在AIDL文件中使用
這些就是AIDL支持的所有數(shù)據(jù)類型涮瞻,其中自定義的Parcelable 對(duì)象和AIDL對(duì)象必須顯示地import進(jìn)來,例如IBookManager 中使用Book類必須聲明 "import com.example.qianwei.myapplication.aidl.Book;"假褪。
我們需要注意署咽,如果AIDL文件中使用了自定義的Parcelable 對(duì)象,那么必須創(chuàng)建一個(gè)與該對(duì)象同名的AIDL文件,并在其中聲明它是Parcelable 類型宁否,我們?yōu)锽ook類創(chuàng)建Book.aidl文件:
// Book.aidl
package com.example.qianwei.myapplication.aidl;
// Declare any non-default types here with import statements
parcelable Book;
除此之外窒升,AIDL中除基本類型之外的其他參數(shù)都必須指定方向:in(輸入型參數(shù))、out(輸出型參數(shù))或inout(輸入輸出型參數(shù))慕匠。AIDL不支持聲明靜態(tài)常量饱须,這點(diǎn)有別于傳統(tǒng)接口。
遠(yuǎn)程服務(wù)端Service實(shí)現(xiàn)
我們創(chuàng)建一個(gè)BookManagerService台谊,并在其中實(shí)現(xiàn)我們定義的AIDL接口:
public class BookManagerService extends Service {
//使用支持并發(fā)讀寫CopyOnWriteArrayList蓉媳,是因?yàn)锳IDL的方法是在服務(wù)
// 端的Binder池中執(zhí)行的,會(huì)有多線程同時(shí)訪問的情況
private CopyOnWriteArrayList<Book> bookCopyOnWriteArrayList = new CopyOnWriteArrayList<>();
private Binder binder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return bookCopyOnWriteArrayList;
}
@Override
public void addBook(Book book) throws RemoteException {
bookCopyOnWriteArrayList.add(book);
}
};
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onCreate() {
super.onCreate();
bookCopyOnWriteArrayList.add(new Book(10001, "Android開發(fā)藝術(shù)探索"));
}
}
接著我們?cè)贏ndroidManifest.xml中注冊(cè)BookManagerService 青伤,讓他運(yùn)行在獨(dú)立進(jìn)程中督怜。
<service
android:name=".service.BookManagerService"
android:process=":remotebookmanagerservice"/>
客戶端實(shí)現(xiàn)
客戶端要做的是在綁定遠(yuǎn)程服務(wù)成功后,將服務(wù)端返回的Binder對(duì)象轉(zhuǎn)換成AIDL接口狠角,然后使用這個(gè)接口調(diào)用服務(wù)端遠(yuǎn)程方法号杠。為了節(jié)省篇幅,這里只展示核心代碼:
【其它代碼與上節(jié)實(shí)現(xiàn)類似丰歌,此處省略…】
private ServiceConnection bookManagerServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//將服務(wù)端返回的Binder對(duì)象轉(zhuǎn)換成AIDL(IBookManager)接口類型
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
List<Book> bookList = bookManager.getBookList();
LogUtils.d("bookList = "+bookList.toString());
bookManager.addBook(new Book(2,"Java編程思想"));
LogUtils.d("add finish, bookList = "+bookManager.getBookList().toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
啟動(dòng)MainActivity姨蟋,客戶端成功打印出服務(wù)端的Book信息,證明跨進(jìn)程方法getBookList調(diào)用是成功的立帖。
到此為止眼溶,我們已經(jīng)完整地使用AIDL進(jìn)行一次跨進(jìn)程通信。
假設(shè)用戶提出一個(gè)新的需求:要求有新書增加的時(shí)候服務(wù)端自動(dòng)通知用戶晓勇,而不需要用戶自己去主動(dòng)獲取圖書信息堂飞。這種情形在我們的日常開發(fā)中很常見,我們很容易想到觀察者模式绑咱,接下來我們就試著簡(jiǎn)單實(shí)現(xiàn)一下這個(gè)需求绰筛。
首先我們新建一個(gè)IBookArrivedListener.aidl 用于監(jiān)聽"新增新書",用戶通過注冊(cè)這個(gè)接口來申請(qǐng)新書提醒功能描融。
// IBookArrivedListener.aidl
package com.example.qianwei.myapplication.aidl;
// Declare any non-default types here with import statements
import com.example.qianwei.myapplication.aidl.Book;
interface IBookArrivedListener {
void onBookArrive(in Book book);
}
接著在IBookManager 中新增注冊(cè)與解除注冊(cè)方法铝噩,并且在服務(wù)端實(shí)現(xiàn)這兩個(gè)方法:
// IBookManager.aidl
package com.example.qianwei.myapplication.aidl;
import com.example.qianwei.myapplication.aidl.Book;
import com.example.qianwei.myapplication.aidl.IBookArrivedListener;
interface IBookManager {
/**
* 獲取圖書列表
*/
List<Book> getBookList();
/**
* 添加圖書
*/
void addBook(in Book book);
/**
* 注冊(cè)監(jiān)聽事件
*/
void registerListener(IBookArrivedListener listener);
/**
* 反注冊(cè)監(jiān)聽事件
*/
void unRegisterListener(IBookArrivedListener listener);
}
private Binder binder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return bookCopyOnWriteArrayList;
}
@Override
public void addBook(Book book) throws RemoteException {
bookCopyOnWriteArrayList.add(book);
for (IBookArrivedListener bookArrivedListener : bookArrivedListenerCopyOnWriteArrayList) {
try {
//notify registered listener
LogUtils.d("notify registered listener:");
bookArrivedListener.onBookArrive(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
/**
* 注冊(cè)監(jiān)聽事件
*
* @param listener
*/
@Override
public void registerListener(IBookArrivedListener listener) throws RemoteException {
if (!bookArrivedListenerCopyOnWriteArrayList.contains(listener)) {
bookArrivedListenerCopyOnWriteArrayList.add(listener);
} else {
LogUtils.d("Listener has already registered!");
}
LogUtils.d("Listener list size = "+bookArrivedListenerCopyOnWriteArrayList.size());
}
/**
* 反注冊(cè)監(jiān)聽事件
*
* @param listener
*/
@Override
public void unRegisterListener(IBookArrivedListener listener) throws RemoteException {
if (!bookArrivedListenerCopyOnWriteArrayList.contains(listener)) {
LogUtils.d("unregister listener failed!");
} else {
bookArrivedListenerCopyOnWriteArrayList.remove(listener);
}
}
};
最后我們?cè)诳蛻舳松献远x一個(gè)IBookArrivedListener 接口,并且將它注冊(cè)到服務(wù)端接口窿克。
private IBookArrivedListener bookArrivedListener = new IBookArrivedListener.Stub() {
@Override
public void onBookArrive(Book book) throws RemoteException {
LogUtils.d("New book arrived: "+book.toString());
}
};
//#######################################
//注冊(cè)bookArrivedListener
bookManager.registerListener(bookArrivedListener);
//反注冊(cè)bookArrivedListener
if (bookManager != null) {
try {
bookManager.unRegisterListener(bookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
我們?cè)俅芜\(yùn)行程序骏庸,可以看到如下結(jié)果:
正如結(jié)果顯示,客戶端確實(shí)接收到新書到來的通知年叮,但是當(dāng)我們退出MainActivity試圖解除bookArrivedListener通知的時(shí)候具被,告訴我們解除失敗,這顯然不是我們想要的結(jié)果只损。
但是仔細(xì)想想硬猫,好像這種方法確實(shí)無法完成反注冊(cè)功能,因?yàn)閷?duì)象的跨進(jìn)程傳輸本質(zhì)上都是反序列化的過程,對(duì)象通過Binder傳遞到客戶端后啸蜜,得到的對(duì)象將會(huì)是一個(gè)全新的對(duì)象,自然也就無法完成反注冊(cè)過程辈挂。
那么這種情況下我們?cè)撊绾瓮瓿煞醋?cè)呢衬横?答案就是使用RemoteCallbackList,我們接下來就詳細(xì)分析它的用法(暫時(shí)不介紹RemoteCallbackList的工作原理终蒂,以后會(huì)補(bǔ)上)蜂林。
RemoteCallbackList使用起來很方便,我們首先用它代替現(xiàn)有的CopyOnWriteArrayList拇泣,然后直接在相應(yīng)位置調(diào)用它的register和unRegister方法噪叙,我們看看核心代碼。
private RemoteCallbackList<IBookArrivedListener> bookArrivedListenerRemoteCallbackList = new RemoteCallbackList<>();
…
//注冊(cè)
bookArrivedListenerRemoteCallbackList.register(listener);
…
//反注冊(cè)
bookArrivedListenerRemoteCallbackList.unregister(listener);
接著就是給所有注冊(cè)了通知的客戶端發(fā)送通知霉翔,RemoteCallbackList 的遍歷方式很有意思睁蕾,我們必須按照以下方式遍歷RemoteCallbackList ,而且beginBroadcast和finishBroadcast必須成對(duì)使用债朵,否則程序會(huì)出錯(cuò)子眶。
int size = bookArrivedListenerRemoteCallbackList.beginBroadcast();
for (int i = 0; i < size; i++) {
IBookArrivedListener bookArrivedListener =
bookArrivedListenerRemoteCallbackList.getBroadcastItem(i);
if (bookArrivedListener != null) {
bookArrivedListener.onBookArrive(book);
}
}
bookArrivedListenerRemoteCallbackList.finishBroadcast();
我們可以試試程序修改后的運(yùn)行結(jié)果。
從結(jié)果來看序芦,RemoteCallbackList的確可以完成跨進(jìn)程的解注冊(cè)功能臭杰。
5、使用ContentProvider
ContentProvider 是Android中提供的專門用于不同進(jìn)程間數(shù)據(jù)共享的方式谚中,ContentProvider作為Android中的四大組件之一渴杆,可見它在Android中是比較重要的。它的底層實(shí)現(xiàn)同樣是Binder宪塔,但是它的使用過程比AIDL方便得多磁奖,因?yàn)橄到y(tǒng)為我們做了封裝,使得我們不需要關(guān)心底層細(xì)節(jié)就可以輕松實(shí)現(xiàn)進(jìn)程間通信蝌麸。
系統(tǒng)也為開發(fā)者提供了很多內(nèi)置的ContentProvider 点寥,例如通訊錄、相冊(cè)信息等来吩,要跨進(jìn)程訪問這些數(shù)據(jù)敢辩,我們就必須先了解ContentProvider 的創(chuàng)建以及使用規(guī)則。
推薦閱讀
Android深入理解IPC機(jī)制(一)基礎(chǔ)知識(shí)概要
Android深入理解IPC機(jī)制(二)淺談Binder
Android深入理解IPC機(jī)制(四)Binder連接池
參考書籍
《Android 開發(fā)藝術(shù)探索》