安卓开发之Jetpack初探(Java与Kotlin)

在学习View的中间插个小插曲,这里学习一下Jetpack的基本用法。参考书籍为**《Andorid第一行代码》**。Jetpack 是一套库、工具和指南,可帮助开发者更轻松地编写优质应用。这些组件可帮助程序员遵循最佳做法、摆脱编写样板代码的工作并简化复杂任务,以便将精力集中放在所需的代码上。详细的介绍可以去看Android developer

Android developer的介绍中也可以看到,Jetpack主要由基础、架构、行为、界面构成,我们这里主要学习架构的基础知识。Jetpack中的许多架构组件是专门为MVVM架构打造的,MVVM架构是目前Android官方推荐的项目架构之一。MVVM架构可以将程序结构主要分成3部分:Model(数据模型部分)、View(界面展示部分)、ViewModel(连接数据模型和界面展示的桥梁,从而实现业务逻辑和界面展示分离的程序结构设计)。MVVM简化架构图如下图所示:
在这里插入图片描述
由上图可看到,MVVM架构将程序分为了若干层:

  • UI控制层包含了Activity、布局文件等与界面相关的一些东西。
  • ViewModel 层用于持有和UI元素相关的数据,以保证这些数据在屏幕旋转时不会丢失,并且还要提供接口给UI控制层调用以及和仓库层进行通信。
  • 仓库层要做的主要工作是判断调用方请求的数据应该是从本地数据源中获取还 是从网络数据源中获取,并将获取到的数据返回给调用方。本地数据源可以使用数据库、SharedPreferences等持久化技术来实现,而网络数据源通常需要进行网络请求访问服务器的Webservice借口来实现。
    要注意的是,每一层的组建只能与它相邻层的组建进行交互而不能跨层交互。

Kotlin

先看Kotlin版的,最后会写出对应的Java版,大致逻辑是相同的,只不过代码方面会有一些变化。

ViewModel

前面说过ViewModel是连接数据模型和界面展示的桥梁,从而实现业务逻辑和界面展示分离的程序结构设计,是Jetpack中最重要的组件之一,简单点说就是存放界面的相关数据的,例如Activity上的相关变量都应该存放在ViewModel中而不是Activity中,以减少Activity中的逻辑。此外,ViewModel还有一个重要的作用就是保证这些数据在屏幕旋转时不会丢失,ViewModel的生命周期与Activity不同,它在屏幕旋转的时候不会被重建,只有当Activity退出的时候才会跟着一起销毁,也就是ViewModel的生命周期长于Activity。

通过计数器Demo来学习,首先在app/build.gradle中添加依赖:

implementation ‘androidx.lifecycle:lifecycle-extensions:2.2.0’

接下来要编写MainActivity的ViewModel,通常每一个Activity、Fragment都要创建一个对应的ViewModel。新建MainViewModel继承ViewModel,并添加界面数据counter:

class MainViewModel:ViewModel() {
    var counter = 0
}

修改布局文件添加一个"加一"按钮,比较简单这里就不写了。修改MainActivity,点击按钮然后修改MainViewModel中存放的数据:

class MainActivity : AppCompatActivity() {

    lateinit var mainViewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)//获取MainViewModel实例
        plusone.setOnClickListener{
            mainViewModel.counter++
            textview.text = mainViewModel.counter.toString()
        }
        textview.text = mainViewModel.counter.toString()
    }
}

效果如下:
在这里插入图片描述
屏幕旋转后,数据没有丢失:
在这里插入图片描述
如果想要往ViewModel中传递参数,需要借助ViewModelProvider.Factory接口来实现。上面的程序虽然在屏幕旋转时不会丢失数据,但是在退出再重新打开时还是会丢失数据。因此需要再退出程序前进行保存,在Activity重新打开时,更准确的说onCreate中新建MainViewModel时读取保存的数据并传递给MainViewModel:

修改MainViewModel代码,构造函数中添加参数:

class MainViewModel(counter: Int) :ViewModel() {
    var counter = counter
}

新建MainViewModelFactory实现ViewModelProvider.Factory接口来完成向MainViewModel的构造函数传递参数:

class MainViewModelFactory(private val counter:Int):ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MainViewModel(counter) as T
    }
}

MainViewModelFactory的构造参数重也接收了一个counter参数,并重写create方法,我们就在此创建MainViewModel的实例,并将counter参数传递进去。

