需要定义 Entity,Dao 以及 Database 三块即可完成数据库的配置,其他的数据库实现交由框架即可。
@Entity
class Movie() : BaseObservable() {
@PrimaryKey(autoGenerate = true)
var id = 0
@ColumnInfo(name = "movie_name", defaultValue = "Harry Potter")
lateinit var name: String
...
}
@Dao
interface MovieDao {
@Insert
fun insert(vararg movies: Movie?): LongArray?
@Delete
fun delete(movie: Movie?): Int
@Update
fun update(vararg movies: Movie?): Int
@get:Query("SELECT * FROM movie")
val allMovies: LiveData<List<Movie?>?>
}
@Database(entities = [Movie::class], version = 1)
abstract class MovieDataBase : RoomDatabase() {
abstract fun movieDao(): MovieDao
companion object {
@Volatile
private var sInstance: MovieDataBase? = null
private const val DATA_BASE_NAME = "jetpack_movie.db"
@JvmStatic
fun getInstance(context: Context): MovieDataBase? {
if (sInstance == null) {
synchronized(MovieDataBase::class.java) {
if (sInstance == null) {
sInstance = createInstance(context)
}
}
}
return sInstance
}
private fun createInstance(context: Context): MovieDataBase {
return Room.databaseBuilder(context.applicationContext,
MovieDataBase::class.java, DATA_BASE_NAME).build()
}
}
}
在 ViewModel 初始化 DataBase 接口之后即可利用其提供的 DAO 接口执行操作,接着利用 LiveData 将数据发射到 UI。
class MovieViewModel(application: Application) : AndroidViewModel(application) {
private val mediatorLiveData = MediatorLiveData<List<Movie?>?>()
private val db: MovieDataBase?
init {
db = MovieDataBase.getInstance(application)
if (db != null) {
mediatorLiveData.addSource(db.movieDao().allMovies) { movieList ->
if (db.databaseCreated.value != null) {
mediatorLiveData.postValue(movieList)
}
}
};
}
fun getMovieList(owner: LifecycleOwner?, observer: Observer<List<Movie?>?>?) {
if (owner != null && observer != null)
mediatorLiveData.observe(owner, observer)
}
}
Room 具备很多优势值得选作数据库的开发首选:
- 简洁高效,通过简单注解即可完成数据库的创建和 CRUD 封装
- 直接返回目标 POJO 实例,避免自行处理 Cursor 的风险
- 支持事务处理、数据库迁移、关系数据库等完整功能
- 支持 LiveData、Flow 等方式观察式查询
- AS 的 Database Inspector 可以实时查看、编辑和部署 Room 的数据库
- 内置异步处理
ViewModel 框架和 AppCompat、Lifecycle 框架一样,可谓是 Jetpack 框架最重要的几个基础框架。虽功能不仅限于此,但我们想要借此探讨一下它在数据缓存方面的作用。
通常怎么处理横竖屏切换导致的 Activity 重绘?一可以选择自生自灭,只有部分 View 存在自行恢复的处理、也可以配置 ConfigurationChange 手动复原重要的状态、或者保存数据至 BundleState,在 onCreate 等时机去手动恢复。
得益于 ViewModel 实例在 Activity 重绘之后不销毁,其缓存的数据不受外部配置变化的影响,进而确保数据可以自动恢复数据,无需处理。
这里定义一个 ViewModel,其中提供一个获取数据的方法,用来返回一个 30 岁名叫 Ellison 的朋友。Activity 取得 vm 实例之后观察数据的变化,并将数据反映到 UI 上。当屏幕方向变化后,名字和年龄的 TextView 可自动恢复,无需额外处理。
class PersonContextModel(application: Application) : AndroidViewModel(application) {
val personLiveData = MutableLiveData<Person>()
val personInWork: Unit
get() {
val testPerson = Person(30, "Ellison")
personLiveData.postValue(testPerson)
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
val model = ViewModelProvider(this).get(
PersonContextModel::class.java
)
model.personLiveData.observe(this, Observer { person: Person ->
binding.name.setText(person.name)
binding.age.setText(person.age.toString())
})
binding.get.setOnClickListener({ view -> model.personInWork })
}
}
ViewModel 的众多优势:
- 基于 Lifecycle 实现以注重生命周期的方式存储和管理界面相关的数据
- 画面销毁前存储 vm 实例并在重建后恢复,让数据可在发生屏幕旋转等配置更改后继续留存
- 可用于 Fragment 之间共享数据
- 作为数据和 UI 交互的媒介,用作 MVVM 架构的 VM 层
- 。。。
完成一个相机预览的功能,使用 Camera2 的话需要如下诸多流程,会比较繁琐:
而采用 CameraX进行开发的话,几十行代码即可完成预览功能。
private void setupCamera(PreviewView previewView) {
ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(() -> {
try {
mCameraProvider = cameraProviderFuture.get();
bindPreview(mCameraProvider, previewView);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}, ContextCompat.getMainExecutor(this));
}
private void bindPreview(@NonNull ProcessCameraProvider cameraProvider,
PreviewView previewView) {
mPreview = new Preview.Builder().build();
mCamera = cameraProvider.bindToLifecycle(this,
CameraSelector.DEFAULT_BACK_CAMERA, mPreview);
mPreview.setSurfaceProvider(previewView.getSurfaceProvider());
}
上面是 CameraX 的架构,可以看到其底层仍然是 Camera2,外加高度封装的接口,以及 Vendor 自定义的功能库。
使用它来作为全新的相机使用框架,具备很多优势:
- 代码简单,易用
- 自动绑定 Lifecycle,自动确定打开相机、何时创建拍摄会话以及何时停止和关闭
- 多设备的相机开发体验统一:国内外主流平台的设备都支持,国内的华米 OV 都在对这个框架支持和贡献
- 完美支持人像、HDR、夜间和美颜模式等拍摄模式的 Extensions
Monzo 利用 CameraX 缩减了 9,000 多行代码并使注册流程中的访问者流失率降低了 5 倍
5.8 其他框架这是一家银行服务公司并提供了同名应用,仅在移动设备上提供数字金融服务。他们的使命是向每个人传授生财之道。为了完成新客户注册,Monzo 应用会拍摄身份证明文件(例如护照、驾照或身份证)的图片,并拍摄自拍视频来证明身份证明文件属于申请者。
早期版本使用的是 camera2 API。在某些设备上会随机发生崩溃和异常行为,这导致 25% 的潜在客户无法继续进行身份证明拍摄和自拍视频步骤。
篇幅有限,Jetpack 集合中还有非常多其他的优质框架等待大家的挖掘。
框架作用竞品DataStore异步、一致性的轻量级数据的存储框架,支持键值对和对象数据SharedPreferences、MMKVStartUp简化应用启动的组件初始化,提高应用启动性能的框架-Navigation简化画面跳转,支持标签导航、抽屉导航等复杂设计的路由框架ARouterActivityResultActivity、Fragment 之间传递数据的新框架onActivityResult/IntentPaging3按需加载节省网络流量和内存消耗的分页加载框架-WorkManager调度退出应用或重启设备后仍可运行的可延期异步任务框架。JobService、Alarm、BroadcastHiltAndroid 专用的DI框架,快速建立之间的依赖关系和生命周期Dagger2、KoilAppCompat提供Activity、Dialog 和 View 的 Base 类,兼容 Jetpack 的大量处理-ViewPager2实现经典的标签导航设计的新框架ViewPager...
在开发某个功能的时候,看看是否有轮子可用,尤其是官方的。
5.9 官方推荐的应用架构我在官方的推荐架构上做了些补充,一般的 App 推荐采用如下的架构组件。