从项目角度学习Jetpack

151 阅读7分钟

学习Jetpack的原因

  • 安卓的碎片化严重
  • Support库过于分散
  • 统一开发规范的系列组件
  • 进阶高级开发者

显著降低APP日常异常率,平均下降43%。为什么?

  • 空安全
  • LiveData跟生命周期绑定

核心思想

  • 通过观察者模式实现View层和数据层解耦

项目环境

  • Android Studio Chipmunk
  • Gradle 5.4.1
  • Gradle Plugin 3.5.3
  • JDK 1.8+
  • NDK 21.4.7075529
  • compileSdkVersion 29
  • minSdkVersion 21
  • targetSdkVersion 29
  • AndroidX true
  • dataBinding true

基本架构

image.png

Navigator导航框架

编写页面导航Json注解生成器

  • 编写注解代码
  • 主工程依赖注解
  • 生成注解
  • 编写BottomViewBar自定义View
  • BottomViewBar+NavController实现导航框架

网络库+Room缓存

网络库封装

  • Okhttp

image.png

Room数据库实现缓存

image.png

Databinding

image.png

LiveData

image.png

ViewModel+Paging

image.png

DataBinding和ViewBinding的区别?

ViewBinding:

  • 仅仅支持绑定 View
  • 不需要在布局文件中添加 layout 标签
  • 需要在模块级 build.gradle 文件中添加 viewBinding = true 即可使用
  • 效率高于 DataBinding,因为避免了与数据绑定相关的开销和性能问题
  • 相比于 kotlin-android-extensions 插件避免了空异常

DataBinding:

  • 包含了 ViewBinding 所有的功能
  • 需要在模块级 build.gradle 文件内添加 dataBinding = true 并且需要在布局文件中添加 layout 标签才可以使用
  • 支持 data 和 view 双向绑定
  • 效率低于 ViewBinding,因为注释处理器会影响数据绑定的构建时间。

ViewPager2

image.png

短视频项目难点分析

列表视频自动播放

image.png

沉浸式布局的应用以及问题处理

image.png

应用启动白屏的处理

image.png

CameraX拍摄、录制

image.png

组成部分:基础、架构、行为、界面

基础

  • AndroidKTX 编写更高效的 Kotlin代码,就是少些点代码,多做点事情
  • AppCompat 向下兼容,就是保证在所有版本都能正确运行

架构

  • 数据绑定 声明的方式把数据和界面绑定 白话:就是让数据和界面合体,你变我也变
  • Lifecycles 管理UI生命周期 白话:就是UI从出生到毁灭,我都知道
  • LiveData 数据库更改的时候通知视图
  • Navigation 处理导航
  • Paging 逐步从数据源中按需加载信息
  • Room 流畅的访问SQLite数据库
  • ViewModel 以管理生命周期的方式,管理数据 白话:数据出生到毁灭都被他管理
  • WorkManager 管理Android 后台,说白了就是搞点后台定时任务

ViewModel

  • 有自己的声明周期,贯穿整个UI
  • UI发生旋转,数据不会丢失
  • ViewModel编写
package com.example.jatpackdemo

import androidx.lifecycle.ViewModel

class MainViewModel(countReserved: Int) : ViewModel() {
    var count = countReserved
}
  • ViewModel传值
package com.example.jatpackdemo

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider

class MainViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MainViewModel(countReserved) as T
    }
}
  • ViewModel使用
package com.example.jatpackdemo

import android.content.Context
import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.core.content.edit
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import androidx.lifecycle.ViewModelStore
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    lateinit var viewModel: MainViewModel
    lateinit var sp: SharedPreferences

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

        sp = getPreferences(Context.MODE_PRIVATE)
        val countReserved = sp.getInt("count_reserved", 0)
        viewModel = ViewModelProvider(this, MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
        plusButton.setOnClickListener {
            viewModel.count++
            refreshCounter()
        }
        clearButton.setOnClickListener {
            viewModel.count = 0
            refreshCounter()
        }
        refreshCounter()
    }

    override fun onPause() {
        super.onPause()
        sp.edit {
            putInt("count_reserved", viewModel.count)
        }
    }
    private fun refreshCounter() {
        countTv.text = viewModel.count.toString()
    }
}

