}
在真实的应用中,最终会有太多管理界面和其他组件的调用,以响应生命周期的当前状态。
管理多个组件会在生命周期方法(如 onStart() 和 onStop())中放置大量的代码,这使得它们难以维护。
此外,无法保证组件会在 Activity 或 Fragment 停止之前启动。在我们需要执行长时间运行的操作(如 onStart() 中的某种配置检查)时尤其如此。这可能会导致出现一种竞争条件,在这种条件下,onStop() 方法会在 onStart() 之前结束,这使得组件留存的时间比所需的时间要长。
androidx.lifecycle 软件包提供的类和接口可帮助您以弹性和隔离的方式解决这些问题。
Lifecycle 是一个类,用于存储有关组件(如 Activity 或 Fragment)的生命周期状态的信息,并允许其他对象观察此状态。
//类可以通过向其方法添加注解来监控组件的生命周期状态。
//然后,您可以通过调用 Lifecycle 类的 addObserver() 方法并传递观察者的实例来添加观察者
class MyObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun connectListener() {
…
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun disconnectListener() {
…
}
}
//myLifecycleOwner 对象实现了 LifecycleOwner 接口
//LifecycleOwner 是单一方法接口,表示类具有 Lifecycle。
myLifecycleOwner.getLifecycle().addObserver(MyObserver())
下面我们重新写一下MyLocationListener示例、
我们可以让 MyLocationListener 类实现 LifecycleObserver,
然后在 onCreate() 方法中使用 Activity 的 Lifecycle 对其进行初始化。
这样,MyLocationListener 类便可以“自给自足”,这意味着,对生命周期状态的变化做出响应的逻辑会在 MyLocationListener(而不是在 Activity)中进行声明。让各个组件存储自己的逻辑,可使 Activity 和 Fragment 逻辑更易于管理。
internal class MyLocationListener(
private val context: Context,
private val lifecycle: Lifecycle,
private val callback: (Location) -> Unit
) {
private var enabled = false
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun start() {
if (enabled) {
// connect
}
}
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 类中的调用顺序不符。
示例:
-
Sunflower,这是一个演示应用,演示架构组件的最佳做法
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 by lazy {
MutableLiveData()
}
// 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 { 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 一起使用
https://developer.android.google.cn/topic/libraries/architecture/coroutines
扩展 LiveData
class StockLiveData(symbol: String) : LiveData() {
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 { price: BigDecimal? ->
// Update the UI.
})
}
转换LiveData
LiveData 对象分派给观察者之前对存储的值进行更改,
根据另一个实例的值返回不同的 LiveData 实例
Lifecycle 软件包会提供 Transformations 类,该类包括可应对这些情况的辅助程序方法。
val userLiveData: LiveData = UserLiveData()
val userName: LiveData = Transformations.map(userLiveData) {
user -> “${user.name} ${user.lastName}”
}
与 map() 类似,对存储在 LiveData 对象中的值应用函数,并将结果解封和分派到下游。传递给 switchMap() 的函数必须返回 LiveData 对象,如以下示例中所示:
private fun getUser(id: String): LiveData {
…
}
val userId: LiveData = …
val user = Transformations.switchMap(userId) { id -> getUser(id) }
要实现自定义转换,您可以使用 MediatorLiveData
类
合并多个 LiveData 源
MediatorLiveData 是 LiveData 的子类,允许您合并多个 LiveData 源。只要任何原始的 LiveData 源对象发生更改,就会触发 MediatorLiveData 对象的观察者。
例如,如果界面中有可以从本地数据库或网络更新的 LiveData 对象,则可以向 MediatorLiveData 对象添加以下源:
-
与存储在数据库中的数据关联的 LiveData 对象。
-
与从网络访问的数据关联的 LiveData 对象。
您的 Activity 只需观察 MediatorLiveData 对象即可从这两个源接收更新。
示例
-
Sunflower,这是一个演示应用,演示架构组件的最佳做法
导航是指支持用户导航、进入和退出应用中不同内容片段的交互。Android Jetpack 的导航组件可帮助您实现导航,无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模式,该组件均可应对。导航组件还通过遵循一套既定原则来确保一致且可预测的用户体验。
分页库可帮助您一次加载和显示一小块数据。按需载入部分数据会减少网络带宽和系统资源的使用量。
使用 PagedList 对象的 LiveData 存储器加载和显示数据:
class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
val concertList: LiveData<PagedList> =
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
对象不提供任何错误处理。如果发生错误,请遵循结果回调,并在稍后重试请求。有关此行为的示例,请参阅 PagingWithNetwork 示例。
本地数据库
设置您的 RecyclerView
以观察本地存储空间,最好使用 Room 持久性库。这样,无论您何时在应用数据库中插入或修改数据,这些更改都会自动反映在显示此数据的 RecyclerView
中。
网络和数据库
在开始观察数据库之后,您可以使用 PagedList.BoundaryCallback
监听数据库中的数据何时耗尽。然后,您可以从网络中获取更多项目并将它们插入到数据库中。如果界面正在观察数据库,则您只需执行此操作即可。
处理网络错误
由于服务器不稳定或者网络异常,如果数据刷新步骤不起作用,您可以提供“重试”按钮供用户选择。如果在数据分页步骤中发生错误,则最好自动重新尝试分页请求。
更新现有应用
1.在应用中将 List
对象替换成 PagedList
对象,后者不需要对应用界面结构或数据更新逻辑进行任何更改。
2.使用 CursorAdapter
3.使用 AsyncListUtil 异步加载内容
数据库示例
使用 LiveData 观察分页数据
@Dao
interface 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> =
concertDao.concertsByDate().toLiveData(pageSize = 50)
}
class ConcertActivity : AppCompatActivity() {
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel = ViewModelProviders.of(this)
.get()
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 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 观察分页数据
如果您倾向于使用 RxJava2 而不是 LiveData
,则可以改为创建 Observable
或 Flowable
对象:
class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
val concertList: Observable<PagedList> =
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()
recyclerView.setAdapter(adapter)
}
override fun onStart() {
super.onStart()
disposable.add(viewModel.concertList
.subscribe(adapter::submitList)))
}
override fun onStop() {
super.onStop()
disposable.clear()
}
}
示例
Room 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。
Room 包含 3 个主要组件:
- 数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。
使用 @Database
注释的类应满足以下条件:
-
是扩展
RoomDatabase
的抽象类。 -
在注释中添加与数据库关联的实体列表。
-
包含具有 0 个参数且返回使用
@Dao
注释的类的抽象方法。
在运行时,您可以通过调用 Room.databaseBuilder()
或 Room.inMemoryDatabaseBuilder()
获取 Database
的实例。
应用使用 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
@Dao
interface UserDao {
@Query(“SELECT * FROM user”)
fun getAll(): List
@Query(“SELECT * FROM user WHERE uid IN (:userIds)”)
fun loadAllByIds(userIds: IntArray): List
@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
实例。
示例
-
Sunflower,这是一款园艺应用,展示了使用 Android Jetpack 进行 Android 开发的最佳做法。
博客
7、ViewModel -以注重生命周期的方式管理界面相关的数据
ViewModel 解决的问题:
1、某个 Activity 包含用户列表。因配置更改而重新创建 Activity 后,新 Activity 必须重新提取用户列表
2、界面控制器经常需要异步调用,并确保系统在其销毁后清理这些调用以避免潜在的内存泄露,并且在因配置更改而重新创建对象的情况下,会造成资源的浪费,因为对象可能需要重新发出已经发出过的调用。
3、如果要求界面控制器也负责从数据库或网络加载数据,那么会使类越发膨胀。
从界面控制器逻辑中分离出视图数据所有权的做法更易行且更高效。
实现 ViewModel
架构组件为界面控制器提供了 ViewModel辅助程序类,该类负责为界面准备数据。 在配置更改期间会自动保留 ViewModel
对象,以便它们存储的数据立即可供下一个 Activity 或 Fragment 实例使用。
例如,如果您需要在应用中显示用户列表,请确保将获取和保留该用户列表的责任分配给 ViewModel
,而不是 Activity 或 Fragment,如以下示例代码所示:
class MyViewModel : ViewModel() {
private val users: MutableLiveData<List> by lazy {
MutableLiveData().also {
loadUsers()
}
}
fun getUsers(): LiveData<List> {
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)[MyViewModel::class.java]
model.getUsers().observe(this, Observer<List>{ 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()
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)[SharedViewModel::class.java]
} ?: 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)[SharedViewModel::class.java]
} ?: throw Exception(“Invalid Activity”)
model.selected.observe(this, Observer { item ->
// Update the UI
})
}
}
请注意,这两个 Fragment 都会检索包含它们的 Activity。这样,当这两个 Fragment 各自获取 ViewModelProvider
时,它们会收到相同的 SharedViewModel
实例(其范围限定为该 Activity)。
此方法具有以下优势:
-
Activity 不需要执行任何操作,也不需要对此通信有任何了解。
-
除了
SharedViewModel
约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。 -
每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。
将加载器替换为 ViewModel
诸如 [CursorLoader]( )
之类的加载器类经常用于使应用界面中的数据与数据库保持同步。您可以将 ViewModel
与一些其他类一起使用来替换加载器。使用 ViewModel
可将界面控制器与数据加载操作分离,这意味着类之间的强引用更少
ViewModel
与 Room 和 LiveData 一起使用可替换加载器。ViewModel
确保数据在设备配置更改后仍然存在。Room 在数据库发生更改时通知 LiveData
,LiveData 进而使用修订后的数据更新界面。
将协程与 ViewModel 一起使用
ViewModel
支持 Kotlin 协程。如需了解详情,请参阅将 Kotlin 协程与 Android 架构组件一起使用。
示例
如需更多与协程相关的信息,请参阅以下链接:
8、WorkManager - 管理您的 Android 后台作业
当前稳定版:
| 2020 年 1 月 22 日 | 2.3.0 |
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.
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上我搜集整理的2019-2021BATJ 面试真题解析,我把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《960全网最全Android开发笔记》
[外链图片转存中…(img-UA9vOYvE-1723742587402)]
《379页Android开发面试宝典》
历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
[外链图片转存中…(img-DvyQu3ta-1723742587403)]
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析
[外链图片转存中…(img-km1saWPB-1723742587403)]
资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图
rk_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.
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上我搜集整理的2019-2021BATJ 面试真题解析,我把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《960全网最全Android开发笔记》
[外链图片转存中…(img-UA9vOYvE-1723742587402)]
《379页Android开发面试宝典》
历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
[外链图片转存中…(img-cTfEKQYf-1723742587403)]
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
[外链图片转存中…(img-DvyQu3ta-1723742587403)]
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析
[外链图片转存中…(img-km1saWPB-1723742587403)]
资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图