修改布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/textview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:textSize="32sp"/>
    <Button
        android:id="@+id/plusone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="加1"/>
    <Button
        android:id="@+id/clear"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="清0"/>
</LinearLayout>

修改MainActivity代码:

class MainActivity : AppCompatActivity() {
    lateinit var mainViewModel: MainViewModel
    lateinit var sp : SharedPreferences

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

        sp = getSharedPreferences("data_jetpack",Context.MODE_PRIVATE)
        val counter = sp.getInt("counter",0)

        mainViewModel = ViewModelProvider(this,MainViewModelFactory(counter)).get(MainViewModel::class.java)

        plusone.setOnClickListener{
            mainViewModel.counter++
            textview.text = mainViewModel.counter.toString()
        }
        clear.setOnClickListener{
            mainViewModel.counter = 0
            textview.text = mainViewModel.counter.toString()
        }
        textview.text = mainViewModel.counter.toString()
    }

    override fun onPause() {
        super.onPause()
        sp.edit {
            putInt("counter",mainViewModel.counter)
        }
    }
}

MainActivity中首先获取了SharedPreferences的实例,之后读取在应用程序关闭前我们保存到SharedPreferences中的counter值,如果没读到的话,默认值设为0。然后在获取MainViewModel实例时,额外传入了一个MainViewModelFactory参数,并将SharedPreferences中读到的的counter值传给MainViewModelFactory的构造函数,通过这种做法,最后counter值就传递给ViewModel了。

效果如下,退出程序后重新打开,值不会清零了:
在这里插入图片描述

Lifecycles

Lifecycles可以让任何一个类都能感知Activity的生命周期。接着上面那个Demo来学习。新建MyObserver类实现LifecycleObserver接口:

class MyObserver(): LifecycleObserver{
    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun activityStart(){
        Log.d("MyObserver","activityStart");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun activityStop(){
        Log.d("MyObserver","activityStop");
    }
}

在MyObserver中使用了OnLifecycleEvent注解,并传入了一种生命周期事件。生命周期事件一共有7种:ON_CREATE、ON_START、ON_RESUME、ON_PAUSE、ON_STOP、ON_DESTORY,分别匹配Activity中相应的生命周期回调;此外还有ON_ANY类型可以匹配Activity的任何生命周期回调。

然后我们就可以在Activity中通过Lifecycle对象的addObserver(MyObserver())方法来通知MyObserver—Activity的生命周期发生了变化:
在这里插入图片描述
这样当Activity的onStart()和onStop()触发的时候MyObserver中的响应方法就会被执行:
在这里插入图片描述

LiveData

LiveData是Jetpack提供的一种响应式编程组件,它可以包含任何类型的数据,并在数据发生变化的时候通知给观察者,经常和ViewModel结合在一起使用。

前面的计数器Demo在单线程模式下点击加1按钮确实可以正常工作,但是如果ViewModel的内部开启了线程去执行一些耗时逻辑,那么在点击按钮后就立即去获取最新的数据,得到的肯定还是原来的数据。那么我们是否可以在ViewModel数据发生变化时主动告知Activity而不是在Activity中手动获取呢? 这就要使用到LiveData了,我们可以将counter值使用LiveData包装,然后在Activity中去观察它,就可以主动将数据变化通知给Activity了。

修改MainViewModel代码:

class MainViewModel(counter: Int) :ViewModel() {
    val counterData = MutableLiveData<Int>()//MutableLiveData是一种可变的LivaData
    init {
        counterData.value = counter //初始化时给counterData设置数据
    }
    fun plusone(){
        val count = counterData.value?:0//?:意为左边式子不为空就等于左边否则为右边
        counterData.value = count + 1
    }
    fun clear(){
        counterData.value = 0
    }
}

代码中首先初始化 MutableLiveData对象counterData替代我们之前的counter变量,并指定泛型为Int。MutableLiveData主要有三种读写数据的方法,分别为getValue():用于获取LiveData中包含的数据、setValue():在主线程中使用,用于给LiveData设置数据、postValue():在非主线程中给LiveData设置数据。接着新增plusone方法和clear方法,对counterData的值进行改变。

接下来就要修改MainActivity了,以便这种数据的变化Activity能知晓:

class MainActivity : AppCompatActivity() {

    lateinit var mainViewModel: MainViewModel
    lateinit var sp : SharedPreferences

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

        lifecycle.addObserver(MyObserver())

