Xposed--Android N 以上 XSharedPreferences

  • Android N 以上使用 XSharedPreferences ,但是还是只读.

  • 资料来源:

    https://github.com/ElderDrivers/EdXposed/issues/149
    https://github.com/ElderDrivers/EdXposed/issues/260
    https://github.com/Mikanoshi/SudoHide
    https://github.com/GravityBox/GravityBox

  • 更新

    1
    2
    20.04.11 初始化
    20.04.27 更新权限

导语

  • 这算是远古巨坑吗?我也不知道,当时 MyPrivacy 直接降级 SDK 版本规避的 XSharedPreferences 问题,但该来的总是要来的…

  • NoWakeLock 的 IPC 方案分为两个部分,唤醒锁相关的数据保存,这个可以通过 ContentProvider 解决.但是拦截相关的数据确没办法等到系统启动完毕再加载,搞得我非常头疼.. ContentProvider 实在是无法满足要求..就这样停滞了…

  • 于是重新开始看代码模式..直到翻到了重力工具箱..重新搜索 issue ,这个一言难尽啊..

XSharedPreferences

  • XSharedPreferences 具体的使用就不多说了,这里仅仅是 N 以上如何使用 XSharedPreferences.

  • N 以前还能改改文件权限,曲线使用 XSharedPreferences ,但是 N 以后这条路也被堵死了,N 以后设备加密等也有很多新变化.随着 rovo89 的弃坑,官方解决遥遥无期..

  • 还有个问题是 N 以后,基本都有全局加密,在用户解锁前无法访问 /data .

  • 重力工具箱作者 @C3C0 发现可以把 xml 放到直接启动的设备加密存储,修改权限后,通过文件建立 XSharedPreferences.这样依旧能够正常读取数据.

  • 至此破案…

实现

  • 一个示例Xptest

  • Android 7.0 以后 /data 加密问题,可以使用直接启动模式,官方介绍

  • 简而言之,系统为了闹钟短信等无需解锁也要使用的应用,提供了两个存储位置.

    • 凭据加密存储,这是默认存储位置.仅在用户解锁设备后可用.
    • 设备加密存储,该存储位置在”直接启动”模式下和用户解锁设备后均可使用.(这是我们要的)
  • 使用设备加密存储也很简单,使用 Context.createDeviceProtectedStorageContext() 获取到对应设备存储的 Context ,其他一切如常.

  • 默认情况下设备加密存储在 “/data/user_de/0/包名” 下,这里是设备正常启动后,解锁前可以正常访问到的地方,当然权限设定少不了.

  • 有些 Rom 总是会默认的把文件/文件夹的权限改回去,如果是重启之后改回去,可以注册一个 ACTION_LOCKED_BOOT_COMPLETED 的广播,在每次启动时,把权限改回去.

  • 但是 @C3C0 还提到了有些自定义 Rom 上,开机状态下读写文件,权限都可能被改回去..重力工具箱的做法是注册文件监听期,每次写入文件后自动再改一次权限.(还没遇到,遇到再说) 已经遇到了…目前是每次读写文件后重新改一次文件,注册文件监听貌似没成.

  • 以下就以 SharedPreferences 为例.

  • 新建一个 SharedPreferences,保存测试值.

    1
    2
    3
    4
    5
    6
    pref = this.createDeviceProtectedStorageContext()
    .getSharedPreferences(preferencesFileName, MODE_PRIVATE)

    pref?.edit()
    ?.putString("Test", "Test233")
    ?.apply()
  • 修改权限(抄袭自 SudoHide 😂)

    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
    @SuppressLint("SetWorldReadable", "SetWorldWritable")
    fun fixPermissionsAsync() {
    AsyncTask.execute {
    try {
    Thread.sleep(500)
    } catch (t: Throwable) {
    }
    val pkgFolder = this.createDeviceProtectedStorageContext().filesDir.parentFile
    if (pkgFolder.exists()) {
    pkgFolder.setExecutable(true, false)
    pkgFolder.setReadable(true, false)
    //pkgFolder.setWritable(true, false);
    val sharedPrefsFolder =
    File(pkgFolder.absolutePath + "/shared_prefs")
    Log.d("Xposed.Test",": activity ${pkgFolder.absolutePath}/shared_prefs")
    if (sharedPrefsFolder.exists()) {
    sharedPrefsFolder.setExecutable(true, false)
    sharedPrefsFolder.setReadable(true, false)
    //sharedPrefsFolder.setWritable(true, false);
    val f =
    File(sharedPrefsFolder.absolutePath + "/" + preferencesFileName + "xml")
    if (f.exists()) {
    f.setReadable(true, false)
    f.setExecutable(true, false)
    //f.setWritable(true, false);
    }
    }
    }
    }
    }
  • initZygote 时读取

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    private val prefsFileProt =
    File("/data/user_de/0/com.js.xptest/shared_prefs/com.js.xptest_preferences.xml")

    var pref: XSharedPreferences? = null

    try {
    pref = XSharedPreferences(prefsFileProt)
    pref!!.edit().putString("Test2", "2333").apply
    } catch (t: Throwable) {
    XposedBridge.log("$TAG : $t")
    }
    XposedBridge.log(
    "$TAG : 1 ${pref?.getString("Test", "default")}," +
    "2 ${pref?.getString("Test2", "default"
    )}"
    )
  • 这里可以尝试一下写入,会提示只读错误.

结语

  • 这篇有点敷衍的感觉..终于解决了 XSharedPreferences 问题,接下来..就没了..要加班一段时间了.😂.