Android Jetpack(三) 架构
fun enable() {enabled = true
//如果 Lifecycle 如今未处于精良的状态,则应避免调用某些回调。
//比方,如果回调在 Activity 状态保存后运行 Fragment 事务,就会引发崩溃,因此我们绝不能调用该回调。
//以是,Lifecycle 类允许其他对象查询当前状态。
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// connect if not connected
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() {
// disconnect if connected
}
}
class MyActivity : AppCompatActivity() {
private lateinit var myLocationListener: MyLocationListener
override fun onCreate(...) {
myLocationListener = MyLocationListener(this, lifecycle) { location ->
// update UI
}
Util.checkUserStatus { result ->
if (result) {
myLocationListener.enable()
}
}
}
}
LocationListener 类可以完全感知生命周期。如果我们需要从另一个 Activity 或 Fragment 使用 LocationListener,只需对其进行初始化。所有设置和拆解操作都由类本身管理,我们建议您使用生命周期感知型组件,可以轻松集成这些组件,而无需在客户端进行手动生命周期管理。
#### []( )生命周期感知型组件的最佳做法
**1.使界面控制器(Activity 和 Fragment)保持精简。它们不应直接获取数据,而应使用 ViewModel 获取,并观察 LiveData 对象更新UI视图。**
**2.设法编写数据驱动型界面,界面控制器的责任是随着数据更改而更新视图,或者将用户操作通知给 ViewModel。**
**3.将数据逻辑放在 ViewModel 类中。 ViewModel 应充当界面控制器与应用其余部分之间的连接器。不过要注意,ViewModel 不负责获取数据(例如,从网络获取)。ViewModel 应调用相应的组件来获取数据,然后将结果提供给界面控制器。**
**4.使用 Data Binding 在视图与界面控制器之间维持干净的接口,您可以使视图更具声明性,并尽量减少需要在 Activity 和 Fragment 中编写的更新代码。**
**5.如果界面很复杂,不妨考虑创建 presenter 类来处理界面的修改。**
**6.避免在 ViewModel 中引用 View 或 Activity 上下文。 如果 ViewModel 存在的时间比 Activity 更长(在配置更改的情况下),Activity 将泄露并且不会由垃圾回收器妥善处置。**
**7.使用 Kotlin 协程管理长时间运行的任务和其他可以异步运行的操作。**
#### []( )生命周期感知型组件的使用场景
生命周期感知型组件可使您在各种情况下更轻松地管理生命周期。下面列举几个例子:
1.在粗粒度和细粒度位置更新之间切换。在位置应用可见时启用细粒度位置更新,在应用位于后台时切换到粗粒度更新。借助生命周期感知型组件 LiveData,应用可以在用户使用位置发生变化时自动更新界面。
2.停止和开始视频缓冲。可尽快开始视频缓冲,但会推迟播放,直到应用完全启动。此外,应用销毁后,您还可以使用生命周期感知型组件终止缓冲。
3.开始和停止网络连接。可在应用位于前台时启用网络数据的实时更新(流式传输),并在应用进入后台时自动暂停。
4.暂停和恢复动画可绘制资源。可在应用位于后台时暂停动画可绘制资源,并在应用位于前台后恢复可绘制资源。
#### []( )处理 ON\_STOP 事件
如果 Lifecycle 属于 AppCompatActivity 或 Fragment,那么调用 AppCompatActivity 或 Fragment 的 onSaveInstanceState() 时,Lifecycle 的状态会更改为 CREATED 并且会分派 ON\_STOP 事件。
通过 onSaveInstanceState() 保存 Fragment 或 AppCompatActivity 的状态后,其界面被视为不可变,直到调用 ON\_START。如果在保存状态后尝试修改界面,很可能会导致应用的导航状态不一致,因此应用在保存状态后运行 FragmentTransaction 时,FragmentManager 会抛出异常。
LiveData 本身可防止出现这种极端情况,方法是在其观察者的关联 Lifecycle 还没有至少处于 STARTED 状态时避免调用其观察者。 在后台,它会在决定调用其观察者之前调用 isAtLeast()。
遗憾的是,AppCompatActivity 的 onStop() 方法会在 onSaveInstanceState() 之后调用,这样就会留下一个缺口,即不允许界面状态发生变化,但 Lifecycle 尚未移至 CREATED 状态。
为防止出现这个问题,beta2 及更低版本中的 Lifecycle 类会将状态标记为 CREATED 而不分派事件,这样一来,即使未分派事件(直到系统调用 onStop()),检查当前状态的任何代码也会获得实际值。
#### []( )遗憾的是,此解决方案有两个主要问题:
在 API 23 及更低级别,Android 系统实际上会保存 Activity 的状态,即使它的一部分被另一个 Activity 覆盖。换句话说,Android 系统会调用 onSaveInstanceState(),但不一定会调用 onStop()。这样可能会产生很长的时间间隔,在此时间间隔内,观察者仍认为生命周期处于活动状态,虽然无法修改其界面状态。
要向 LiveData 类公开类似行为的任何类都必须实现由 Lifecycle 版本 beta 2 及更低版本提供的解决方案。
注意:为了简化此流程并让其与较低版本实现更好的兼容性,自 1.0.0-rc1 版本起,当调用 onSaveInstanceState() 时,会将 Lifecycle 对象标记为 CREATED 并分派 ON\_STOP,而不等待调用 onStop() 方法。这不太可能影响您的代码,但您需要注意这一点,因为它与 API 26 及更低级别的 Activity 类中的调用顺序不符。
### []( )示例:
* ( )
* ( ),这是一个演示应用,演示架构组件的最佳做法
[]( )( )
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
[`LiveData`]( ) 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。
如果观察者(由 [`Observer`]( ) 类表示)的生命周期处于 [`STARTED`]( ) 或 [`RESUMED`]( ) 状态,则 LiveData 会认为该观察者处于活跃状态。LiveData 只会将更新通知给活跃的观察者。为观察 [`LiveData`]( ) 对象而注册的非活跃观察者不会收到更改通知
### []( )LiveData 的优势
##### []( )确保界面符合数据状态
LiveData 遵循观察者模式。当生命周期状态发生变化时,LiveData 会通知 Observer 对象,观察者可以在每次发生更改时更新界面,而不是在每次应用数据发生更改时更新界面。
##### []( )不会发生内存泄露
观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁后进行自我清理。
##### []( )不会因 Activity 停止而导致崩溃
如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。
##### []( )不再需要手动处理生命周期
界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。
##### []( )数据始终保持最新状态
如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。
##### []( )适当的配置更改
如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。
##### []( )共享资源
您可以使用单一实例模式扩展 LiveData 对象以封装系统服务,以便在应用中共享它们。LiveData 对象连接到系统服务一次,然后需要相应资源的任何观察者只需观察 LiveData 对象
### []( )使用LiveData
1.创建 LiveData 实例以存储某种类型的数据。这通常在 ViewModel 类中完成。
2.创建可定义 onChanged() 方法的 Observer 对象,该方法可以控制当 LiveData 对象存储的数据更改时会发生什么。通常情况下,您可以在界面控制器(如 Activity 或 Fragment)中创建 Observer 对象。
3.使用 observe() 方法将 Observer 对象附加到 LiveData 对象。observe() 方法会采用 LifecycleOwner 对象。这样会使 Observer 对象订阅 LiveData 对象,以使其收到有关更改的通知。通常情况下,您可以在界面控制器(如 Activity 或 Fragment)中附加 Observer 对象。
注意:您可以使用 observeForever(Observer) 方法来注册未关联 LifecycleOwner 对象的观察者。在这种情况下,观察者会被视为始终处于活跃状态,因此它始终会收到关于修改的通知。您可以通过调用 removeObserver(Observer) 方法来移除这些观察者。
当您更新存储在 LiveData 对象中的值时,它会触发所有已注册的观察者(只要附加的 LifecycleOwner 处于活跃状态)。
LiveData 允许界面控制器观察者订阅更新。当 LiveData 对象存储的数据发生更改时,界面会自动更新以做出响应。
### []( )创建LiveData对象
[`LiveData`]( ) 对象通常存储在 [`ViewModel`]( ) 对象中,并可通过 getter 方法进行访问
class NameViewModel : ViewModel() {
// Create a LiveData with a String
val currentName: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
// Rest of the ViewModel...
}
注意:请确保将用于更新界面的 LiveData 对象存储在 ViewModel 对象中,而不是将其存储在 Activity 或 Fragment 中,原因如下:
* 避免 Activity 和 Fragment 过于庞大。现在,这些界面控制器负责显示数据,但不负责存储数据状态。
* 将 LiveData 实例与特定的 Activity 或 Fragment 实例分离开,并使 对象在配置更改后继续存在。
### []( )观察 LiveData 对象
一般在 onCreate() 方法是开始观察 LiveData 对象的正确着手点,原因如下:
* 确保系统不会从 Activity 或 Fragment 的 onResume() 方法进行冗余调用。
* 确保 Activity 或 Fragment 变为活跃状态后具有可以立即显示的数据。一旦应用组件处于 STARTED 状态,就会从它正在观察的 LiveData 对象接收最新值。只有在设置了要观察的 LiveData 对象时,才会发生这种情况。
//在传递 nameObserver 参数的情况下调用 observe() 后,系统会立刻调用 onChanged(),从而提供 mCurrentName 中存储的最新值。 如果 LiveData 对象尚未在 mCurrentName 中设置值,则不会调用 onChanged()。
class NameActivity : AppCompatActivity() {
private lateinit var model: NameViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Other code to setup the activity...
// Get the ViewModel.
model = ViewModelProviders.of(this).get(NameViewModel::class.java)
// 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)
}
}
### []( )更新 LiveData 对象
LiveData 没有公开可用的方法来更新存储的数据。MutableLiveData 类将公开 setValue(T) 和 postValue(T) 方法,如果您需要修改存储在 LiveData 对象中的值,则必须使用这些方法。
通常情况下会在 ViewModel 中使用 MutableLiveData,然后 ViewModel 只会向观察者公开不可变的 LiveData 对象。
设置观察者关系后,您可以更新 LiveData 对象的值(如以下示例中所示),这样当用户点按某个按钮时会触发所有观察者:
button.setOnClickListener {
val anotherName = "John Doe"
model.currentName.setValue(anotherName)
}
**注意**:您必须调用 [`setValue(T)`]( ) 方法以从主线程更新 `LiveData` 对象。如果在 worker 线程中执行代码,则您可以改用 [`postValue(T)`]( ) 方法来更新 `LiveData` 对象。
### []( )将LiveData与Room一起使用
当数据库更新时,Room 会生成更新 `LiveData` 对象所需的所有代码。在需要时,生成的代码会在后台线程上异步运行查询。此模式有助于使界面中显示的数据与存储在数据库中的数据保持同步。
### []( )将协程与 LiveData 一起使用
( )
### []( )扩展 LiveData
class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
private val stockManager = StockManager(symbol)
private val listener = { price: BigDecimal ->
value = price
}
//LiveData有活跃观察者时
override fun onActive() {
stockManager.requestPriceUpdates(listener)
}
//LiveData没有任何活跃观察者时
override fun onInactive() {
stockManager.removeUpdates(listener)
}
}
LiveData 可以在多个 Activity、Fragment 和 Service 之间共享它们。您可以将 LiveData 类实现为单一实例
class StockLiveData(symbol: String) : LiveData() {
private val stockManager: StockManager = StockManager(symbol) private val listener = { price: BigDecimal -> value = price } override fun onActive() {
stockManager.requestPriceUpdates(listener)
}
override fun onInactive() { stockManager.removeUpdates(listener) } companion object { private lateinit var sInstance: StockLiveData @MainThread fun get(symbol: String): StockLiveData { sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol) return sInstance } }}
调用:
class MyFragment : Fragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
StockLiveData.get(symbol).observe(this, Observer<BigDecimal> { price: BigDecimal? ->
// Update the UI.
})
}
### []( )转换LiveData
LiveData 对象分派给观察者之前对存储的值进行更改,
根据另一个实例的值返回不同的 LiveData 实例
Lifecycle 软件包会提供 Transformations 类,该类包括可应对这些情况的辅助程序方法。
val userLiveData: LiveData = UserLiveData()
val userName: LiveData<String> = Transformations.map(userLiveData) {
user -> "${user.name} ${user.lastName}"
}
与 map() 类似,对存储在 LiveData 对象中的值应用函数,并将结果解封和分派到下游。传递给 switchMap() 的函数必须返回 LiveData 对象,如以下示例中所示:
private fun getUser(id: String): LiveData {
...
}
val userId: LiveData<String> = ...
val user = Transformations.switchMap(userId) { id -> getUser(id) }
要实现自定义转换,您可以使用 [`MediatorLiveData`]( ) 类
### []( )合并多个 LiveData 源
MediatorLiveData 是 LiveData 的子类,允许您合并多个 LiveData 源。只要任何原始的 LiveData 源对象发生更改,就会触发 MediatorLiveData 对象的观察者。
例如,如果界面中有可以从本地数据库或网络更新的 LiveData 对象,则可以向 MediatorLiveData 对象添加以下源:
* 与存储在数据库中的数据关联的 LiveData 对象。
* 与从网络访问的数据关联的 LiveData 对象。
您的 Activity 只需观察 MediatorLiveData 对象即可从这两个源接收更新。
### []( )示例
* ( ),这是一个演示应用,演示架构组件的最佳做法
* ( )
[]( )( )
--------------------------------------------------------------------------------------------------------------------------------------------------
导航是指支持用户导航、进入和退出应用中不同内容片段的交互。Android Jetpack 的导航组件可帮助您实现导航,无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模式,该组件均可应对。导航组件还通过遵循[一套既定原则]( )来确保一致且可预测的用户体验。
[]( )( )
-------------------------------------------------------------------------------------------------------------------------------------------------
分页库可帮助您一次加载和显示一小块数据。按需载入部分数据会减少网络带宽和系统资源的使用量。
使用 PagedList 对象的 LiveData 存储器加载和显示数据:
class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
val concertList: LiveData<PagedList<Concert>> =
concertDao.concertsByDate().toLiveData(pageSize = 50)
}
每个 [`PagedList`]( ) 实例都会从对应的 [`DataSource`]( ) 对象加载应用数据
@Dao
interface ConcertDao {
// The Int type parameter tells Room to use a PositionalDataSource object.
@Query("SELECT * FROM concerts ORDER BY date DESC")
fun concertsByDate(): DataSource.Factory<Int, Concert>
}
要详细了解如何将数据加载到 `PagedList` 对象中,请参阅有关如何[加载分页数据]( )的指南。
### []( )示例
* ( )
* [网络分页展示示例]( )
### []( )界面:
[`PagedList`]( ) 类使用 `PagedListAdapter` 将项加载到 [`RecyclerView`]( )。这些类共同作用,在内容加载时抓取和显示内容,预取不在视线范围内的内容以及针对内容更改添加动画。
要了解详情,请参阅有关如何[显示分页列表]( )的指南。
### []( )支持不同的数据架构
获取数据的3种方式:网络,本地数据库,网络+本地数据库
##### []( )网络(Retrofit):
**注意**:由于不同的应用处理和显示错误界面的方式不同,因此分页库的 [`DataSource`]( ) 对象不提供任何错误处理。如果发生错误,请遵循结果回调,并在稍后重试请求。有关此行为的示例,请参阅 ( )。
##### []( )本地数据库
设置您的 [`RecyclerView`]( ) 以观察本地存储空间,最好使用 ( )。这样,无论您何时在应用数据库中插入或修改数据,这些更改都会自动反映在显示此数据的 `RecyclerView` 中。
#### []( )网络和数据库
在开始观察数据库之后,您可以使用 [`PagedList.BoundaryCallback`]( ) 监听数据库中的数据何时耗尽。然后,您可以从网络中获取更多项目并将它们插入到数据库中。如果界面正在观察数据库,则您只需执行此操作即可。
### []( )处理网络错误
由于服务器不稳定或者网络异常,如果数据刷新步骤不起作用,您可以提供“重试”按钮供用户选择。如果在数据分页步骤中发生错误,则最好自动重新尝试分页请求。
### []( )更新现有应用
1.在应用中将 [`List`]( ) 对象替换成 `PagedList` 对象,后者不需要对应用界面结构或数据更新逻辑进行任何更改。
2.使用 CursorAdapter
3.使用 AsyncListUtil 异步加载内容
### []( )数据库示例
##### []( )使用 LiveData 观察分页数据
@Daointerface ConcertDao { // The Int type parameter tells Room to use a PositionalDataSource // object, with position-based loading under the hood. @Query("SELECT * FROM concerts ORDER BY date DESC") fun concertsByDate(): DataSource.Factory<Int, Concert>}class ConcertViewModel(concertDao: ConcertDao) : ViewModel() { val concertList: LiveData<PagedList<Concert>> =
concertDao.concertsByDate().toLiveData(pageSize = 50)
}
class ConcertActivity : AppCompatActivity() { public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val viewModel = ViewModelProviders.of(this) .get<ConcertViewModel>() val recyclerView = findViewById(R.id.concert_list) val adapter = ConcertAdapter() viewModel.livePagedList.observe(this, PagedList(adapter::submitList)) recyclerView.setAdapter(adapter) }}class ConcertAdapter() : PagedListAdapter<Concert, ConcertViewHolder>(DIFF_CALLBACK) { fun onBindViewHolder(holder: ConcertViewHolder, position: Int) { val concert: Concert? = getItem(position) // Note that "concert" is a placeholder if it's null. holder.bindTo(concert) } companion object { private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Concert>() { // Concert details may have changed if reloaded from the database, // but ID is fixed. override fun areItemsTheSame(oldConcert: Concert, newConcert: Concert) = oldConcert.id == newConcert.id override fun areContentsTheSame(oldConcert: Concert, newConcert: Concert) = oldConcert == newConcert } }}
##### []( )使用 RxJava2 观察分页数据
如果您倾向于使用 ( ) 而不是 [`LiveData`]( ),则可以改为创建 `Observable` 或 `Flowable` 对象:
class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
val concertList: Observable<PagedList<Concert>> =
concertDao.concertsByDate().toObservable(pageSize = 50)
}
然后,您可以使用以下代码段中的代码来开始和停止观察数据:
class ConcertActivity : AppCompatActivity() {
private val adapter: ConcertAdapter()
private lateinit var viewModel: ConcertViewModel
private val disposable = CompositeDisposable()
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val recyclerView = findViewById(R.id.concert_list)
viewModel = ViewModelProviders.of(this)
.get<ConcertViewModel>()
recyclerView.setAdapter(adapter)
}
override fun onStart() {
super.onStart()
disposable.add(viewModel.concertList
.subscribe(adapter::submitList)))
}
override fun onStop() {
super.onStop()
disposable.clear()
}
}
### []( )示例
* ( )
* [网络分页展示示例]( )
[]( )( )
----------------------------------------------------------------------------------------------------------------------------------------------------------------
( ) 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。
Room 包含 3 个主要组件:
* [**数据库**]( ):包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。
使用 [`@Database`]( ) 注释的类应满足以下条件:
* 是扩展 [`RoomDatabase`]( ) 的抽象类。
* 在注释中添加与数据库关联的实体列表。
* 包含具有 0 个参数且返回使用 [`@Dao`]( ) 注释的类的抽象方法。
在运行时,您可以通过调用 [`Room.databaseBuilder()`]( ) 或 [`Room.inMemoryDatabaseBuilder()`]( ) 获取 [`Database`]( ) 的实例。
* [**Entity**]( ):表示数据库中的表。
* [**DAO**]( ):包含用于访问数据库的方法。
应用使用 Room 数据库来获取与该数据库关联的数据访问对象 (DAO)。然后,应用使用每个 DAO 从数据库中获取实体,然后再将对这些实体的所有更改保存回数据库中。最后,应用使用实体来获取和设置与数据库中的表列相对应的值。
### []( )[使用 Room 将数据保存到本地数据库]( )
#### []( )基本用法
User
@Entity
data class User(
@PrimaryKey val uid: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
UserDao
@Daointerface
UserDao
{ @Query("SELECT * FROM user") fun getAll(): List<User> @Query("SELECT * FROM user WHERE uid IN (:userIds)") fun loadAllByIds(userIds: IntArray): List<User> @Query("SELECT * FROM user WHERE first_name LIKE :first AND " + "last_name LIKE :last LIMIT 1") fun findByName(first: String, last: String): User @Insert fun insertAll(vararg users: User) @Delete fun delete(user: User)}
AppDatabase
@Database(entities = arrayOf(User::class), version = 1)
abstract class
AppDatabase
: RoomDatabase() { abstract fun userDao():
UserDao
}
调用数据库实例:
val db = Room.databaseBuilder(
applicationContext,
AppDatabase
::class.java, "database-name" ).build() **注意**:如果您的应用在单个进程中运行,则在实例化 `
AppDatabase
` 对象时应依照单例设计模式。每个 [`RoomDatabase`]( ) 实例的本钱相称高,而您几乎不需要在单个进程中访问多个实例。如果您的应用在多个进程中运行,请在数据库构建器调用中包罗 `enableMultiInstanceInvalidation()`。如许,如果您在每个进程中都有一个 `
AppDatabase
` 实例,就可以在一个进程中使共享数据库文件失效,而且这种失效会自动传播到其他进程中的 `
AppDatabase
` 实例。### []( )示例* ( ),这是一款园艺应用,展示了使用 Android Jetpack 举行 Android 开发的最佳做法。* ( )* Room 和 RxJava 示例 [(Java)]( ) [(Kotlin)]( )### []( )博客* ( )* ( )* [从 SQLite 逐步迁移到 Room]( )* [有关 Room 的 7 个专业提示]( )* [了解借助 Room 举行的迁移]( )* [测试 Room 迁移]( )* ( )[]( )( )----------------------------------------------------------------------------------------------------------------------------------------------------------------------------##### []( )ViewModel 解决的题目:1、某个 Activity 包罗用户列表。因设置更改而重新创建 Activity 后,新 Activity 必须重新提取用户列表2、界面控制器经常需要异步调用,并确保系统在其烧毁后清算这些调用以避免潜在的内存泄露,而且在因设置更改而重新创建对象的情况下,会造成资源的浪费,由于对象可能需要重新发出已经发出过的调用。3、如果要求界面控制器也负责从数据库或网络加载数据,那么会使类越发膨胀。##### []( )从界面控制器逻辑中分离出视图数据所有权的做法更易行且更高效。### []( )实现 ViewModel架构组件为界面控制器提供了 ViewModel辅助程序类,该类负责为界面准备数据。 在设置更改期间会自动保留 [`ViewModel`]( ) 对象,以便它们存储的数据立刻可供下一个 Activity 或 Fragment 实例使用。比方,如果您需要在应用中显示用户列表,请确保将获取和保留该用户列表的责任分配给 [`ViewModel`]( ),而不是 Activity 或 Fragment,如以下示例代码所示: 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.
}
}
然后,您可以从 Activity 访问该列表,如下所示:
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.
val model = ViewModelProviders.of(this)
model.getUsers().observe(this, Observer<List<User>>{ users ->
// update UI
})
}
}
如果重新创建了该 Activity,它接收的 `MyViewModel` 实例与第一个 Activity 创建的实例相同。当所有者 Activity 完成时,框架会调用 [`ViewModel`]( ) 对象的 [`onCleared()`]( ) 方法,以便它可以清理资源。
**注意**:[`ViewModel`]( ) 绝不能引用视图、[`Lifecycle`]( ) 或可能存储对 Activity 上下文的引用的任何类。
### []( )ViewModel 的生命周期
[`ViewModel`]( ) 对象存在的时间范围是获取 [`ViewModel`]( ) 时传递给 [`ViewModelProvider`]( ) 的 [`Lifecycle`]( )。[`ViewModel`]( ) 将一直留在内存中,直到限定其存在时间范围的 [`Lifecycle`]( ) 永久消失:对于 Activity,是在 Activity 完成时;而对于 Fragment,是在 Fragment 分离时。
系统首次调用 Activity 对象的 onCreate()方法时请求 ViewModel,系统可能会在 Activity 的整个生命周期内多次调用 onCreate(),ViewModel存在的时间范围是从您首次请求 ViewMode 直到 Activity 完成并销毁。
### []( )在 Fragment 之间共享数据
AFragment和BFragment数据传递,两个 Fragment 都需要定义接口描述,这两个 Fragment 都必须处理另一个 Fragment 尚未创建或不可见的情况。
可以使用 ViewModel对象解决这一常见的难点。这两个 Fragment 可以使用其 Activity 范围共享 ViewModel来处理此类通信,如以下示例代码所示:
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this)
} ?: throw Exception("Invalid Activity")
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this)
} ?: throw Exception("Invalid Activity")
model.selected.observe(this, Observer<Item> { item ->
// Update the UI
})
}
}
请注意,这两个 Fragment 都会检索包含它们的 Activity。这样,当这两个 Fragment 各自获取 [`ViewModelProvider`]( ) 时,它们会收到相同的 `SharedViewModel` 实例(其范围限定为该 Activity)。
此方法具有以下优势:
* Activity 不需要执行任何操作,也不需要对此通信有任何了解。
* 除了 `SharedViewModel` 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。
* 每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。
### []( )将加载器替换为 ViewModel
诸如 `( )` 之类的加载器类经常用于使应用界面中的数据与数据库保持同步。您可以将 [`ViewModel`]( ) 与一些其他类一起使用来替换加载器。使用 [`ViewModel`]( ) 可将界面控制器与数据加载操作分离,这意味着类之间的强引用更少
[`ViewModel`]( ) 与 ( ) 和 ( ) 一起使用可替换加载器。[`ViewModel`]( ) 确保数据在设备配置更改后仍然存在。( ) 在数据库发生更改时通知 [`LiveData`]( ),( ) 进而使用修订后的数据更新界面。
!(https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTJiOWZlOGNmMzVlNzM5YzgucG5n?x-oss-process=image/format,png)
### []( )[将协程与 ViewModel 一起使用]( )
`ViewModel` 支持 Kotlin 协程。如需了解详情,请参阅[将 Kotlin 协程与 Android 架构组件一起使用]( )。
### []( )示例
* ( )
如需更多与协程相关的信息,请参阅以下链接:
* [利用 Kotlin 协程提升应用性能]( )
* [协程概览]( )
* [在 CoroutineWorker 中进行线程处理]( )
[]( )( )
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
当前稳定版:
| 2020 年 1 月 22 日 | ( ) |
dependencies {
def work_version = "2.3.0"
// (Java only)
implementation "androidx.work:work-runtime:$work_version"
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"
// optional - RxJava2 support
implementation "androidx.work:work-rxjava2:$work_version"
// optional - GCMNetworkManager support
implementation "androidx.work:work-gcm:$work_version"
// optional - Test helpers
androidTestImplementation "androidx.work:work-testing:$work_version"
}
WorkManager 旨在用于可延迟运行(即不需要立即运行)并且在应用退出或设备重启时必须能够可靠运行的任务。例如:
* 向后端服务发送日志或分析数据
* 定期将应用数据与服务器同步
WorkManager 不适用于应用进程结束时能够安全终止的运行中后台工作,也不适用于需要立即执行的任务。请查看[后台处理指南]( ),了解哪种解决方案符合您的需求。
### []( )创建后台任务
要创建后台任务,请扩展 Worker 类并替换 doWork() 方法。例如,要创建上传图像的 Worker:
class UploadWorker(appContext: Context, workerParams: WorkerParameters) Worker(appContext, workerParams) { override fun doWork(): Result {
// Do the work here--in this case, upload the images.
uploadImages()
// Indicate whether the task finished successfully with the Result
return Result.success()
}
}
从 `doWork()` 返回的 [`Result`]( ) 会通知 WorkManager 任务是否:
* 已成功完成:`Result.success()`
* 已失败:`Result.failure()`
* 需要稍后重试:`Result.retry()`
**注意**:`Worker` 是运行工作的最简单方式。要了解 Worker 的更多高级选项,请参阅 WorkManager [线程指南]( )。
##### []( )WorkManager 提供了四种不同类型的工作基元:
* [`Worker`]( ) 是最简单的实现,前面几节已经有所介绍。WorkManager 会在后台线程上自动运行它(您可以将它替换掉)。请参阅[工作器中的线程处理]( ),详细了解 `Worker` 中的线程处理。
* 建议 Kotlin 用户实现 [`CoroutineWorker`]( )。`CoroutineWorker` 针对后台工作公开挂起函数。默认情况下,它们运行默认的 `Dispatcher`,您可以对其进行自定义。请参阅 ( ),详细了解 `CoroutineWorker` 中的线程处理。
* 建议 RxJava2 用户实现 [`RxWorker`]( )。如果您有很多现有异步代码是用 RxJava 建模的,则应使用 RxWirkers。与所有 RxJava2 概念一样,您可以自由选择所需的线程处理策略。请参阅 ( ),详细了解 `RxWorker` 中的线程处理。
* [`ListenableWorker`]( ) 是 `Worker`、`CoroutineWorker` 和 `RxWorker` 的基类。该类专为需要与基于回调的异步 API(例如 `FusedLocationProviderClient`)进行交互并且不使用 RxJava2 的 Java 开发者而设计。请参阅 ( ),详细了解 `ListenableWorker` 中的线程处理。
### []( )配置运行任务的方式和时间
`Worker` 定义工作单元,[`WorkRequest`]( ) 则定义工作的运行方式和时间。任务可以是一次性的,也可以是周期性的。对于一次性 `WorkRequest`,请使用 [`OneTimeWorkRequest`]( ),对于周期性工作,请使用 [`PeriodicWorkRequest`]( )。
val uploadWorkRequest = OneTimeWorkRequestBuilder()
.build()
`WorkRequest` 中还可以包含其他信息,例如任务在运行时应遵循的约束、工作输入、延迟,以及重试工作的退避时间政策。关于这些选项,在[定义工作指南]( )中有更详细的说明。
在本指南中,您将了解如何自定义工作请求来处理常见用例:
* 处理网络可用性等任务约束
* 保证任务执行的延迟时间最短
* 处理任务重试和退避
* 处理任务输入和输出
* 使用标记对任务进行分组
##### []( )1、工作约束:
您可以向工作添加 Constraints,以指明工作何时可以运行:
// Create a Constraints object that defines when the task should runval constraints = Constraints.Builder() .setRequiresDeviceIdle(true) .setRequiresCharging(true) .build()
// ...then create a OneTimeWorkRequest that uses those constraintsval compressionWork = OneTimeWorkRequestBuilder<CompressWorker>() .setConstraints(constraints) .build()
##### []( )2、初始延迟
任务设置为在加入队列后至少经过 10 分钟再运行:
val uploadWorkRequest = OneTimeWorkRequestBuilder()
.setInitialDelay(10, TimeUnit.MINUTES)
.build()
##### []( )3、重试和退避政策
如果您需要让 WorkManager 重新尝试执行您的任务,可以从工作器返回 [`Result.retry()`]( )。
val uploadWorkRequest = OneTimeWorkRequestBuilder()
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)
.build()
##### []( )4、定义任务的输入/输出
输入和输出值以键值对的形式存储在 [`Data`]( ) 对象中。下面的代码展示了如何在 `WorkRequest` 中设置输入数据。
// workDataOf (part of KTX) converts a list of pairs to a object.
val imageData = workDataOf(Constants.KEY_IMAGE_URI to imageUriString)
val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.setInputData(imageData)
.build()
Worker 类可通过调用 Worker.getInputData() 访问输入参数。
类似地,Data 类可用于输出返回值。要返回 Data 对象,请将它包含到 Result 的 Result.success() 或 Result.failure() 中,如下所示。
class UploadWorker(appContext: Context, workerParams: WorkerParameters)
: Worker(appContext, workerParams) {
override fun doWork(): Result {
// Get the input
val imageUriInput = getInputData().getString(Constants.KEY_IMAGE_URI)
// TODO: validate inputs.
// Do the work
val response = uploadFile(imageUriInput)
// Create the output of the work
val outputData = workDataOf(Constants.KEY_IMAGE_URL to response.imageUrl)
// Return the output
return Result.success(outputData)
}
}
##### []( )5、标记工作
以下代码展示了如何使用 [`WorkRequest.Builder.addTag(String)`]( ) 向任务添加“cleanup”标记:
val cacheCleanupTask =
OneTimeWorkRequestBuilder<CacheCleanupWorker>()
.setConstraints(constraints)
.addTag("cleanup")
.build()
### []( )将您的任务提交给系统
定义 `WorkRequest` 之后,您现在可以通过 [`WorkManager`]( ) 使用 [`enqueue()`]( ) 方法来调度它。
WorkManager.getInstance(myContext).enqueue(uploadWorkRequest)
执行 Worker 的确切时间取决于 WorkRequest 中使用的约束以及系统优化。WorkManager 的设计目的就是要在这些限制下提供尽可能好的表现。
### []( )方法指南
* [定义您的 WorkRequest]( )
* [观察工作状态]( )
* [观察工作器的中间进度]( )
* [将工作链接在一起]( )
* [取消和停止工作]( )
* [处理重复性工作]( )
* [处理特有的工作]( )
* [测试工作器]( )
### []( )高级概念
* [自定义和初始化]( )
* [在 WorkManager 中进行线程处理]( )
* [在工作器中进行线程处理]( )
* [在 CoroutineWorker 中进行线程处理]( )
* [在 RxWorker 中进行线程处理]( )
* [在 ListenableWorker 中进行线程处理]( )
### []( )迁移指南
* [从 Firebase JobDispatcher 迁移]( )
* [从 GCMNetworkManager 迁移]( )
### []( )示例
* ( ),一个简单的图像处理应用
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]