        sp = getSharedPreferences("data_jetpack",Context.MODE_PRIVATE)
        val counter = sp.getInt("counter",0)

        mainViewModel = ViewModelProvider(this,MainViewModelFactory(counter)).get(MainViewModel::class.java)

        plusone.setOnClickListener{
            //mainViewModel.counter++
            //textview.text = mainViewModel.counter.toString()
            mainViewModel.plusone()
        }
        clear.setOnClickListener{
            //mainViewModel.counter = 0
            //textview.text = mainViewModel.counter.toString()
            mainViewModel.clear()
        }
        //textview.text = mainViewModel.counter.toString()
        mainViewModel.counterData.observe(this, Observer {
            input -> textview.text = input.toString()//input变量名随意指定
        })
    }

    override fun onPause() {
        super.onPause()
        sp.edit {
            //putInt("counter",mainViewModel.counter)
            putInt("counter",mainViewModel.counterData.value?:0)
        }
    }
}

可见在Activity中,我们不再手动的去获取这个counter值,而是直接调用ViewModel的相关方法让ViewModel去改变值,当然我们还要有观察数据的地方,因此之后就调用mainViewModel.counterData的observe方法来观察,input就是变化后的数据,将其更新到UI上即可。Java这里写法如下,最后再贴完整的代码。

mainViewModel.counterData.observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                textView.setText(integer.toString());
            }
 });

到这里为止,其实实现的效果和上面的还是一样的,只不过这种编写更加规范,也不用再担心ViewModel的内部进行是否开启线程进行了耗时操作。但是这仍然不是最规范的写法,最主要的是我们将counterData这个可变的LiveData暴露给了外部,这样在ViewModel的外面其实还是可以设置/修改数据的。因此还需对上述程序进行改造,不暴露可变的LiveData给外部,这样只有在ViewModel内部才能设置数据,安全性更高。修改MainViewModel代码,将counterData设置为LiveData对象,将原来的counterData重新命名为_counterData,并加上private修饰符,这样_counterData这个可变的LiveData对外部就是不可见的了,当外部调用counterData时,虽然实际上获得到的是_counterData的实例,但是已经无法给counterData设置数据了,因为它是不可变的LiveData而非MutableLiveData:

class MainViewModel(counter: Int) :ViewModel() {

    private val _counterData = MutableLiveData<Int>()
    val counterData : LiveData<Int> get() =_counterData

    init {
        _counterData.value = counter
    }

    fun plusone(){
        val count = _counterData.value?:0
        _counterData.value = count + 1
    }

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

map和switchMap

map方法的作用简单来说就是将实际包含数据的LiveData和仅用户观察数据的LiveData进行转换。还是紧接上面的Demo进行学习:

这里我们新建个User类:

class User(var firstName:String,var lastName:String,var age:Int)

我们可以在ViewModel中创建一个相应的LiveData来包含User类型的数据:

  val userData = MutableLiveData<User>()

但是如果我们想Activity中只显示用户的姓名而非全部信息,这时候将User类型的LiveData暴露给外部就显得不合适了。map()可以解决这种问题,它可以将User类型的LiveData自由的转换成任意其他类型的LiveData,修改MainViewModel代码:
在这里插入图片描述
这里为了对照方便给出Java代码:
在这里插入图片描述
可以看到,调用了Transformations的map方法对LiveData的数据类型进行转换,map()方法接收两个参数,第一个是原始的LiveData对象,第二个是一个转换函数,在这里编写具体的转换逻辑即可。这样外部使用观察者观察userName这个LiveData就可以了,当userData的值发生变化时,map()方法会监听到变化并执行转换逻辑,然后将转换后的数据通知给userName的观察者。

接下来看switchMap的使用,前面编写的程序都有一个前提就是LiveData对象的实例都是在ViewModel中创建的,但是也有可能这个LiveData对象是调用另外的方法得到的,那么必然会存在多个LiveData实例,因为每次调用这个方法都会返回一个新的LiveData实例,而如果不加以转换,直接调用observe观察的其实一直是老的LiveData实例,从而观察不到数据的变化。switchMap就是用来解决这种情况的,它会将这个LiveData对象转换成另外一个可观察的LiveData对象,接着上面的Demo进行学习。

新建单例类Repository,其中的唯一方法用来返回LiveData对象:

object Repository {
    fun getUser(userId:String):LiveData<User>{
        val livedata = MutableLiveData<User>()
        livedata.value = User(userId,userId,22)
        return livedata
    }
}

getUser方法接收userId参数并将它作为User对象的firstName与lastName,最后返回一个LiveData对象。这时如果我们在ViewModel中调用getUser()方法来得到LiveData对象,就会出现上面说的无法观察到数据变化的情况。switchMap可以解决这个问题,修改MainViewModel代码:

class MainViewModel(counter: Int) :ViewModel() {
   ......
    private val userIdData = MutableLiveData<String>()
    val userId:LiveData<User> = Transformations.switchMap(userIdData){
         input -> Repository.getUser(input)
    }

