如何在后台线程上执行 LiveData 转换?
How can I perform LiveData transformations on a background thread?
我需要将 LiveData
对象返回的一种类型的数据转换为另一种形式 在后台线程上 以防止 UI滞后。
在我的具体情况下,我有:
MyDBRow
对象(由原始 long
和 String
组成的 POJO);
- a Room DAO 实例通过
LiveData<List<MyDBRow>>
发射这些;和
- a UI 期待更丰富的
MyRichObject
对象(带有基元的 POJO 膨胀成例如 date/time objects)
所以我需要将我的 LiveData<List<MyDBRow>>
转换为 LiveData<List<MyRichObject>>
,但 不在 UI 线程上 。
Transformations.map(LiveData<X>, Function<X, Y>)
方法执行此所需的转换,但我不能使用它,因为它 在主线程上执行转换:
Applies the given function on the main thread to each value emitted by source
LiveData and returns LiveData, which emits resulting values.
The given function func
will be executed on the main thread.
什么是进行 LiveData
转换的干净方法:
- 在主线程之外的某个地方,并且
- 仅在需要时(即仅当某些东西正在观察预期的转换时)?
- 原始“来源”
LiveData
can be monitored by a new Observer
实例。
- 这个
Observer
实例,当源 LiveData
被发出时,可以准备一个后台线程来执行所需的转换,然后通过一个新的、“转换后的” LiveData
发出它。
- 转换后的
LiveData
可以在源 LiveData
有活动的 Observer
时将上述 Observer
附加到源 LiveData
,并在没有活动时将其分离,确保来源 LiveData
仅在必要时才被观察。
该问题给出了示例源 LiveData<List<MyDBRow>>
并需要转换后的 LiveData<List<MyRichObject>>
。合并后的 LiveData
和 Observer
看起来像这样:
class MyRichObjectLiveData
extends LiveData<List<MyRichObject>>
implements Observer<List<MyDBRow>>
{
@NonNull private LiveData<List<MyDBRow>> sourceLiveData;
MyRichObjectLiveData(@NonNull LiveData<List<MyDBRow>> sourceLiveData) {
this.sourceLiveData = sourceLiveData;
}
// only watch the source LiveData when something is observing this
// transformed LiveData
@Override protected void onActive() { sourceLiveData.observeForever(this); }
@Override protected void onInactive() { sourceLiveData.removeObserver(this); }
// receive source LiveData emission
@Override public void onChanged(@Nullable List<MyDBRow> dbRows) {
// set up a background thread to complete the transformation
AsyncTask.execute(new Runnable() {
@Override public void run() {
assert dbRows != null;
List<MyRichObject> myRichObjects = new LinkedList<>();
for (MyDBRow myDBRow : myDBRows) {
myRichObjects.add(MyRichObjectBuilder.from(myDBRow).build());
}
// use LiveData method postValue (rather than setValue) on
// background threads
postValue(myRichObjects);
}
});
}
}
如果需要多个这样的转换,上面的逻辑可以像这样通用:
abstract class TransformedLiveData<Source, Transformed>
extends LiveData<Transformed>
implements Observer<Source>
{
@Override protected void onActive() { getSource().observeForever(this); }
@Override protected void onInactive() { getSource().removeObserver(this); }
@Override public void onChanged(@Nullable Source source) {
AsyncTask.execute(new Runnable() {
@Override public void run() {
postValue(getTransformed(source));
}
});
}
protected abstract LiveData<Source> getSource();
protected abstract Transformed getTransformed(Source source);
}
问题给出的示例的子类可能如下所示:
class MyRichObjectLiveData
extends TransformedLiveData<List<MyDBRow>, List<MyRichObject>>
{
@NonNull private LiveData<List<MyDBRow>> sourceLiveData;
MyRichObjectLiveData(@NonNull LiveData<List<MyDBRow>> sourceLiveData) {
this.sourceLiveData = sourceLiveData;
}
@Override protected LiveData<List<MyDBRow>> getSource() {
return sourceLiveData;
}
@Override protected List<MyRichObject> getTransformed(List<MyDBRow> myDBRows) {
List<MyRichObject> myRichObjects = new LinkedList<>();
for (MyDBRow myDBRow : myDBRows) {
myRichObjects.add(MyRichObjectBuilder.from(myDBRow).build());
}
return myRichObjects;
}
}
使用 MediatorLiveData
可能更容易。 Transformations.map()
是通过 MediatorLiveData
实现的。
@MainThread
public static <X, Y> LiveData<Y> mapAsync(
@NonNull LiveData<X> source,
@NonNull final Function<X, Y> mapFunction) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@Override
public void onChanged(@Nullable final X x) {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
result.postValue(mapFunction.apply(x));
}
});
}
});
return result;
}
协程解决方案:
class RichLiveData(val rows: LiveData<List<MyDBRow>>) : LiveData<List<MyRichObject>>(),
CoroutineScope by CoroutineScope(Dispatchers.Default) {
private val observer = Observer<List<MyDBRow>> { rows ->
launch {
postValue(/*computationally expensive stuff which returns a List<MyRichObject>*/)
}
}
override fun onActive() {
rows.observeForever(observer)
}
override fun onInactive() {
rows.removeObserver(observer)
}
}
另一种可能的协程解决方案:
object BackgroundTransformations {
fun <X, Y> map(
source: LiveData<X>,
mapFunction: (X) -> Y
): LiveData<Y> {
val result = MediatorLiveData<Y>()
result.addSource(source, Observer<X> { x ->
if (x == null) return@Observer
CoroutineScope(Dispatchers.Default).launch {
result.postValue(mapFunction(x))
}
})
return result
}
fun <X, Y> switchMap(
source: LiveData<X>,
switchMapFunction: (X) -> LiveData<Y>
): LiveData<Y> {
val result = MediatorLiveData<Y>()
result.addSource(source, object : Observer<X> {
var mSource: LiveData<Y>? = null
override fun onChanged(x: X) {
if (x == null) return
CoroutineScope(Dispatchers.Default).launch {
val newLiveData = switchMapFunction(x)
if (mSource == newLiveData) {
return@launch
}
if (mSource != null) {
result.removeSource(mSource!!)
}
mSource = newLiveData
if (mSource != null) {
result.addSource(mSource!!) { y ->
result.setValue(y)
}
}
}
}
})
return result
}
}
希望对您有所帮助
这样怎么样:
@Query("SELECT * FROM " + PeriodicElement.TABLE_NAME)
abstract fun getAll(): LiveData<List<PeriodicElement>>
fun getAllElements(): LiveData<HashMap<String, PeriodicElement>> {
return Transformations.switchMap(getAll(), ::transform)
}
private fun transform(list: List<PeriodicElement>): LiveData<HashMap<String, PeriodicElement>> {
val map = HashMap<String, PeriodicElement>()
val liveData = MutableLiveData(map)
AsyncTask.execute {
for (p in list) {
map[p.symbol] = p
if (!liveData.hasObservers()) {
//prevent memory leak
break
}
}
liveData.postValue(map)
}
return liveData
}
听一个 MediatorLiveData<T>
听另外两个 LiveData<T>
。
例如:
val exposed: LiveData<List<T>> = MediatorLiveData<List<T>>().apply {
addSource(aLiveDataToMap) { doWorkOnAnotherThread(it) }
addSource(aMutableLiveData) { value = it }
}
private fun doWorkOnAnotherThread(t: T) {
runWorkOnAnotherThread {
val t2 = /* ... */
aMutableLiveData.postValue(t2)
}
}
每当 aLiveDataToMap
改变时,它会触发 doWorkOnAnotherThread()
,然后设置 aMutableLiveData
的值,最终设置为 exposed
的值,这是一个生命周期-老板会听的。将 T
s 替换为您想要的类型。
感谢@jaychang0917
Kotlin 形式:
@MainThread
fun <X, Y> mapAsync(source: LiveData<X>, mapFunction: androidx.arch.core.util.Function<X, Y>): LiveData<Y> {
val result = MediatorLiveData<Y>()
result.addSource(source) { x -> AsyncTask.execute { result.postValue(mapFunction.apply(x)) } }
return result
}
我需要将 LiveData
对象返回的一种类型的数据转换为另一种形式 在后台线程上 以防止 UI滞后。
在我的具体情况下,我有:
MyDBRow
对象(由原始long
和String
组成的 POJO);- a Room DAO 实例通过
LiveData<List<MyDBRow>>
发射这些;和 - a UI 期待更丰富的
MyRichObject
对象(带有基元的 POJO 膨胀成例如 date/time objects)
所以我需要将我的 LiveData<List<MyDBRow>>
转换为 LiveData<List<MyRichObject>>
,但 不在 UI 线程上 。
Transformations.map(LiveData<X>, Function<X, Y>)
方法执行此所需的转换,但我不能使用它,因为它 在主线程上执行转换:
Applies the given function on the main thread to each value emitted by
source
LiveData and returns LiveData, which emits resulting values.The given function
func
will be executed on the main thread.
什么是进行 LiveData
转换的干净方法:
- 在主线程之外的某个地方,并且
- 仅在需要时(即仅当某些东西正在观察预期的转换时)?
- 原始“来源”
LiveData
can be monitored by a newObserver
实例。 - 这个
Observer
实例,当源LiveData
被发出时,可以准备一个后台线程来执行所需的转换,然后通过一个新的、“转换后的”LiveData
发出它。 - 转换后的
LiveData
可以在源LiveData
有活动的Observer
时将上述Observer
附加到源LiveData
,并在没有活动时将其分离,确保来源LiveData
仅在必要时才被观察。
该问题给出了示例源 LiveData<List<MyDBRow>>
并需要转换后的 LiveData<List<MyRichObject>>
。合并后的 LiveData
和 Observer
看起来像这样:
class MyRichObjectLiveData
extends LiveData<List<MyRichObject>>
implements Observer<List<MyDBRow>>
{
@NonNull private LiveData<List<MyDBRow>> sourceLiveData;
MyRichObjectLiveData(@NonNull LiveData<List<MyDBRow>> sourceLiveData) {
this.sourceLiveData = sourceLiveData;
}
// only watch the source LiveData when something is observing this
// transformed LiveData
@Override protected void onActive() { sourceLiveData.observeForever(this); }
@Override protected void onInactive() { sourceLiveData.removeObserver(this); }
// receive source LiveData emission
@Override public void onChanged(@Nullable List<MyDBRow> dbRows) {
// set up a background thread to complete the transformation
AsyncTask.execute(new Runnable() {
@Override public void run() {
assert dbRows != null;
List<MyRichObject> myRichObjects = new LinkedList<>();
for (MyDBRow myDBRow : myDBRows) {
myRichObjects.add(MyRichObjectBuilder.from(myDBRow).build());
}
// use LiveData method postValue (rather than setValue) on
// background threads
postValue(myRichObjects);
}
});
}
}
如果需要多个这样的转换,上面的逻辑可以像这样通用:
abstract class TransformedLiveData<Source, Transformed>
extends LiveData<Transformed>
implements Observer<Source>
{
@Override protected void onActive() { getSource().observeForever(this); }
@Override protected void onInactive() { getSource().removeObserver(this); }
@Override public void onChanged(@Nullable Source source) {
AsyncTask.execute(new Runnable() {
@Override public void run() {
postValue(getTransformed(source));
}
});
}
protected abstract LiveData<Source> getSource();
protected abstract Transformed getTransformed(Source source);
}
问题给出的示例的子类可能如下所示:
class MyRichObjectLiveData
extends TransformedLiveData<List<MyDBRow>, List<MyRichObject>>
{
@NonNull private LiveData<List<MyDBRow>> sourceLiveData;
MyRichObjectLiveData(@NonNull LiveData<List<MyDBRow>> sourceLiveData) {
this.sourceLiveData = sourceLiveData;
}
@Override protected LiveData<List<MyDBRow>> getSource() {
return sourceLiveData;
}
@Override protected List<MyRichObject> getTransformed(List<MyDBRow> myDBRows) {
List<MyRichObject> myRichObjects = new LinkedList<>();
for (MyDBRow myDBRow : myDBRows) {
myRichObjects.add(MyRichObjectBuilder.from(myDBRow).build());
}
return myRichObjects;
}
}
使用 MediatorLiveData
可能更容易。 Transformations.map()
是通过 MediatorLiveData
实现的。
@MainThread
public static <X, Y> LiveData<Y> mapAsync(
@NonNull LiveData<X> source,
@NonNull final Function<X, Y> mapFunction) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@Override
public void onChanged(@Nullable final X x) {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
result.postValue(mapFunction.apply(x));
}
});
}
});
return result;
}
协程解决方案:
class RichLiveData(val rows: LiveData<List<MyDBRow>>) : LiveData<List<MyRichObject>>(),
CoroutineScope by CoroutineScope(Dispatchers.Default) {
private val observer = Observer<List<MyDBRow>> { rows ->
launch {
postValue(/*computationally expensive stuff which returns a List<MyRichObject>*/)
}
}
override fun onActive() {
rows.observeForever(observer)
}
override fun onInactive() {
rows.removeObserver(observer)
}
}
另一种可能的协程解决方案:
object BackgroundTransformations {
fun <X, Y> map(
source: LiveData<X>,
mapFunction: (X) -> Y
): LiveData<Y> {
val result = MediatorLiveData<Y>()
result.addSource(source, Observer<X> { x ->
if (x == null) return@Observer
CoroutineScope(Dispatchers.Default).launch {
result.postValue(mapFunction(x))
}
})
return result
}
fun <X, Y> switchMap(
source: LiveData<X>,
switchMapFunction: (X) -> LiveData<Y>
): LiveData<Y> {
val result = MediatorLiveData<Y>()
result.addSource(source, object : Observer<X> {
var mSource: LiveData<Y>? = null
override fun onChanged(x: X) {
if (x == null) return
CoroutineScope(Dispatchers.Default).launch {
val newLiveData = switchMapFunction(x)
if (mSource == newLiveData) {
return@launch
}
if (mSource != null) {
result.removeSource(mSource!!)
}
mSource = newLiveData
if (mSource != null) {
result.addSource(mSource!!) { y ->
result.setValue(y)
}
}
}
}
})
return result
}
}
希望对您有所帮助
这样怎么样:
@Query("SELECT * FROM " + PeriodicElement.TABLE_NAME)
abstract fun getAll(): LiveData<List<PeriodicElement>>
fun getAllElements(): LiveData<HashMap<String, PeriodicElement>> {
return Transformations.switchMap(getAll(), ::transform)
}
private fun transform(list: List<PeriodicElement>): LiveData<HashMap<String, PeriodicElement>> {
val map = HashMap<String, PeriodicElement>()
val liveData = MutableLiveData(map)
AsyncTask.execute {
for (p in list) {
map[p.symbol] = p
if (!liveData.hasObservers()) {
//prevent memory leak
break
}
}
liveData.postValue(map)
}
return liveData
}
听一个 MediatorLiveData<T>
听另外两个 LiveData<T>
。
例如:
val exposed: LiveData<List<T>> = MediatorLiveData<List<T>>().apply {
addSource(aLiveDataToMap) { doWorkOnAnotherThread(it) }
addSource(aMutableLiveData) { value = it }
}
private fun doWorkOnAnotherThread(t: T) {
runWorkOnAnotherThread {
val t2 = /* ... */
aMutableLiveData.postValue(t2)
}
}
每当 aLiveDataToMap
改变时,它会触发 doWorkOnAnotherThread()
,然后设置 aMutableLiveData
的值,最终设置为 exposed
的值,这是一个生命周期-老板会听的。将 T
s 替换为您想要的类型。
感谢@jaychang0917
Kotlin 形式:
@MainThread
fun <X, Y> mapAsync(source: LiveData<X>, mapFunction: androidx.arch.core.util.Function<X, Y>): LiveData<Y> {
val result = MediatorLiveData<Y>()
result.addSource(source) { x -> AsyncTask.execute { result.postValue(mapFunction.apply(x)) } }
return result
}