Android jetpack(架构篇)
文章目录
一、依赖汇总
dependencies {
def lifecycle_version = "2.3.1"
def arch_version = "2.1.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// Lifecycles only (without ViewModel or LiveData)
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
// Saved state module for ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
// Jetpack Compose Integration for ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha04"
// Annotation processor
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
// alternately - if using Java8, use the following instead of lifecycle-compiler
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// optional - helpers for implementing LifecycleOwner in a Service
implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version"
// optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
// optional - ReactiveStreams support for LiveData
implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version"
// optional - Test helpers for LiveData
testImplementation "androidx.arch.core:core-testing:$arch_version"
}
二、Lifecycles
观察fragment/activity的生命周期
Handling Lifecycles with Lifecycle-Aware Components |Android Developers
1、Lifecycles的创建
1.1、LifecycleObserver接口实现类的创建
Handling Lifecycles with Lifecycle-Aware Components |Android Developers
Lifecycle
class MyObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun connectListener() {
...
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun disconnectListener() {
...
}
}
2、对需要观察生命周期的活动进行绑定
src -> main -> com.lej.test -> NeedToObserverActivity.kt
class NeedToObserverActivity : AppCompatActivity() {
override fun onCreate(...) {
myLifecycleOwner.getLifecycle().addObserver(MyObserver())
}
}
3、Lifecycles的使用用途
3.1、使用Lifecycle解耦页面和组件
即解藕了系统组件和应用组件
bilibili
动脑学院 -> 2021年最全面的Jecpack系统学习课程,看他就够了,更新中
eg: 有关时间控件(Chronometer)的需要在活动中的逻辑解耦
组件类
public class MyChronometer extends Chronometer implements LifecycleObserver{
//重写生命周期方法及要做的逻辑
}
需要用到这个组件的活动
public class UserActivity extends AppCompatActivity{
private MyChronometer myChronometer;
@Overide
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
myChronometer = findViewById(R.id. myChronometer);
//添加监听
getLifecycle().addObserver(myChronometer);s
}
}
一个活动新建的时候同时开启后台服务
3.2、使用LifecycleService解耦Service与组件
3.3、使用ProcessLifecycleOwner监听应用程序生命周期
4、Lifecycles的好处
-
解决了一致性问题(使用custom接口也可以解决)
-
解决了在生命周期里面编写代码的严重问题(代码入侵,接口也无法解决)
-
解决了内存泄露问题
因为使用接口解决一致性问题的时候可能出现内存泄露customInterfaceImpVariable.xxxAction(this);
三、LiveData
可以做到仅在组件处于生命周期的激活状态时才更新UI数据。
可以理解为 "直播"中的数据(step 观察,感应,监听), 与生命周期进行了关联, 可以避免类似打开一个活动马上关闭crash的异常,因为数据和lifecycle绑定了已经有感知能力了,可以知道活动要关闭了,系统自己将感知并且不会执行让程序崩溃的代码
专门用来实现对数据进行观察,从而数据发生改变的时候,布局里显示数据的控件视图也发生改变
1、LiveData的创建
1.1、依赖的导入
1.2、创建LiveData Object
LiveData Overview |Android Developers
Create LiveData objects
ViewModel里创建LiveData Object
class NameViewModel : ViewModel() {
// Create a LiveData with a String
val currentName: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
// Rest of the ViewModel...
}
1.3、对数据进行观察和数据改变时布局的监听操作
LiveData Overview |Android Developers
Observe LiveData objects
class NameActivity : AppCompatActivity() {
// Use the 'by viewModels()' Kotlin property delegate
// from the activity-ktx artifact
private val model: NameViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Other code to setup the activity...
// Create the observer which updates the UI.
val nameObserver = Observer<String> { newName ->
// Update the UI, in this case, a TextView.
nameTextView.text = newName
}
// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
model.currentName.observe(this, nameObserver)
}
}
1.4、更新被观察的数据
LiveData Overview |Android Developers
Update LiveData objects
button.setOnClickListener {
val anotherName = "John Doe"
model.currentName.setValue(anotherName)
}
1.5、LiveData的好处
与Lifecycle进行了关联,即LiveData也具备生命周期的感应,此时不会发生如onCreate方法里setContentView方法后立即finish活动崩溃的现象了(即不会发生一打开活动立马关掉崩溃的错误了)
LiveData是Android Architecture Components提出的框架。LiveData是一个可以被观察的数据持有类,它可以感知并遵循Activity、Fragment或Service等组件的生命周期。正是由于LiveData对组件生命周期可感知特点,因此可以做到仅在组件处于生命周期的激活状态时才更新UI数据。
LiveData需要一个观察者对象,一般是Observer类的具体实现。当观察者的生命周期处于STARTED或RESUMED状态时,LiveData会通知观察者数据变化;在观察者处于其他状态时,即使LiveData的数据变化了,也不会通知。
LiveData的优点
-
UI和实时数据保持一致,因为LiveData采用的是观察者模式,这样一来就可以在数据发生改变时获得通知,更新UI。
-
避免内存泄漏,观察者被绑定到组件的生命周期上,当被绑定的组件销毁(destroy)时,观察者会立刻自动清理自身的数据。
-
不会再产生由于Activity处于stop状态而引起的崩溃,例如:当Activity处于后台状态时,是不会收到LiveData的任何事件的。
-
RxLifeCycle: 可以在一定的时机如onstop,ondestroy取消订阅,但是在onstart,OnResume不会恢复,而liveData可以做到
-
不需要再解决生命周期带来的问题,LiveData可以感知被绑定的组件的生命周期,只有在活跃状态才会通知数据变化。
-
实时数据刷新,当组件处于活跃状态或者从不活跃状态到活跃状态时总是能收到最新的数据。
-
解决Configuration Change问题,在屏幕发生旋转或者被回收再次启动,立刻就能收到最新的数据。
1.6、自定义LiveData
四、ViewModel
以注重生命周期的方式来管理界面相关的数据,(让数据具备感应生命周期的特性)
1、ViewModel的创建
1.1、依赖的导入
1.2、activity中的ViewModel
ViewModel Overview |Android Developers
Implement a ViewModel
1.2.1、创建ViewModel子类
名字创建的规则eg: MainActivity --> MainViewModel
class MyViewModel : ViewModel() {
//存放要监听的数据
private val users: MutableLiveData<List<User>> by lazy {
MutableLiveData().also {
loadUsers()
}
}
fun getUsers(): LiveData<List<User>> {
return users
}
private fun loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
1.2.2、活动中对数据进行观察
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
// Use the 'by viewModels()' Kotlin property delegate
// from the activity-ktx artifact
//导包:implementation "androidx.fragment:fragment-ktx:1.2.5"//by viewModels()
val model: MyViewModel by viewModels()
//by 委托, viewModels()内部getValue()实现了返回ViewModel类的实现
model.getUsers().observe(this, Observer<List<User>>{ users ->
// update UI
})
// private val mainViewModel by viewModels<MainViewModel>()
/*
等价于
lateinit var mainViewModel: MainViewModel
mainViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
.get(MainViewModel::class.java)
*/
}
}
1.3、fragment中的ViewModel
1.4、ViewModel的好处
ViewModel在生命周期重建的时候(如旋转屏幕),数据不会丢失
- ViewModel 数据层 和 UI Controller 之间分离的很干净。UI Controller 不用负责获取数据,也不用在重建时负责数据的有效性。
- ViewModel 数据层能感知到 UI Controller 的生命周期:保证 UI Controller 重建后,持有的是同一个ViewModel数据实例; UI Controller 结束生命周期后,系统自动调用ViewModel的clear(),释放资源。
- 配合 LiveData 使用效果更佳。
- 之前放到onSaveInstanceState()的复杂数据,现在可以放到ViewModel(系统UI相关的除外)
- 由于职责划分更加清晰,测试更方便。
1.5、ViewModel的注意点
五、ViewBinding
解决了需要findviewbyid的写法,将view和xml布局进行了绑定,可以直接通过拿到的id表示这个id所在的view控件
1、ViewBinding的创建
1.1、环境的配置
View Binding|Android Developers
Setup instructions 详情
app/library module下
android {
...
buildFeatures {
viewBinding true
}
}
1.2、activity中的使用
1.2.1、root view的设置
View Binding|Android Developers
Setup instructions
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>
1.2.2、代码中的设置
View Binding|Android Developers
Use view binding in activities
//lateinit延迟加载,变量只能为var若想为val需委托lazy加载(懒加载单例)
private lateinit var binding: ResultProfileBinding
//ResultProfileBinding命名规则: result_profile.xml -> ResultProfileBinding
//此时的ResultProfileBinding就等同于result_profile.xml(会自动映射)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ResultProfileBinding.inflate(layoutInflater)
//Java的这里layoutInflater是getLayoutInflater()
val view = binding.root
setContentView(view)
}
监听里的赋值操作
binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }
1.3、fragment中的使用
1.2.1、root view的设置
View Binding|Android Developers
Setup instructions
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>
1.2.2、代码中的设置
View Binding|Android Developers
Use view binding in fragments
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
_binding = null
}
3、ViewBinding好处
- 代替findViewById方法
- 取代butterknife
- 代替kotlin的synthetics
六、DataBinding
让布局文件承担了部分原本属于页面的工作,使页面与布局耦合度进一步降低, 也不用经常需要findviewbyid方法了
进行xml布局里控件数据与ViewModel的数据进行绑定, 这样ViewModel数据改变,布局也自动改变,即不用手动添加监听更新视图,只用观察即可
1、DataBinding的创建
1.1、构造环境
Get started |Android Developers
Build environment
android {
...
buildFeatures {
dataBinding true
}
}
1.2、单向绑定
单向绑定指的是数据改变,页面布局改变
1.2.1、布局中配置
Data Binding Library |Android Developers
Using the Data Binding Library
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewmodel"
type="com.myapp.data.ViewModel" />
</data>
<ConstraintLayout... >
<!-- UI layout's root element -->
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@{viewmodel.rememberMe}"
android:onCheckedChanged="@{viewmodel.rememberMeChanged}"
/>
</ConstraintLayout>
</layout>
1.3、双向绑定
比单向绑定多的是,当页面布局改变的时候,数据也会发生改变
1.3.1、布局中绑定
Data Binding Library |Android Developers
Using the Data Binding Library
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewmodel"
type="com.myapp.data.ViewModel" />
</data>
<ConstraintLayout... >
<!-- UI layout's root element -->
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@{viewmodel.rememberMe}"
android:onCheckedChanged="@{viewmodel.rememberMeChanged}"
/>
</ConstraintLayout>
</layout>
1.3.2、适配器类中绑定适配器
Data Binding Library |Android Developers
Using the Data Binding Library
此时要使用注解需要应用插件:
apply plugin: 'koltin-kapt'
@BindingAdapter("app:goneUnless")
fun goneUnless(view: View, visible: Boolean) {
view.visibility = if (visible) View.VISIBLE else View.GONE
}
1.3.3、与数据绑定
与JavaBean数据绑定
- 活动/碎片中绑定数据(JavaBean)
Layouts and binding expressions |Android Developers
Binding data
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(
this, R.layout.activity_main)
binding.user = User("Test", "User")
}
与ViewModel数据绑定
Bind layout views to Architecture Components |Android Developers
Use ViewModel to manage UI-related data
class ViewModelActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Obtain the ViewModel component.
val userModel: UserModel by viewModels()
// Inflate view and obtain an instance of the binding class.
val binding: UserBinding = DataBindingUtil.setContentView(this, R.layout.user)
// Assign the component to a property in the binding class.
binding.viewmodel = userModel
}
}
1.3.4、BaseObservable实现双向绑定
@Bindable : 可以实现当数据改变,界面改变的单向绑定
1.3.5、ObservableField实现双向绑定
1.3.6、绑定的几种方式
官网解释
Generated binding classes |Android Developers
Create a binding object
使用DataBindingUtil 的方式
如图我们可以看见DataBindingUtil 中可以使用 inflate 、bind 、setContentVIew 的方式绑定布局。
其中:
inflate :可以在任意地方进行使用,一般在Fragment / recyclerview 等地方进行使用
bind : 可以在任意地方进行使用。
setContentVIew : 只可以在Activity 中进行使用。
使用 xmlBinding 的方式
xmlBinding 和上面的DataBindingUtil 相似。
只不过如果使用 xmlBinding 之后必须要使用 setContentView (Activity 中)重新设置数据。
开发中使用哪种?
我个人推荐使用 DataBIndingUtil 的方式。大家都知道如果要写项目,那么就会抽取出Base 类,所以我们为了使用的方便一般就自己对于setCOntentView方法进行各种设置。比如我的BaseActviity。
abstract class BaseActviity<T : ViewDataBinding> : AppCompatActivity() {
var mBInding: T? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBInding = DataBindingUtil.setContentView(this,getLayoutIds())
}
abstract fun getLayoutIds(): Int
}
这样我们就可以在自己的类中使用统一的方式进行设置。
2、可能用到的情况
2.1、设置view的可见性
Layouts and binding expressions
Imports
布局中导入
<data>
<import type="android.view.View"/>
</data>
对布局中的需要控制可见性的某个控件进行设置
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
3、DataBinding踩过的坑
4、DataBinding高级用法
如下所示通过DataBinding定义加载图片的方法
4.1、布局中的属性设置
Binding adapters |Android Developers
Provide custom logic
<data>
<variable
name="venue"
type="com.myapp.data.ViewModel" />
</data>
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />
<!--
//注意这里的venue.imageUrl常用viewModel里的数据来替代
-->
4.2、代码中的设置
Binding adapters |Android Developers
Provide custom logic
class ImageViewBindingAdapter{
//BindingAdapter让原有的View/ViewGroup不支持的属性变得支持
@BindingAdapter("imageUrl", "error")//imageUrl和error是要拓展的属性
fun loadImage(view: ImageView, url: String, error: Drawable) {
Picasso.get().load(url).error(error).into(view)
}
//这个函数将在imageUrl和error的属性值发生改变的时候调用
}
5、DataBinding在RecyclerView中的使用
Generated binding classes |Android Developers
Dynamic Variables
override fun onBindViewHolder(holder: BindingHolder, position: Int) {
item: T = items.get(position)
holder.binding.setVariable(BR.item, item);
holder.binding.executePendingBindings();
//必须要有的,让数据可以生效到RecyclerView上面
//意思是:当数据改变时,binding会在下一帧去改变数据,如果我们需要立即改变,就去调用executePendingBindings方法。
}
- demo
6、DataBinding结合import导入工具类
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewmodel"
type="com.myapp.data.ViewModel" />
<import type = "com.lwj.lwj_demo.MyUtils"/>
</data>
<ConstraintLayout... >
<!-- UI layout's root element -->
<TextView
android:id="@+id/rememberMeCheckBox"
android:text="@{MyUtils.formatStr}"
android:onClick="@{MyUtils.rememberMeChanged}"
/>
</ConstraintLayout>
</layout>
7、Include标签引入二级页面
七、MVVM
八、Room
推荐观看官方文档案例
Android Room with a View - Kotlin(推荐) |Android Developers
1、依赖的导入
Save data in a local database using Room |Android Developers
Setup
dependencies {
def room_version = "2.3.0"
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor "androidx.room:room-compiler:$room_version"
// To use Kotlin annotation processing tool (kapt)
kapt("androidx.room:room-compiler:$room_version")
// To use Kotlin Symbolic Processing (KSP)
ksp("androidx.room:room-compiler:$room_version")
// optional - Kotlin Extensions and Coroutines support for Room
implementation("androidx.room:room-ktx:$room_version")
// optional - RxJava2 support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// optional - RxJava3 support for Room
implementation "androidx.room:room-rxjava3:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
// optional - Test helpers
testImplementation("androidx.room:room-testing:$room_version")
}
- Room说明
谷歌推的,维护、稳定性各方面都有保障一点。新项目还是值得使用的,不过封装得再好要上手还是挺麻烦的,尤其需求比较奇葩的时候,得花时间学习下。
Room 包含 3 个主要组件:数据库、Entity和DAO
官方文档
2、Database
官方做法:
Save data in a local database using Room |Android Developers
Database
官方文档
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
参考博客:
保存数据库的数据库类,并用作与应用程序持久数据的底层连接的主要访问点。
@Database(
version = 2,
entities = {
Device.class,
DeviceCollectionInfo.class,
Manufacturer.class,
User.class,
UserRelateDevice.class
}
)
@TypeConverters(value = {BaseCollectionData.class})
public abstract class LzDatabase extends RoomDatabase {
static LzDatabase getDatabase(Context context) {
if (context == null) {
throw new NullPointerException("context is null");
}
RoomDatabase.Builder<LzDatabase> builder = Room.databaseBuilder(context.getApplicationContext(),
LzDatabase.class, "lz_db.db");
// 内存中创建数据库,不用了就会被回收
// builder = Room.inMemoryDatabaseBuilder(context.getApplicationContext(), BookDB.class);
// 添加构建SupportSQLiteOpenHelper的工厂,SQLiteOpenHelper就是在里面实现的,默认是FrameworkSQLiteOpenHelperFactory
// builder.openHelperFactory(null);
// 数据库迁移操作添加,相当于SQLiteOpenHelper.onUpgrade的一些操作
builder.addMigrations(MIGRATION_1_2);
builder.allowMainThreadQueries(); // 允许主线程执行查询操作
// 有些APP的数据库需要预置数据,这两个就是导入预置数据库的
// builder.createFromAsset();
// builder.createFromFile()
// 多进程需要调用,这个在inMemoryDatabaseBuilder没用
// builder.enableMultiInstanceInvalidation();
// 允许破坏性迁移,数据会被丢弃
// builder.fallbackToDestructiveMigration();
// 允许特定版本迁移到当前版本进行破坏性迁移,数据会被丢弃
// builder.fallbackToDestructiveMigrationFrom(2, 3);
// 允许降级破坏性迁移,数据会被丢弃
// builder.fallbackToDestructiveMigrationOnDowngrade();
// 这个默认就好,默认是WRITE_AHEAD_LOGGING
// builder.setJournalMode(JournalMode.WRITE_AHEAD_LOGGING);
// 看名称就知道是啥,这两个都是ArchTaskExecutor,是fixed线程池,核心线程4个,一般来说是够用的
// builder.setQueryExecutor();
// builder.setTransactionExecutor();
// builder.addCallback(new Callback() {
// @Override
// public void onCreate(@NonNull SupportSQLiteDatabase db) {
// // 第一次创建数据库调用
// super.onCreate(db);
// }
//
// @Override
// public void onOpen(@NonNull SupportSQLiteDatabase db) {
// // 数据库打开调用
// super.onOpen(db);
// }
//
// @Override
// public void onDestructiveMigration(@NonNull SupportSQLiteDatabase db) {
// // 破坏性迁移
// super.onDestructiveMigration(db);
// }
// });
return builder.build();
}
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE user ADD COLUMN age interger NOT NULL default 10");
}
};
public abstract DeviceCollectionInfoDao getCollectionInfoDao();
public abstract DeviceDao getDeviceDao();
public abstract ManufacturerDao getManufacturerDao();
public abstract UserDao getUserDao();
public abstract UserRelateDeviceDao getUserRelateDeviceDao();
public static class LzDatabaseFactory {
private static Context context;
private static volatile LzDatabase database;
public static void initContext(Context context) {
LzDatabaseFactory.context = context.getApplicationContext();
}
public static LzDatabase build() {
if (context == null) {
throw new NullPointerException("LzDatabaseFactory.initContext has't called");
}
if (database == null) {
synchronized (LzDatabaseFactory.class) {
if (database == null) {
database = LzDatabase.getDatabase(context);
}
}
}
return database;
}
}
}
以上就会构建一个数据库,最好用单例模式,getDatabase可以写在任何地方,当然从封装得角度来说,写这里最好。RoomDatabase有三个抽象方法,都由apt生成不用管。
- @Database
- version是数据库版本
- entities表实体,会根据这些实体来创建数据库表
- views视图,会根据这些视图定义类来构建视图
- exportSchema是否允许导出架构,默认true
getOpenHelper可以获取读/写数据库(实现在FrameworkSQLiteDatabase),实际上就是在调用SQLiteDatabase。
这里需要特别注意的是allowMainThreadQueries,建议调用,dao生成的代码都是在调用方的线程中执行的,如果在主线程中调用,而你没有设置这个,就会被断言报错。
这两个线程池在源码中仅在livedata的体系中有调用。
2.1、数据库数据设计/版本迁移
-
数据迁移
在Migration中执行一些操作,构建数据库时调用addMigrations进行添加,针对数据库版本变动执行,两个参数都是数据库版本。理论上在这里面干啥都行,但是最好只做版本间的必要变动(别浪,这玩意乱来可能让你的数据库回到解放前),其实就是onUpgrade里面的一些操作,写过数据库升级的很容易理解,这玩意比那更直观。这个可以多个组成一个路径,即1-> 2 + 2-> 3 = 1-> 3。
fallbackToDestructiveMigration、fallbackToDestructiveMigrationFrom、fallbackToDestructiveMigrationOnDowngrade都是处理破坏性迁移的,但是尽量不要走这种方式,通过设计来避免这种问题是最好的方式 -
@DatabaseView
- value:查询语句
- viewName:视图名
-
@TypeConverter和@TypeConverters
public class BaseCollectionData {
public static final int THERMAL_MONITOR = 1;
public static final int WATCH = 2;
@SerializedName("clzName")
private String clzName;
{
clzName = this.getClass().getName();
}
public String getClzName() {
return clzName;
}
@TypeConverter
public static BaseCollectionData fromJson(String json) {
JsonElement element = JsonParser.parseString(json);
if (element == null || !element.isJsonObject()) {
return null;
}
JsonObject obj = element.getAsJsonObject();
if (!obj.has("clzName")) {
return null;
}
try {
String clzName = obj.get("clzName").getAsString();
if (TextUtils.isEmpty(clzName)) {
return null;
}
return (BaseCollectionData) new Gson().fromJson(json, Class.forName(clzName));
} catch (Exception ignore) {
}
return null;
}
@TypeConverter
public static String toJson(BaseCollectionData person) {
return new Gson().toJson(person);
}
}
@Entity(
tableName = "collection_info",
foreignKeys = @ForeignKey(entity = Device.class, parentColumns = "id", childColumns = "device_id", onDelete = ForeignKey.CASCADE)
)
public class DeviceCollectionInfo {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
public long id;
@ColumnInfo(name = "device_id", index = true)
public long deviceId;
@ColumnInfo(name = "data")
public BaseCollectionData data;
}
这个是用来自定义数据类型的,这个设计可能非常有用。
不是所有的数据都去直接建表的,一些拓展性比较强的数据建表会造成表的频繁变动,很可能会使用json来统合数据,这个时候可以让相应的继承上面的的类来避免自己一个个的去写解析。上面都是写的静态方法,可以写普通方法,加好@TypeConverter就行了。TypeConverter需要通过@TypeConverters注册到数据库类上
3、Entity
表示应用程序数据库中表的数据实体
这一块有两种:建表实体和关系实体。
3.1、 建表实体
@Entity(
tableName = "device",
foreignKeys = @ForeignKey(entity = Manufacturer.class, parentColumns = "id", childColumns = "manufacturer_id", onDelete = ForeignKey.CASCADE)
)
public class Device {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
public long id;
@ColumnInfo(name = "name")
public String name;
@ColumnInfo(name = "manufacturer_id", index = true)
public long manufacturerId;
@ColumnInfo(name = "device_type")
public int deviceType;
}
@Entity(tableName = "user")
public class User {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
public long id;
@ColumnInfo(name = "name", collate = UNICODE)
public String name;
@ColumnInfo(name = "age", defaultValue = "10")
public int age;
@Embedded(prefix = "user_")
public BodyInfo bodyInfo;
@Ignore
public String test;
}
public class BodyInfo {
@ColumnInfo(name = "height")
public int height;
@ColumnInfo(name = "weight")
public float weight;
}
上面就是一个简单合格的建表实体了,必须实现get和set方法或者把属性写成public,不然报错。
- @Entitiy:实体
tableName:表名,最好是写下,不写就会直接使用类名(注意混淆),不区分大小写
indices:索引,索引里面有unique标识,需要unique可以在这里配置
inheritSuperIndices:继承父类索引
primaryKeys:主键,单个主键用@PrimaryKey比较好(直观),如果是交叉表可以使用这个来配置组合外键
foreignKeys:外键
ignoredColumns:忽略属性,用@Ignore就好了,比较直观
- @PrimaryKey:主键
autoGenerate:主键是否自增,默认是false(感觉用自增的情况多一点,为啥默认false)
- @ColumnInfo:列
name:列名,最好写下,不写就会直接使用属性名(注意混淆),不区分大小写
typeAffinity:列数据类型,没必要写,room会自己推断
index:true:构建列索引
collate:BINARY:大小写敏感;NOCASE:大小写不敏感;RTRIM:首尾空格忽略;LOCALIZED:系统编码应用;UNICODE:unicode编码应用(感觉就这个可能有用,类似颜文字的编码写入数据库会报错,但是能不使用就不要使用)
defaultValue:列默认值
-
@Ignore:忽略,能忽略方法、构造器和属性,因为room会根据entity进行推断,随便写个属性也会被识别为列,当entity存在不希望建表的列时,需要进行忽略
-
@ForeignKey:外键,这个没有配置@Target,直接放在Entitiy里面啊,别像我写在属性上,这是无效的
entity:父表的实体
parentColumns:父表的对应列
childColumns:子表列名
onDelete:父表元素删除时,关联的子表元素的关联操作。默认NO\_ACTION;CASCADE:同步删除或更新;RESTRICT:存在子表记录时,父表记录不允许删除或更新,受deferred影响;SET\_NULL:赋空;SET_DEFAULT:赋列默认值;
onUpdate:和onDelete一样,约束更新操作;
deferred:延迟外键约束,只在提交的时候进行约束检查。[外键约束](http://blog.itpub.net/29227735/viewspace-1064119/)
-
@Fts3/@Fts4:虚表,模糊查询对比普通表会有极大的性能提高(还有没有啥特性我也不知道,毕竟没用过),官方文档
-
@Embedded:镶嵌实体,把本该在一个实体中的部分属性分离到另一个实体中(强迫症晚期必备),和写在一个里面没啥区别,建表时,字段在同一个表中。
prefix:防字段冲突的,加个前缀,会和ColumnInfo的name拼接,当然没写ColumnInfo就会和推断名拼接
3.2、 关系实体
public class UserAndDevice {
@Embedded
public UserRelateDevice userRelateDevice;
@Relation(
parentColumn = "user_id",
entityColumn = "id",
entity = User.class
)
public User user;
@Relation(
parentColumn = "device_id",
entityColumn = "id",
entity = Device.class
)
public DeviceAndManufacturer device;
}
public class DeviceAndManufacturer {
@Embedded
public Device device;
@Relation(
parentColumn = "manufacturer_id",
entityColumn = "id"
)
public Manufacturer manufacturer;
}
public class DevicesBelongToUser {
@Embedded
public User user;
@Relation(
parentColumn = "id",
entityColumn = "id",
associateBy = @Junction(value = UserRelateDevice.class, parentColumn = "user_id", entityColumn = "device_id"),
entity = Device.class
)
public List<DeviceAndManufacturer> devices;
}
- @Relation
entity:关联实体:必须是建表实体,和entityColumn对应
parentColumn:父实体关联字段,就是@Embedded注释实体里面的字段
entityColumn:关联实体中对应的字段,如果@Relation注释的字段本身就是一个关系实体,那就对应其@Embedded注释的实体
associateBy:父实体和关联实体本身没有关联关系,需要其它实体来提供关联关系,如果DevicesBelongToUser
projection:可以通过这个字段设置查询输出。比如@Relation注释的是新建的类,其仅包含entity的部分字段,通过这个字段就可以对应设置。
- @Junction
value:提供关联关系的关系实体
parentColumn:value对应实体里面的字段,和Relation.parentColumn对应
entityColumn:value对应实体里面的字段,和Relation.entityColumn对应
这一部分并不难,奈何官方文档的举例太简单,测试了好久才搞明白这一块咋设计的,查询挺好用。
关系实体首先最重要的是@Embedded,这个是查询语句查询的实体,每个关系实体都必须有一个@Embedded修饰的实体,利用这个实体去关联其它实体。@Relation注释的可以是建表实体,也可以是关系实体,关系实体里面又可以关联其它实体(禁止套娃),怎么关联在上面已经解释清楚了,不详说。
4、DAO
Data Access Object/数据访问对象 (DAO)提供了您的应用程序可用于查询、更新、插入和删除数据库中数据的方法。
@Dao
public interface DeviceDao {
@Insert
void insert(Device device);
@Insert
@Transaction
void bulkInsert(List<Device> list);
@Delete
void delete(Device device);
@Update
void update(Device device);
@Query("SELECT * FROM device WHERE id = :id")
Device queryById(long id);
@Query("SELECT * FROM device WHERE name = :name")
Device queryByName(String name);
@Query("SELECT * FROM device")
List<Device> queryAll();
@Query("SELECT * FROM device")
@Transaction
List<DeviceAndManufacturer> queryTakeManufacturer();
@Query("DELETE FROM device")
void clearTable();
@Query("update SQLITE_SEQUENCE set seq = 0 where name='device'")
void resetSeq();
@Transaction
@RawQuery(observedEntities = {UserRelateDevice.class})
LiveData<List<DeviceAndManufacturer>> rawQueryDevice(SupportSQLiteQuery query);
@Transaction
@Query("select * from device where id in (SELECT device_id from (select device_id, COUNT(*) as c from user_relate_device GROUP BY device_id) where c > 1)")
LiveData<List<DeviceAndManufacturer>> querySharedDevice();
}
- dao里面的注解都特别简单,不细讲。增删改的注释根据参数可以是单个处理也可以是批量处理,自己选择就好,批量处理最好加上事务@Transaction。
- 关系实体查询是需要@Transaction的,一般都会涉及多个表的查询。
- 增改的onConflict:ABORT:发生冲突时,事务回滚;REPLACE:发生冲突时,替换已存在记录;IGNORE:忽略冲突保留已存在的记录(增有这些正常,改是啥情况,修改被拒绝?搞不明白)
- @RawQuery和@Query,这两个有些区别:
- RawQuery必须有返回值,Query更随意一点;
- 在查询监控(LiveData)上,Query不需要配置监听对象,RawQuery需要。比如上面的querySharedDevice和rawQueryDevice用同一查询时,如果不写observedEntities = {UserRelateDevice.class},那UserRelateDevice的变化不会引起rawQueryDevice查询的监听回调;
- 在查询监控(LiveData)上,它们都会根据返回值推断要监控的变化。
- @SkipQueryVerification:跳过sql语句校验,上面这个dao除了rawQueryDevice都可以配置,已经是定型的语句,有没有问题早就验过了,确实没必要检查,但是我懒得写,也感觉没啥必要去纠结这个。
5.初始化数据库
Android Room with a View - Kotlin |Android Developers
在 WordRoomDatabase 中,您将创建 RoomDatabase.Callback() 的自定义实现,该实现也会获取 CoroutineScope 作为构造函数参数。然后,您将替换 onOpen 方法以填充数据库。
以下是在 WordRoomDatabase 类中创建回调所用的代码:
private class WordDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.wordDao())
}
}
}
suspend fun populateDatabase(wordDao: WordDao) {
// Delete all content here.
wordDao.deleteAll()
// Add sample words.
var word = Word("Hello")
wordDao.insert(word)
word = Word("World!")
wordDao.insert(word)
// TODO: Add your own words!
}
}
最后,将回调添加到数据库构建序列,然后在
Room.databaseBuilder() 上调用 .build():
.addCallback(WordDatabaseCallback(scope))
最终代码应如下所示:
@Database(entities = arrayOf(Word::class), version = 1, exportSchema = false)
abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
private class WordDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
INSTANCE?.let { database ->
scope.launch {
var wordDao = database.wordDao()
// Delete all content here.
wordDao.deleteAll()
// Add sample words.
var word = Word("Hello")
wordDao.insert(word)
word = Word("World!")
wordDao.insert(word)
// TODO: Add your own words!
word = Word("TODO!")
wordDao.insert(word)
}
}
}
}
companion object {
@Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(
context: Context,
scope: CoroutineScope
): WordRoomDatabase {
// if the INSTANCE is not null, then return it,
// if it is, then create the database
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
WordRoomDatabase::class.java,
"word_database"
)
.addCallback(WordDatabaseCallback(scope))
.build()
INSTANCE = instance
// return instance
instance
}
}
}
}
参考博客原始实现,非自定义
创建一个RoomDatabase.Callback:
private static RoomDatabase.Callback sRoomDatabaseCallback = new RoomDatabase.Callback() {
@Override
public void onOpen(@NonNull SupportSQLiteDatabase db) {
super.onOpen(db);
//同理,插入操作不能在主线程中进行,所以这里使用了AsyncTask
new PopulateDbAsync(INSTANCE).execute();
}
};
private static class PopulateDbAsync extends AsyncTask<Void, Void, Void> {
private final StudentDao mDao;
PopulateDbAsync(StudentRoomDatabase db) {
this.mDao = db.studentDao();
}
@Override
protected Void doInBackground(Void... voids) {
mDao.deleteAll();
Student student = new Student(1, "Tom", "Math");
mDao.insert(student);
student = new Student(2, "Bob", "English");
mDao.insert(student);
return null;
}
}
创建RoomDatabase是加入这个callback:
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
StudentRoomDatabase.class, "student_database")
.allowMainThreadQueries() //允许在主线程进行查询操作
.addCallback(sRoomDatabaseCallback) //启动应用程序时删除所有内容并重新填充数据库
.build();