Android Jetpack 使用指南总结

本文同步发布于公众号:移动开发那些事 Android Jetpack 使用指南总结

JetpackGoogle提供的一套构建高质量、高性能应用的标准工具集,可以帮忙开发者更轻松构建高质量的Android应用。Jetpack提供的主要组件有:

  • 架构相关的:
    • Lifecycle : 帮助管理生命周期感知组件,如 ViewModelLiveData。
    • ViewModel : 管理与 UI 相关的数据,确保数据在配置更改(如屏幕旋转)时保持存活。
    • LiveData : 一个生命周期感知的数据持有者,当数据发生变化时自动更新 UI
    • Navigation : 简化应用内的导航,支持 FragmentActivity 之间的跳转
  • UI 相关的
    • Compose: 采用声明式编程替代传统的 XML 布局
  • 后台任务
    • WorkManager: 简化后台任务的管理,支持持久化任务(即使应用被杀死也能继续执行),并可根据网络状态、充电状态等条件执行任务。
  • 数据库
    • Room:提供编译时检查 SQL 查询、简化数据库访问的注解;
  • 数据存储
    • DataStore : 替代SharedPreferences,提供更好的性能和类型安全,支持协程和Flow;

1 依赖

使用前,需要在项目的build.gradle文件中添加Jetpack组件的依赖(需要注意整个工程的kotlin的版本需要与Jetpack的版本相匹配)

dependencies {
	// 其他的依赖
   implementation "androidx.core:core-ktx:1.7.0"
   implementation "androidx.appcompat:appcompat:1.4.1"
   implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
   implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
   implementation "androidx.navigation:navigation-fragment-ktx:2.4.1"
   implementation "androidx.navigation:navigation-ui-ktx:2.4.1"
   implementation "androidx.room:room-ktx:2.4.2"
   kapt "androidx.room:room-compiler:2.4.2"
   implementation "androidx.work:work-runtime-ktx:2.7.1"
   // 其他的依赖 .....
}

2 架构组件

2.1 Lifecycle

Lifecycle主要目标是帮助我们更好地管理组件的生命周期,减少内存泄漏和崩溃,同时简化代码逻辑。核心组件为:

  • LifecycleOwner : 拥有生命周期的组件,如ActivityFragment。它们的生命周期状态会被Lifecycle管理 ;
  • LifecycleObserver: 观察者,用于监听生命周期事件并执行相应操作;

一个简单的使用示例为:

class AActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)

        // 使用 LifecycleObserver
        lifecycle.addObserver(ALifecycleObserver())
    }
}

class ALifecycleObserver : DefaultLifecycleObserver {
    override fun onCreate(owner: LifecycleOwner) {
        super.onCreate(owner)
        println("Lifecycle: onCreate")
    }
    // 按需要覆写其他生命周期的方法
 }

Lifecycle 更多是与LiveDataViewModel集成来提供更强大的功能;

2.2 LiveData & ViewModel

LiveData是一个可观察的数据存储器,能够感知生命周期的变化,并确保只在生命周期处于活跃状态时更新UI,可避免常见的内存泄露问题。它一般与LifecycleOwner绑定,能够感知到其生命周期的状态,并只在生命周期处于活跃时通知观察者(Observer),避免了在非活跃状态下更新UI,能减小内存泄露的风险。而且LiveData的操作是线程安全的,观察者只会在线程上收到相关的回调通知。

ViewModel用于存储和管理与UI相关的数据。它的主要目的是在配置更改(如屏幕旋转、语言切换等)时保持数据的持久性,并确保数据与 UI 的分离。它的生命周期是独立于Activity或者Fragment的,并且与LifecycleOwner绑定,但不持有对应的引用,从而避免内存泄露的问题,但它会在LifecycleOwner被销毁时自动清理自身资源;

2.2.1 使用

LiveData一般与ViewModel结合使用,用于实现数据的持久性和生命周期的管理。

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
// 定义ViewModel
class MyViewModel : ViewModel() {
    // MutableLiveData 是 LiveData 的可变版本,允许直接修改数据
    private val _data = MutableLiveData<String>()
    val data: LiveData<String> = _data  // 外部通过 LiveData 访问,保证只读

    // 修改数据的方法
    fun updateData(newData: String) {
        _data.value = newData
    }
}

// 使用示例
 // 使用 ViewModelProvider 获取 ViewModel 实例
   private val viewModel: MyViewModel by viewModels();
   // 观察 LiveData 数据
    viewModel.data.observe(this, Observer { newData ->
         // 更新 UI
         textView.text = newData
    })

    // 数据更新
    button.setOnClickListener {
         viewModel.updateData("Hello, LiveData!")
    }

2.2.2 LiveData数据倒灌

数据倒灌是指LiveData会把之前已经发送过的数据再次发送给观察者,这个问题主要是由于LiveData具备粘性事件的特性(新观察者注册时会立刻收到LiveData的最新数据)。

如果不想发生数据倒灌的问题,可通过自定义SingleLiveEvent来处理,它可保证每个事件只被消费一次

