Как поток, созданный приложением, будет считаться отличным от приложения ContentProvider?
У меня есть приложение, которое при получении уведомления от ContentObserver
изменения в ContentProvider
, пытается запросить провайдера в фоновом потоке. Это вызывает SecurityException
быть брошенным
8-10 15:54:29.577 3057-3200/com.xxxx.mobile.android.xxx W/Binder﹕ Обнаружено исключение RuntimeException из реализации заглушки Binder. java.lang.SecurityException: отказ в разрешении: чтение com.xxx.mobile.android.mdk.model.customer.ContentProvider uri content://com.xxx.mobile.android.consumer.xxx/vehicle from pid=0, uid=1000 требует, чтобы поставщик был экспортирован, или grantUriPermission() на android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:539) на android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:452) на android.content.ContentProvider$Transport.query(ContentProvider.java:205) на android.content.ContentResolver.query(ContentResolver.java:478) на android.content.ContentResolver.query(ContentResolver.java:422)
Как поток, созданный приложением, может получить другой UID, отличный от ContentProvider?
Размещая точку останова исключения в android.content.ContentProvider
я вижу это UserHandle.isSameApp(uid, mMyUid)
является false
а также UserHandle.isSameUser(uid, mMyUid)
является true
, Я также вижу, что UID провайдера составляет 10087.
3 ответа
У меня возникла та же проблема при попытке взаимодействия с моим ContentProvider в системном обратном вызове (LeScanCallback
). Проблема в том, что поток обратных вызовов принадлежит системе Android, а не моему приложению, даже если код находится в моем приложении.
Передача работы от обратного вызова до одного из потоков моего приложения перед попыткой взаимодействия с моим ContentProvider успешно разрешила проблему.
Чтобы уменьшить шаблон для создания и переработки потоков (необходим для частых обратных вызовов для уменьшения накладных расходов), я использовал AndroidAnnotation's @Background
аннотации к моему методу делегата (но я бы использовал Kotlin Coroutines сегодня).
Значение uid 1000 принадлежит системе Android. Многие функции Android включают передачу запросов в системный поток для обработки. Если во время этого события возникнет исключение, ошибка будет включать uid системы, а не исходный запросчик.
Для других пунктов:
UserHandle.isSameApp(uid, mMyUid) is false
UserHandle.isSameUser(uid, mMyUid) is true
Это проще всего объяснить, посмотрев на источник. На устройстве Android с многопользовательской поддержкой каждый пользователь определяется диапазоном UID. isSameApp
ложно, потому что модуль идентификаторов не совпадают:
public static final boolean isSameApp(int uid1, int uid2) {
return getAppId(uid1) == getAppId(uid2);
}
public static final int getAppId(int uid) {
return uid % PER_USER_RANGE;
}
Аналогично, два идентификатора принадлежат одному и тому же пользователю, поскольку они находятся в одном диапазоне:
public static final boolean isSameUser(int uid1, int uid2) {
return getUserId(uid1) == getUserId(uid2);
}
public static final int getUserId(int uid) {
if (MU_ENABLED) {
return uid / PER_USER_RANGE;
} else {
return 0;
}
}
Обратите внимание, что эта логика имеет недостатки, поскольку означает, что все системные Android-идентификаторы (< 10000) будут считаться "принадлежащими" первому пользователю.
Также обратите внимание, что если второй пользователь установит более 1000 приложений (!), Существует вероятность того, что приложение будет ошибочно принято за системное приложение (оба uid % PER_USER_RANGE
вернет 1000). Это не будет иметь большого значения, потому что сильная песочница предотвратит что-то слишком плохое.
Если Thread
запускается любым компонентом приложения, у которого есть поставщик, затем вы можете получить доступ к ContentProvider
без всяких SecurityException
,
Я использую ContentProvider
в моем приложении просто как дополнительный уровень абстракции, и я не предоставляю контент другим приложениям. Я получаю доступ к ContentProvider
в фоновом режиме (не AsyncTask
, но простой java.lang.Thread
). Я не получаю SecurityException
, Ниже приведен код из моего приложения.
AndroidManifest.xml
<provider
android:authorities="com.sample.provider"
android:name="com.sample.MyProvider"
android:exported="false" />
Основная деятельность
public void performContinue(Bundle extras){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
String AUTHORITY = "com.sample.provider";
Uri BASE_URI = Uri.parse("content://" + AUTHORITY);
Uri currentUri = BASE_URI.buildUpon().appendPath("SAMPLE_COUNT").build();
final Cursor query = InputActivity.this.getContentResolver().query(currentUri, null, null, null, null);
if (query != null) {
final int count = query.getCount();
Log.d("DEBUG","CONTENT = " + count);
}else{
Log.d("DEBUG","CONTENT = CURSOR NULL");
}
}
});
thread.setName("THREAD_1");
thread.start();
}
Я, кажется, не понимаю SecurityException
, В идеале мы должны использовать AsyncQueryHandler
для доступа к ContentProvider
потому что это позволяет вам выполнять весь процесс выборки в фоновом потоке и будет использовать поток пользовательского интерфейса для публикации результатов в пользовательском интерфейсе. Но, увидев этот пост, я просто хотел посмотреть, смогу ли я просто использовать Thread
и проверьте, смогу ли я получить к нему доступ без каких-либо исключений. Работает нормально.