当前位置: 首页 > news >正文

wordpress文章的分享seo流量排名软件

wordpress文章的分享,seo流量排名软件,国家反诈中心app下载安装注册,经典网站首页设计1. 简介 在需要轻量级本地持久化的场景中#xff0c;DataStore 是一个理想的选择#xff08;详见《Android Jetpack 系列#xff08;四#xff09;DataStore 全面解析与实践》#xff09;。但当你面临如下需求时#xff0c;本地数据库便显得尤为重要#xff1a; 复杂的…1. 简介 在需要轻量级本地持久化的场景中DataStore 是一个理想的选择详见《Android Jetpack 系列四DataStore 全面解析与实践》。但当你面临如下需求时本地数据库便显得尤为重要 复杂的数据模型管理数据之间存在关联关系支持部分字段更新实现离线缓存与增量更新。 为此Google 在 Jetpack 架构组件中推出了新一代本地数据库解决方案Room。它对原生 SQLite 进行了封装提供类型安全、编译期校验的数据库访问方式极大简化了样板代码的编写。 Room 的典型使用场景包括 缓存网络请求结果实现离线访问管理本地复杂结构化数据实现高效的增量数据更新及多表关系维护。 主要优势包括 SQL 编译期校验提前发现错误提升稳定性注解驱动开发减少冗余代码简化数据迁移支持版本演进与 LiveData / Flow 集成实现响应式 UI 更新。 2. 添加依赖 2.1 添加 Room 依赖项 要在项目中使用 Room需要在模块的 build.gradle.kts 或 build.gradle 文件中添加如下依赖项 dependencies {val room_version 2.7.2implementation(androidx.room:room-runtime:$room_version)ksp(androidx.room:room-compiler:$room_version)// Kotlin 扩展与协程支持implementation(androidx.room:room-ktx:$room_version)// 可选 Test helperstestImplementation(androidx.room:room-testing:$room_version) }2.2启用 KSPKotlin Symbol Processing Room 使用 KSP 编译时执行注解处理KSP类似于 kpat但更高效特别适合 Kotlin 项目。ksp允许库开发者创建编译时代码生成器比如自动生成依赖注入代码、路由代码、数据库操作代码等。 ksp和 kapt 的区别 项目 ksp kapt 性能 更快、更轻量 较慢尤其在大型项目中 对 Kotlin 支持 原生支持 Kotlin 本质是处理 Java 注解Kotlin 兼容性一般 编译时间 更短 更长 错误提示 更接近源码容易定位问题 有时提示不准确 下面是启用 KSP 的方式 工程级 build.gradle.kts 中添加 plugins {alias(libs.plugins.android.application) apply falsealias(libs.plugins.kotlin.android) apply falsealias(libs.plugins.kotlin.compose) apply falseid(com.google.devtools.ksp) version 2.0.21-1.0.27 apply false }注意 KSP 的前缀版本需与 Kotlin 版本保持一致。如 Kotlin 版本为 2.0.21KSP 的版本必须以 2.0.21 开头。 模块级 build.gradle.kts 中启用 plugins {alias(libs.plugins.android.application)alias(libs.plugins.kotlin.android)alias(libs.plugins.kotlin.compose)id(com.google.devtools.ksp) }3. 了解 Room Room 的架构由三大核心组件构成 3.1 Entity实体类 对应数据库中的一张表使用 Entity(tableName xxx) 注解声明类的每个字段就是表中的一列至少要有一个主键PrimaryKey支持复合主键、忽略字段、嵌套对象。 3.2 DAO数据访问对象 使用 Dao 注解声明用于定义 SQL 操作Query或封装常用方法如 Insert、Update、Delete支持挂起函数suspend、LiveData、Flow 等多种返回类型可以组合多个操作为一个事务使用 Transaction 注解支持多表查询与动态SQL构建。 3.3 Database数据库类 使用 Database 注解声明并指定 entities、version必须继承自 RoomDatabase是 Room 的核心入口提供 DAO 实例访问建议通过单例或其他线程安全方式创建实现。 三者关系 Room 中Database 是数据库的核心入口负责提供 DAO 实例。而 DAO 提供访问 Entity 数据的能力。通过 DAO可以实现对 Entity 对应表的增删改查操作。如下图说明了 Room 的不同组件之间的关系。 4. 实战示例 本节将构建一个完整的 Room 使用案例涉及三个典型数据表用户表User、商品表Product、订单表Order。内容涵盖了 Entity 注解的多种用法、DAO 接口的定义以及数据库的创建与使用。 4.1 数据实体Entity Room 中的实体类Entity用于定义数据库中的数据结构。每个实体类对应数据库中的一张表其字段对应表中的列。通过注解即可完成表结构定义无需手写 SQL 语句。 用户表User Entity(tableName user,indices [Index(value [username], unique true)] ) data class User(PrimaryKey(autoGenerate true)ColumnInfo(name user_id)val userId: Long 0,val username: String,val password: String,ColumnInfo(name full_name)val fullName: String,Embedded(prefix addr_)val address: Address ) {Ignoreval isOnline: Boolean false }地址嵌套类Address data class Address(val province:String,val city: String,val zipCode: String )商品表Product Entity(tableName products,indices [Index(value [name], unique true)] ) data class Product(PrimaryKey(autoGenerate true)ColumnInfo(name product_id)val productId: Long 0L,val name: String,val price: Double )订单表Order Entity(tableName orders,foreignKeys [ForeignKey(entity User::class,parentColumns [user_id],childColumns [user_owner_id],onDelete ForeignKey.CASCADE),ForeignKey(entity Product::class,parentColumns [product_id],childColumns [product_id],onDelete ForeignKey.NO_ACTION)],indices [Index(value [user_owner_id]),Index(value [product_id])] ) data class Order(PrimaryKey(autoGenerate true)ColumnInfo(name order_id)val orderId: Long 0L,ColumnInfo(name user_owner_id)val userOwnerId: Long, ColumnInfo(name product_id)val productId: Long, val quantity: Int )注意SQLite 表名和列名称不区分大小写。 常用注解说明 注解 说明 Entity 声明表结构可设置表名、索引、外键、主键等。 tableName 参数表示表名为空则 Room 将类名称用作数据库表名称 indices 参数表示为某一个表字段加上索引其中value表示字段名unique表示是否唯一。 ForeignKeys 参数用于声明多个外键。ForeignKey内的参数 entity 参数表示外键指向的实体类 parentColumns 参数表示指向实体类的表字段名 childColumns 表示当前表中的字段名 onDelete 表示主表删除后是否会自动删除本表的数据CASCADE 会删除NO_ACTION不删除。 primaryKeys 参数可设置多个列的组合对实体实例进行唯一标识也就是通过多个列定义一个复合主键。例如 Entity(primaryKeys [firstName, lastName]) data class User(     val firstName: String?,     val lastName: String? ) ignoredColumns 参数用于指定某字段是忽略字段例如实体继承了父实体的字段则通过该参数进行指定。例如 open class User {     var picture: Bitmap? null } Entity(ignoredColumns [picture]) data class RemoteUser(     PrimaryKey val id: Int,     val hasVpn: Boolean ) : User() ColumnInfo 映射字段名称到数据库列名name参数为空或者省略该注解Room 默认使用字段名称作为数据库中的列名称。 PrimaryKey 声明列字段为表主键若autoGenerate参数为true则表示可自动递增生成。 Embedded 声明字段为嵌套字段字段类型对应类内的字段同样会存储在数据库。prefix参数表示为内部字段加上前缀。例如上面User的address字段它在数据库实际上是对应三个字段addr_province、addr_city 和 addr_zipCode。 Ignore 声明字段仅用于类本身但不会创建数据库列。 4.2 数据访问对象DAO DAOData Access Object是访问数据库的核心接口。Room 会在编译时自动生成其实现代码保障类型安全。 推荐将 DAO 定义为接口也可为抽象类并始终使用 Dao 注解标记。 用户DaoUserDao Dao interface UserDao {// 增Insertfun insertUser(user: User): Long// 增Insertfun insertUsers(vararg users: User): ListLong// 增Insertfun insertBothUsers(user1: User, user2: User)// 增Insertfun insertUsersAndFriends(user: User, friends: ListUser)// 删Deletefun deleteUser(user: User): Int// 删Deletefun deleteUsers(vararg users: User) : Int// 改Updatefun updateUser(user: User): Int// 改Updatefun updateUsers(vararg users: User) : Int// 更改用户密码Query(UPDATE user SET password :newPassword WHERE username :username)fun updatePassword(username: String, newPassword: String): Int// 根据用户ID查询用户Query(SELECT * FROM user WHERE user_id :userId)fun getUserByUserId(userId: Long): User?// 无条件查询所有用户Query(SELECT * FROM user)fun getAllUserList(): ListUser// 支持LiveDataQuery(SELECT * FROM user)fun getAllUsersLiveData(): LiveDataListUser// 支持响应式流Query(SELECT * FROM user)fun getAllUsersFlow(): FlowListUser// 输入用户数组查询包含的结果Query(SELECT * FROM user WHERE user_id IN (:userIds))fun getUsersByUserIds(userIds: IntArray): ListUser// 根据用户名查询用户Query(SELECT * FROM user WHERE username LIKE :username LIMIT 1)fun getUserByUsername(username: String): User?// 根据全名模糊查找用户Query(SELECT * FROM user WHERE full_name LIKE % || :fullName || %)fun findUsersByFullName(fullName: String): ListUser// 强制插入用户通过事务方式先删除再插入新的Transactionfun forceInstallUser(user: User): Long {val newUser getUserByUsername(user.username)newUser?.let {val result deleteUser(it)if (result 0) {val userId insertUser(user)return userId}}return 0} }商品DaoProductDao Dao interface ProductDao {// 若已存在则替换Insert(onConflict OnConflictStrategy.REPLACE)fun insert(product: Product): LongQuery(SELECT * FROM products WHERE name LIKE :name LIMIT 1)fun getProduct(name: String): ProductQuery(SELECT * FROM products)fun getAllProducts(): ListProduct }订单DaoOrderDao Dao interface OrderDao {Insertfun insert(order: Order): LongQuery(SELECT * FROM orders WHERE user_owner_id :userId)fun getOrdersByUser(userId: Long): ListOrderQuery(SELECT * FROM user JOIN orders ON user.user_id orders.user_owner_id)fun loadUserAndOrders(): MapUser, ListOrder }Dao注解说明 注解 说明 Dao 声明针于数据表的增删改查操作。 Insert 插入数据方法onConflict 参数可设置若新增数据主键或索引存在冲突时的处理方法REPLACE 替换ABORT 中止IGNORE 忽略。 Delete 删除数据方法 Update 更新数据方法 Query 根据SQL语句执行操作的方法大多情况下用于查询单表或联表数据也可以用于更复杂的删除、更新或插入数据操作。 用于查询返回多个结果时还可以配合LiveData或Flow 返回。 注意Room 会在编译时验证 SQL 查询。这意味着如果查询出现问题则会出现编译错误而不是运行时失败。 Transaction 标记方法在数据库中作为一个事务执行方法中所有的数据库操作要么全部成功执行并提交要么在中途出错时全部回滚以保障数据一致性。 4.3 数据库类AppDatabase 数据库类需继承自 RoomDatabase并使用 Database 注解指定实体类与版本。 Database(entities [User::class, Product::class, Order::class], version 1) abstract class AppDatabase : RoomDatabase() {abstract fun userDao(): UserDaoabstract fun productDao(): ProductDaoabstract fun orderDao(): OrderDaocompanion object {Volatileprivate var INSTANCE: AppDatabase? nullfun getInstance(context: Context): AppDatabase {return INSTANCE ?: synchronized(this) {INSTANCE ?: Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, app_database).build().also { INSTANCE it }}}} }数据库类必须满足以下条件 该类必须带有 Database 注解该注解包含列出所有与数据库关联的数据实体的 entities 数组。 该类必须是一个抽象类用于扩展 RoomDatabase。对于与数据库关联的每个 DAO 类数据库类必须定义返回其实例的抽象方法。 注解说明 注解 说明 Database 声明数据库。 entities 参数用于声明数据库中的表。 version 参数用于声明数据库版本。 注意 Room 实例化成本较高建议采用单例模式避免重复创建。 多进程支持 如需支持多进程访问数据库请调用 .enableMultiInstanceInvalidation()。这样每个进程中都有一个 AppDatabase 实例可以在一个进程中使共享数据库文件失效并且这种失效会自动传播到其他进程中 AppDatabase 的实例。 Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, app_database).enableMultiInstanceInvalidation().build() 数据库文件说明 databaseBuilder方法中name 参数指定数据库文件名数据库文件默认保存在 app 私有目录的 databases/ 子目录下完整路径如下 /data/data/应用包名/databases/数据库名.db 如果你想将数据库放在自定义路径可以使用 Room.databaseBuilder(context,AppDatabase::class.java,File(context.filesDir, custom/path/app_database).absolutePath )可以通过以下方式获取数据库文件的绝对路径 val dbFile: File context.getDatabasePath(app_database) Log.d(TAG, 数据库路径为${dbFile.absolutePath})如果你在测试或调试中想确认数据库是否存在可以这样判断 if (dbFile.exists()) {Log.d(TAG, 数据库已存在) } else {Log.d(TAG, 数据库尚未创建) }注意 只有在你第一次访问数据库如调用 db.userDao().getAll()或显式触发数据库操作后数据库文件才会真正创建。 4.4 调用示例 suspend fun test(context: Context) withContext(Dispatchers.IO) {val db AppDatabase.getInstance(context)val dbFile: File context.getDatabasePath(app_database)Log.d(TAG, 数据库路径为${dbFile.absolutePath})if (dbFile.exists()) {Log.d(TAG, 数据库已存在)} else {Log.d(TAG, 数据库尚未创建)}// 插入用户val user User(username zyx,password 123456,fullName 子云心,address Address(广东省, 广州市, 510000))val userId db.userDao().insertUser(user)Log.d(TAG, insert result, userId: $userId)if (dbFile.exists()) {Log.d(DB_PATH, 数据库已存在)} else {Log.d(DB_PATH, 数据库尚未创建)}// 更新全名val updatedUser user.copy(userId userId, fullName 马户)val updateUserNumber db.userDao().updateUser(updatedUser)Log.d(TAG, update user result, number: $updateUserNumber)// 更改用户密码val updatePasswordResult db.userDao().updatePassword(zyx, 9527)Log.d(TAG, update password result, number: $updatePasswordResult)// 根据用户名查询用户val userResult db.userDao().getUserByUsername(zyx)Log.d(TAG, getUserByUsername result: $userResult)// 根据全名模糊查找用户val usersResult db.userDao().findUsersByFullName(马)Log.d(TAG, getUsersByFullName result: $usersResult)// 无条件查询所有用户val usersList db.userDao().getAllUserList()Log.d(TAG, getAllUserList result: $usersList) }注意 Room 所有数据库操作必须在主线程之外执行例如使用 Dispatchers.IO。 5. Room 的进阶使用 5.1 类型转换TypeConverter Room 支持的字段类型是有限的比如 不支持直接存储 Date、List、Map、Enum 等类型如果你的实体类中包含这些类型的字段就会报错。 此时可以使用 TypeConverter 注解来自定义转换逻辑让 Room 知道如何将这些类型转换为数据库支持的类型进行存储和读取。 以 User 实体为例我们为其新增一个 Date 类型的 birthday 字段 Entity(tableName user,indices [Index(value [username], unique true)] ) data class User(PrimaryKey(autoGenerate true)ColumnInfo(name user_id)val userId: Long 0,val username: String,val password: String,ColumnInfo(name full_name)val fullName: String,Embedded(prefix addr_)val address: Address,val birthday: Date ) {Ignoreval isOnline: Boolean false }创建类型转换器 class Converters {TypeConverterfun fromTimestamp(value: Long?): Date? {return value?.let { Date(it) }}TypeConverterfun dateToTimestamp(date: Date?): Long? {return date?.time} }然后在数据库类中注册该转换器 TypeConverters(Converters::class) Database(entities [User::class, Product::class, Order::class], version 1) abstract class AppDatabase : RoomDatabase() {abstract fun userDao(): UserDaoabstract fun productDao(): ProductDaoabstract fun orderDao(): OrderDaocompanion object {Volatileprivate var INSTANCE: AppDatabase? nullfun getInstance(context: Context): AppDatabase {return INSTANCE ?: synchronized(this) {INSTANCE ?: Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, app_database).build().also { INSTANCE it }}}} }这样Room 会自动将 Date 类型转换为 Long 存储到数据库中再反向转换回来整个过程无需手动干预。 同理支持 ListString 类型 Room 同样不支持直接存储集合类型如 ListString。可以将其序列化为逗号拼接的字符串 TypeConverter fun fromString(value: String): ListString {return if (value.isEmpty()) emptyList() else value.split(,) }TypeConverter fun fromList(list: ListString): String {return list.joinToString(,) }5.2 数据库版本升级Migration 在上一节中我们为 User 表新增了 birthday 字段运行时会遇到如下异常 java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like youve changed schema but forgot to update the version number. You can simply fix this by increasing the version number. Expected identity hash: xxx, found: xxx 这是因为修改了数据库结构但未更新版本号。更新版本后如果未提供迁移逻辑又会出现 java.lang.IllegalStateException: A migration from 1 to 2 was required but not found. Please provide the necessary Migration path via RoomDatabase.Builder.addMigration(...) or allow for destructive migrations via one of the RoomDatabase.Builder.fallbackToDestructiveMigration* functions. 这个错误说明 Room 发现版本变化但无法找到迁移路径。 5.2.1 推荐方案添加 Migration数据保留 定义版本 1 → 2 的迁移逻辑 val MIGRATION_1_2 object : Migration(1, 2) {override fun migrate(db: SupportSQLiteDatabase) {db.execSQL(ALTER TABLE user ADD COLUMN birthday INTEGER DEFAULT 0 NOT NULL)} }使用addMigrations注册迁移 Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, app_database).addMigrations(MIGRATION_1_2).build()注意 ALTER TABLE 仅支持新增字段但不支持删除或修改字段类型。如需修改列或结构需要手动新建临时表转存数据关于 SQL 语句的相关知识这里就不作过多演示。一般情况下已发布版本的数据库尽量不进行修改结构和删除字段。 5.2.2 替换方案破坏性迁移开发期可用数据会被清除 使用 fallbackToDestructiveMigration 设置破坏迁移 Room.databaseBuilder(context, AppDatabase::class.java, app_database).fallbackToDestructiveMigration(true) // 每次版本变动就清空旧库重建.build()注意 此方式会清空旧数据并重建数据库不推荐在正式环境使用。 5.3 多表联查 5.3.1 使用 SQL JOIN灵活强大 定义结果数据类 data class OrderInfo(val orderId: Long,val quantity: Int,val username: String,val fullName: String,val productName: String,val productPrice: Double )在 OrderDao 中书写 JOIN 查询语句 Dao interface OrderDao {// ……Query(SELECT o.order_id AS orderId,o.quantity AS quantity,u.username AS username,u.full_name AS fullName,p.name AS productName,p.price AS productPriceFROM orders oINNER JOIN user u ON o.user_owner_id u.user_idINNER JOIN products p ON o.product_id p.product_id)fun getOrderInfoList(): ListOrderInfo }优点灵活、可控、支持复杂筛选。 缺点字段映射需手动维护代码稍显冗长。 5.3.2 使用 Relation结构清晰 定义嵌套数据类 data class OrderDetail(Embedded val order: Order,Relation(parentColumn user_owner_id,entityColumn user_id)val user: User,Relation(parentColumn product_id,entityColumn product_id)val product: Product )说明 Relation 会自动根据外键字段将 User 和 Product 加载进来Embedded val order: Order 是基础订单表本身。 在 OrderDao 中添加查询方法 Dao interface OrderDao {//……Query(SELECT * FROM orders)fun getOrderDetailList(): ListOrderDetail }优点类型安全、自动加载。 缺点不支持复杂过滤或自定义字段性能略低于原生 JOIN。 5.4数据库加密 5.4.1 使用SQLCipher 如果你存储在本地的数据较为敏感不希望数据库文件被导出后被 SQLite工具直接打开可以集成 SQLCipher 版本的 Room实现数据库加密。 添加依赖 dependencies {// ……implementation (net.zetetic:android-database-sqlcipher:4.5.4) }创建加密数据库 import net.sqlcipher.database.SQLiteDatabase import net.sqlcipher.database.SupportFactoryval passphrase: ByteArray SQLiteDatabase.getBytes(your-secure-password.toCharArray()) val factory SupportFactory(passphrase)Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, app_database).openHelperFactory(factory) // 使用加密支持工厂.build()注意 密码必须妥善保管否则数据无法恢复。性能相较未加密数据库略有下降但一般可接受。 5.4.2 使用 Android Keystore 管理密码 在上述示例中虽然使用了SQLCipher对数据库文件进行了加密但是密码明文定义在代码中这样会被攻击者通过反编译代码从而获取密码加密的动作就变得形同虚设。一般希望对本地数据加密保护时会将密码通过 Android Keystore动态生成和存储。 Keystore 是 Android 系统提供的一套安全机制用于在设备上安全地生成、存储和使用加密密钥而不让应用本身直接接触密钥的原始内容。这有助于防止密钥被反编译、提取或泄露。 创建 Keystore 加密解密辅助类 object KeystoreHelper {private const val KEY_ALIAS my_db_key_aliasprivate const val ANDROID_KEYSTORE AndroidKeyStoreprivate const val TRANSFORMATION AES/GCM/NoPaddingfun encrypt(plainText: String): PairString, String {generateSecretKeyIfNeeded()val cipher Cipher.getInstance(TRANSFORMATION)cipher.init(Cipher.ENCRYPT_MODE, getSecretKey())val iv cipher.ivval encrypted cipher.doFinal(plainText.toByteArray())return Base64.encodeToString(encrypted, Base64.NO_WRAP) to Base64.encodeToString(iv, Base64.NO_WRAP)}fun decrypt(encryptedBase64: String, ivBase64: String): String {val encrypted Base64.decode(encryptedBase64, Base64.NO_WRAP)val iv Base64.decode(ivBase64, Base64.NO_WRAP)val cipher Cipher.getInstance(TRANSFORMATION)val spec GCMParameterSpec(128, iv)cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), spec)return String(cipher.doFinal(encrypted))}private fun generateSecretKeyIfNeeded() {val keyStore KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }if (!keyStore.containsAlias(KEY_ALIAS)) {val keyGen KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE)val parameterSpec KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT).setBlockModes(KeyProperties.BLOCK_MODE_GCM).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE).build()keyGen.init(parameterSpec)keyGen.generateKey()}}private fun getSecretKey(): SecretKey {val keyStore KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }return keyStore.getKey(KEY_ALIAS, null) as SecretKey} }定义获取密码方法 fun getDatabasePassword(context: Context): String {val prefs: SharedPreferences context.getSharedPreferences(secure_prefs, Context.MODE_PRIVATE)// 获取上次生成的 encrypted 和 ivval encrypted prefs.getString(encrypted, null)val iv prefs.getString(iv, null)if (encrypted ! null iv ! null) {// 解密出密码return KeystoreHelper.decrypt(encrypted, iv)}// 生成随机密码val password UUID.randomUUID().toString()// 加密密码获取 encrypted 和 ivval (newEncrypted, newIv) KeystoreHelper.encrypt(password)// 保存 encrypted 和 ivprefs.edit { putString(encrypted, newEncrypted).putString(iv, newIv) }return password }替换明文密码创建加密数据库 import net.sqlcipher.database.SQLiteDatabase import net.sqlcipher.database.SupportFactoryval password getDatabasePassword(context) val passphrase: ByteArray SQLiteDatabase.getBytes(password.toCharArray()) val factory SupportFactory(passphrase)Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, app_database).openHelperFactory(factory) // 使用加密支持工厂.build()Keystore 的优势 密钥保护密钥被存储在专用的硬件或系统区域中如 TEE 或 StrongBox不暴露给应用层。受限使用密钥只能通过特定操作使用不能直接导出避免明文或反编译暴露。生命周期控制可以设置使用限制比如只允许解锁设备时使用、绑定到生物识别认证、设定失效时间等。绑定安全环境即使 APK 被反编译也无法还原出 Keystore 中的密钥。因为 Keystore 里的密钥是绑定到包名 签名 用户空间的如上述示例就算通过ROOT手机后导出 SharedPreferences获取到 encrypted 和 iv并且反编译后看到KeystoreHelper.decrypt() 的代码逻辑在别的设备上也会解密失败。或者就算同样的设备同样的APK反编译后重打包因为签名不一样无会解失败。 6. 总结 Room 作为 Jetpack 架构组件中本地数据库解决方案拥有良好的类型安全、编译时校验与响应式扩展能力。 推荐搭配 Paging、WorkManager 等组件构建现代 Android 应用架构对于需要结构化缓存、本地持久化、关系数据管理等场景尤其适合实际开发中应重视数据库升级策略与数据安全保护。 更多详细的 Room 介绍请访问 Android 开发者官网。
http://www.zqtcl.cn/news/813901/

