項目中用到了ContentProvider存取數據,同時想動態監聽數據變化。使用getContentResolver().registerContentObserver監聽。java
Settings.Secure.putInt(getContentResolver(), SECURE_KEY_VOLUME_UP, 1); getContentResolver().notifyChange(Settings.Secure.getUriFor(SECURE_KEY_VOLUME_UP), null);
代碼很簡單,Settings.Secure其實就是封裝的ContentProvider用法,put值,同時notify告知全部監聽者。node
context.getContentResolver().registerContentObserver(uri, false, volumeUpObserver); private ContentObserver volumeUpObserver = new ContentObserver(null) { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); int value = Settings.Secure.getInt(context.getContentResolver(), SECURE_KEY_VOLUME_UP, -1); Log.d(GabbroAssistantOemService.TAG, SECURE_KEY_VOLUME_UP + " : " + value); } };
打印日誌發現會有兩條onChange改變的日誌。web
1、首先用法是沒有問題的,很標準。因而網上搜有沒有同類問題。發現了一會相似的問題:
https://blog.csdn.net/serapme/article/details/7404233api
2、因而好奇爲啥會回調兩次,初步懷疑是getContentResolver內部未知緣由存了兩個observer。
一、先看下ContentResolver註冊代碼ide
public final void registerContentObserver(@NonNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer) { Preconditions.checkNotNull(uri, "uri"); Preconditions.checkNotNull(observer, "observer"); registerContentObserver( ContentProvider.getUriWithoutUserId(uri), notifyForDescendants, observer, ContentProvider.getUserIdFromUri(uri, mContext.getUserId())); } /** @hide - designated user version */ @UnsupportedAppUsage public final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer, @UserIdInt int userHandle) { try { getContentService().registerContentObserver(uri, notifyForDescendents, observer.getContentObserver(), userHandle, mTargetSdkVersion); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
二、最終是調用了ContentService.registerContentObserver,代碼在ContentService.java中svg
@Override public void registerContentObserver(Uri uri, boolean notifyForDescendants, IContentObserver observer, int userHandle, int targetSdkVersion) { if (observer == null || uri == null) { throw new IllegalArgumentException("You must pass a valid uri and observer"); } ...... synchronized (mRootNode) { mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode, uid, pid, userHandle); if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri + " with notifyForDescendants " + notifyForDescendants); } } public static final class ObserverNode { private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>(); 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)) { mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock, uid, pid, userHandle, uri)); return; } // Look to see if the proper child already exists String segment = getUriSegment(uri, index); if (segment == null) { throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer"); } 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); } }
代碼很明顯,存到了mObservers,是個list,中間並無什麼邏輯添加兩次。
三、register沒問題,那會不會是notify有問題,因而看了notify代碼,代碼在ContentResolver.javaoop
public void notifyChange(@NonNull Uri uri, ContentObserver observer, boolean syncToNetwork, @UserIdInt int userHandle) { try { getContentService().notifyChange( uri, observer == null ? null : observer.getContentObserver(), observer != null && observer.deliverSelfNotifications(), syncToNetwork ? NOTIFY_SYNC_TO_NETWORK : 0, userHandle, mTargetSdkVersion, mContext.getPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
四、再看下ContentService.notifyChange,代碼在ContentService.javaui
@Override public void notifyChange(Uri uri, IContentObserver observer, boolean observerWantsSelfNotifications, int flags, int userHandle, int targetSdkVersion, String callingPackage) { ...... // 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) { mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications, flags, 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, userHandle); if (DEBUG) Slog.d(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri); } catch (RemoteException ex) { synchronized (mRootNode) { Log.w(TAG, "Found dead observer, removing"); IBinder binder = oc.mObserver.asBinder(); final ArrayList<ObserverNode.ObserverEntry> list = oc.mNode.mObservers; int numList = list.size(); for (int j=0; j<numList; j++) { ObserverNode.ObserverEntry oe = list.get(j); if (oe.observer.asBinder() == binder) { list.remove(j); j--; numList--; } } } } } ...... } finally { restoreCallingIdentity(identityToken); } }
代碼也很明顯,就是遍歷mObserver.onChange了。同時代碼裏注意下mObserver.asBinder(),跨進程傳遞了observer,這裏asBinder後的對象是客戶端的IBinder,能夠用來判斷是否同一個對象。this
3、研究了一通,發現彷佛沒什麼問題,可是不管怎麼搞就是onchange回調了兩次。接口api中也沒有明確指明這種漏洞。不過,忽然想到,會不會Settings.Secure.putInt自己就作了notify!!!!
一、先看下Settings.puInt接口spa
/** * Convenience function for updating a single settings value as an * integer. This will either create a new entry in the table if the * given name does not exist, or modify the value of the existing row * with that name. Note that internally setting values are always * stored as strings, so this function converts the given value to a * string before storing it. * * @param cr The ContentResolver to access. * @param name The name of the setting to modify. * @param value The new value for the setting. * @return true if the value was set, false on database errors */ public static boolean putInt(ContentResolver cr, String name, int value) { return putIntForUser(cr, name, value, cr.getUserId()); }
接口描述中沒提到notify的事情
二、繼續往代碼深處看,仍是在Settings.java類中
private static class NameValueCache { ...... public boolean putStringForUser(ContentResolver cr, String name, String value, String tag, boolean makeDefault, final int userHandle) { try { Bundle arg = new Bundle(); arg.putString(Settings.NameValueTable.VALUE, value); arg.putInt(CALL_METHOD_USER_KEY, userHandle); if (tag != null) { arg.putString(CALL_METHOD_TAG_KEY, tag); } if (makeDefault) { arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true); } IContentProvider cp = mProviderHolder.getProvider(cr); cp.call(cr.getPackageName(), mProviderHolder.mUri.getAuthority(), mCallSetCommand, name, arg); } catch (RemoteException e) { Log.w(TAG, "Can't set key " + name + " in " + mUri, e); return false; } return true; } }
三、調用了IContentProvider.call(),代碼在SettingsProvider.java
private static int getRequestingUserId(Bundle args) { final int callingUserId = UserHandle.getCallingUserId(); return (args != null) ? args.getInt(Settings.CALL_METHOD_USER_KEY, callingUserId) : callingUserId; } @Override public Bundle call(String method, String name, Bundle args) { final int requestingUserId = getRequestingUserId(args); switch (method) { case Settings.CALL_METHOD_PUT_SECURE: { String value = getSettingValue(args); String tag = getSettingTag(args); final boolean makeDefault = getSettingMakeDefault(args); insertSecureSetting(name, value, tag, makeDefault, requestingUserId, false); break; } } } private boolean insertSecureSetting(String name, String value, String tag, boolean makeDefault, int requestingUserId, boolean forceNotify) { ...... if (forceNotify || success) { notifyForSettingsChange(key, name); } return success; } private void notifyForSettingsChange(int key, String name) { ...... // Always notify that our data changed mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget(); } private final class MyHandler extends Handler { private static final int MSG_NOTIFY_URI_CHANGED = 1; private static final int MSG_NOTIFY_DATA_CHANGED = 2; public MyHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_NOTIFY_URI_CHANGED: { final int userId = msg.arg1; Uri uri = (Uri) msg.obj; try { getContext().getContentResolver().notifyChange(uri, null, true, userId); } catch (SecurityException e) { Slog.w(LOG_TAG, "Failed to notify for " + userId + ": " + uri, e); } if (DEBUG || true) { Slog.v(LOG_TAG, "Notifying for " + userId + ": " + uri); } } break; case MSG_NOTIFY_DATA_CHANGED: { mBackupManager.dataChanged(); } break; } } }
handler內部作了getContext().getContentResolver().notifyChange
永遠不要懷疑谷歌的源碼有bug(除非你是專家),很明顯,最終的結論是多此一舉了。Settings.Secure.putInt後並不須要本身作getContentResolver().notifyChange。因此若是你碰到onchange回調兩次的問題,能夠追查下put的代碼,看是否已經作了notify