    fun getUser(userId:String){
        userIdData.value =userId
    }

  ......
}

定义了一个新的userIdData对象,用来观察userId的数据变化,然后调用Transformations的switchMap方法,用来对另一个可观察的LiveData对象进行转换。具体来说,当ViewModel外部调用getUser()方法获取用户数据时,只会将参数设置到userIdData中,userIdData的值一但改变,那么Transformations的switchMap()方法就会执行,并执行转换逻辑,然后将转换后的数据通知给userId的观察者即可。
修改MainActivity代码(布局文件新增一个按钮):
在这里插入图片描述
效果如下:
在这里插入图片描述

Room

Room是Jetpack提供的一个ORM(对象关系映射)框架,可以将面向对象的语言和面向关系的数据库之间建立一种映射关系,简单点说就是可以使用面向对象的思维来和数据库进行交互。Room主要由Entity、Dao、Database三部分组成:

  • Entity:用于定义封装实际数据的实体类,每个实体类都会在数据库中有一张表,表中的列根据实体类中的字段自动生成。我们操作实体类实际上也就等于在操作表,这就是前面所说的映射关系。
  • Dao:数据访问对象,用于对数据库的各项操作例如增删改查进行封装。
  • Database:用于定义数据库中的关键信息,包括数据库版本号、包括哪些实体类以及提供Dao层的访问实例。

继续上面的Demo学习一下Room框架,首先在app/build.gradle中添加依赖:

......
apply plugin: 'kotlin-kapt'
......
dependencies {
    ......
    implementation "androidx.room:room-runtime:2.1.0"
    kapt "androidx.room:room-compiler:2.1.0"
}

接着修改User类,添加主键字段id,并声明为Entity实体类:

@Entity
class User(var firstName:String,var lastName:String,var age:Int){
    @PrimaryKey(autoGenerate = true)//代表主键自动生成
    var id : Long = 0
}

接下来就要定义Dao,访问数据库的操作都是在Dao中进行封装:

@Dao
interface UserDao {
    @Insert
    fun insertUser(user: User) : Long
    
    @Update
    fun updateUser(newUser:User)
    
    @Query("select * from User")
    fun loadAllUsers():List<User>
    
    @Delete
    fun deleteUser(user:User)
    
    @Query("delete from User where lastName = :lastName")
    fun deleteByLastName(lastName:String):Int
}

这里insertUser会返回的其实就是PrimaryKey(主键)id,之后使用@Update和@Delete时都是基于这个id来操作的。

Dao编写完成后就要编写Database了,这里新建抽象类UserDatabase继承RoomDatabase:

@Database(version = 1,entities = [User::class])
abstract class UserDatabase : RoomDatabase(){
    abstract fun userDao():UserDao //提供抽象方法用于获取之前编写的Dao实例 具体方法实现由Room在底层自动自动完成的

    companion object{//静态(类)方法
        private var instance:UserDatabase ?=null
        
        @Synchronized
        fun getDatabase(context: Context):UserDatabase{
            instance?.let { 
                return it
            }
            return  Room.databaseBuilder(context.applicationContext,UserDatabase::class.java,"user_database").build().apply { instance = this }
        }
    }
}

Database中在 companion object中编写了一个单例模式,保证全局只保存一份UserDatabase实例。

接着布局中新增增删改查按钮,修改MainActivity:

class MainActivity : AppCompatActivity() {
    ......
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ......
        var userDao = UserDatabase.getDatabase(this).userDao()
        var user1 = User("Tom","Brady",22)
        var user2 = User("Andy","Hanks",33)

