前面一篇文章 深入理解Android四大組件之一ContentProvider 講了ContentProvider的簡單使用和它的啓動過程.
這裏接着講解一下關於ContentProvider共享數據更新通知機制.
數據更新通知是很有必要的,比如我們在第二個應用程序中添加了一個聯繫人(接着上一篇文章例子),返回到顯示主界面時,我們不會去更新列表數據,所以就沒有剛剛添加的聯繫人信息,這樣數據不同步,影響客戶體驗.不單單是第二個程序不會察覺到數據庫裏數據已經更新了,其他的客戶端程序,這些程序都是共享第一個應用程序的數據的,也不會察覺到.因此,在第一個應用程序裏面,我們需要做一件事,就是當本地數據庫的數據更新時,就通知其他對該數據庫數據感興趣的客戶端程序,好讓它們及時更新數據.
Android應用程序組件ContentProvider中的數據更新通知機制和Android系統中的廣播(Broadcast)通知機制的實現思路是相似的。在Android的廣播機制中,首先是接收者對自己感興趣的廣播進行註冊,接着當發送者發出這些廣播時,接收者就會得到通知了。然而,ContentProvider中的數據監控機制與Android系統中的廣播機制又有三個主要的區別,一是前者是通過URI來把通知的發送者和接收者關聯在一起的,而後者是通過Intent來關聯的,二是前者的通知註冊中心是由ContentService服務來扮演的,而後者是由ActivityManagerService服務來扮演的,三是前者負責接收數據更新通知的類必須要繼承ContentObserver類,而後者要繼承BroadcastReceiver類。之所以會有這些區別,是由於ContentProivder組件的數據共享功能本身就是建立在URI的基礎之上的,因此專門針對URI來設計另外一套通知機制會更實用和方便,而Android系統的廣播機制是一種更加通用的事件通知機制,它的適用範圍會更廣泛一些。
這裏我們把ContentProvider的數據更新機制劃分爲三個單元進行分析,第一個單元是ContentService的啓動過程,第二個單元是監控數據變化的ContentObserver的註冊過程,第二個單元是數據更新通知的發送過程。
1,ContentService在系統啓動的時候就啓動起來了,以便後面啓動起來的應用程序可以使用它.關於ContentService的啓動過程這裏沒分析了.
2.ContentObserver的註冊過程分析
resolver.registerContentObserver(Uri.parse("content://com.cj.mycontentprovider/contact"),
true,new MyContentObserver(new Handler()));
在第二個應用程序中 通過調用ContentResolver對象的registerContentObserver()方法 來註冊一個自定義的ContentObserver(MyContentObserver)來監控MyContentProvider這個Content Provider中的數據變化.
private class MyContentObserver extends ContentObserver{
/**
* Creates a content observer.
*
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
public MyContentObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
myAdapter.notifyDataSetChanged();
}
}
從ContentObserver繼承下來的子類必須要實現onChange函數。當這個ContentObserver子類負責監控的數據發生變化時,ContentService就會調用它的onChange函數來處理,參數selfChange表示這個變化是否是由自己引起的。在這個應用程序中,MyContentObserver繼承了ContentObserver類,它負責監控的URI是"content://com.cj.mycontentprovider/contact".當以這個URI爲前綴的URI對應的數據發生改變時,ContentService都會調用這個MyContentObserver類的onChange函數來處理。在MyContentObserver類的onChange函數中,執行的操作就是重新獲取MyContentProvider中的數據來更新界面上的聯繫人信息列表。
在MyContentObserver類的構造函數中,有一個參數handler,它的類型爲Handler,它是從MainActivity類的onCreate函數中創建並傳過來的。這個handler是用來分發和處理消息用的。由於MainActivity類的onCreate函數是在應用程序的主線程中被調用的,因此,這個handler參數就是和應用程序主線程的消息循環關聯在一起的。在後面我們分析數據更新通知的發送過程時,便會看到這個handler參數是如何使用的了。
下面我們就開始分析註冊MyContentObserver來監控MyContentProvider中的數據變化的過程.
第一步.ContentResolver.registerContentObserver()
在frameworks/base/core/java/android/content/ContentResolver.java文件中:
public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
ContentObserver observer)
{
registerContentObserver(uri, notifyForDescendents, observer, UserHandle.myUserId());
}
/** @hide - designated user version */
public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
ContentObserver observer, int userHandle)
{
try {
getContentService().registerContentObserver(uri, notifyForDescendents,
observer.getContentObserver(), userHandle);
} catch (RemoteException e) {
}
}
當參數notifyForDescendents爲true時,表示要監控所有以uri爲前綴的URI對應的數據變化。上面函數做了三件事情,一是調用getContentService函數來獲得前面已經啓動起來了的ContentService服務,二是調用從參數傳進來的ContentObserver對象observer的getContentObserver函數來獲得一個Binder對象,三是通過調用這個ContentService遠程接口的registerContentObserver函數來把這個Binder對象註冊到ContentService中去。
第二步. ContentResolver.getContentService()
在frameworks/base/core/java/android/content/ContentResolver.java文件中:
public static IContentService getContentService() {
if (sContentService != null) {
return sContentService;
}
IBinder b = ServiceManager.getService(CONTENT_SERVICE_NAME);
...
sContentService = IContentService.Stub.asInterface(b);
..
return sContentService;
}
在ContentResolver類中,有一個靜態成員變量sContentService,開始時它的值爲null。當ContentResolver類的getContentService函數第一次被調用時,它便會通過ServiceManager類的getService函數來獲得前面已經啓動起來了的ContentService服務的遠程接口,然後把它保存在sContentService變量中。這樣,當下次ContentResolver類的getContentService函數再次被調用時,就可以直接把這個ContentService遠程接口返回給調用者了。
第三步. ContentResolver.getContentObserver()
在frameworks/base/core/java/android/database/ContentObserver.java文件中:
public abstract class ContentObserver {
private final Object mLock = new Object();
private Transport mTransport; // guarded by mLock
Handler mHandler;
/**
* Creates a content observer.
*
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
public ContentObserver(Handler handler) {
mHandler = handler;
}
/**
* Gets access to the binder transport object. Not for public consumption.
*
* {@hide}
*/
public IContentObserver getContentObserver() {
synchronized (mLock) {
if (mTransport == null) {
mTransport = new Transport(this);
}
return mTransport;
}
}
/**
ContentObserver類的getContentObserver函數返回的是一個成員變量mTransport,它的類型爲ContentObserver的內部類Transport。ContentObserver類的成員變量mTransport是一個Binder對象,它是要傳遞給ContentService服務的,以便當ContentObserver所監控的數據發生變化時,ContentService服務可以通過這個Binder對象通知相應的ContentObserver它監控的數據發生變化了。
第四步. ContentService.registerContentObserver()
在frameworks/base/core/java/android/content/ContentService.java文件中:
public void registerContentObserver(Uri uri, boolean notifyForDescendants,
IContentObserver observer, int userHandle) {
.....
synchronized (mRootNode) {
mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
Binder.getCallingUid(), Binder.getCallingPid(), userHandle);
...
}
}
它調用了ContentService類的成員變量mRootNode的addObserverLocked函數來註冊這個ContentObserver對象observer。成員變量mRootNode的類型爲ContentService在內部定義的一個類ObserverNode。
第五步. ObserverNode.addObserverLocked()
在frameworks/base/core/java/android/content/ContentService.java文件中:
public void addObserverLocked(Uri uri, IContentObserver observer,
boolean notifyForDescendants, Object observersLock,
int uid, int pid, int userHandle) {
addObserverLocked(uri, 0, observer, notifyForDescendants, observersLock,
uid, pid, userHandle);
}
private void addObserverLocked(Uri uri, int index, IContentObserver observer,
boolean notifyForDescendants, Object observersLock,
int uid, int pid, int userHandle) {
// If this is the leaf node add the observer
if (index == countUriSegments(uri)) {//這裏的index 上一步傳進來的是0
mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock,
uid, pid, userHandle));
return;
}
// Look to see if the proper child already exists
String segment = getUriSegment(uri, index);
int N = mChildren.size();
for (int i = 0; i < N; i++) {
ObserverNode node = mChildren.get(i);
if (node.mName.equals(segment)) {
node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
observersLock, uid, pid, userHandle);
return;
}
}
// No child found, create one
ObserverNode node = new ObserverNode(segment);
mChildren.add(node);
node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
observersLock, uid, pid, userHandle);
}
從這裏我們就可以看出,註冊到ContentService中的ContentObserver按照樹形來組織,樹的節點類型爲ObserverNode,而樹的根節點就爲ContentService類的成員變量mRootNode。
private final ObserverNode mRootNode = new ObserverNode("");
每一個ObserverNode節點都對應一個名字,它是從URI中解析出來的。
在我們這個情景中,傳進來的uri爲"content://com.cj.mycontentprovider/contact",從第五步第一個用mRootNode的addObserverLocked()函數來往樹上增加一個ObserverNode節點時,傳進來的參數index的值爲0,而調用countUriSegments("content://com.cj.mycontentprovider/contact")函數的返回值爲2,不等於index的值.這裏看一下countUriSegments()函數:
private int countUriSegments(Uri uri) {
if (uri == null) {
return 0;
}
return uri.getPathSegments().size() + 1;
}
--getPathSegments得到uri的path部分,並拆分,去掉"/",取到第一個元素(從第0個開始)。這裏只有contact一個元素 .
因此就會往下執行,而通過調用getUriSegment("content://com.cj.mycontentprovider/contact", 0)函數得到的返回值爲"com.cj.mycontentprovider"。
private String getUriSegment(Uri uri, int index) {
if (uri != null) {
if (index == 0) {
return uri.getAuthority();
} else {
return uri.getPathSegments().get(index - 1);
}
} else {
return null;
}
}
假設這裏是第一次調用樹的根節點mRootNode來增加"content://com.cj.mycontentprovider/contact"這個URI,那麼在接下來的for循環中,就不會在mRootNode的孩子節點列表mChildren中找到與名稱"com.cj.mycontentprovider"對應的ObserverNode,於是就會以"com.cj.mycontentprovider"爲名稱來創建一個新的ObserverNode,並增加到mRootNode的孩子節點列表mChildren中去,並以這個新的ObserverNode來開始新一輪的addObserverLocked()函數調用。
第二次進入到addObserverLocked()函數時,countUriSegments("content://com.cj.mycontentprovider/contact")的值仍爲2(參考前面函數),而index的值爲1,因此就會往下執行,這時候通過調用getUriSegment("content://shy.luo.providers.articles/item", 1)函數得到的返回值爲"contact"。這裏不存在名稱爲"contact"的孩子節點,於是又會以"contact"爲名稱來創建一個新的ObserverNode,並以這個新的ObserverNode來開始新一輪的addObserverLocked函數調用。
第三次進入到addObserverLocked()函數時,countUriSegments("content://com.cj.mycontentprovider/contact")的值仍爲2,而index的值也爲2,因此就會新建一個ObserverEntry對象,並保存在這個以"contact"爲名稱的ObserverNode的ContentObserver列表mObervers中。
最終我們得到的樹形結構如下所示:
mRootNode("")
-- ObserverNode("com.cj.mycontentprovider")
--ObserverNode("contact") , which has a ContentObserver in mObservers
這樣,ContentObserver的註冊過程就完成了。
2. 數據更新通知的發送過程
在第二個應用程序中,在添加聯繫人的界面田添加一個聯繫人時:
ContentValues contentValues = new ContentValues();
contentValues.put("name",name);
contentValues.put("number",num);
getContentResolver().insert(Uri.parse("content://com.cj.mycontentprovider/contact"),contentValues);
便會進入到第一個應用程序的MyContentProvider類的insert函數中:
public Uri insert(Uri uri, ContentValues values) {
Uri u = null;
if(uriMatcher.match(uri) == CONTACT){
SQLiteDatabase database = dbHelp.getWritableDatabase();
long d = database.insert("contact", "_id", values);
u = ContentUris.withAppendedId(uri,d);
contentResolver.notifyChange(u,null);
}
return u;
}
從上面傳來的參數uri的值爲"content://com.cj.mycontentprovider/contact"。假設當這個函數把數據成功增加到SQLite數據庫之後,返回來的id值爲d,於是通過調用ContentUris.withAppendedId(uri,d);得到的newUri的值就爲"content://com.cj.mycontentprovider/contact/d"。這時候就會調用下面語句來通知那些註冊了監控"content://com.cj.mycontentprovider/contact/d"這個URI的ContentObserver,它監控的數據發生變化了:
contentResolver.notifyChange(u,null);
下面就根據源碼來分析這個數據變化通知的發送過程:
第一步:ContentResolver.notifyChange()
在frameworks/base/core/java/android/content/ContentResolver.java文件中:
public void notifyChange(Uri uri, ContentObserver observer) {
notifyChange(uri, observer, true /* sync to network */);
}
public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
notifyChange(uri, observer, syncToNetwork, UserHandle.getCallingUserId());
}
/**
* Notify registered observers within the designated user(s) that a row was updated.
*
* @hide
*/
public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork,
int userHandle) {
try {
getContentService().notifyChange(
uri, observer == null ? null : observer.getContentObserver(),
observer != null && observer.deliverSelfNotifications(), syncToNetwork,
userHandle);
} catch (RemoteException e) {
}
}
這裏調用了ContentService的遠接程口來調用它的notifyChange()函數來發送數據更新通知。
第二步: ContentService.notifyChange()
在frameworks/base/core/java/android/content/ContentService.java文件中
public void notifyChange(Uri uri, IContentObserver observer,
boolean observerWantsSelfNotifications, boolean syncToNetwork,
int userHandle) {
...
// This makes it so that future permission checks will be in the context of this
// process rather than the caller's process. We will restore this before returning.
try {
ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
synchronized (mRootNode) {
mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
userHandle, calls);
}
final int numCalls = calls.size();
for (int i=0; i<numCalls; i++) {
ObserverCall oc = calls.get(i);
try {
oc.mObserver.onChange(oc.mSelfChange, uri);
..
} catch (RemoteException ex) {
..
}
}
if (syncToNetwork) {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle,
uri.getAuthority());
}
}
} finally {
restoreCallingIdentity(identityToken);
}
}
這個函數主要做了兩件事情,第一件事情是調用ContentService的成員變量mRootNode的collectObserverLocked()函數來收集那些註冊了監控"content://com.cj.mycontentprovider/contact/d"這個URI的ContentObserver,第二件事情是分別調用了這些ContentObserver的onChange()函數來通知它們監控的數據發生變化了。
第三步: ObserverNode.collectObserversLocked()
在frameworks/base/core/java/android/content/ContentService.java文件中:
public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
boolean observerWantsSelfNotifications, int targetUserHandle,
ArrayList<ObserverCall> calls) {
String segment = null;
int segmentCount = countUriSegments(uri);
if (index >= segmentCount) {
// This is the leaf node, notify all observers
collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
targetUserHandle, calls);
} else if (index < segmentCount){
segment = getUriSegment(uri, index);
// Notify any observers at this level who are interested in descendants
collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
targetUserHandle, calls);
}
int N = mChildren.size();
for (int i = 0; i < N; i++) {
ObserverNode node = mChildren.get(i);
if (segment == null || node.mName.equals(segment)) {
// We found the child,
node.collectObserversLocked(uri, index + 1,
observer, observerWantsSelfNotifications, targetUserHandle, calls);
if (segment != null) {
break;
}
}
}
}
private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
boolean observerWantsSelfNotifications, int targetUserHandle,
ArrayList<ObserverCall> calls) {
int N = mObservers.size();
IBinder observerBinder = observer == null ? null : observer.asBinder();
for (int i = 0; i < N; i++) {
ObserverEntry entry = mObservers.get(i);
// Don't notify the observer if it sent the notification and isn't interested
// in self notifications
boolean selfChange = (entry.observer.asBinder() == observerBinder);
if (selfChange && !observerWantsSelfNotifications) {
continue;
}
// Does this observer match the target user?
if (targetUserHandle == UserHandle.USER_ALL
|| entry.userHandle == UserHandle.USER_ALL
|| targetUserHandle == entry.userHandle) {
// Make sure the observer is interested in the notification
if (leaf || (!leaf && entry.notifyForDescendants)) {
calls.add(new ObserverCall(this, entry.observer, selfChange));
}
}
}
}
第一次調用collectObserversLocked()時,是在mRootNode的這個ObserverNode節點中進行收集ContentObserver的。這時候傳進來的uri的值爲"content://com.cj.mycontentprovider/contact/d",index的值爲0。調用countUriSegments("content://shy.luo.providers.articles/item/n")函數得到的返回值爲3(參考前面就知道是3 2+1=3),於是就會調用下面語句:
segment = getUriSegment(uri, 0);
// Notify any observers at this level who are interested in descendants
collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
targetUserHandle, calls);
這裏得到的segment爲"com.cj.mycontentprovider"。在我們這個情景中,mRootNode這個節點中沒有註冊ContentObserver,於是調用collectMyObserversLocked()函數就不會收集到ContentObserver。
在接下來的for循環中,在mRootNode的孩子節點列表mChildren中查找名稱等於"com.cj.mycontentprovider"的OberverNode節點。在上面分析ContentObserver的註冊過程時,我們已經往mRootNode的孩子節點列表mChildren中增加了一個名稱爲"com.cj.mycontentprovider"的OberverNode節點,因此,這裏會成功找到它,並且調用它的collectObserversLocked()函數來繼續收集ContentObserver。
第二次進入到collectObserversLocked()函數時,是在名稱爲"com.cj.mycontentprovider"的OberverNode節點中收集ContentObserver的。這時候傳來的uri值不變,但是index的值爲1,於是執行下面語句:
segment = getUriSegment(uri, 1);
// Notify any observers at this level who are interested in descendants
collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
targetUserHandle, calls);
</pre><pre name="code" class="java" style="font-size: 14px;">
這裏得到的segment爲"contact"。在我們這個情景中,我們沒有在名稱爲"com.cj.mycontentprovider"的OberverNode節點中註冊有ContentObserver,因此這裏調用collectMyObserversLocked()函數也不會收集到ContentObserver。
在接下來的for循環中,在名稱爲"com.cj.mycontentprovider"的ObserverNode節點的孩子節點列表mChildren中查找名稱等於"contact"的OberverNode節點。在上面分析ContentObserver的註冊過程時,我們已經往名稱爲"com.cj.mycontentprovider"的ObserverNode節點的孩子節點列表mChildren中增加了一個名稱爲"contact"的OberverNode節點,因此,這裏會成功找到它,並且調用它的collectObserversLocked()函數來繼續收集ContentObserver。
第三次進入到collectObserversLocked()函數時,是在名稱爲"com.cj.mycontentprovider"的OberverNode節點的子節點中名稱爲"contact"的ObserverNode節點中收集ContentObserver的。這時候傳來的uri值不變,但是index的值爲2,於是執行下面語句:
segment = getUriSegment(uri, 2);
// Notify any observers at this level who are interested in descendants
collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
targetUserHandle, calls);
這裏得到的segment爲"d"。前面我們已經在名稱爲"com.cj.mycontentprovider"的OberverNode節點的子節點中名稱爲"contact"的ObserverNode節點中註冊了一個ContentObserver,即MyContentObserver,因此這裏調用collectMyObserversLocked()函數會收集到這個ContentObserver。注意,這次調用collectMyObserversLocked()函數時,雖然傳進去的參數leaf爲false,但是由於我們註冊MyContentObserver時,指定了notifyForDescendents參數爲true,因此,這裏可以把它收集回來。
在接下來的for循環中,繼續在該節點的子節點列表mChildren中查找名稱等於"d"的OberverNode節點。在我們這個情景中,不存在這個名稱爲"d"的子節點了,於是收集ContentObserver的工作就結束了,收集結果是隻有一個ContentObserver,即我們在前面註冊的MyContentObserver。
返回到第二步中,調用下面語句來通知相應的ContentObserver,它們監控的數據發生變化了:
for (int i=0; i<numCalls; i++) {
ObserverCall oc = calls.get(i);
try {
oc.mObserver.onChange(oc.mSelfChange, uri);
} catch (RemoteException ex) {
}
}
前面我們在分析ContentObserver的註冊過程的第三步時,介紹到註冊到ContentService服務中的mObserver是一個在ContentObserver內部定義的一個類Transport的對象的遠程接口,於是這裏調用這個接口的onChange()函數時,就會進入到ContentObserver的內部類Transport的onChange()函數中去。
第四步: Transport.onChange()
在frameworks/base/core/java/android/database/ContentObserver.java文件中:
private static final class Transport extends IContentObserver.Stub {
private ContentObserver mContentObserver;
public Transport(ContentObserver contentObserver) {
mContentObserver = contentObserver;
}
@Override
public void onChange(boolean selfChange, Uri uri) {
ContentObserver contentObserver = mContentObserver;
if (contentObserver != null) {
contentObserver.dispatchChange(selfChange, uri);
}
}
public void releaseContentObserver() {
mContentObserver = null;
}
}
前面我們在分析ContentObserver的註冊過程的第三步時,把MyContentObserver這個ContentObserver保存在了這個Transport對象的mContentObserver成員變量中,因此,會調用它的dispatchChange()函數來執行數據更新通知的操作。
第五步:. ContentObserver.dispatchChange()
在frameworks/base/core/java/android/database/ContentObserver.java文件中:
public final void dispatchChange(boolean selfChange, Uri uri) {
if (mHandler == null) {
onChange(selfChange, uri);
} else {
mHandler.post(new NotificationRunnable(selfChange, uri));
}
}
在前面分析MyContentObserver的註冊過程時,我們以第二個應用程序的主線程的消息循環創建了一個Handler,並且以這個Handler來創建了這個MyContentObserver,這個Handler就保存在MyContentObserver的父類ContentObserver的成員變量mHandler中。因此,這裏的mHandler不爲null,於是把這個數據更新通知封裝成了一個消息,放到第二個應用程序的主線程中去處理,最終這個消息是由NotificationRunnable類的run()函數來處理的。
第六步:. NotificationRunnable.run()
在frameworks/base/core/java/android/database/ContentObserver.java文件中:
private final class NotificationRunnable implements Runnable {
private final boolean mSelfChange;
private final Uri mUri;
public NotificationRunnable(boolean selfChange, Uri uri) {
mSelfChange = selfChange;
mUri = uri;
}
@Override
public void run() {
ContentObserver.this.onChange(mSelfChange, mUri);
}
}
這個函數就直接調用ContentObserver的子類的onChange函數來處理這個數據更新通知了。在我們這個情景中,這個ContentObserver子類便是MyContentObserver了。
這裏它要執行的操作便是更新界面上的ListView列表中的聯繫人信息了。
以上就是ContentProvider的共享數據更新通知機制的運行過程.