应用程序创建的线程如何被视为与应用程序的 ContentProvider 不同的应用程序?

How would a thread created by an app be considered a different app from the app's ContentProvider?

我有一个应用程序,当 ContentObserver 通知 ContentProvider 发生更改时,它会尝试在后台线程上查询提供程序。这会导致抛出 SecurityException

8-10 15:54:29.577    3057-3200/com.xxxx.mobile.android.xxx W/Binder﹕ Caught a RuntimeException from the binder stub implementation.
  java.lang.SecurityException: Permission Denial: reading com.xxx.mobile.android.mdk.model.customer.ContentProvider uri content://com.xxx.mobile.android.consumer.xxx/vehicle from pid=0, uid=1000 requires the provider be exported, or grantUriPermission()
at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:539)
           at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:452)
           at android.content.ContentProvider$Transport.query(ContentProvider.java:205)
           at android.content.ContentResolver.query(ContentResolver.java:478)
           at android.content.ContentResolver.query(ContentResolver.java:422)

应用程序创建的线程如何以与应用程序的 ContentProvider 不同的 UID 结束?

通过在 android.content.ContentProvider 中放置一个异常断点,我看到 UserHandle.isSameApp(uid, mMyUid)falseUserHandle.isSameUser(uid, mMyUid)true。我还看到提供者 UID 是 10087。

uid值1000属于Android系统。 Android 的许多功能都涉及将请求代理到系统线程进行处理。如果在此期间抛出异常,错误将包括系统的uid,而不是原始请求者。

其他要点:

UserHandle.isSameApp(uid, mMyUid) is false

UserHandle.isSameUser(uid, mMyUid) is true

这些最容易通过查看 source 来解释。在支持多用户的 Android 设备上,每个用户都由一系列 UID 定义。 isSameApp 为假,因为 id 的模数不匹配:

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

同样,这两个id属于同一个用户,因为他们生活在同一个范围内:

 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 系统 uid (< 10000) 都将假定为第一个用户的 "belong"。

另请注意,如果第二个用户安装了超过 1000 个应用程序 (!),则应用程序可能会被误认为是系统应用程序(uid % PER_USER_RANGE 都将 return 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" />

MainActivity

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,因为这允许您在后台线程中执行所有获取过程,并将使用 UI 线程来 post结果为 UI。但是在看到这个 post 之后,我只是想看看我是否可以只使用 Thread 并检查我是否仍然可以毫无异常地访问它。它工作正常。

我在系统回调中尝试与我的 ContentProvider 交互时遇到了同样的问题 (LeScanCallback)。问题是回调线程属于 Android 系统,而不是我的应用程序,即使代码在我的应用程序中。

在尝试与我的 ContentProvider 交互之前将工作从回调传递到我的应用程序线程之一成功解决了问题。

为了减少线程创建和回收的样板文件(需要频繁回调以减少开销),我在我的委托方法上使用了 AndroidAnnotation's @Background annotation(但今天会使用 Kotlin Coroutines)。