        add.setOnClickListener{
            thread {
                user1.id = userDao.insertUser(user1)
                user2.id = userDao.insertUser(user2)
            }
        }
        update.setOnClickListener{
            thread {
                user1.age = 42
                userDao.updateUser(user1)
            }
        }
        delete.setOnClickListener{
            thread {
                userDao.deleteByLastName("Hanks")
            }
        }
        query.setOnClickListener{
            thread {
                for (user in userDao.loadAllUsers()){
                    Log.d("MainActivity",user.firstName+" " +user.lastName + " " +user.age)
                }
            }
        }

    }
   ......
}

这里由于数据库操作属于耗时操作,因此需要在子线程中进行增删改查操作。效果如下:

点击AddUser然后点击Query按钮查询:
在这里插入图片描述
然后点击UpdateUser然后再点击Query按钮:
在这里插入图片描述
点击DeleteUser然后再点击Query按钮:
在这里插入图片描述

Room数据库的升级

例如在上面已经有User表的基础上,想要再增加一张Book表,那么首先创建Book实体类:

@Entity
class Book (var name:String,var pages:Int){
    @PrimaryKey(autoGenerate = true)
    var id : Long = 0
}

与之对应的BookDao接口,和前面User类一样:

@Dao
interface BookDao {
    @Insert
    fun insertBook(book: Book) : Long
    
    @Query("select * from Book")
    fun loadAllBooks():List<Book>
}

接下来修改UserDatabase代码:

@Database(version = 2,entities = [User::class,Book::class])
abstract class UserDatabase : RoomDatabase(){
    abstract fun userDao():UserDao //提供抽象方法用于获取之前编写的Dao实例 具体方法实现由Room在底层自动自动完成的

    abstract fun bookDao():BookDao

    companion object{
        val MIGRATION_1_2 = object :Migration(1,2){
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("create table if not exists Book (id integer primary key autoincrement not null, name text not null,pages integer not null)")
            }

        }

    private var instance:UserDatabase ?=null
    @Synchronized
    fun getDatabase(context: Context):UserDatabase{
        instance?.let {
            return it
        }
        return  Room.databaseBuilder(context.applicationContext,UserDatabase::class.java,"user_database").addMigrations(
            MIGRATION_1_2).build().apply { instance = this }
        }
    }
}

主要就是修改@Database注解,然后实现了一个Migration的匿名类,并传入1和2这两个参数,代表数据库版本从1升级到2的时候执行这个匿名类中的升级逻辑。最后在构建UserDatabase实例的时候,加入一个addMigrations方法,将这个匿名类实例变量传入即可。

WorkManager

WorkManager组件用于处理一些要求定时执行的任务,这里学习最简单的用法,处理复杂任务的可以自行参考《Android第一行代码》。

首先添加依赖:

implementation “androidx.work:work-runtime:2.2.0”

然后定义一个后台任务继承Worker类,并调用它唯一的构造函数,重写doWork方法,在这个方法中编写具体的后台任务逻辑:

class SimpleWorker(context: Context, workerParams: WorkerParameters):Worker(context, workerParams) {
    override fun doWork(): Result {
        Log.d("SimpleWorker","do work in SimpleWorker")
        return Result.success()
    }
}

doWork中要求返回一个Result对象代表任务执行是否成功,这里我们就直接指定任务执行成功,返回Result.success()。

然后修改布局文件增加dowork按钮,在MainActivity中编写点击逻辑:

 dowork.setOnClickListener{
            val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java).build()
            WorkManager.getInstance().enqueue(request)
  }

这里OneTimeWorkRequest.Builder是WorkRequest.Builder的子类,用于构建单次运行的后台任务请求。如果想要构建周期性运行的后台任务请求,可以使用WorkRequest.Builder的另一个子类PeriodicWorkRequest.Builder,且运行周期间隔不得短于15分钟,具体的用法参考《Android第一行代码》。然后将构建出的后台任务请求传入WorkManager的enqueue()中,系统就会在合适的时间去执行了。

所谓合适的时间,是由我们自己指定约束以及系统自身的一些优化决定的,这里为了简单就没有指定任何约束,因此点击按钮后任务会立即执行,效果如下:
在这里插入图片描述

Java

这里就直接贴代码,逻辑和Kotlin的一样,中间对照着转换的时候遇到过不少错误,有需要的可以参考参考。

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:textSize="32sp"/>

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="加1"/>
    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="清零"/>
    <Button
        android:id="@+id/button3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="getUser"/>

    <Button
        android:id="@+id/add"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="addUser"/>

    <Button
        android:id="@+id/delete"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="deleteUser"/>

    <Button
        android:id="@+id/update"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="updateUser"/>

    <Button
        android:id="@+id/query"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="queryUser"/>

    <Button
        android:id="@+id/dowork"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="doWork"/>

