【Android基础】系统数据库通信使用方式

本文介绍了Android系统数据库通信使用方式
Settings.System数据库本来是用来存储用户偏好设置的机制,在车载系统开发时,由于车辆的设置信号都发给了底层的控制器ECU等去记忆了,这一块更多的是被用来做键值对存储和 IPC跨进程通信使用。
存储位置和权限
在Android中,用户的默认设置和偏好设置是存在数据库中,在Android 6.0 以前,settings的数据是存在settings.db中,系统可以通过sqlite来进行读写。 这样的话,所有的第三方应用都可以对settings.db进行操作,修改用户设置的数据。存储位置为,{system, secure, global} 对应的是目录 /data/data/com.android.providers.settings/databases/settings.db 的三个表。
所以在 在Android 6.0版本以后,SettingsProvider被重构,从安全和性能等方面考虑,把SettingsProvider中原本保存在settings.db中的数据,目前全部保存在XML文件中。一般位于 /data/system/users/0 目录下,该目录的settings_global.xml,settings_secure.xml和settings_system.xml三个xml文件就是SettingsProvider中的数据文件。
Settingsprovider中对数据也进行了分类,分别是Global、System、Secure、Ssaid四种类型,说明如下:
- Global:所有的偏好设置对系统的所有用户公开,第三方APP有读没有写的权限;
- System:包含各种各样的用户偏好系统设置,第三方APP有读没有写的权限;
- Secure:安全性的用户偏好系统设置,第三方APP有读没有写的权限。
- 另外,还有一个不被熟知的Ssaid 表:此表包括所有应用的id;这样的话,只是可以从文件权限类型来做权限的管控,可以让第三方APP有读没有写的权限,或者直接不给读写权限等
车载使用很多,可以当作键值对存储使用,也可以多进程共享发通知使用。
第三方APP使用
读取数据
使用adb写入一个测试字段
montecarlo:/ # settings put global audio_test_result 234
使用Settings.Global.getInt来读取这个字段,可以看到显示。
CoroutineScope(Dispatchers.IO).launch {
val tesetData = Settings.Global.getInt(this@MainActivity.contentResolver, "audio_test_result")
Log.i(TAG, "getGlobalData : $tesetData")
}
监听
除了单次读取数据库的值,还可以通过 contentResolver.registerContentObserver 来添加持续的监听。
private val observer = object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
val stringData =
Settings.System.getString(
appContext.contentResolver,
ACTION_MUTUAL_NOTIFY
)
debugLog("onChange: data:$stringData")
}
}
fun registerSystemSettingOberver() {
appContext.contentResolver.registerContentObserver(
Settings.System.getUriFor(ACTION_MUTUAL_NOTIFY),
true,
observer
)
}
fun unRegisterSystemSettingOberver() {
appContext.contentResolver.unregisterContentObserver(
observer
)
}
尝试写入
CoroutineScope(Dispatchers.IO).launch {
Settings.Global.putInt(this@MainActivity.contentResolver, "audio_test_result", 2415)
}
报错信息可以看到系统拒绝了第三方APP的写入操作:
Process: com.example.composedemo, PID: 28630
java.lang.SecurityException: Permission denial: writing to settings requires:android.permission.WRITE_SECURE_SETTINGS
at android.os.Parcel.createExceptionOrNull(Parcel.java:3011)
at android.os.Parcel.createException(Parcel.java:2995)
at android.os.Parcel.readException(Parcel.java:2978)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:190)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:142)
at android.content.ContentProviderProxy.call(ContentProviderNative.java:732)
at android.provider.Settings$NameValueCache.putStringForUser(Settings.java:3017)
at android.provider.Settings$Global.putStringForUser(Settings.java:16970)
at android.provider.Settings$Global.putString(Settings.java:16811)
at android.provider.Settings$Global.putInt(Settings.java:17041)
at com.example.composedemo.MainActivity$onCreate$1.invokeSuspend(MainActivity.kt:42)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:115)
at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:103)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@d6b0268, Dispatchers.IO]
Caused by: android.os.RemoteException: Remote stack trace:
at com.android.providers.settings.SettingsProvider.enforceWritePermission(SettingsProvider.java:2299)
at com.android.providers.settings.SettingsProvider.mutateGlobalSetting(SettingsProvider.java:1452)
at com.android.providers.settings.SettingsProvider.insertGlobalSetting(SettingsProvider.java:1406)
at com.android.providers.settings.SettingsProvider.call(SettingsProvider.java:450)
at android.content.ContentProvider.call(ContentProvider.java:2511)
系统APP的通信
系统app可以写入系统数据库的内容,很多场景下也被用来作为 IPC 多进程通信的方式。
有两种使用场景
数据传输
一方来修改需要传输的值,另一方监听变化读取获取。
修改数据库的值,以字符串数据为例,调用 Settings.System.putString()
修改方:
fun changeSystemSettingData() {
val tsStringData = "test string"
Settings.System.putString(
appContext.contentResolver,
ACTION_MUTUAL_NOTIFY,
tsStringData
)
}
接收方,这里和第三方APP没有区别:
private val observer = object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
val stringData =
Settings.System.getString(
appContext.contentResolver,
ACTION_MUTUAL_NOTIFY
)
debugLog("onChange: data:$stringData")
}
}
fun registerSystemSettingOberver() {
appContext.contentResolver.registerContentObserver(
Settings.System.getUriFor(ACTION_MUTUAL_NOTIFY),
true,
observer
)
}
fun unRegisterSystemSettingOberver() {
appContext.contentResolver.unregisterContentObserver(
observer
)
}
单次通知式
这种类似于广播,做触发式的逻辑,但是希望点对点建立通信协议,互相发通知。
一般是在一个单独的Service里面加入监听和发送通知的逻辑,同时还要屏蔽自己发出去的通知。
修改数据库的值,以字符串数据为例,调用 Settings.System.putString()
需要注意的是,这里的onChange回调是只有在变化时才会调用的。如果两次写入的是一样的值,接收方是收不到通知的。
所以这种连续发通知式的调用,再调用写入之后,还要调用notifyChange()方法。
fun changeSystemSettingData() {
val tsStringData = "same string"
Settings.System.putString(
appContext.contentResolver,
ACTION_MUTUAL_NOTIFY,
tsStringData
)
appContext.contentResolver.notifyChange(
Settings.System.getUriFor(ACTION_MUTUAL_NOTIFY), null
)
}
如果是两方需要互相发通知怎么办呢,自己发出去的修改,自己的observer也收到了。
这时候我们打开notifyChange的源码看一看:
/**
* Notify registered observers that a row was updated and attempt to sync
* changes to the network.
* <p>
* To observe events sent through this call, use
* {@link #registerContentObserver(Uri, boolean, ContentObserver)}.
* <p>
* Starting in {@link android.os.Build.VERSION_CODES#O}, all content
* notifications must be backed by a valid {@link ContentProvider}.
*
* @param uri The uri of the content that was changed.
* @param observer The observer that originated the change, may be
* <code>null</null>. The observer that originated the change
* will only receive the notification if it has requested to
* receive self-change notifications by implementing
* {@link ContentObserver#deliverSelfNotifications()} to return
* true.
*/
public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer) {
notifyChange(uri, observer, true /* sync to network */);
}
在调用notifyChange的时候,将第二个参数observer传入自己这方的监听器,同时observer在继承的时候,需要复写deliverSelfNotifications()方法返回true,这样在自己发通知的时候,onChangee方法的回调selfChange标志位会被正确的置为true,可以用以筛选。
private val observer = object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
val stringData =
Settings.System.getString(
appContext.contentResolver,
ACTION_MUTUAL_NOTIFY
)
debugLog("onChange: data:$stringData selfchange:$selfChange")
}
override fun deliverSelfNotifications() = true
}