diff --git a/LibKeyValue/src/main/java/com/mx/keyvalue/MXKeyValue.kt b/LibKeyValue/src/main/java/com/mx/keyvalue/MXKeyValue.kt index 35de7836ca72932774df976618c917faf3450cd3..b046ec99ccb1a312708a9e37a1af64e6846c25d7 100644 --- a/LibKeyValue/src/main/java/com/mx/keyvalue/MXKeyValue.kt +++ b/LibKeyValue/src/main/java/com/mx/keyvalue/MXKeyValue.kt @@ -5,14 +5,26 @@ import com.mx.keyvalue.base.IMXKeyValue import com.mx.keyvalue.db.MXDBKeyValue import com.mx.keyvalue.secret.IMXSecret import com.mx.keyvalue.secret.MXNoSecret +import com.mx.keyvalue.utils.MXKVObservable +import com.mx.keyvalue.utils.MXKVObserver +import com.mx.keyvalue.utils.MXUtils +import kotlin.collections.HashMap class MXKeyValue( private val context: Context, - name: String, - secret: IMXSecret = MXNoSecret() + private val name: String, + private val secret: IMXSecret = MXNoSecret() ) { - private val dbKeyValue: IMXKeyValue = + companion object { + fun setDebug(debug: Boolean) { + MXUtils.setDebug(debug) + } + } + + private val observerSet = HashMap() + private val dbKeyValue: IMXKeyValue by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { MXDBKeyValue(context.applicationContext, name.trim(), secret) + } init { if (!secret.validate()) { @@ -40,11 +52,13 @@ class MXKeyValue( fun set(key: String, value: String?, expire_time: Long? = null): Boolean { val key_trim = key.trim() if (key_trim.isBlank()) return false - return if (value != null && value.isNotEmpty()) { + val result = if (value != null && value.isNotEmpty()) { dbKeyValue.set(key_trim, value, expire_time) } else { dbKeyValue.delete(key_trim) } + observerSet[key]?.set(value) + return result } /** @@ -62,7 +76,9 @@ class MXKeyValue( fun delete(key: String): Boolean { val key_trim = key.trim() if (key_trim.isBlank()) return false - return dbKeyValue.delete(key_trim) + val result = dbKeyValue.delete(key_trim) + observerSet[key]?.set(null) + return result } fun getAll(): Map { @@ -82,4 +98,23 @@ class MXKeyValue( fun cleanAll(): Boolean { return dbKeyValue.cleanAll() } + + fun addKeyObserver(key: String, observer: MXKVObserver) { + var observable = observerSet[key] + if (observable == null) { + observable = MXKVObservable(key, get(key)) + observerSet[key] = observable + } + observable.addObserver(observer) + } + + fun removeKeyObserver(key: String, observer: MXKVObserver) { + val observable = observerSet[key] ?: return + observable.deleteObserver(observer) + } + + fun release() { + observerSet.clear() + dbKeyValue.release() + } } \ No newline at end of file diff --git a/LibKeyValue/src/main/java/com/mx/keyvalue/base/IMXKeyValue.kt b/LibKeyValue/src/main/java/com/mx/keyvalue/base/IMXKeyValue.kt index 6de043bd7779119f245ef2925c9d9e46a78486c3..7bfed2756fa9ecd404c06b63852c9a3fe10f1800 100644 --- a/LibKeyValue/src/main/java/com/mx/keyvalue/base/IMXKeyValue.kt +++ b/LibKeyValue/src/main/java/com/mx/keyvalue/base/IMXKeyValue.kt @@ -9,4 +9,6 @@ internal interface IMXKeyValue { fun cleanExpire(): Boolean fun cleanAll(): Boolean + + fun release() } \ No newline at end of file diff --git a/LibKeyValue/src/main/java/com/mx/keyvalue/db/MXDBKeyValue.kt b/LibKeyValue/src/main/java/com/mx/keyvalue/db/MXDBKeyValue.kt index 69c5d241fafa21f2f4e4208b6684395cef19c02c..8b5947a34c5f8cc7ece227e72039c68a3ffd3b91 100644 --- a/LibKeyValue/src/main/java/com/mx/keyvalue/db/MXDBKeyValue.kt +++ b/LibKeyValue/src/main/java/com/mx/keyvalue/db/MXDBKeyValue.kt @@ -3,27 +3,41 @@ package com.mx.keyvalue.db import android.content.ContentValues import android.content.Context import android.database.Cursor +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper import com.mx.keyvalue.base.IMXKeyValue import com.mx.keyvalue.secret.IMXSecret +import com.mx.keyvalue.utils.MXUtils import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read +import kotlin.concurrent.write internal class MXDBKeyValue( - context: Context, + private val context: Context, private val dbName: String, private val mxSecret: IMXSecret ) : IMXKeyValue { private val lock = ReentrantReadWriteLock(true) - private val read_lock = lock.readLock() - private val write_lock = lock.writeLock() + private var openHelper: SQLiteOpenHelper? = null - private val openHelper = MXKVSQLiteOpenHelper(context, dbName) + private fun getDatabase(): SQLiteDatabase { + var sqLiteOpenHelper = openHelper + if (sqLiteOpenHelper == null) { + synchronized(this) { + if (sqLiteOpenHelper == null) { + sqLiteOpenHelper = MXKVSQLiteOpenHelper(context, dbName) + this.openHelper = sqLiteOpenHelper + } + } + } + return sqLiteOpenHelper!!.writableDatabase + } override fun get(key: String): String? { - read_lock.lock() - try { - val database = openHelper.readableDatabase + lock.read { var cursor: Cursor? = null try { + val database = getDatabase() cursor = database.query( dbName, arrayOf( @@ -46,27 +60,22 @@ internal class MXDBKeyValue( } } catch (e: Exception) { e.printStackTrace() + MXUtils.log("get错误 -> $key -- ${e.message}") } finally { try { cursor?.close() } catch (e: Exception) { } - try { - // database.close() - } catch (e: Exception) { - } } - } finally { - read_lock.unlock() } return null } override fun set(key: String, value: String, dead_time: Long?): Boolean { - write_lock.lock() - try { - val database = openHelper.writableDatabase + lock.write { + val database = getDatabase() try { + database.beginTransaction() val salt = mxSecret.generalSalt() val value_encrypt = mxSecret.encrypt(key, value, salt) @@ -76,46 +85,44 @@ internal class MXDBKeyValue( values.put(MXKVSQLiteOpenHelper.DB_KEY_SALT, salt) values.put(MXKVSQLiteOpenHelper.DB_KEY_UPDATE_TIME, System.currentTimeMillis()) values.put(MXKVSQLiteOpenHelper.DB_KEY_DEAD_TIME, dead_time ?: 0L) - return database.replace(dbName, null, values) >= 0 + val result = database.replace(dbName, null, values) >= 0 + database.setTransactionSuccessful() + return result } catch (e: Exception) { e.printStackTrace() + MXUtils.log("set错误 -> $key = $value ; $dead_time -- ${e.message}") } finally { - // database.close() + database.endTransaction() } - } finally { - write_lock.unlock() } return false } override fun delete(key: String): Boolean { - write_lock.lock() - try { - val database = openHelper.writableDatabase + lock.write { + val database = getDatabase() try { - return database.delete( + database.beginTransaction() + val result = database.delete( dbName, "${MXKVSQLiteOpenHelper.DB_KEY_NAME}=?", arrayOf(key) ) > 0 + database.setTransactionSuccessful() + return result } catch (e: Exception) { e.printStackTrace() + MXUtils.log("delete错误 -> $key -- ${e.message}") } finally { - try { - // database.close() - } catch (e: Exception) { - } + database.endTransaction() } - } finally { - write_lock.unlock() } return false } override fun getAll(): Map { - read_lock.lock() - try { - val database = openHelper.readableDatabase + lock.read { + val database = getDatabase() val result = HashMap() var cursor: Cursor? = null try { @@ -136,62 +143,53 @@ internal class MXDBKeyValue( } } catch (e: Exception) { e.printStackTrace() + MXUtils.log("getAll错误 -> ${e.message}") } finally { try { cursor?.close() } catch (e: Exception) { } - try { - // database.close() - } catch (e: Exception) { - } } return result - } finally { - read_lock.unlock() } } override fun cleanExpire(): Boolean { - write_lock.lock() - try { - val database = openHelper.writableDatabase + lock.write { + val database = getDatabase() try { - return database.delete( + database.beginTransaction() + val result = database.delete( dbName, "${MXKVSQLiteOpenHelper.DB_KEY_DEAD_TIME}>0 and ${MXKVSQLiteOpenHelper.DB_KEY_DEAD_TIME} 0 + database.setTransactionSuccessful() + return result } catch (e: Exception) { e.printStackTrace() + MXUtils.log("cleanExpire错误 -> ${e.message}") } finally { - try { - // database.close() - } catch (e: Exception) { - } + database.endTransaction() } - } finally { - write_lock.unlock() } return false } override fun cleanAll(): Boolean { - write_lock.lock() - try { - val database = openHelper.writableDatabase + lock.write { + val database = getDatabase() try { - return database.delete(dbName, null, null) > 0 + database.beginTransaction() + val result = database.delete(dbName, null, null) > 0 + database.setTransactionSuccessful() + return result } catch (e: Exception) { e.printStackTrace() + MXUtils.log("cleanAll错误 -> ${e.message}") } finally { - try { - // database.close() - } catch (e: Exception) { - } + database.endTransaction() } - } finally { - write_lock.unlock() } return false } @@ -216,9 +214,14 @@ internal class MXDBKeyValue( value = mxSecret.decrypt(key, value, salt) return Pair(key, value) - } catch (e: java.lang.Exception) { + } catch (e: Exception) { e.printStackTrace() } return null } + + override fun release() { + openHelper?.close() + openHelper = null + } } \ No newline at end of file diff --git a/LibKeyValue/src/main/java/com/mx/keyvalue/secret/MXAESSecret.kt b/LibKeyValue/src/main/java/com/mx/keyvalue/secret/MXAESSecret.kt index dc001c7ea676cdebf18e31927b51b2cb58c4406b..cde7dc7461f6d975f010949939a95a0b956a7254 100644 --- a/LibKeyValue/src/main/java/com/mx/keyvalue/secret/MXAESSecret.kt +++ b/LibKeyValue/src/main/java/com/mx/keyvalue/secret/MXAESSecret.kt @@ -45,9 +45,6 @@ open class MXAESSecret(private val key: String) : IMXSecret { private val transformation = "AES/CBC/PKCS5Padding" - private var encryptCipher: Cipher? = null - private var decryptCipher: Cipher? = null - private fun generalMixKey(): ByteArray { val list = key.toMutableList() val keys = StringBuilder() @@ -74,16 +71,16 @@ open class MXAESSecret(private val key: String) : IMXSecret { return MXUtils.md5(keys.toString().toByteArray(), 16).toByteArray() } - init { - val keySpec = SecretKeySpec(generalMixKey(), "AES") - val ivSpec = IvParameterSpec(generalMixIv()) - encryptCipher = Cipher.getInstance(transformation).apply { + private val keySpec = SecretKeySpec(generalMixKey(), "AES") + private val ivSpec = IvParameterSpec(generalMixIv()) + private val encryptCipher: Cipher + get() = Cipher.getInstance(transformation).apply { init(Cipher.ENCRYPT_MODE, keySpec, ivSpec) } - decryptCipher = Cipher.getInstance(transformation).apply { + private val decryptCipher: Cipher + get() = Cipher.getInstance(transformation).apply { init(Cipher.DECRYPT_MODE, keySpec, ivSpec) } - } override fun generalSalt(): String { val length = Random.nextInt(15, 30) @@ -92,7 +89,7 @@ open class MXAESSecret(private val key: String) : IMXSecret { override fun encrypt(key: String, value: String, salt: String): String? { val relBytes = try { - encryptCipher?.doFinal((salt + value + key).toByteArray()) ?: return null + encryptCipher.doFinal((salt + value + key).toByteArray()) ?: return null } catch (e: Exception) { return null } @@ -102,7 +99,7 @@ open class MXAESSecret(private val key: String) : IMXSecret { override fun decrypt(key: String, secretValue: String, salt: String): String? { val bytes = try { - decryptCipher?.doFinal(Base64.decode(secretValue, Base64.DEFAULT)) ?: return null + decryptCipher.doFinal(Base64.decode(secretValue, Base64.DEFAULT)) ?: return null } catch (e: Exception) { return null } diff --git a/LibKeyValue/src/main/java/com/mx/keyvalue/utils/MXKVObservable.kt b/LibKeyValue/src/main/java/com/mx/keyvalue/utils/MXKVObservable.kt new file mode 100644 index 0000000000000000000000000000000000000000..8f339a52836f0fe4b05d2cf758185aeae0f29a64 --- /dev/null +++ b/LibKeyValue/src/main/java/com/mx/keyvalue/utils/MXKVObservable.kt @@ -0,0 +1,55 @@ +package com.mx.keyvalue.utils + +import android.os.Handler +import android.os.Looper + +internal class MXKVObservable(val key: String, defaultValue: String?) { + private val mHandler = Handler(Looper.getMainLooper()) + private val lock = Object() + private val observerList = HashSet() + + private var _value: String? = defaultValue + + fun set(value: String?) { + if (value == _value) { + return + } + _value = value + val list = synchronized(lock) { + observerList.toList() + } + if (list.isEmpty()) return + if (Looper.myLooper() == Looper.getMainLooper()) { + list.forEach { it.onChange(key, value) } + } else { + mHandler.post { + list.forEach { it.onChange(key, value) } + } + } + } + + fun get() = _value + + fun addObserver(o: MXKVObserver?) { + o ?: return + synchronized(lock) { + observerList.add(o) + } + mHandler.post { o.onChange(key, _value) } + } + + fun deleteObserver(o: MXKVObserver?) { + o ?: return + synchronized(lock) { + observerList.remove(o) + } + } + + fun deleteObservers() { + synchronized(lock) { + mHandler.removeCallbacksAndMessages(null) + observerList.clear() + } + } +} + diff --git a/LibKeyValue/src/main/java/com/mx/keyvalue/utils/MXKVObserver.kt b/LibKeyValue/src/main/java/com/mx/keyvalue/utils/MXKVObserver.kt new file mode 100644 index 0000000000000000000000000000000000000000..55936e8330bb358149557df6e396b4aa1aa5eecb --- /dev/null +++ b/LibKeyValue/src/main/java/com/mx/keyvalue/utils/MXKVObserver.kt @@ -0,0 +1,5 @@ +package com.mx.keyvalue.utils + +interface MXKVObserver { + fun onChange(key: String, value: String?) +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4431f16cc95cdd566563d84cbcaa66a134dba16d..3fdb13469b6489b07192ee28db324ce45398770a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ package="com.mx.example"> @@ -18,7 +19,9 @@ - + + + \ No newline at end of file diff --git a/app/src/main/java/com/mx/example/MainActivity.kt b/app/src/main/java/com/mx/example/MainActivity.kt deleted file mode 100644 index a43237dc936d575f5cf3beda5f4436c2fd69fc4c..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/mx/example/MainActivity.kt +++ /dev/null @@ -1,169 +0,0 @@ -package com.mx.example - -import android.os.Bundle -import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity -import com.fasterxml.jackson.annotation.JsonIgnoreProperties -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.registerKotlinModule -import com.mx.keyvalue.MXKeyValue -import com.mx.keyvalue.delegate.* -import com.mx.keyvalue.secret.MXAESSecret -import com.mx.keyvalue.secret.MXNoSecret -import kotlinx.android.synthetic.main.activity_main.* -import java.lang.StringBuilder -import java.util.concurrent.TimeUnit -import kotlin.concurrent.thread -import kotlin.random.Random - -class MainActivity : AppCompatActivity() { - private val KV by lazy { - MXKeyValue( - application, "mx_kv_test", - MXAESSecret("89qew0lkcjz;lkui1=2=--093475kjhzcklj") - ) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - KV.cleanAll() - syncTestTxv.setOnClickListener { - repeat(10) { - thread { - repeat(100000) { - val key = generalString(12) - KV.get(key) - Thread.sleep(Random.nextLong(10, 100)) - } - } - } - repeat(10) { - thread { - repeat(100000) { - val key = generalString(12) - val value = generalString(1280) - KV.set(key, value) - Thread.sleep(Random.nextLong(10, 100)) - } - } - } - } - - - setExpireTxv.setOnClickListener { - KV.set( - "test_expire_key", - "1分钟失效:" + System.currentTimeMillis(), - System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(1) - ) - Toast.makeText(this, KV.get("test_expire_key", "失效"), Toast.LENGTH_SHORT).show() - } - readExpireTxv.setOnClickListener { - Toast.makeText(this, KV.get("test_expire_key", "失效"), Toast.LENGTH_SHORT).show() - } - deleteTxv.setOnClickListener { - KV.delete("test_expire_key") - Toast.makeText(this, KV.get("test_expire_key", "失效"), Toast.LENGTH_SHORT).show() - } - - var spend = 0L - val time = 200 - repeat(time) { - val key = generalString(12) - val value = generalString(1280) - val start = System.currentTimeMillis() - KV.set(key, value) - val read_value = KV.get(key) - if (read_value != value) { - println("错误:$key -> $value -> $read_value") - } else { -// println("正确:$key -> $value") - } - spend += (System.currentTimeMillis() - start) - } - println("平均耗时:${spend / time.toFloat()} ms") - - var boolDelegate by MXBoolDelegate(KV, "bool_test", true) - println("测试 MXBoolDelegate -> $boolDelegate") - boolDelegate = false - println("测试 MXBoolDelegate -> $boolDelegate") - - - var doubleDelegate by MXDoubleDelegate(KV, "double_test", 1.0) - println("测试 MXDoubleDelegate -> $doubleDelegate") - doubleDelegate = 2.0 - println("测试 MXDoubleDelegate -> $doubleDelegate") - - - var floatDelegate by MXFloatDelegate(KV, "float_test", 1f) - println("测试 MXFloatDelegate -> $floatDelegate") - floatDelegate = 2f - println("测试 MXFloatDelegate -> $floatDelegate") - - - var intDelegate by MXIntDelegate(KV, "int_test", 1) - println("测试 MXIntDelegate -> $intDelegate") - intDelegate = 2 - println("测试 MXIntDelegate -> $intDelegate") - - - var longDelegate by MXLongDelegate(KV, "long_test", 1) - println("测试 MXLongDelegate -> $longDelegate") - longDelegate = 2 - println("测试 MXLongDelegate -> $longDelegate") - - - var stringDelegate by MXStringDelegate(KV, "string_test", "testdef") - println("测试 MXStringDelegate -> $stringDelegate") - stringDelegate = "2" - println("测试 MXStringDelegate -> $stringDelegate") - - - var beanDelegate by MXBeanDelegate(KV, TestBean::class.java, "bean_test", null) - println("测试 BeanDelegate -> ${beanDelegate?.id} -> ${beanDelegate?.name}") - beanDelegate = TestBean("2sdf", "name") - println("测试 BeanDelegate -> ${beanDelegate?.id} -> ${beanDelegate?.name}") - - } - - private fun generalString(size: Int): String { - val KEYS = ('a'..'z') + ('A'..'Z') + ('0'..'9') - val stringBuilder = StringBuilder() - repeat(size) { - stringBuilder.append(KEYS.random()) - } - return stringBuilder.toString() - } - - - @JsonIgnoreProperties(ignoreUnknown = true) - data class TestBean constructor(val id: String, val name: String) - - class MXBeanDelegate( - kv: MXKeyValue, - private val clazz: Class, - name: String, - default: T - ) : MXBaseDelegate(kv, name, default) { - override fun stringToObject(value: String): T { - try { - val mapper = ObjectMapper() - mapper.registerKotlinModule() - return mapper.readValue(value, clazz) - } catch (e: Exception) { - } - return default - } - - override fun objectToString(obj: T): String? { - obj ?: return null - try { - val mapper = ObjectMapper() - return mapper.writeValueAsString(obj) - } catch (e: Exception) { - } - return null - } - } -} diff --git a/app/src/main/java/com/mx/example/MyApp.kt b/app/src/main/java/com/mx/example/MyApp.kt new file mode 100644 index 0000000000000000000000000000000000000000..e7d70de4c65f94b281c7bdad89f21d941eb52700 --- /dev/null +++ b/app/src/main/java/com/mx/example/MyApp.kt @@ -0,0 +1,18 @@ +package com.mx.example + +import android.app.Application +import com.mx.keyvalue.MXKeyValue + +class MyApp : Application() { + companion object { + var _appContext: Application? = null + val appContext: Application + get() = _appContext!! + } + + override fun onCreate() { + super.onCreate() + _appContext = this + MXKeyValue.setDebug(true) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mx/example/app/DelegateTestActivity.kt b/app/src/main/java/com/mx/example/app/DelegateTestActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..006c537ff2aadb472660441ba8691101a9692399 --- /dev/null +++ b/app/src/main/java/com/mx/example/app/DelegateTestActivity.kt @@ -0,0 +1,99 @@ +package com.mx.example.app + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.registerKotlinModule +import com.mx.example.R +import com.mx.example.utils.SPUtils +import com.mx.keyvalue.MXKeyValue +import com.mx.keyvalue.delegate.* +import kotlinx.android.synthetic.main.activity_delegate_test.* + +class DelegateTestActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_delegate_test) + + testBtn.setOnClickListener { + var boolDelegate by MXBoolDelegate(SPUtils.KV, "bool_test", true) + println("测试 MXBoolDelegate -> $boolDelegate") + boolDelegate = false + println("测试 MXBoolDelegate -> $boolDelegate") + + + var doubleDelegate by MXDoubleDelegate(SPUtils.KV, "double_test", 1.0) + println("测试 MXDoubleDelegate -> $doubleDelegate") + doubleDelegate = 2.0 + println("测试 MXDoubleDelegate -> $doubleDelegate") + + + var floatDelegate by MXFloatDelegate(SPUtils.KV, "float_test", 1f) + println("测试 MXFloatDelegate -> $floatDelegate") + floatDelegate = 2f + println("测试 MXFloatDelegate -> $floatDelegate") + + + var intDelegate by MXIntDelegate(SPUtils.KV, "int_test", 1) + println("测试 MXIntDelegate -> $intDelegate") + intDelegate = 2 + println("测试 MXIntDelegate -> $intDelegate") + + + var longDelegate by MXLongDelegate(SPUtils.KV, "long_test", 1) + println("测试 MXLongDelegate -> $longDelegate") + longDelegate = 2 + println("测试 MXLongDelegate -> $longDelegate") + + + var stringDelegate by MXStringDelegate(SPUtils.KV, "string_test", "testdef") + println("测试 MXStringDelegate -> $stringDelegate") + stringDelegate = "2" + println("测试 MXStringDelegate -> $stringDelegate") + + + var beanDelegate by MXBeanDelegate( + SPUtils.KV, + TestBean::class.java, + "bean_test", + null + ) + println("测试 BeanDelegate -> ${beanDelegate?.id} -> ${beanDelegate?.name}") + beanDelegate = TestBean("2sdf", "name") + println("测试 BeanDelegate -> ${beanDelegate?.id} -> ${beanDelegate?.name}") + + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + data class TestBean constructor(val id: String, val name: String) + + class MXBeanDelegate( + kv: MXKeyValue, + private val clazz: Class, + name: String, + default: T + ) : MXBaseDelegate(kv, name, default) { + override fun stringToObject(value: String): T { + try { + val mapper = ObjectMapper() + mapper.registerKotlinModule() + return mapper.readValue(value, clazz) + } catch (e: Exception) { + } + return default + } + + override fun objectToString(obj: T): String? { + obj ?: return null + try { + val mapper = ObjectMapper() + return mapper.writeValueAsString(obj) + } catch (e: Exception) { + } + return null + } + } +} diff --git a/app/src/main/java/com/mx/example/app/ExpireTestActivity.kt b/app/src/main/java/com/mx/example/app/ExpireTestActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..503c47e72a6015babf4af75a44e0b8c5d06f70e2 --- /dev/null +++ b/app/src/main/java/com/mx/example/app/ExpireTestActivity.kt @@ -0,0 +1,33 @@ +package com.mx.example.app + +import android.os.Bundle +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.mx.example.R +import com.mx.example.utils.SPUtils +import kotlinx.android.synthetic.main.activity_expire_test.* +import java.util.concurrent.TimeUnit + +class ExpireTestActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_expire_test) + + setExpireTxv.setOnClickListener { + SPUtils.set( + "test_expire_key", + "1分钟失效:" + System.currentTimeMillis(), + System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(1) + ) + Toast.makeText(this, SPUtils.get("test_expire_key", "失效"), Toast.LENGTH_SHORT).show() + } + readExpireTxv.setOnClickListener { + Toast.makeText(this, SPUtils.get("test_expire_key", "失效"), Toast.LENGTH_SHORT).show() + } + deleteTxv.setOnClickListener { + SPUtils.delete("test_expire_key") + Toast.makeText(this, SPUtils.get("test_expire_key", "失效"), Toast.LENGTH_SHORT).show() + } + } +} diff --git a/app/src/main/java/com/mx/example/app/MainActivity.kt b/app/src/main/java/com/mx/example/app/MainActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..4c29fecf5a88b6c02027f3837b16429c17a9894f --- /dev/null +++ b/app/src/main/java/com/mx/example/app/MainActivity.kt @@ -0,0 +1,35 @@ +package com.mx.example.app + +import android.content.Intent +import android.os.Bundle +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.registerKotlinModule +import com.mx.example.R +import com.mx.example.utils.SPUtils +import com.mx.keyvalue.MXKeyValue +import com.mx.keyvalue.delegate.* +import kotlinx.android.synthetic.main.activity_main.* +import java.lang.StringBuilder +import java.util.concurrent.TimeUnit +import kotlin.concurrent.thread +import kotlin.random.Random + +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + expireTestTxv.setOnClickListener { + startActivity(Intent(this, ExpireTestActivity::class.java)) + } + threadTestTxv.setOnClickListener { + startActivity(Intent(this, MultThreadTestActivity::class.java)) + } + delegateTestTxv.setOnClickListener { + startActivity(Intent(this, DelegateTestActivity::class.java)) + } + } +} diff --git a/app/src/main/java/com/mx/example/app/MultThreadTestActivity.kt b/app/src/main/java/com/mx/example/app/MultThreadTestActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..74ce3ab837a2ae784bfbde1a299872c2509fe930 --- /dev/null +++ b/app/src/main/java/com/mx/example/app/MultThreadTestActivity.kt @@ -0,0 +1,43 @@ +package com.mx.example.app + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.mx.example.R +import com.mx.example.utils.SPUtils +import com.mx.example.utils.StringUtils +import kotlinx.android.synthetic.main.activity_mult_thread_test.* +import kotlin.concurrent.thread +import kotlin.random.Random + +class MultThreadTestActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_mult_thread_test) + syncTestTxv.setOnClickListener { + repeat(5) { index -> + thread { + repeat(100000) { count -> + getSetUnit(index, count) + if (count > 0 && count % 10 == 0) { + println("次数:$count") + } + } + } + } + } + } + + private fun getSetUnit(index: Int, count: Int) { + val key = "key_index_" + index + "_" + count + val value = "value_index_" + index + "_" + count + if (!SPUtils.set(key, value)) { + println("写入错误:$key -> $value") + } + val read = SPUtils.get(key) + if (read != value) { + println("读取错误1:$key -> $value") + println("读取错误2:$key -> $read") + } + } + +} diff --git a/app/src/main/java/com/mx/example/MySecret.kt b/app/src/main/java/com/mx/example/utils/MySecret.kt similarity index 94% rename from app/src/main/java/com/mx/example/MySecret.kt rename to app/src/main/java/com/mx/example/utils/MySecret.kt index 2ee2c7810ebe267fedc656e901427b45c9023b46..55b11fcac14a04b81d986aa03c712f15058ca8ec 100644 --- a/app/src/main/java/com/mx/example/MySecret.kt +++ b/app/src/main/java/com/mx/example/utils/MySecret.kt @@ -1,4 +1,4 @@ -package com.mx.example +package com.mx.example.utils import com.mx.keyvalue.secret.IMXSecret import java.util.* diff --git a/app/src/main/java/com/mx/example/utils/SPUtils.kt b/app/src/main/java/com/mx/example/utils/SPUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..5f985569802c3d07ec6019ec6eef83960f8c61c8 --- /dev/null +++ b/app/src/main/java/com/mx/example/utils/SPUtils.kt @@ -0,0 +1,57 @@ +package com.mx.example.utils + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.registerKotlinModule +import com.mx.example.MyApp +import com.mx.keyvalue.MXKeyValue +import com.mx.keyvalue.delegate.MXBaseDelegate +import com.mx.keyvalue.secret.MXAESSecret + +// 缓存类 +object SPUtils { + val KV by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + MXKeyValue(MyApp.appContext, "kvdb_kv_v1", MXAESSecret("27e2125d0a11a9aa65b9c9773673bc2a")) + } + fun get(key: String, def: String? = null): String? { + return KV.get(key, def) + } + + fun set(key: String, value: String?, expire_time: Long? = null): Boolean { + return KV.set(key, value, expire_time) + } + + fun cleanAll() { + KV.cleanAll() + } + + fun delete(key: String): Boolean { + return KV.delete(key) + } + + class MXBeanDelegate( + kv: MXKeyValue, + private val clazz: Class, + name: String, + default: T? + ) : MXBaseDelegate(kv, name, default) { + override fun stringToObject(value: String): T? { + try { + val mapper = ObjectMapper() + mapper.registerKotlinModule() + return mapper.readValue(value, clazz) + } catch (e: Exception) { + } + return default + } + + override fun objectToString(obj: T?): String? { + obj ?: return null + try { + val mapper = ObjectMapper() + return mapper.writeValueAsString(obj) + } catch (e: Exception) { + } + return null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mx/example/utils/StringUtils.kt b/app/src/main/java/com/mx/example/utils/StringUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..871b6c7ee045c3ca2bf82b58ce616411b048ee95 --- /dev/null +++ b/app/src/main/java/com/mx/example/utils/StringUtils.kt @@ -0,0 +1,41 @@ +package com.mx.example.utils + +import java.lang.StringBuilder +import java.security.MessageDigest +import java.util.* + +object StringUtils { + fun generalKey(): String { + return (UUID.randomUUID().toString() + generalString(32)).md5() + } + + fun generalString(size: Int): String { + val KEYS = ('a'..'z') + ('A'..'Z') + ('0'..'9') + val stringBuilder = StringBuilder() + repeat(size) { + stringBuilder.append(KEYS.random()) + } + return stringBuilder.toString() + } + + private fun String.md5(): String { + try { + val instance = MessageDigest.getInstance("MD5")//获取md5加密对象 + val digest = instance.digest(this.toByteArray())//对字符串加密,返回字节数组 + val sb = StringBuffer() + for (b in digest) { + val i: Int = b.toInt() and 0xff//获取低八位有效值 + var hexString = Integer.toHexString(i)//将整数转化为16进制 + if (hexString.length < 2) { + hexString = "0$hexString"//如果是一位的话,补0 + } + sb.append(hexString) + } + return sb.toString() + + } catch (e: Exception) { + e.printStackTrace() + } + return "" + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_delegate_test.xml b/app/src/main/res/layout/activity_delegate_test.xml new file mode 100644 index 0000000000000000000000000000000000000000..bec6fedb8e60b4543ab7771ae83bef69f98b3c58 --- /dev/null +++ b/app/src/main/res/layout/activity_delegate_test.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_expire_test.xml b/app/src/main/res/layout/activity_expire_test.xml new file mode 100644 index 0000000000000000000000000000000000000000..414e5b0cafaf637999c2b933be33009caa323416 --- /dev/null +++ b/app/src/main/res/layout/activity_expire_test.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index a1e24b25bb6660b4767bef96ce6eb7bebb38d009..bcaad9167e2aab44a74c9d275090ddd599774a89 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -5,33 +5,26 @@ android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" - tools:context=".MainActivity"> + tools:context=".app.MainActivity"> + android:text="超时测试" /> - - + android:text="多线程测试" /> + android:text="对象代理测试" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_mult_thread_test.xml b/app/src/main/res/layout/activity_mult_thread_test.xml new file mode 100644 index 0000000000000000000000000000000000000000..386b1d71d19f91fdf997c1bb2c9ef6bc4a239094 --- /dev/null +++ b/app/src/main/res/layout/activity_mult_thread_test.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b891b8a3064bd5a9cb1397103dee30fd02d651f6..db178d5c1e5a73b1cc1b4a677cd7b4c0ab4c9c3e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Aug 09 13:56:32 CST 2021 +#Sat Jun 25 11:44:57 CST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME