map()和switchMap()方法有什么区别?

Igo*_*lov 42 android android-livedata android-architecture-components

LiveData类的这两个方法有什么区别?官方文档和教程对此非常模糊.在map()方法中,第一个参数叫做source,但在switchMap()中它调用了trigger.这背后的理由是什么?

Rup*_*esh 32

根据文档

Transformations.map()

对存储在LiveData对象中的值应用函数,并将结果传播到下游.

Transformations.switchMap()

与map类似,将函数应用于存储在LiveData对象中的值,并将结果解包并调度到下游.传递给switchMap()的函数必须返回一个LiveData对象.

换句话说,如果您熟悉RxJava,我可能不是100%正确; Transformations#map有点类似于Observable#mapTransformations#switchMap类似Observable#flatMap.

让我们举一个例子,有一个LiveData发出一个字符串,我们想用大写字母显示该字符串.

一种方法如下; 在活动或片段中

Transformations.map(stringsLiveData, String::toUpperCase)
    .observe(this, textView::setText);
Run Code Online (Sandbox Code Playgroud)

传递给函数的函数map只返回一个字符串,但它Transformation#map最终会返回一个字符串LiveData.

第二种方法; 在活动或片段中

Transformations.switchMap(stringsLiveData, this::getUpperCaseStringLiveData)
            .observe(this, textView::setText);

private LiveData<String> getUpperCaseStringLiveData(String str) {
    MutableLiveData<String> liveData = new MutableLiveData<>();
    liveData.setValue(str.toUpperCase());
    return liveData;
}
Run Code Online (Sandbox Code Playgroud)

如果你看到Transformations#switchMap实际上已经切换了LiveData.因此,再次按照文档传递给switchMap()的函数必须返回一个LiveData对象.

因此,如果map它是您正在转换的 LiveData,并且在switchMap传递的情况下LiveData将充当触发器,LiveData在解包并向下游调度结果之后它将切换到另一个触发器.

  • 这可以解释命名.所以他们都应该在每次基础LiveData更改时触发,"切换"意味着LiveData将切换到另一个LiveData对象.谢谢! (10认同)
  • 很好的解释 - 我在我的仓库中添加了 switchMap 和 Map 组合的示例.. https://github.com/febaisi/ListenableWorkerExample/blob/master/app/src/main/java/com/febaisi/ Listenableworkerexample/ui/MainViewModel.kt ..“SwitchMap”仅用于侦听按钮事件并切换到正确的 LiveData(即 Worker 结果的 Map)。我希望它也有帮助。 (2认同)

fra*_*yan 23

上面已经有一些很好的答案,但我仍然在纠结直到我理解它,所以我将尝试以我的思维方式为人们解释一个具体的例子,而不涉及技术细节和代码。

在这两个mapswitchMap有一个来源(或触发)实时数据,并要这两种情况下转换到另一个实时数据。您将使用哪一个 - 取决于您的转换正在执行的任务。

map

考虑无处不在的相同简单示例 - 您的实时数据包含一个User对象 - LiveData<User>,它指向当前登录的用户。你想在你的 UI 中显示一个文本,说Current user: <USERNAME>. 在这种情况下,来自源的每个变化信号都应该触发结果“映射”中的一个信号LiveData。例如,当前User对象是“Bob”,则 UI 文本显示Current user: Bob。一旦您LiveData<User>触发更改,您的 UI 将观察它并将文本更新为Current user: Alice. 很简单,线性,一对一的变化。

switchMap

考虑以下示例 - 您想要创建一个 UI,该 UI 显示名称与给定搜索词匹配的用户。我们可以很聪明地将搜索词作为 LiveData 保存!因此LiveData<String>,每次用户输入新的查询字符串时,我们的Fragment/Activity都会简单地将文本输入值设置为ViewModel. 因此,此实时数据将触发更改信号。一旦我们得到这个信号,我们就开始搜索用户。现在让我们考虑一下我们的搜索速度如此之快以至于它立即返回一个值。此时你认为你可以只使用一个map并返回将更新 UI 的匹配用户。好吧,您现在会遇到一个错误 - 假设您定期更新数据库,下次更新后更多用户出现与搜索词匹配!如您所见,在这种情况下,源触发器(搜索词)不一定会导致映射实时数据的单个触发器,在添加新用户后,提供给 UI 的映射实时数据可能仍需要继续触发值数据库。在这一点上,您可能会说,我们可以返回一个“更智能”的实时数据,它不仅会等待源触发器,还会监视数据库中与给定术语匹配的用户(您可以使用RoomDB out来做到这一点)的盒子)。但随之而来的另一个问题是 - 如果搜索词发生变化怎么办?所以你的任期是x,它触发了一个实时数据,它查询用户并关注数据库,它返回userx, userxx,然后在五分钟后返回userx, userxxx,依此类推。然后这个词变成了y。现在我们需要以某种方式停止收听为我们提供用户的智能实时数据x,并将其切换为新的智能实时数据,该数据将监控并为我们提供用户y名。而这正是switchMap正在做的!请注意,此切换需要以这样的方式完成,即在您的 UI 中您只需编写switchMap(...).observe一次,这意味着switchMap必须返回一个包装器LiveData,该包装器将在整个执行过程中保持不变,但会切换引擎盖下的实时数据源我们。

