ContentService能夠看作Android中一個系統級別的消息中心,能夠說搭建了一個系統級的觀察者模型,APP能夠向消息中心註冊觀察者,選擇訂閱本身關心的消息,也能夠經過消息中心發送信息,通知其餘進程,簡單模型以下:java
ContentService服務伴隨系統啓動,自己是一個Binder系統服務,運行在SystemServer進程。做爲系統服務,最好能保持高效運行,所以ContentService通知APP都是異步的,也就是oneway的,僅僅插入目標進程(線程)的Queue隊列,沒必要等待執行。下面簡單分析一下總體的架構,主要從一下幾個方面瞭解下運行流程:node
ContentService服務伴隨系統啓動,更準確的說是伴隨SystemServer進程啓動,其入口函數以下:架構
public static ContentService main(Context context, boolean factoryTest) {
<!--新建Binder服務實體-->
ContentService service = new ContentService(context, factoryTest);
<!--添加到ServiceManager中-->
ServiceManager.addService(ContentResolver.CONTENT_SERVICE_NAME, service);
return service;
}
複製代碼
同AMS、WMS等系統服務相似,ContentService也是一個Binder服務實體,並且受ServiceManager管理,須要註冊ServiceManager中,方便APP未來獲取該服務的代理。ContentService是一個Binder服務實體,具體實現以下:框架
<!--關鍵點1-->
public final class ContentService extends IContentService.Stub {
private static final String TAG = "ContentService";
private Context mContext;
private boolean mFactoryTest;
private final ObserverNode mRootNode = new ObserverNode("");
private SyncManager mSyncManager = null;
private final Object mSyncManagerLock = new Object();
。。。
複製代碼
IContentService.Stub由IContentService.aidl文件生成,IContentService.aidl文件中定義了ContentService能提供的基本服務,好比註冊/註銷觀察者、通知觀察者等,以下:異步
interface IContentService {
<!--註銷一個觀察者-->
void unregisterContentObserver(IContentObserver observer);
<!--註冊一個觀察者-->
void registerContentObserver(in Uri uri, boolean notifyForDescendants,
IContentObserver observer, int userHandle);
<!--通知觀察者-->
void notifyChange(in Uri uri, IContentObserver observer,
boolean observerWantsSelfNotifications, boolean syncToNetwork,
int userHandle);
...
}
複製代碼
雖然從使用上來講,ContentService跟ContentProvider關係緊密,可是理論上講,這是徹底獨立的兩套東西,ContentService是一個獨立的消息分發模型,能夠徹底獨立於ContentProvider使用(總覺的這種設計是否是有些問題),看一下基本用法:ide
一、註冊一個觀察者:函數
public static void registerObserver(Context context,ContentObserver contentObserver) {
ContentResolver contentResolver = context.getContentResolver();
contentResolver.registerContentObserver(Uri.parse("content://"+"test"), true, contentObserver);
}
複製代碼
二、通知觀察者post
public static void notity(Context context) {
ContentResolver contentResolver = context.getContentResolver();
contentResolver.notifyChange(Uri.parse("content://"+"test"),null);
}
複製代碼
能夠看到,期間只是借用了ContentResolver,可是並無牽扯到任何ContentProvider,也就是說,ContentService其實主要是爲了提供了一個系統級的消息中心,下面簡單看一下注冊跟通知流程ui
App通常都是藉助ContentResolver來註冊Content觀察者,ContextResoler實際上是Context的一個成員變量,自己是一個ApplicationContentResolver對象,它是ContentResolver的子類,this
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
Display display, Configuration overrideConfiguration, int createDisplayWithId) {
...
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
...
複製代碼
經過ContentResolver註冊ContentObserver代碼以下:
public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
ContentObserver observer, int userHandle) {
try {
<!--獲取ContentService,並註冊-->
getContentService().registerContentObserver(uri, notifyForDescendents,
observer.getContentObserver(), userHandle);
} catch (RemoteException e) {
}
}
複製代碼
能夠看到,註冊的過程首先是獲取ContentService服務代理,而後經過這個代理像ContentService註冊觀察者,典型的Binder服務通訊模型,獲取服務的實現以下,
/** @hide */
public static final String CONTENT_SERVICE_NAME = "content";
/** @hide */
public static IContentService getContentService() {
if (sContentService != null) {
return sContentService;
}
IBinder b = ServiceManager.getService(CONTENT_SERVICE_NAME);
sContentService = IContentService.Stub.asInterface(b);
return sContentService;
}
複製代碼
其實就是經過系統服務的名稱,向ServiceManager查詢並獲取服務代理,請求成功後,即可以經過代理髮送請求,這裏請求的任務是註冊,這裏有一點要注意,那就是在註冊的時候,要同時打通ContentService向APP發送消息的鏈路,這個鏈路其實就是另外一個Binder通訊路線,具體作法就是將ContentObserver封裝成一個Binder服務實體註冊到ContentService中,註冊成功後,ContentService就會握有ContentObserver的代理,未來須要通知APP端的時候,就能夠經過該代理髮送通知,雙C/S模型在Android框架中很是常見。具體代碼是,經過ContentObserver獲取一個IContentObserver對象,APP端將該對象經過binder傳遞到ContentService服務,如此ContentService便能經過Binder向APP端發送通知
public IContentObserver getContentObserver() {
synchronized (mLock) {
if (mTransport == null) {
mTransport = new Transport(this);
}
return mTransport;
}
}
複製代碼
mTransport本質是一個Binder服務實體,同時握有ContentObserver的強引用,未來通知到達的時候,便能經過ContentObserver分發通知
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, int userId) {
ContentObserver contentObserver = mContentObserver;
if (contentObserver != null) {
<!--經過 contentObserver發送回調通知-->
contentObserver.dispatchChange(selfChange, uri, userId);
}
}
public void releaseContentObserver() {
mContentObserver = null;
}
}
複製代碼
Transport自己是一個Binder實體對象,被註冊到ContentService中,ContentService會維護一個Transport代理的集合,經過代理,能夠通知不一樣的進程,繼續看register流程,registerContentObserver經過binder通訊最終會調用都ContentService的registerContentObserver函數:
@Override
public void registerContentObserver(Uri uri, boolean notifyForDescendants,
IContentObserver observer, int userHandle) {
<!--權限檢查-->
if (callingUserHandle != userHandle &&
mContext.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
!= PackageManager.PERMISSION_GRANTED) {
enforceCrossUserPermission(userHandle,
"no permission to observe other users' provider view");
}
...
<!--2 添加到監聽隊列-->
synchronized (mRootNode) {
mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
uid, pid, userHandle);
}
}
複製代碼
這裏主要看下點2:監聽對象的添加,ContentService對象內部維護了一個樹,用於管理監聽對象,主要是根據Uri的路徑進行分組,既方便管理,同時又提升查找及插入效率,每一個Uri路徑對象對應一個節點,也就是一個ObserverNode對象,每一個節點中維護一個監聽List,而ContentService持有RootNode根對象,
private final ObserverNode mRootNode = new ObserverNode("");
複製代碼
每一個ObserverNode維護了一個ObserverEntry隊列,ObserverEntry與ContentObserver一一對應,一個Uri對應一個ObserverNode,一個ObserverNode下能夠有多個ContentObserver,也就是會多個ObserverEntry,每一個ObserverEntry還有一些其餘輔助信息,好比要跟Uri造成鍵值對,ObserverEntry還將本身設置成了Binder訃告的接受者,一旦APP端進程結束,能夠經過Binder訃告機制讓ContentService端收到通知,並作一些清理工做,具體實現以下:
public static final class ObserverNode {
private class ObserverEntry implements IBinder.DeathRecipient {
public final IContentObserver observer;
public final int uid;
public final int pid;
public final boolean notifyForDescendants;
private final int userHandle;
private final Object observersLock;
public ObserverEntry(IContentObserver o, boolean n, Object observersLock,
int _uid, int _pid, int _userHandle) {
this.observersLock = observersLock;
observer = o;
uid = _uid;
pid = _pid;
userHandle = _userHandle;
notifyForDescendants = n;
try {
observer.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
binderDied();
}
}
<!--作一些清理工做,刪除observer-->
public void binderDied() {
synchronized (observersLock) {
removeObserverLocked(observer);
}
}
。。。
}
public static final int INSERT_TYPE = 0;
public static final int UPDATE_TYPE = 1;
public static final int DELETE_TYPE = 2;
private String mName;
private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>();
<!--維護本身node的回調隊列-->
private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>(); . ..
複製代碼
繼續看看下Observer的add流程,ObserverNode 的addObserverLocked函數被外部調用(被rootnode)的時候,通常傳遞的index是0,本身遞歸調用的時候,纔不是0,其實添加Observer的過程是一個遞歸的過程,首先經過Uri路徑,遞歸找到對應的ObserverNode,而後像ObserverNode的監聽隊列中添加Observer。
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
<!--已經找到葉子節點,那麼能夠直接在node中插入ObserverEntry->
if (index == countUriSegments(uri)) {
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);
}
複製代碼
好比:要查詢content://A/B/C對應的ObserverNode,首先會找到Authority,找到A對應的ObserverNode,以後在A的children中查找Path=B的Node,而後在B的Children中查找Path=C的Node,找到該Node以後,往這個node的ObserverEntry列表中添加一個對象,到這裏就註冊就完成了。
前文已經說過,ContentService能夠看作是通知的中轉站,進程A想要通知其餘註冊了某個Uri的進程,必須首先向ContentService分發中心發送消息,再由ContentService通知其餘進程中的觀察者,簡化模型以下圖:
簡單跟蹤下通知流程,入口函數以下
public static void notity(Context context) {
ContentResolver contentResolver = context.getContentResolver();
contentResolver.notifyChange(Uri.parse("content://"+"test"),null);
}
複製代碼
ContentResolver的notifyChange會進一步經過Binder,請求ContentService發送通知,
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收到請求進一步處理,無非就是搜索以前的樹,找到對應的節點,將節點上註冊回調List通知一遍,具體邏輯以下:
@Override
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.
<!--找回調,處理回調-->
long identityToken = clearCallingIdentity();
try {
ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
synchronized (mRootNode) {
<!--1 從根節點開始查找binder回調代理-->
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 {
<!--2 通知-->
oc.mObserver.onChange(oc.mSelfChange, uri, userHandle);
}
...
複製代碼
從上面代碼能夠看出,其實就是兩步,先蒐集全部的Binder回調,以後經過回調通知APP端,蒐集過程也是個遞歸的過程,也會存在父子粘連的一些回調邏輯(子Uri是否有必要通知路徑中的父Uri回調),理解很簡單,再也不詳述。這步以後,消息就經過Binder被傳送給App端,在APP端,Binder實體的onTransact被回調,並處理相應的事務:
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, int userId) {
ContentObserver contentObserver = mContentObserver;
if (contentObserver != null) {
<!--經過 contentObserver發送回調通知-->
contentObserver.dispatchChange(selfChange, uri, userId);
}
}
public void releaseContentObserver() {
mContentObserver = null;
}
}
複製代碼
這裏有一點須要注意,那就是IContentObserver中onChange是一個oneway請求,能夠說,老是異步的,ContentService將消息塞入到APP端Binder線程的執行隊列後就返回,不會等待處理結果才返回。
interface IContentObserver
{
/**
* This method is called when an update occurs to the cursor that is being
* observed. selfUpdate is true if the update was caused by a call to
* commit on the cursor that is being observed.
*/
contentService 用的是oneway
oneway void onChange(boolean selfUpdate, in Uri uri, int userId);
}
複製代碼
以後其實就是調用ContentObserver的dispatchChange,dispatchChange多是在Binder線程中同步執行,也多是發送到一個與Handler綁定的線程中執行,以下,
private void dispatchChange(boolean selfChange, Uri uri, int userId) {
if (mHandler == null) {
onChange(selfChange, uri, userId);
} else {
mHandler.post(new NotificationRunnable(selfChange, uri, userId));
}
}
複製代碼
可是總體上來看,因爲Binder oneway的存在,ContentService的通知是個異步的過程。
假設有這樣一個場景:
這個時候,若是,採用的是同步,也就是ContentObserver沒有設置Handler,那就會遇到一個問題,系統會提示你沒有權限訪問ContentProvider,
java.lang.SecurityException: Permission Denial: reading XXX uri content://MyContentProvider from pid=0, uid=1000 requires the provider be exported, or grantUriPermission()
爲何,明明是當前App中聲明的ContentProvider,爲何不能訪問,而且pid=0, uid=1000 是怎麼來的,其實這個時候是由於Binder機制中的一個小"BUG",須要用戶本身避免,ContentProvider在使用的時候會校驗權限,
/** {@hide} */
protected int enforceReadPermissionInner(Uri uri, String callingPkg, IBinder callerToken)
throws SecurityException {
final Context context = getContext();
// Binder.getCallingPid獲取的可能不是咱們想要的進程PID
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
String missingPerm = null;
int strongestMode = MODE_ALLOWED;
...
final String failReason = mExported
? " requires " + missingPerm + ", or grantUriPermission()"
: " requires the provider be exported, or grantUriPermission()";
throw new SecurityException("Permission Denial: reading "
+ ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid
+ ", uid=" + uid + failReason);
}
複製代碼
Binder.getCallingPid()獲取的可能並非咱們想要的進程PID,由於以前同步訪問的時候 Binder.getCallingPid()被賦值爲系統進程PID,在同步訪問的時候,因爲ContentProvider自己在A進程中,會直接調用ContentProvider的相應服務函數,可是Binder.getCallingPid()返回值並無被更新,由於這個時候訪問的時候不會走跨進程, Binder.getCallingPid()的返回值不會被 更新,也就是說 Binder.getCallingPid()獲取的進程是上一個notify時候的系統進程,那麼天然也就沒有權限。若是將ContentProvider放到A進程以外的進程,就不會有問題,固然,Android提供瞭解決方案,那就是
<!--將Binder.getCallingPid()的值設定爲當前進程-->
final long identity = Binder.clearCallingIdentity();
...
<!--恢復以前保存的值-->
Binder.restoreCallingIdentity(identity);
複製代碼
以上兩個函數配合使用,就能夠避免以前的問題。這個問題Google不能從Binder上在底層解決嗎?總覺是Binder通訊的BUG。
做者:看書的小蝸牛 Android內容服務ContentService原理淺析
僅供參考,歡迎指正