</LinearLayout>

MainActivity:

public class MainActivity extends AppCompatActivity {
    private MainViewModel mainViewModel;
    private Button button;
    private Button button2;
    private Button button3;
    private Button add,delete,update,query,dowork;
    private TextView textView;
    private SharedPreferences sp;
    private SharedPreferences.Editor editor;
    private UserDatabase userDatabase;
    private UserDao userDao;
    User user1,user2;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getLifecycle().addObserver(new MyObserver());
        setContentView(R.layout.activity_main);

        sp = getSharedPreferences("data",MODE_PRIVATE);
        editor = sp.edit();
        int counter = sp.getInt("counter",0);
        mainViewModel = new ViewModelProvider(this,new MainViewModelFactory(counter)).get(MainViewModel.class);

        bindViews();

        button.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 //mainViewModel.counter+=1;
                 //refreshCounter();
                 mainViewModel.plus();
             }
        });

        button2.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                // mainViewModel.counter = 0;
                 //refreshCounter();
                 mainViewModel.clear();
             }
        });

        button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String userId = getRandomString(5);
                mainViewModel.getUser(userId);
            }
        });

        userDatabase = UserDatabase.getInstance(this);
        userDao = userDatabase.userDao();
        user1 = new User("Tom","Brady",22);
        user2 = new User("Andy","Hanks",33);

        add.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                         user1.id = userDao.insertUser(user1);
                         user2.id = userDao.insertUser(user2);
                    }
                }).start();
            }
        });

         update.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 new Thread(new Runnable() {
                     @Override
                     public void run() {
                         user1.setAge(24);
                         userDao.updateUser(user1);
                     }
                 }).start();
             }
         });

         delete.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 new Thread(new Runnable() {
                     @Override
                     public void run() {
                         userDao.deleteUserByLastName("Hanks");
                     }
                 }).start();
             }
         });

         query.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 new Thread(new Runnable() {
                     @Override
                     public void run() {
                         List<User> list = userDao.loadAllUsers();
                         for(int i=0;i<list.size();i++){
                             User user = list.get(i);
                             Log.d("MainActivity",user.getFirstName()+"," + user.getLastName() + "," +user.getAge());
                         }
                     }
                 }).start();
             }
         });

        dowork.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(SimpleWorker.class).build();
                WorkManager.getInstance(MainActivity.this).enqueue(request);
            }
        });
        //refreshCounter();
        mainViewModel.counterData.observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                textView.setText(integer.toString());
            }
        });

        mainViewModel.userLiveData.observe(this, new Observer<User>() {
            @Override
            public void onChanged(User user) {
                textView.setText(user.getFirstName());
            }
        });
    }

    private void bindViews() {
        button = findViewById(R.id.button);
        button2 = findViewById(R.id.button2);
        button3 = findViewById(R.id.button3);
        textView = findViewById(R.id.textview);
        add = findViewById(R.id.add);
        delete = findViewById(R.id.delete);
        update = findViewById(R.id.update);
        query = findViewById(R.id.query);
        dowork = findViewById(R.id.dowork);
    }

    public LiveData<User> getUser(String userId){
        return Repository.getInstance().getUser(userId);
    }

//    private void refreshCounter() {
//        textView.setText(mainViewModel.counter+"");
//    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.i("onPause","pause执行了");
        //editor.putInt("counter",mainViewModel.counter);
        if(mainViewModel.counterData.getValue()!=null){
            editor.putInt("counter",mainViewModel.counterData.getValue());
        }else {
            editor.putInt("counter",0);
        }
        editor.apply();
    }

    public String getRandomString(int length){
        String str="zxcvbnmlkjhgfdsaqwertyuiopQWERTYUIOPASDFGHJKLZXCVBNM1234567890";
        Random random=new Random();
        StringBuffer stringBuffer = new StringBuffer();
        for(int i=0; i<length; ++i){
            int number=random.nextInt(62);
            stringBuffer.append(str.charAt(number));
        }
        return stringBuffer.toString();
    }
}

MainViewModel:

public class MainViewModel extends ViewModel {
    //int counter;
    private MutableLiveData<Integer> _counterData = new MutableLiveData<>();
    final LiveData<Integer> counterData = _counterData;

