- 卸载不删除
- 需要外置存储读取权限
- 用于Xposed插件配件也是很爽的
package com.meetunknown.up.Util.saved
import android.os.Environment
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import java.io.File
import java.util.*
object Saved {
private val fileDir = try {
Environment.getExternalStorageDirectory().absolutePath
} catch (e: ArrayIndexOutOfBoundsException) {
"/storage/emulated/0"
}
private val fileName = "tzs.json"
private val filePath = fileDir + File.separator + fileName
private lateinit var mMap: ObserverableMap<String, Any>
private val gson = GsonBuilder().setPrettyPrinting().create()
private val mFile by lazy { File(filePath) }
private var mLastModified: Long = -1L
val obj = Object()
init {
syncFromFile()
}
private fun syncFromFile() {
if (mFile.lastModified() != mLastModified) {
val t = Thread {
synchronized(obj) {
mFile.setReadable(true, false)
mFile.setWritable(true)
try {
if (mFile.exists().not() || mFile.readText().isNullOrEmpty()) {
mFile.createNewFile()
mMap = ObserverableMap()
} else {
mMap = gson.fromJson(mFile.readText(), object : TypeToken<ObserverableMap<String, Any>>() {}.type)
}
mMap.syncFromFile = { syncFromFile() }
mMap.syncToFile = { syncToFile() }
} catch (e: Exception) {
}
mLastModified = mFile.lastModified()
obj.notifyAll()
}
}
t.name = "Meet-Load Saved"
t.start()
}
}
private fun syncToFile() {
synchronized(obj){
awaitLoaded()
mFile.writeText(gson.toJson(mMap))
mLastModified = mFile.lastModified()
obj.notifyAll()
}
}
private fun awaitLoaded() {
while (mLastModified != mFile.lastModified() || !::mMap.isInitialized) {
try {
obj.wait()
} catch (e: InterruptedException) {
}
}
}
private inline fun <T> syncAll(key:String,ok:((key:String)->T)):T{
synchronized(obj) {
awaitLoaded()
return ok.invoke(key)
}
}
fun contains(key: String): Boolean {
return syncAll(key) { key in mMap }
}
fun getString(key: String, defaultValue: String = ""): String {
synchronized(obj) {
awaitLoaded()
return (mMap[key] as String?) ?: defaultValue
}
}
fun getInt(key: String, defaultValue: Int = 0): Int {
synchronized(obj) {
awaitLoaded()
return ((mMap[key] as? Number)?.toInt()) ?: defaultValue
}
}
fun getLong(key: String, defaultValue: Long = 0L): Long {
synchronized(obj) {
awaitLoaded()
return ((mMap[key] as? Number)?.toLong()) ?: defaultValue
}
}
fun getBoolean(key: String, defaultValue: Boolean = false): Boolean {
synchronized(obj) {
awaitLoaded()
return mMap[key]?.toString()?.toBoolean() ?: defaultValue
}
}
fun update(key:String, value:Any){
synchronized(obj){
awaitLoaded()
mMap[key] = value
}
}
}
class ObserverableMap<K, T> : LinkedHashMap<K, T> {
var syncFromFile: (() -> Unit)? = null
var syncToFile: (() -> Unit)? = null
constructor() : super()
constructor(p0: MutableMap<out K, out T>?) : super(p0)
override fun remove(key: K): T? {
syncFromFile?.invoke()
return super.remove(key).also { syncToFile?.invoke() }
}
override fun get(key: K): T? {
syncFromFile?.invoke()
return super.get(key)
}
override fun put(key: K, value: T): T? {
syncFromFile?.invoke()
return super.put(key, value).also { syncToFile?.invoke() }
}
override fun clear() {
super.clear().also { syncToFile?.invoke() }
}
override fun toString(): String {
var temp = ""
for (en in this.entries) {
temp = temp.plus("${(en.value as Any)::class.qualifiedName!!.padEnd(40)} ${(en.key as String).padEnd(20)} ${en.value} \n")
}
return temp
}
}