registerContentObserverコールバック2回、ContentObserverコールバック2回
背景
プロジェクトではContentProviderがデータにアクセスし、データの変化を動的に傍受したいと考えています.getContentResolver()を使用します.registerContentObserver傍受.
データ・コードの保存
コードは簡単だSecureは実はパッケージされたContentProviderの使い方であり、put値であり、notifyはすべてのリスナーに知らせる.
リスニングコード
結果
ログを印刷すると、2つのonChangeが変更されるログが見つかります.
この問題を解決する
一、まず使い方は問題なく、標準的です.そこでネット上で同類の問題があるかどうかを調べた.似たような問題が見つかりましたhttps://blog.csdn.net/serapme/article/details/7404233
二、そこでなぜ2回コールバックしたのか知りたくて、getContentResolverの内部に未知の原因で2つのobserverが保存されているのではないかと初歩的に疑っています.1、ContentResolver登録コードを見てから
2、最終的にContentServicesが呼び出される.registerContentObserver,コードはContentServices.JAvaで
コードは明らかにmObserversに保存され、listであり、その間に2回の論理追加はありません.3、registerは大丈夫です.それはnotifyに問題があるのではないでしょうか.そこでnotifyコードを見て、コードはContentResolverにあります.java
4、ContentServicesをもう一度見てください.notifyChange、コードはContentServicesにあります.java
コードも明らかにmObserverを巡る.onChangeしました.同時にコードの中でmObserverに注意します.asBinder()は、プロセス間でobserverが渡されます.ここでasBinderの後のオブジェクトはクライアントのIBinderで、同じオブジェクトかどうかを判断するために使用できます.
三、研究してみると、何の問題もないようだが、いずれにしてもonchangeは2回もコールバックした.インタフェースapiにもこのような脆弱性は明確に示されていない.でも、ふと思った.Secure.putInt自体がnotifyを作った!!!1、まずセティングを見てみましょう.puIntインタフェース
インタフェースの説明ではnotifyのことは言及していません2、コードの奥を見続けますか、それともSettingsですか.JAvaクラス
3、IContentProviderを呼び出した.Call()コードはSettingsProvider.java
handler内部でgetContext().getContentResolver().notifyChange
事件を解決する
グーグルのソースコードにバグがあることを決して疑わないでください(専門家でない限り)、明らかに、最終的な結論は蛇を描くことです.Settings.Secure.putInt後は自分でgetContentResolver()を作る必要はありません.notifyChange.だからonchangeコールバックの2回の問題に遭遇したら、putのコードを追跡して、notifyをしたかどうかを見ることができます.
プロジェクトではContentProviderがデータにアクセスし、データの変化を動的に傍受したいと考えています.getContentResolver()を使用します.registerContentObserver傍受.
データ・コードの保存
Settings.Secure.putInt(getContentResolver(), SECURE_KEY_VOLUME_UP, 1);
getContentResolver().notifyChange(Settings.Secure.getUriFor(SECURE_KEY_VOLUME_UP), null);
コードは簡単だ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);
}
};
結果
ログを印刷すると、2つのonChangeが変更されるログが見つかります.
この問題を解決する
一、まず使い方は問題なく、標準的です.そこでネット上で同類の問題があるかどうかを調べた.似たような問題が見つかりましたhttps://blog.csdn.net/serapme/article/details/7404233
二、そこでなぜ2回コールバックしたのか知りたくて、getContentResolverの内部に未知の原因で2つの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、最終的にContentServicesが呼び出される.registerContentObserver,コードはContentServices.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であり、その間に2回の論理追加はありません.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、ContentServicesをもう一度見てください.notifyChange、コードはContentServicesにあります.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は2回もコールバックした.インタフェースapiにもこのような脆弱性は明確に示されていない.でも、ふと思った.Secure.putInt自体がnotifyを作った!!!1、まずセティングを見てみましょう.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
事件を解決する
グーグルのソースコードにバグがあることを決して疑わないでください(専門家でない限り)、明らかに、最終的な結論は蛇を描くことです.Settings.Secure.putInt後は自分でgetContentResolver()を作る必要はありません.notifyChange.だからonchangeコールバックの2回の問題に遭遇したら、putのコードを追跡して、notifyをしたかどうかを見ることができます.