    private MutableLiveData<User> userData = new MutableLiveData<>();
    private MutableLiveData<String> userIdData = new MutableLiveData<>();

    final LiveData<String> userName = Transformations.map(userData, new Function<User, String>() {
        @Override
        public String apply(User input) {
            return input.getFirstName()+input.getLastName();
        }
    });

    final LiveData<User> userLiveData = Transformations.switchMap(userIdData, new Function<String, LiveData<User>>() {
        @Override
        public LiveData<User> apply(String input) {
            return Repository.getInstance().getUser(input);
        }
    });

    public void getUser(String userId){
        userIdData.setValue(userId);
    }

    public MainViewModel() {}

    public MainViewModel(int counter) {
        //this.counter = counter;
        _counterData.setValue(counter);
    }

    public void plus(){
        int counter;
        if(_counterData.getValue()!=null){
            counter = _counterData.getValue();
        }else {
            counter = 0;
        }
        _counterData.setValue(counter+1);
    }

    public void clear(){
        _counterData.setValue(0);
    }
}

MainViewModelFactory:

public class MainViewModelFactory implements ViewModelProvider.Factory {
    private int counter;

    public MainViewModelFactory(int counter) {
        this.counter = counter;
    }

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        return (T) new MainViewModel(counter);
    }
}

MyObserver:

public class MyObserver implements LifecycleObserver {
    private Lifecycle lifecycle;

    public MyObserver() {
    }

    public MyObserver(Lifecycle lifecycle) {
        this.lifecycle = lifecycle;
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    public void activityStart(){
       Log.d("MyObserver","activityStart");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    public void activityStop(){
        Log.d("MyObserver","activityStop");
    }
}

Repository:

public class Repository {
    private static Repository instance;

    private Repository() { }

    public synchronized static Repository getInstance(){
        if(instance == null){
            instance = new Repository();
        }
        return instance;
    }

    public LiveData<User> getUser(String userid){
        MutableLiveData<User>  userData = new MutableLiveData<>();
        userData.setValue(new User(userid,userid,20));
        return userData;
    }
}

User类:

@Entity
public class User {
    private String firstName;
    private String lastName;
    private int age;

    @PrimaryKey(autoGenerate = true)
    Long id;

    public User(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getId() {
        return id;
    }
}

UserDao接口

@Dao
public interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    Long insertUser(User user);

    @Update
    int updateUser(User newUser);

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

    @Query("select * from User where age>:age")
    List<User> loadUserOlderThan(int age);

    @Delete
    void deleteUser(User user);

    @Query("delete from User where lastName = :lastName")
    int deleteUserByLastName(String lastName);

}

UserDatabase抽象类:

@Database(version = 2,entities = {User.class,Book.class})
public abstract class UserDatabase extends RoomDatabase {
    public abstract UserDao userDao();
    public abstract BookDao bookDao();

    private static UserDatabase instance;

    static final Migration MIGRATION_1_2 = new Migration(1,2) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            database.execSQL("create table if not exists Book (id integer primary key autoincrement not null, name text not null,pages integer not null)");
        }
    };

    public  static synchronized UserDatabase getInstance(Context context){
        if(instance == null){
            instance =  Room.databaseBuilder(context.getApplicationContext(),UserDatabase.class,"user_databae").addMigrations(MIGRATION_1_2).build();
        }
        return instance;
    }
}

Book类:

@Entity
public class Book {
     private String name;
     private int pages;

    @PrimaryKey(autoGenerate = true)
     Long id;

    public Book(String name, int pages) {
        this.name = name;
        this.pages = pages;
    }

    public String getName() {
        return name;
    }

    public int getPages() {
        return pages;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPages(int pages) {
        this.pages = pages;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getId() {
        return id;
    }
}

BookDao接口:

@Dao
public interface BookDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    Long insertUser(Book book);

    @Query("select * from Book")
    List<Book> loadAllBooks();

}

SimpleWorker类:

public class SimpleWorker extends Worker {

    public SimpleWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {
        Log.d("SimpleWorker","do work in SimpleWorker");
        return Result.success();
    }
}

这里就学习完Jetpack的基础知识了,当然还有其他常用的功能更加强大的组件没有提到,之后有时间会再深入学习。

猜你喜欢

转载自blog.csdn.net/weixin_42011443/article/details/107726923