结论

尽管它们乍一看似乎相同,但map和的用例switchMap不同,一旦开始实现您的用例,您就会感觉到使用哪个,主要是当您意识到在映射函数中必须调用一些来自其他模块(如Repositories)的代码返回LiveData.


tro*_*tto 19

Map() 在概念上与 RXJava 中的使用相同,基本上您是在另一个参数中更改 LiveData 的参数 在此处输入图片说明

SwitchMap()相反,您将用另一个替代 LiveData 本身!典型的情况是,例如,当您从存储库中检索一些数据并“消除”之前的 LiveData(进行垃圾收集,以提高内存效率)时,您会传递一个执行相同操作的LiveData(获取对实例)

  • 到目前为止,唯一能用原始的简单例子反映现实的答案 (4认同)

Che*_*eng 13

我的观察是,如果您的转换过程很快(不涉及数据库操作或网络活动),那么您可以选择使用map.

但是,如果您的转换过程很慢(涉及数据库操作或网络活动),则需要使用 switchMap

switchMap 在执行耗时的操作时使用

class MyViewModel extends ViewModel {
    final MutableLiveData<String> mString = new MutableLiveData<>();
    final LiveData<Integer> mCode;


    public MyViewModel(String string) {

        mCode = Transformations.switchMap(mString, input -> {
            final MutableLiveData<Integer> result = new MutableLiveData<>();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    // Pretend we are busy
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    int code = 0;
                    for (int i=0; i<input.length(); i++) {
                        code = code + (int)input.charAt(i);
                    }

                    result.postValue(code);
                }
            }).start();

            return result;
        });

        if (string != null) {
            mString.setValue(string);
        }
    }

    public LiveData<Integer> getCode() {
        return mCode;
    }

    public void search(String string) {
        mString.setValue(string);
    }
}
Run Code Online (Sandbox Code Playgroud)

map 不适合耗时的操作

class MyViewModel extends ViewModel {
    final MutableLiveData<String> mString = new MutableLiveData<>();
    final LiveData<Integer> mCode;


    public MyViewModel(String string) {

        mCode = Transformations.map(mString, input -> {
            /* 
                Note: You can't launch a Thread, or sleep right here. 
                If you do so, the APP will crash with ANR.
            */
            /*
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            */

            int code = 0;
            for (int i=0; i<input.length(); i++) {
                code = code + (int)input.charAt(i);
            }
            return code;
        });

        if (string != null) {
            mString.setValue(string);
        }
    }

    public LiveData<Integer> getCode() {
        return mCode;
    }

    public void search(String string) {
        mString.setValue(string);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 简单而清晰的响应,大多数响应只是一遍又一遍地解释它在内部的工作方式,但是我关心的第一件事就是为什么要使用它,而无需了解它的内部行为。谢谢。 (4认同)

Thr*_*ian 8

首先,map()switchMap()方法都在主线程上调用。它们与用于快速或慢速任务无关。但是,如果您在这些方法(而不是工作线程)中执行复杂的计算或耗时任务,例如解析或转换较长和/或复杂的json响应,则可能会导致UI滞后,因为它们是在UI线程上执行的。

  • 地图()

map()方法的代码是

@MainThread
public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
        @NonNull final Function<X, Y> func) {
    final MediatorLiveData<Y> result = new MediatorLiveData<>();
    result.addSource(source, new Observer<X>() {
        @Override
        public void onChanged(@Nullable X x) {
            result.setValue(func.apply(x));
        }
    });
    return result;
}
Run Code Online (Sandbox Code Playgroud)

它的作用是,它使用源LiveData,我是输入类型,并在LiveData上调用setValue(O),其中O是输出类型。

为了清楚起见,让我举一个例子。您希望在用户更改时将用户名和姓氏写入textView。

  /**
     * Changes on this user LiveData triggers function that sets mUserNameLiveData String value
     */
    private MutableLiveData<User> mUserLiveData = new MutableLiveData<>();

    /**
     * This LiveData contains the data(String for this example) to be observed.
     */
    public final LiveData<String> mUserNameLiveData;
Run Code Online (Sandbox Code Playgroud)

现在让我们在mUserLiveData更改时触发mUserNameLiveData的String的更改。

   /*
     * map() method emits a value in type of destination data(String in this example) when the source LiveData is changed. In this example
     * when a new User value is set to LiveData it trigger this function that returns a String type
     *         
     *              Input, Output
     * new Function<User, String>
     *
     *  public String apply(User input) { return output;}
     */

    // Result<Output>                        Source<Input>               Input, Output
    mUserNameLiveData = Transformations.map(mUserLiveData, new Function<User, String>() {
        @Override
        public String apply(User input) {
            // Output
            return input.getFirstName() + ", " + input.getLastName();
        }
    });
Run Code Online (Sandbox Code Playgroud)

让我们做同样的事情 MediatorLiveData

