如何在后台线程上执行LiveData转换?

时间:2017-11-19 07:22:33

标签: android android-room android-architecture-components android-thread android-architecture-lifecycle

我需要将LiveData对象返回的一种数据转换为后台线程上的另一种形式,以防止UI延迟。

在我的具体案例中,我有:

  • MyDBRow个对象(由原始longString组成的POJO);
  • Room DAO个实例通过LiveData<List<MyDBRow>>发出这些内容;和
  • 一个期待更丰富的MyRichObject对象的用户界面(原始广告被夸大为date/time objects的POJO)

所以我需要将LiveData<List<MyDBRow>>转换为LiveData<List<MyRichObject>>,但不要转换为UI线程

Transformations.map(LiveData<X>, Function<X, Y>)方法执行此操作需要转换,但我无法使用此方法,因为在主线程上执行转换

  

将主线程上的给定函数应用于source LiveData发出的每个值,并返回生成结果值的LiveData。

     

给定的函数func将在主线程上执行。

进行LiveData转换的干净方法是什么:

  1. 离主线程的某个地方,
  2. 仅在需要时(即仅在观察到预期的转变时)?

7 个答案:

答案 0 :(得分:15)

  • 原始的“来源”LiveData可由新的Observer个实例监控。
  • 当发出源Observer时,此LiveData实例可以准备后台线程来执行所需的转换,然后通过新的“转换后的”LiveData发出它。
  • 转换后的LiveData可以将前面提到的Observer附加到源LiveData,当它有活跃Observer时,并在它没有时将其分离,确保只在必要时才会发现来源LiveData

问题提供了一个示例来源LiveData<List<MyDBRow>>,需要转换后的LiveData<List<MyRichObject>>。合并后的LiveDataObserver组合可能如下所示:

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;
    }
}

答案 1 :(得分:6)

使用from flask.ext.xxx import xxx可能更容易。 MediatorLiveDataTransformations.map()一起使用。

MediatorLiveData

答案 2 :(得分:3)

听一个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替换为所需的类型。

答案 3 :(得分:2)

协程的另一种可能的解决方案:

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
    }

}

希望有帮助

答案 4 :(得分:1)

感谢@jaychang0917

科特林表单:

@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
}

答案 5 :(得分:0)

带有协程的解决方案:

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)
    }
}

答案 6 :(得分:0)

这样怎么样:

@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
}