背景

项目中用到了ContentProvider存取数据,同时想动态监听数据变化。使用getContentResolver().registerContentObserver监听。

存数据代码

Settings.Secure.putInt(getContentResolver(), SECURE_KEY_VOLUME_UP, 1);getContentResolver().notifyChange(Settings.Secure.getUriFor(SECURE_KEY_VOLUME_UP), null);

代码很简单,Settings.Secure其实就是封装的ContentProvider用法,put值,同时notify告知所有监听者。

监听代码

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改变的日志。

解决此问题

一、首先用法是没有问题的,很标准。于是网上搜有没有同类问题。发现了一会类似的问题:
https://blog.csdn.net/serapme/article/details/7404233

二、于是好奇为啥会回调两次,初步怀疑是getContentResolver内部未知原因存了两个observer。
1、先看下ContentResolver注册代码

    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();        }    }

2、最终是调用了ContentService.registerContentObserver,代码在ContentService.java中

    @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,中间并没有什么逻辑添加两次。
3、register没问题,那会不会是notify有问题,于是看了notify代码,代码在ContentResolver.java

    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();        }    }

4、再看下ContentService.notifyChange,代码在ContentService.java

    @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,可以用来判断是否同一个对象。

三、研究了一通,发现似乎没什么问题,但是无论怎么搞就是onchange回调了两次。接口api中也没有明确指明这种漏洞。不过,突然想到,会不会Settings.Secure.putInt本身就做了notify!!!!
1、先看下Settings.puInt接口

        /**         * 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的事情
2、继续往代码深处看,还是在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;        }   }

3、调用了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

更多相关文章

  1. eclipse中查看android的SDK源代码
  2. Android(安卓)使用 URL 和 AsyncTask 加载网络数据
  3. Android(安卓)判断SD卡是否存在及容量查询
  4. Android(安卓)ListView控件显示数据库中图片
  5. android的短信发送全过程源代码分析
  6. Android(安卓)取消GridView和ListView item被点击时的效果
  7. android下数据库SQLite

随机推荐

  1. 关于ant 打包android引入第三方类库libra
  2. 浅谈Android五大布局(二)——RelativeLayou
  3. Android(安卓)OpenGL自学笔记
  4. Android多线程下安全访问数据库
  5. WakeLock
  6. 显示界面的时候直接获取到EditText焦点弹
  7. Android底部导航栏实现
  8. Android(安卓)Intent 使用 Parcel 反序列
  9. anroid发送短信接口
  10. eclipse 导入Android项目时报告 Invalid