Lifecycles

  • 为了解决Activity引入的子类无法感知其本身的生命周期问题
  • 为了使得任何一个类都能感知Activity的生命周期,同时又不需要在Activity中编写大量的逻辑处理
  • 继承LifecycleObserver
  • 使用注解重写方法,被动感应Activity生命周期
  • 直接传入Lifecycle,直接获得生命周期状态
package com.example.jatpackdemo.lifecycle

import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent

class MyObserver(private val lifecycle: Lifecycle): LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun activityStart() {
        if (lifecycle.currentState == Lifecycle.State.STARTED) {
            Log.d("MyObserver", "activityStart")
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun activityStop() {
        Log.d("MyObserver", "activityStop")
    }
}
  • Activity中一行代码使其关联生命周期
// 添加lifecycle实例
        lifecycle.addObserver(MyObserver(lifecycle))
  • 这样的话MyObserver类就可以感知Activity的生命周期了

LiveData

  • 可以包含任何类型的数据,并且在数据变化的时候通知给观察者,使其本身数据发生变化的时候,使用它的地方也跟着发生变化
  • 说白了,就是你变了,我就知道了
  • getValue()获取数据
  • setValue()在主线程中设置数据
  • postValue()在子线程中发送数据
  • 在Activity中使用viewModel.count.observer方法来观察数据的变化
  • LiveData之所以能够成为Activity和ViewModel之间通信的桥梁,并且不会又内存泄漏的风险,靠的就是lifecycle组件
  • LiveData内部使用了Lifecycles组件来自我感知生命周期的变化,从而可以在Activity销毁的时候及时释放引用,避免产生内存泄漏的问题
  • 手机息屏、不活跃的时候,LiveData不通知给观察者这种特性,其实也是根据其内部的Lifecycle组件来实现的
package com.example.jatpackdemo

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class MainViewModel(countReserved: Int) : ViewModel() {
    var counter = MutableLiveData<Int>()
    init {
        counter.value = countReserved
    }

    fun plusOne() {
        val count = counter.value ?: 0
        counter.value = count + 1
    }

    fun clear() {
        counter.value = 0
    }
}
package com.example.jatpackdemo

import android.content.Context
import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.core.content.edit
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import androidx.lifecycle.ViewModelStore
import com.example.jatpackdemo.lifecycle.MyObserver
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    lateinit var viewModel: MainViewModel
    lateinit var sp: SharedPreferences

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

        // 添加lifecycle实例
        lifecycle.addObserver(MyObserver(lifecycle))

        sp = getPreferences(Context.MODE_PRIVATE)
        val countReserved = sp.getInt("count_reserved", 0)
        viewModel = ViewModelProvider(this, MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
        plusButton.setOnClickListener {
            viewModel.plusOne()
        }
        clearButton.setOnClickListener {
            viewModel.clear()
        }
        viewModel.counter.observe(this, Observer {count ->
            countTv.text = count.toString()
        })
    }

    override fun onPause() {
        super.onPause()
        sp.edit {
            putInt("count_reserved", viewModel.counter.value ?: 0)
        }
    }
}
  • 很重要的一点,ViewModel中,需要给外面暴露不可变的get方法以供使用,但是内部使用可变的更为安全,修改代码如下
package com.example.jatpackdemo

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

/**
 * ViewModel的最佳写法
 */
class MainViewModel(countReserved: Int) : ViewModel() {

    // 外部暴露不可变
    val counter : LiveData<Int>
        get() = _counter

    // 内部使用可变的
    private val _counter = MutableLiveData<Int>()
    init {
        _counter.value = countReserved
    }

    fun plusOne() {
        val count = _counter.value ?: 0
        _counter.value = count + 1
    }

    fun clear() {
        _counter.value = 0
    }
}

map() 和 swithMap()

  • map(): 将数据中的某一个字段转换出来一个公用的,以供外界使用
  • switchWap() : 将viewModel中通过外部得到的LiveData对象,转换成viewModel中的可观测的LiveData对象。

Room

  • 其实就是更简单的操作数据库,而不是像SqLite那样,写一堆数据库操作的代码
  • Entity、Dao、Database三部分组成
  • 注意Room有个坑,就是Insert数据的时候需要添加冲突策略,不然多次insert相同的数据对象,就会报错崩溃,代码在下面
 plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions'
    id 'kotlin-kapt'
}

