Android笔记-ContentProvider call方法

  • ContentProvider call方法 使用.

  • 资料来源:

    https://developer.android.com/guide/topics/providers/content-providers
    https://juejin.im/entry/5a4c39605188257c4d1b8a4a

  • 更新

    1
    2
    3
     20.04.17 初始化
    21.02.11 添加一个错误修正
    21.02.13 补充完整

导语

  • 开始实现的 NoWakeLock 使用了 ContentProvider 标准的查询和插入,结果实在不尽如人意.太死板了.
  • 进入瓶颈,去翻 Xprivacylua 的源码,发现 Xprivacylua 也使用了 ContentProvider ,不过大段是自定义的 call 方法.
  • 从网上资料来看,call 方法确实灵活多了,也是同步的调用,底层与查询/插入都是一样的实现.传输数据一次不能超过 1M.传输的数据需要可序列化.

声明 ContentProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class DSContentProvider : ContentProvider() {  
override fun onCreate(): Boolean {
return true
}

override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
//无序处理
if (extras == null) {
return null
}
return context?.let { ProviderHandler.getInstance(it).getMethod(method, extras) }
}

override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? {
return null
}

override fun insert(uri: Uri, values: ContentValues?): Uri? {
return uri
}

override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
return 0
}

override fun getType(uri: Uri): String? {
return null
}

override fun update(
uri: Uri, values: ContentValues?, selection: String?,
selectionArgs: Array<String>?
): Int {
return 0
}
}

在 AndroidManifest.xml -> application 下声明

1
2
3
4
5
<provider
android:name=".data.provider.DSContentProvider"
android:authorities="com.js.deepsleep"
android:enabled="true"
android:exported="false" />

call 方法

  • fun call(method: String, arg: String?, extras: Bundle?): Bundle? {}

    • method: String 一般是我们自定义 call 方法的方法名.
    • arg: String? 大概是可有可无的字段.
    • extras: Bundle? 传输数据的载体,需要从 Bundle 中取出对应数据.
  • 实际的例子也非常简单

    1
    2
    3
    4
    5
    6
    7
    override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
    //no need handler
    if (extras == null) {
    return null
    }
    return context?.let { ProviderHandler.getInstance(it).getMethod(method, extras) }
    }
  • 传入 method 到具体的 Handler,再根据具体的方法名,执行获取结果.(kt 真香😂)

  • 以一个 test 方法为例,因为 call 方法要求返回一个 Bundle? 这里直接返回了 “Test”.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @VisibleForTesting
    fun test(bundle: Bundle): Bundle? {
    val test = bundle.get("Test") as String?

    LogUtil.d(TAG, "$test")

    val tmp = Bundle()
    tmp.putString("Test", "Test")
    return tmp
    }
  • 调用 Test 方法

    1
    2
    3
    4
    5
    6
    fun getMethod(methodName: String, bundle: Bundle): Bundle? {
    return when (methodName) {
    "test" -> test(bundle)
    else -> null
    }
    }
  • AndroidTest 类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    class NWLContentProviderTest {
    private var mContentResolver: ContentResolver? = null
    private val authority = "com.js.nowakelock"

    @Before
    fun setUp() {
    val context = ApplicationProvider.getApplicationContext<Context>()
    mContentResolver = context.contentResolver
    }

    @Test
    fun call() {
    val method = "test"
    val url = Uri.parse("content://$authority")

    val extras = Bundle()
    extras.putString("Test", "Test")

    val bundle = mContentResolver?.call(url, method, null, extras)
    //if (bundle != null) {
    // LogUtil.d("test1", bundle.toString())
    //}
    assertEquals(bundle!!.getString("Test"), "Test")
    }
    }
  • 执行测试,取回的结果也是 “Test”.

AndroidX.test dependency error

参考 Set up project for AndroidX Test 添加 Android X Test 依赖

运行 NWLContentProviderTest 会提示错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Execution failed for task ':app:processDebugAndroidTestManifest'.
> Could not resolve all files for configuration ':app:debugAndroidTestRuntimeClasspath'.
> Could not resolve androidx.test:core:1.3.0.
Required by:
project :app
project :app > androidx.test.ext:junit:1.1.2
project :app > androidx.test.ext:truth:1.3.0
project :app > androidx.test.espresso:espresso-intents:3.3.0
> Cannot find a version of 'androidx.test:core' that satisfies the version constraints:
Dependency path 'DeepSleep:app:unspecified' --> 'androidx.test:core:1.3.0'
Constraint path 'DeepSleep:app:unspecified' --> 'androidx.test:core:{strictly 1.2.0}' because of the following reason: debugRuntimeClasspath uses version 1.2.0
Dependency path 'DeepSleep:app:unspecified' --> 'androidx.test.ext:junit:1.1.2' --> 'androidx.test:core:1.3.0'
Dependency path 'DeepSleep:app:unspecified' --> 'androidx.test.ext:truth:1.3.0' --> 'androidx.test:core:1.3.0'
Dependency path 'DeepSleep:app:unspecified' --> 'androidx.test.espresso:espresso-intents:3.3.0' --> 'androidx.test:core:1.3.0'

参考 AndroidX.test dependency error 发现是 androidx.fragment:fragment-testing 要求核心版本是 1.2…这不是大水冲了龙王庙吗…

原帖的解决方案没太看懂,反正先注释掉 androidx.fragment:fragment-testing 目前没有再使用.

结语

  • 为了个 IPC 还折腾了 aidl + service. 还有 call 配合 service,加上之前的 XSharedPreferences ,几乎把android ipc 折腾全了…
  • 之后就是填坑了,希望能尽快填完.