在学习View的中间插个小插曲,这里学习一下Jetpack的基本用法。参考书籍为**《Andorid第一行代码》**。Jetpack 是一套库、工具和指南,可帮助开发者更轻松地编写优质应用。这些组件可帮助程序员遵循最佳做法、摆脱编写样板代码的工作并简化复杂任务,以便将精力集中放在所需的代码上。详细的介绍可以去看Android developer。
在Android developer的介绍中也可以看到,Jetpack主要由基础、架构、行为、界面构成,我们这里主要学习架构的基础知识。Jetpack中的许多架构组件是专门为MVVM架构打造的,MVVM架构是目前Android官方推荐的项目架构之一。MVVM架构可以将程序结构主要分成3部分:Model(数据模型部分)、View(界面展示部分)、ViewModel(连接数据模型和界面展示的桥梁,从而实现业务逻辑和界面展示分离的程序结构设计)。MVVM简化架构图如下图所示:
由上图可看到,MVVM架构将程序分为了若干层:
先看Kotlin版的,最后会写出对应的Java版,大致逻辑是相同的,只不过代码方面会有一些变化。
前面说过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参数传递进去。
修改布局文件:
<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可以让任何一个类都能感知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是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方法的作用简单来说就是将实际包含数据的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是Jetpack提供的一个ORM(对象关系映射)框架,可以将面向对象的语言和面向关系的数据库之间建立一种映射关系,简单点说就是可以使用面向对象的思维来和数据库进行交互。Room主要由Entity、Dao、Database三部分组成:
继续上面的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按钮:
例如在上面已经有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组件用于处理一些要求定时执行的任务,这里学习最简单的用法,处理复杂任务的可以自行参考《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()中,系统就会在合适的时间去执行了。
所谓合适的时间,是由我们自己指定约束以及系统自身的一些优化决定的,这里为了简单就没有指定任何约束,因此点击按钮后任务会立即执行,效果如下:
这里就直接贴代码,逻辑和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的基础知识了,当然还有其他常用的功能更加强大的组件没有提到,之后有时间会再深入学习。