implementation 'androidx.room:room-runtime:2.2.5'   // Room
    kapt 'androidx.room:room-complier:2.1.0'    // 编译注解
   

Entity 实体类

package com.example.jatpackdemo.model

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity
data class User(var firstName: String, var lastName: String, var age: Int) {

    @PrimaryKey(autoGenerate = true)
    var id: Long = 0
}

Dao 操作类

package com.example.jatpackdemo.model

import androidx.room.*

@Dao
interface UserDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertUser(user: User): Long

    @Update
    fun updateUser(newUser: User)

    @Query("select * from User")
    fun loadAllUsers(): List<User>

    @Query("select * from User where age > :age")
    fun loadUsersOlderThan(age: Int): List<User>

    @Delete
    fun deleteUser(user: User)

    @Query("delete from User where lastName = :lastName")
    fun deleteUserByLastName(lastName: String) : Int
}

DataBase 数据库版本信息 升级相关

package com.example.jatpackdemo.model

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

@Database(version = 1, entities = [User::class])
abstract class AppDatabase : RoomDatabase(){

    abstract fun userDao(): UserDao

    companion object {
        private var instance: AppDatabase? = null

        @Synchronized
        fun getDatabase(context: Context): AppDatabase {
            instance?.let {
                return it
            }
            return Room.databaseBuilder(context.applicationContext,
                 AppDatabase::class.java, "app_database")
                .allowMainThreadQueries()
                .build().apply {
                    instance = this
                }
        }
    }
}

数据库的升级工作

  • 新增表:改版本号、增加表、添加升级方法
// 数据库版本由1升级到2 新增表Book
        val MIGRATION_1_2 = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                // 创建Book表
                database.execSQL("create table book (id integer primary key autoincrement not null, name text not null, pages integer not null)")
            }
        }
@Synchronized
        fun getDatabase(context: Context): AppDatabase {
            instance?.let {
                return it
            }
            return Room.databaseBuilder(context.applicationContext,
                 AppDatabase::class.java, "app_database")
                .allowMainThreadQueries()
                .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
                .build().apply {
                    instance = this
                }
        }
  • 新增字段:改版本号、增加alter方法
// 数据库版本由2升级到3 Book表新增字段作者
        val MIGRATION_2_3 = object : Migration(2, 3) {
            override fun migrate(database: SupportSQLiteDatabase) {
                // 创建Book表
                database.execSQL("alter table Book add column author text not null default 'unknown'")
            }
        }
@Synchronized
        fun getDatabase(context: Context): AppDatabase {
            instance?.let {
                return it
            }
            return Room.databaseBuilder(context.applicationContext,
                 AppDatabase::class.java, "app_database")
                .allowMainThreadQueries()
                .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
                .build().apply {
                    instance = this
                }
        }

WorkManager

  • 很早之前,安卓的service很开放,优先级很高,导致很多应用都想占用资源,导致手机内存越来越紧张,耗电越来越快,手机越来越卡
  • 后来,为了解决这些问题,导致Android每更新一个版本,后台限制都会进一步收紧
  • 那么,频繁的api变更导致开发者很难受,我到底编写怎么样的代码才能保证应用程序在不同系统版本的兼容性呢?
  • 于是就有了WorkManager
  • 这个玩意的出现
  • 1:适合用于处理一些要求定时的任务。自动根据版本来兼容性的选择方法,降低了我们的使用成本
  • 2:处理周期性任务
  • 3:链式任务的处理
  • 4:周期性的同步数据等等
  • 使用WorkManager注册的周期性任务不能保证会一定准时执行,这是为了保证手机的续航

WorkManager的基本用法

/**
 * WorkManager
 * 1: 周期执行任务
 * 2:doWork任务不会运行在主线程中,所以可以放心的执行耗时任务
 * 3: 可以用,但是不要指望在国产手机上能够稳定运行
 */
class SimpleWorker(context: Context, params: WorkerParameters) : Worker(context, params) {

    override fun doWork(): Result {
        Log.d("SimpleWorker", "do work in SimpleWorker")
        return Result.success()
    }
}