 /**
     * MediatorLiveData is what {@link Transformations#map(LiveData, Function)} does behind the scenes
     */
    public MediatorLiveData<String> mediatorLiveData = new MediatorLiveData<>();
    /*
     * map() function is actually does this
     */
    mediatorLiveData.addSource(mUserLiveData, new Observer<User>() {
        @Override
        public void onChanged(@Nullable User user) {
            mediatorLiveData.setValue(user.getFirstName() + ", " + user.getLastName());
        }
    });
Run Code Online (Sandbox Code Playgroud)

而且,如果您在Activity或Fragment上观察MediatorLiveData,则得到的结果与观察到的结果相同 LiveData<String> mUserNameLiveData

userViewModel.mediatorLiveData.observe(this, new Observer<String>() {
    @Override
    public void onChanged(@Nullable String s) {
        TextView textView = findViewById(R.id.textView2);

        textView.setText("User: " + s);

        Toast.makeText(MainActivity.this, "User: " + s, Toast.LENGTH_SHORT).show();
    }
});
Run Code Online (Sandbox Code Playgroud)
  • switchMap()

每当SourceLiveData更改时,switchMap()都会返回相同的MediatorLiveData而不是新的 LiveData。

它的源代码是

@MainThread
public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
                                           @NonNull final Function<X, LiveData<Y>> func) {

    final MediatorLiveData<Y> result = new MediatorLiveData<>();

    result.addSource(trigger, new Observer<X>() {
        LiveData<Y> mSource;

        @Override
        public void onChanged(@Nullable X x) {
            LiveData<Y> newLiveData = func.apply(x);
            if (mSource == newLiveData) {
                return;
            }
            if (mSource != null) {
                result.removeSource(mSource);
            }
            mSource = newLiveData;
            if (mSource != null) {
                result.addSource(mSource, new Observer<Y>() {
                    @Override
                    public void onChanged(@Nullable Y y) {
                        result.setValue(y);
                    }
                });
            }
        }
    });
    return result;
}
Run Code Online (Sandbox Code Playgroud)

基本上,它会创建一个最终的MediatorLiveData并将其设置为Result,例如map dos(),但这一次函数返回LiveData

   public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
                                         @NonNull final Function<X, **Y**> func) {

        final MediatorLiveData<Y> result = new MediatorLiveData<>();

        result.addSource(source, new Observer<X>() {

            @Override
            public void onChanged(@Nullable X x) {
                result.setValue(func.apply(x));
            }

        });

        return result;
    }

    @MainThread
    public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
                                               @NonNull final Function<X, **LiveData<Y>**> func) {

        final MediatorLiveData<Y> result = new MediatorLiveData<>();

        result.addSource(trigger, new Observer<X>() {
            LiveData<Y> mSource;

            @Override
            public void onChanged(@Nullable X x) {
                LiveData<Y> newLiveData = func.apply(x);
                if (mSource == newLiveData) {
                    return;
                }
                if (mSource != null) {
                    result.removeSource(mSource);
                }
                mSource = newLiveData;
                if (mSource != null) {
                    result.addSource(mSource, new Observer<Y>() {
                        @Override
                        public void onChanged(@Nullable Y y) {
                            result.setValue(y);
                        }
                    });
                }
            }
        });
        return result;
    }
Run Code Online (Sandbox Code Playgroud)

因此,map()需要LiveData<User>把它转换成一个String,如果User对象更改为实例名称领域的变化。

switchMap()接受一个String并开始LiveData<User>使用它。使用字符串从Web或db查询用户,并得到LiveData<User>一个结果。

  • 很好的答案!! (2认同)

小智 7

switchMap :假设我们正在寻找用户名 Alice。存储库正在创建该 User LiveData 类的新实例,然后显示用户。一段时间后,我们需要查找用户名 Bob,其中存储库创建了一个新的 LiveData 实例,我们的 UI 订阅了该 LiveData。所以此时,我们的 UI 订阅了 LiveData 的两个实例,因为我们从未删除前一个实例。所以这意味着每当我们的存储库更改用户的数据时,它都会发送两次订阅。现在,我们如何解决这个问题……?

我们真正需要的是一种机制,当我们想观察一个新的源时,它允许我们停止从前一个源进行观察。为此,我们将使用 switchMap。在幕后,switchMap 使用 MediatorLiveData,每当添加新源时,它都会删除初始源。简而言之,它为我们完成了删除和添加新观察者的所有机制。

但是地图是静态的,当您不必每次都被迫获取新的实时数据时使用它


Saj*_*jad 6

  • 最后map你有相同的源 livedata但它的数据(值)在发射之前随提供的函数发生变化

  • 使用switchMap,您可以将源实时数据用作返回独立实时数据的触发器(当然,您可以在函数输入中使用触发器数据)

  • 触发器:导致 livedata 的观察者onChanged()调用的一切