3 后台任务

WorkManager它可以在应用退出后继续执行任务,并兼容不同版本的 Android 系统。适用于需要可靠执行的任务,即使应用被关闭或设备重启,任务也能继续执行,其有几个关键的类:

  • Worker: 执行后台任务的类,需要继承Worker类并重写doWork方法
  • WorkRequest : 用于定义任务的执行方式和约束条件
  • WorkManager : 用于调度任务
    简单使用
// 步骤1 创建Worker
class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {

    override fun doWork(): Result {
        // 在这里执行后台任务
        return Result.success()
    }
}

// 步骤2 创建WorkRequest,可以创建 OneTimeWorkRequest 或 PeriodicWorkRequest (执行一次,还是定期任务)
// 创建任务的约束,可选
val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresCharging(true)
    .build()

val workRequest = OneTimeWorkRequest.Builder(MyWorker::class.java)
    .setConstraints(constraints) // 添加任务的约束
    .build()

 // 步骤 3 执行任务的调度
 val workManager = WorkManager.getInstance(context)
 workManager.enqueue(workRequest)

4 Room

Room是一个强大的数据库抽象层,通过声明式编程和编译时检查,简化了SQLite数据库的操作。它支持 自动迁移、分页加载和事务管理,能够显著提升开发效率和应用性能,主要优点:

  • 声明式数据库操作: 通过注解定义数据库模式,简化数据库操作
  • 编译时检查:在编译时检查数据库模式,减少运行时错误
  • 事务支持
  • 自动迁移:从2.4.0版本开始,支持自动迁移功能,简化数据库的版本管理;

简单的使用

// 步骤1:定义实体类
// 表名为:users
@Entity(tableName = "users")
data class User(
    @PrimaryKey(autoGenerate = true) val id: Int, // 主健
    @ColumnInfo(name = "name") val name: String,  // 列名1
    @ColumnInfo(name = "age") val age: Int    // 列名2
)

// 步骤2:定义数据库的操作方法
// Room 会自动实现这些方法
@Dao
interface UserDao {
	// 查询
    @Query("SELECT * FROM users")
    fun getAll(): LiveData<List<User>>

    // 插入 ,通过协程的关键字:suspend关键字来执行异步操作
    @Insert
    suspend fun insert(user: User)

    // 更新
    @Update
    suspend fun update(user: User)

    // 删除
    @Delete
    suspend fun delete(user: User)
}

// 步骤3:创建数据库版本和实体
@Database(entities = [User::class], version = 1)
// 继承自 RoomDatabase 的抽象类,用于定义数据库的配置和访问入口
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "app_database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

// 步骤4 :使用数据库
val db = AppDatabase.getDatabase(context)
val userDao = db.userDao()
// 查询用户
val users = userDao.getAll()
// ....

5 数据存储

DataStore提供了一种更现代、更安全的方式来存储键值对或类型化对象。DataStore 支持异步操作,使得数据存储和读取更加高效和灵活。

  • Preferences DataStore : 用于存储键值对数据,类似于SharedPreferences,但更加安全和高效。
  • Proto DataStore :用于存储类型化对象,适合存储复杂的数据结构

5.1 Preferences DataStore

简单的使用示例:

// 步骤1 :创建DataStore
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
// 步骤2: 定义KEY
val USER_NAME_KEY = stringPreferencesKey("user_name")
// 步骤3 写入数据
suspend fun saveUserName(context: Context, userName: String) {
    context.dataStore.edit { preferences ->
        preferences[USER_NAME_KEY] = userName
    }
}

// 步骤4 读取数据,这里使用了Kotlin的Flow
val userNameFlow: Flow<String> = context.dataStore.data
    .map { preferences ->
        preferences[USER_NAME_KEY] ?: "default_name"
    }

5.2 Proto DataStore

简单的使用示例:

// 步骤1 :使用protobuf 定义对应的数据结构,并生成对应的java类(具体的操作方法请自行google)
// 步骤2 :创建DataStore
val Context.userPreferencesStore: DataStore<UserPreferences> by lazy {
    createDataStore(
        fileName = "user_prefs.pb", // 这里的名字为步骤1里定义的pb文件
        serializer = UserPreferencesSerializer, // 这里为一个自定义的序列化类
        corruptHandler = ReplaceFileCorruptHandler { UserPreferences.getDefaultInstance() }
    )
}
// 实现一个自定义的序列化类,用于实现如何去序列化这个UserPreferences的数据
// object UserPreferencesSerializer : Serializer<UserPreferences> 

// 步骤3 写数据
suspend fun saveUserPreferences(context: Context, userName: String, userAge: Int) {
    context.userPreferencesStore.updateData { preferences ->
        preferences.toBuilder()
            .setUserName(userName)
            .setUserAge(userAge)
            .build()
    }
}

// 步骤4 读取数据
val userPreferencesFlow: Flow<UserPreferences> = context.userPreferencesStore.data

6 参考

posted @   woodWu  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示