相关文章:

  • 中文网站建设小组ios开发者账号申请
  • 月熊志网站福州建网站 做网页
  • 不同的网站有不同的风格宁波设计网站公司
  • 学校网站制作平台电子政务门户网站建设代码
  • 产品推广的网站怎么做网站标题与关键词
  • 青蛙网站建设wordpress修改logo
  • 网站套餐方案引擎搜索对人类记忆的影响
  • 滨州市滨城区建设局网站扎金花网站怎么做
  • 网站开发中视屏怎样编辑到网页上常州建站公司模板
  • 视频涉台互联网网站怎么做1cpu0.5g服务器用来做网站
  • 营销型网站设计官网怎么做网站优化 sit
  • 怎样获得做网站的客户免费企业网站程序上传
  • 新闻排版设计用什么软件网站seo诊断分析
  • 手机网站端域名怎样做解析一诺摄影设计
  • 网站开发行业竞争大吗郑州百度推广代运营公司
  • mvc4做网站五设计一个公司网站多少钱
  • 在什么网站可以做外贸出口劳保鞋北京 代理前置审批 网站备案
  • 邢台建设企业网站房地产宣传推广方案
  • 建设机械网站案例分析餐饮vi设计开题报告范文
  • 做本地生活网站深圳建设工程信息网站
  • C2C电商网站做博客的网站有哪些
  • 住房和城乡建设部网站 事故安微省建设厅田网站
  • 百度一下你就知道官页淘宝seo搜索引擎优化
  • 网站平台维护phpwind做的网站
  • 网站怎么做移动适配怎么样才算是一个网站页面
  • 做pc端网站策划百度网站建立
  • 高级网站开发技术青岛网站建设方案服务
  • 深圳公司网站建设设房地产网址大全
  • 怎么里ip做网站女生学广告学后悔死了
  • 做西餐网站wordpress 作者栏