Jetpack
jetpack时一个开发组件的工具集
1.ViewModel
viewModel的一个重要作用就是可以帮助Activity分担一部分工作(当手机发生横竖屏旋转时Activity会被重新创建,而ViewModel的生命周期不会)
先在app/build.gradle文件中添加依赖:
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
为MainActivity创建一个对应的MainViewModel类
class MainViewModel : ViewModel(){
var counter = 0
}
修改activity_main.xml中的代码
<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"
tools:context=".MainActivity"
android:orientation="vertical">
<TextView
android:id="@+id/infoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="32sp"
></TextView>
<Button
android:id="@+id/plusOneBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="加一"
></Button>
</LinearLayout>
最后修改MainActivity中的代码
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
plusOneBtn.setOnClickListener {
viewModel.counter++
refreshCounter()
}
refreshCounter()
}
private fun refreshCounter(){
infoText.text=viewModel.counter.toString()
}
}
通过ViewModelProviders来获取ViewModel的实例
ViewModelProviders.of(<你的Activity或Fragment 实例>).get(<你的ViewModel>::class.java)
2.向ViewModel传递参数
修改MainViewModel中的代码
class MainViewModel(countReserved:Int) : ViewModel(){
var counter = countReserved;
}
向ViewModel传递参数选哟借助ViewModelProvider.Factory
新建一个MainViewModelFactory类,并让它实现ViewModelProvider.Factory接口
class MainViewModelFactory(private val countReserved) :ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MainViewModel(countReserved ) as T
}
}
我们在界面上新增清零按钮
<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"
tools:context=".MainActivity"
android:orientation="vertical">
...
<Button
android:id="@+id/clearBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="清零"
></Button>
</LinearLayout>
最后修改MainActivity中的代码
class MainActivity : AppCompatActivity() {
...
lateinit var sp : SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
...
sp=getPreferences(Context.MODE_PRIVATE)
val countReserved = sp.getInt("count_reserved",0)
viewModel = ViewModelProviders.of(this,MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
clearBtn.setOnClickListener {
viewModel.counter=0
refreshCounter()
}
refreshCounter()
}
private fun refreshCounter(){
infoText.text=viewModel.counter.toString()
}
override fun onPause() {
super.onPause()
sp.edit{
putInt("count_reserved",viewModel.counter)
}
}
}
2.Lifecycles
首先创建MyObserver类
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")
}
}
生命周期事件一共七种ON_CREATE,ON_START,ON_RESUME,ON_PAUSE,ON_STOP和ON_DESTROY。还有一种ON_ANY类型,表示可以匹配Activity的任何生命周期。
在MainActivity中加入代码,就可以帮我们自动感知
lifecycle.addObserver(MyObserver())
为了能主动感知到Activity的生命周期状态,修改MyObserver
class MyObserver(val lifecycle : Lifecycle) :LifecycleObserver{
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun activityStart(){
Log.d("MyObserver","activityStart")
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun activityStop(){
Log.d("MyObserver","activityStop")
}
}
有了Lifecyle对象之后,我们可以在任意地方调用lifecyle.currentState来主动获知当前生命周期状态。lifecycle.currentState返回的生命周期状态是一个枚举类型,一共又INITIALIZED,DESTROYED,CREATED,STARTED,RESUMED这五种状态类型,
LiveData
在数据变化的时候通知给观察者。LiveData特别适合于ViewModel结合使用
修改MainViewModel中的代码
class MainViewModel(countReserved:Int) : ViewModel(){
val counter = MutableLiveData<Int>()
init {
counter.value=countReserved
}
fun plusOne(){
val count = counter.value ?:0
counter.value=count+1
}
fun clear(){
counter.value=0
}
}
MutableLiveData是一种可变的LiveData,它主要有3种读写数据的方式,分别是getValue(),setValue()和postValue()方法。setValue只能在主线程力给LiveData设置数据,postValue()方法用于在非主线程中给LiveData设置数据。
修改MainActivity中的代码
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MainViewModel
lateinit var sp : SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
...
plusOneBtn.setOnClickListener {
viewModel.plusOne()
}
clearBtn.setOnClickListener {
viewModel.clear()
}
viewModel.counter.observe(this, Observer {count ->
infoText.text=count.toString()
})
...
}
private fun refreshCounter(){
infoText.text=viewModel.counter.toString()
}
override fun onPause() {
super.onPause()
sp.edit{
putInt("count_reserved",viewModel.counter.value ?:0)
}
}
}
这里调用了它的observe()方法来观察数据变化。接收两个参数第一个参数是一个LifecycleOwner对象,第二个参数是一个Observer接口,当counter中包含的数据发送变化时,就会回调到这里。
我们可以加入对observe()方法的语法扩展,需要在app/build.gradle文件中添加如下依赖
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
我们就可以使用如下语法结构的observe()
viewModel.counter.observe(this){ count ->
infoText.text=count.toString()
}
比较推荐的做法永远不爆露不可变的LiveData给外部。这样非ViewModel中就只能观察LiveData的数据变化,而不能给LiveData设置数据
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
}
}
这里先将议案来的counter变量名改为_counter变量,并给它加上private修饰符,这样_counter变量对于外部就是不可见的。然后我们有重新定义了一个counter,将它生命为不可变的LiveData,并在它的get
()属性方法中返回_counter变量。
当外部调用count变量时,实际上获得是_counter的实例
map 和 switchMap
map()这个方法的作用是将实际包含数据的LiveData和仅用于观察数据的LiveData进行转换
创建一个User类
data class User(var firstName : String,var lastName : String, var age: Int)
在ViewModel中创建一个相应的LiveData来包含User类型的数据
class MainViewModel(countReserved:Int) : ViewModel(){
private val userLiveData = MutableLiveData<User>()
val userName : LiveData<String> = Transformations.map(userLiveData){user->
"${user.firstName} ${user.lastName}"
}
...
}
这里我们调用了Transformations的map()方法来对LiveData的数据类型转换。map()方法接收两个参数:第一个是原始的LiveData对象,第二个参数是一个转换函数。
我们来模拟一种情况(服务器请求数据库查一个User对象),新建一个Repository单例类:
object Repository {
fun getUser(userId : String) : LiveData<User>{
val liveData = MutableLiveData<User>()
liveData.value= User(userId,userId,0)
return liveData
}
}
在MainViewModel中定义一个getUser()方法
class MainViewModel(countReserved:Int) : ViewModel(){
...
fun getUser(userId : String) : LiveData<User>{
return Repository.getUser(userId)
}
}
switchMap()方法可以派上用场,修改MainViewModel中的代码
class MainViewModel(countReserved:Int) : ViewModel(){
private val userLiveData = MutableLiveData<User>()
...
private val userIdLiveData = MutableLiveData<String>()
val user:LiveData<User> = Transformations.switchMap(userIdLiveData){userId->
Repository.getUser(userId)
}
fun getUser(userId:String){
userIdLiveData.value=userId
}
/* fun getUser(userId : String) : LiveData<User>{
return Repository.getUser(userId)
}
*/
}
这里定义了一个新的userIdLiveData对象,用来观察userId的数据变化,然后调用了Transformations的switchMap()党法,用来对另一个可观察的LiveData对象进行转换.
switchMap()方法同样接收两个参数:第一个参数传入我们新增的userIdLiveData,第二个参数是一个转换函数,必须在这个转换函数中返回的LiveData对象转换成另一个刻观察的LiveData.我们只需在转换函数中调用Repository的getUser()方法来得到LiveData对象。
创建一个按钮来获取User
<Button
android:id="@+id/getUserBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="获取User"
></Button>
修改MainActivity中的代码
getUserBtn.setOnClickListener {
val userId = (0..1000).random().toString()
viewModel.getUser(userId)
}
viewModel.user.observe(this, Observer {user->
infoText.text = user.firstName
})
ViewModel中某个获取数据的方法是没有参数的这样写
class MyViewModel : ViewModel(){
private val refreshLiveData = MutableLiveData<Any?>()
val refreshResult = Transformation.switchMap(refreshLiveData){
Repository.refresh()
}
fun refresh(){
refreshLiveData.value = refreshLiveData.value
}
}
Room
为Android数据库设计的ORM框架
ORM(Object Relational Mapping)也叫对象关系映射,将面向对象的语言和面向关系的数据库之间建立一种映射关系,这就是ORM。
1.使用Room增删改查
Room整体结构由Entity,Dao和Databases这三部分组成
Entity,用于定义实体类
Dao,数据访问对象
Databases,定义数据库关键信息
apply plugin: 'kotlin-kapt'
dependencies {
implementation 'androidx.room:room-runtime:2.1.0'
kapt 'androidx.room:room-compiler:2.1.0'
}
修改User类,完成实体类声明
@Entity
data class User(var firstName : String,var lastName : String, var age: Int){
@PrimaryKey(autoGenerate = true)
var id:Long = 0
}
使用@Entity将它声明成实体类,在User类中添加一个id字段,并使用@PrimaryKey注解成主键,再把autoGenerate参数指定成true,使得主键的值是自动生成的。
新建一个UserDao接口
@Dao
interface UserDao {
@Insert
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,新建一个Appdatabase类
@Database(version = 1,entities = [User::class])
abstract class AppDatabase : RoomDatabase(){
abstract fun userDao() : UserDao
companion object{
private var instance : AppDatabase?=null
@Synchronized
fun getDatabases(context: Context) :AppDatabase{
instance?.let {
return it
}
return Room.databaseBuilder(context.applicationContext,AppDatabase::class.java,"app_database")
.build().apply {
instance=this
}
}
}
}
我们在companion object结构体中编写了一个单例类,原则上只有一份AppDatabase实例。用Room.databaseBuilder()方法来构建Appdatabase实例。databaseBuilder()方法接收3个参数,注意第一个参数一定要使用applicationoContext,第二个参数是AppDatabase的Class类型,第三个是数据库名。
修改activity_main.xml中的代码
<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"
tools:context=".MainActivity"
android:orientation="vertical">
...
<Button
android:id="@+id/getUserBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="获取User"
></Button>
<Button
android:id="@+id/addDataBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="添加数据"
></Button>
<Button
android:id="@+id/updateDataBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="更新数据"
></Button>
<Button
android:id="@+id/deleteDataBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="删除数据"
></Button>
<Button
android:id="@+id/quertDataBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="查询数据"
></Button>
</LinearLayout>
最后修改MainActivity中的代码
....
val userDao = AppDatabase.getDatabases(this).userDao()
val user1 = User("垃圾佬","王",40)
val user2 = User("高富帅","李",13)
addDataBtn.setOnClickListener {
thread {
user1.id=userDao.insertUser(user1)
user2.id=userDao.insertUser(user2)
}
}
updateDataBtn.setOnClickListener {
thread {
user1.age=18
userDao.updateUser(user1)
}
}
deleteDataBtn.setOnClickListener {
thread {
userDao.deleteUserByLastName("王")
}
}
quertDataBtn.setOnClickListener {
thread {
for (user in userDao.loadAllUsers()){
Log.d("MainActivity",user.toString())
}
}
}
Room默认是不允许在主线程中进行数据库操作的,为了测试方便,提供了一种简单的方法
Room.databaseBuilder(context.applicationContext,AppDatabase::class.java,"app_database")
.allowMainThreadQueries()
.build()
建议只在测试环境下使用
2.Room的数据库升级
在开发阶段,提供了一种简单粗暴的方法
Room.databaseBuilder(context.applicationContext,AppDatabase::class.java,"app_database")
.fallbackToDestructiveMigration()
.build()
但是Room会将当前数据库销毁重建,导致数据库中所有数据全部丢失
新创一张Book表,创建Book实体类
@Entity
class Book(var name:String,var pages : Int) {
@PrimaryKey(autoGenerate = true)
var id : Long=0
}
再创建一个BookDao类
@Dao
interface BookDao {
@Insert
fun insertBook(book:Book) : Long
@Query("select * from Book")
fun loadAllBooks() : List<Book>
}
最后修改AppDatabase中的代码
@Database(version = 2,entities = [User::class,Book::class])
abstract class AppDatabase : RoomDatabase(){
abstract fun userDao() : UserDao
abstract fun bookDao() :BookDao <-------修改这里
companion object{
val MIGRATION_1_2 = object : Migration(1,2){ <-------修改这里
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("create table Book (id integer primary key autoincrement not null," +
"name text not null," +
"pages integer not null)")
}
}
private var instance : AppDatabase?=null
@Synchronized
fun getDatabases(context: Context) :AppDatabase{
instance?.let {
return it
}
return Room.databaseBuilder(context.applicationContext,AppDatabase::class.java,"app_database")
.addMigrations(MIGRATION_1_2) <------修改这里
.build().apply {
instance=this
}
}
}
}
假如增加新的列
@Entity
class Book(var name:String,var pages : Int,var author : String) {
@PrimaryKey(autoGenerate = true)
var id : Long=0
}
修改AppDatabases中的代码
@Database(version = 3,entities = [User::class,Book::class])
abstract class AppDatabase : RoomDatabase(){
...
val MIGRATION_2_3 = object : Migration(2,3){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table Book add column author text not null default 'unknown'")
}
}
@Synchronized
fun getDatabases(context: Context) :AppDatabase{
instance?.let {
return it
}
return Room.databaseBuilder(context.applicationContext,AppDatabase::class.java,"app_database")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build().apply {
instance=this
}
}
}
}
WorkManager
implementation 'androidx.work:work-runtime:2.2.0'
第一步定义一个后台任务,这里创建一个SimpleWorker类
class SimpleWorker(context : Context,params : WorkerParameters) : Worker(context,params) {
override fun doWork(): Result {
Log.d("SimpleWork","SimpleWork正在干活")
return Result.success()
}
}
doWork()方法不会在主线程中执行,可以放心执行耗时任务。doWork方法要求返回一个Result对象,用于表现运行结果,成功就返回Result.success()失败就返回Result.failuer()。还有一个Result.retry()方法,它其实也代表失败,只是可以结合WorkRequest.Builder的setBackoffCriteria()方法来重新执行任务。
最基本的配置
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java).build()
OneTimeWorkRequest.Builder是WorkRequest.Builder的子类。WorkRequest.Builder还有另外一个子类PeriodicWorkRequest.Builder用于构建周期性运行的后台任务请求,为了降低性能,PeriodicWorkRequest.Builder构造函数传入周期间隔不能低于15分钟
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java,15,TimeUnit.MINUTES).build()
最后一步将构建任务请求传入WorkManager
WorkManager.getInstance(this).enqueue(request)
2.WorkManager处理复杂的任务
1.
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.setInitialDelay(5,TimeUnit.MINUTES)
.build()
让SimpleWorker这个后台任务在5分钟后运行
2.给后台任务请求添加标签
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.addTag("simple")
.build()
最主要的一个功能我们可以通过标签来取消后台任务请求
WorkManager.getInstance(this).cancelWorkByTag("simple")
也可以通过id来取消后台任务请求
WorkManager.getInstance(this).cancelWorkById(request.id)
我们也可以一次性取消所有后台
WorkManager.getInstance(this).cancelAllWork()
3.我们可以利用Result.retry(),结合setBackoffCriteria()方法来重新执行任务
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.setBackoffCriteria(BackoffPolicy.LINEAR,10,TimeUnit.SECONDS)
.build()
setBackoffCriteria()方法接收3个参数:第二个和第三个参数指定多久之后重新执行任务,时间最短不能少于10秒。第一个参数用于指定如果任务再次执行失败,下次重试的时间应该是什么样的形式延迟。可选值两种LINEAR和EXPONENTIAL,前者代表下次重试时间以线性方式延迟,后者代表下次重试时间以指数方式延迟。
我们可以使用如下代码对后台任务的运行结果进行监听。
WorkManager.getInstance(this)
.getWorkInfoByIdLiveData(request.id)
.observe(this){ workInfo ->
if(workInfo.state ==WorkInfo.State.SUCCEEDED){
log.d("MainActivity","do work succeeded")
}else if(workInfo.state==WorkInfo.state.FAILED){
log.d("MainActivity","do work failed")
}
}
我们也可以哟个getWorkInfoByTagLiveData()方法来监听同一标签下后台任务请求运行结果
4.WorkManager特色功能—链式任务
这里定义了3个独立的后台任务:同步数据,压缩数据和上传数据
val sync = ...
val compress = ...
val upload = ...
WorkManager.getInstance(this)
.beginWith(sync)
.then(compress)
.then(upload)
.enqueue()
beginWith()方法开创一个链式任务,后面只需使用then()连接即可。必须前一个后台任务运行成功之后,下一个后台任务才会运行
WorkManager千万别依赖它实现核心功能,它在国产手机上会非常不稳定
Kotlin课堂:DSL构建语法结构
1.使用DSL可以实现类似的语法结构
class Dependency{
val libraries = ArrayList<String>()
fun implementtion(lib : String){
libraries.add(lib)
}
}
fun dependencies(block : Dependency.() ->Unit) : List<String>{
val dependency = Dependency()
dependency.block()
return dependency.libraries
}
fun main(){
val libraries = dependencies {
implementtion("com.squareup.retrofit2:retrofit:2.6.1")
implementtion("com.squareup.retrofit2:converter-gson:2.6.1")
}
for (lib in libraries){
println(lib)
}
}
2.
class Td{
var content = ""
fun html()="\n\t\t<td>$content</td>"
}
class Tr{
private val children = ArrayList<Td>()
fun td(block: Td.() -> String){
val td = Td()
td.content = td.block()
children.add(td)
}
fun html() : String{
val builder = StringBuilder()
builder.append("\n\t<tr>")
for (childTag in children){
builder.append(childTag.html())
}
builder.append("\n\t</tr>")
return builder.toString()
}
}
class Table {
private val children = ArrayList<Tr>()
fun tr(block: Tr.() -> Unit){
val tr =Tr()
tr.block()
children.add(tr)
}
fun html() : String{
val builder = StringBuilder()
builder.append("<table>")
for (childTag in children){
builder.append(childTag.html())
}
builder.append("\n</table>")
return builder.toString()
}
}
fun main(){
val table =Table()
table.tr {
td { "apple" }
td { "banana" }
td { "pear" }
}
table.tr {
td { "apple" }
td { "banana" }
td { "pear" }
}
print(table.html())
}
另外DSL中可以使用Kotlin的其他语法特性
fun main(){
val html = table{
repeat(2){
tr{
val fruits = listOf("apple","grape","orange")
for(fruit in fruits){
td{fruit}
}
}
}
}
pritln(html)
}