我是靠谱客的博主 无聊西装,这篇文章主要介绍MVVM,RxJava和Retrofit的一次实践联系与交流,现在分享给大家,希望可以做个参考。

该原创文章首发于微信公众号“字节流动”

Sample 简介

一个简单的结合 Retrofit 和 RxJava 框架实现 MVVM 架构的例子。

最近在研究 Kotlin for Android,做了一个基于 Clean 架构以及 Retrofit , RxKotlin , Dagger 框架实现的 Kotlin for Android App ,更多详情请戳这里。

效果预览

Demo 下载

准备知识

MVC

  • 视图(View):用户界面。
  • 控制器(Controller):业务逻辑
  • 模型(Model):数据保存

  • View 传送指令到 Controller
  • Controller 完成业务逻辑后,要求 Model 改变状态
  • Model 将新的数据发送到 View,使用户得到反馈
  • 缺陷:View 和 Model 是相互可知,耦合性大,像 Activity 或者 Fragment 既是 Controller 层,又是 View 层,造成工程的可扩展性可维护性非常差。

    MVP

    在 MVP 设计架构中,Controller 变成了 Presenter。

    1. 各层之间的通信,都是双向的。
    2. View 与 Model 不直接发生联系,都通过 Presenter 进行间接通信。
    3. Model 层与 Presenter 层,Presenter 层与 View 层之间通过接口建立联系。

    采用 MVP 设计架构,Activity 与 Fragment 只位于 View 层。

    MVP 的缺陷在于:由于我们使用了接口的方式去连接 View 层和 Presenter 层,这样就导致了一个问题,当你的页面逻辑很复杂的时候,你的接口会有很多,如果你的 app 中有很多个这样复杂的页面,维护接口的成本就会变的非常的大。

    MVVM

    MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。
    区别在于: View 层与 ViewModel 层通过DataBinding相互绑定,View的变动,自动反映在 ViewModel,反之亦然。

    RxJava

    RxJava 在 GitHub 主页上的自我介绍是 “a library for composing asynchronous and event-based programs using observable sequences for the Java VM”(一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库)。

    RxJava 本质上是一个异步操作库,是一个能让你用极其简洁的逻辑去处理繁琐复杂任务的异步事件库。

    简而言之,RxJava 可以用几个关键字概括:简洁队列化异步

    Retrofit

    一个 Android 和 Java 上 HTTP 库(利用注解和 okhttp 来实现和服务器的数据交互)。

    Retrofit 官方文档:http://square.github.io/retrofit/

    DataBinding

    在今年的 Google IO 2015 中,Google 在 support-v7 中新增了 Data Binding,使用 Data Binding 可以直接在布局的 xml 中绑定布局与数据,从而简化代码,Android Data Binding 是Android 的 MVVM 框架。因为 Data Binding 是包含在 support-v7 包里面的,所以可以向下兼容到最低 Android 2.1 (API level 7+).

    实践

    嫌代码不够高亮?请移步博客http://haohaochang.cn

    直接上代码。

    依赖的第三方类库

    复制代码
    1
    2
    3
    4
    5
    6
    7
    compile 'io.reactivex:rxjava:1.1.0' compile 'io.reactivex:rxandroid:1.1.0' compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4' compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4' compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4' compile 'com.github.bumptech.glide:glide:3.7.0'

    API

    https://api.douban.com/v2/movie/top250?start=0&count=20

    引入DataBinding

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    android { ...... dataBinding { enabled = true } }

    工程目录结构

    MVVM 之 View

    MainActivity.java

    复制代码
    1
    2
    3
    getFragmentManager().beginTransaction().add(R.id.movie_fragment, MovieFragment.getInstance()).commit();

    MovieFragment.java

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    public class MovieFragment extends Fragment implements CompletedListener,SwipeRefreshLayout.OnRefreshListener{ private static String TAG = MovieFragment.class.getSimpleName(); private MainViewModel viewModel; private MovieFragmentBinding movieFragmentBinding; private MovieAdapter movieAdapter; public static MovieFragment getInstance() { return new MovieFragment(); } @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View contentView = inflater.inflate(R.layout.movie_fragment, container, false); movieFragmentBinding = MovieFragmentBinding.bind(contentView); initData(); return contentView; } private void initData() { movieAdapter = new MovieAdapter(); movieFragmentBinding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false)); movieFragmentBinding.recyclerView.setItemAnimator(new DefaultItemAnimator()); movieFragmentBinding.recyclerView.setAdapter(movieAdapter); movieFragmentBinding.swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary, R.color.colorPrimaryDark); movieFragmentBinding.swipeRefreshLayout.setOnRefreshListener(this); viewModel = new MainViewModel(movieAdapter,this); movieFragmentBinding.setViewModel(viewModel); } @Override public void onRefresh() { movieAdapter.clearItems(); viewModel.refreshData(); } @Override public void onCompleted() { if (movieFragmentBinding.swipeRefreshLayout.isRefreshing()) { movieFragmentBinding.swipeRefreshLayout.setRefreshing(false); } } }

    activity_main.xml

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context=".view.MainActivity"> <!-- ... --> <FrameLayout android:layout_marginTop="?attr/actionBarSize" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/movie_fragment"/> <!-- ... --> </android.support.design.widget.CoordinatorLayout>

    movie_fragment.xml

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="com.jc.mvvmrxjavaretrofitsample.viewModel.MainViewModel"/> </data> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v4.widget.SwipeRefreshLayout android:visibility="@{viewModel.contentViewVisibility}" android:id="@+id/swipe_refresh_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:background="#ddd" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="8dp"> </android.support.v7.widget.RecyclerView> </android.support.v4.widget.SwipeRefreshLayout> <ProgressBar style="?android:attr/progressBarStyleLarge" android:id="@+id/progress_bar" android:visibility="@{viewModel.progressBarVisibility}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true"/> <LinearLayout android:layout_width="match_parent" android:id="@+id/error_info_layout" android:visibility="@{viewModel.errorInfoLayoutVisibility}" android:orientation="vertical" android:layout_height="match_parent"> <TextView android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.exception}"/> </LinearLayout> </RelativeLayout> </layout>

    movie_item.xml

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/tools"> <data> <variable name="viewModel" type="com.jc.mvvmrxjavaretrofitsample.viewModel.MovieViewModel"/> </data> <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto" android:id="@+id/card_view" android:layout_width="match_parent" android:layout_height="wrap_content" card_view:cardCornerRadius="4dp" card_view:cardBackgroundColor="@color/background" card_view:cardUseCompatPadding="true"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <ImageView android:layout_margin="8dp" android:layout_width="60dp" android:layout_height="100dp" android:src="@drawable/cover" app:imageUrl="@{viewModel.imageUrl}" android:id="@+id/cover"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_margin="8dp" android:orientation="vertical"> <TextView android:textColor="@android:color/black" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.title}" android:textSize="12sp"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:orientation="horizontal"> <android.support.v7.widget.AppCompatRatingBar android:id="@+id/ratingBar" style="?android:attr/ratingBarStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:isIndicator="true" android:max="10" android:numStars="5" android:rating="@{viewModel.rating}" /> <TextView android:id="@+id/rating_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginLeft="6dp" android:text="@{viewModel.ratingText}" android:textColor="?android:attr/textColorSecondary" android:textSize="10sp" /> </LinearLayout> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="?android:attr/textColorSecondary" android:textSize="10sp" android:text="@{viewModel.movieType}" android:id="@+id/movie_type_text" android:layout_marginTop="6dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="?android:attr/textColorSecondary" android:textSize="10sp" android:text="@{viewModel.year}" android:id="@+id/year_text" android:layout_marginTop="6dp" /> </LinearLayout> </LinearLayout> </android.support.v7.widget.CardView> </layout>

    MovieAdapter.java

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    public class MovieAdapter extends RecyclerView.Adapter<MovieAdapter.BindingHolder> { private List<Movie> movies; public MovieAdapter() { movies = new ArrayList<>(); } @Override public BindingHolder onCreateViewHolder(ViewGroup parent, int viewType) { MovieItemBinding itemBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.movie_item, parent, false); return new BindingHolder(itemBinding); } @Override public void onBindViewHolder(BindingHolder holder, int position) { MovieViewModel movieViewModel = new MovieViewModel(movies.get(position)); holder.itemBinding.setViewModel(movieViewModel); } @Override public int getItemCount() { return movies.size(); } public void addItem(Movie movie) { movies.add(movie); notifyItemInserted(movies.size() - 1); } public void clearItems() { movies.clear(); notifyDataSetChanged(); } public static class BindingHolder extends RecyclerView.ViewHolder { private MovieItemBinding itemBinding; public BindingHolder(MovieItemBinding itemBinding) { super(itemBinding.cardView); this.itemBinding = itemBinding; } } }

    回调接口** CompletedListener.java**

    复制代码
    1
    2
    3
    4
    public interface CompletedListener { void onCompleted(); }

    MVVM 之 ViewModel

    MainViewModel.java

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    public class MainViewModel { public ObservableField<Integer> contentViewVisibility; public ObservableField<Integer> progressBarVisibility; public ObservableField<Integer> errorInfoLayoutVisibility; public ObservableField<String> exception; private Subscriber<Movie> subscriber; private MovieAdapter movieAdapter; private CompletedListener completedListener; public MainViewModel(MovieAdapter movieAdapter,CompletedListener completedListener) { this.movieAdapter = movieAdapter; this.completedListener = completedListener; initData(); getMovies(); } private void getMovies() { subscriber = new Subscriber<Movie>() { @Override public void onCompleted() { Log.d("[MainViewModel]", "onCompleted"); hideAll(); contentViewVisibility.set(View.VISIBLE); completedListener.onCompleted(); } @Override public void onError(Throwable e) { hideAll(); errorInfoLayoutVisibility.set(View.VISIBLE); exception.set(e.getMessage()); } @Override public void onNext(Movie movie) { movieAdapter.addItem(movie); } }; RetrofitHelper.getInstance().getMovies(subscriber, 0, 20); } public void refreshData() { getMovies(); } private void initData() { contentViewVisibility = new ObservableField<>(); progressBarVisibility = new ObservableField<>(); errorInfoLayoutVisibility = new ObservableField<>(); exception = new ObservableField<>(); contentViewVisibility.set(View.GONE); errorInfoLayoutVisibility.set(View.GONE); progressBarVisibility.set(View.VISIBLE); } private void hideAll(){ contentViewVisibility.set(View.GONE); errorInfoLayoutVisibility.set(View.GONE); progressBarVisibility.set(View.GONE); } }

    MovieViewModel.java

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    public class MovieViewModel extends BaseObservable { private Movie movie; public MovieViewModel(Movie movie) { this.movie = movie; } public String getCoverUrl() { return movie.getImages().getSmall(); } public String getTitle() { return movie.getTitle(); } public float getRating() { return movie.getRating().getAverage(); } public String getRatingText(){ return String.valueOf(movie.getRating().getAverage()); } public String getYear() { return movie.getYear(); } public String getMovieType() { StringBuilder builder = new StringBuilder(); for (String s : movie.getGenres()) { builder.append(s + " "); } return builder.toString(); } public String getImageUrl() { return movie.getImages().getSmall(); } @BindingAdapter({"app:imageUrl"}) public static void loadImage(ImageView imageView,String url) { Glide.with(imageView.getContext()) .load(url) .placeholder(R.drawable.cover) .error(R.drawable.cover) .into(imageView); } }

    MVVM 之 Model

    DouBanMovieService.java

    复制代码
    1
    2
    3
    4
    5
    6
    7
    public interface DouBanMovieService { String BASE_URL = "https://api.douban.com/v2/movie/"; @GET("top250") Observable<Response<List<Movie>>> getMovies(@Query("start") int start, @Query("count") int count); }

    RetrofitHelper.java

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    public class RetrofitHelper { private static final int DEFAULT_TIMEOUT = 10; private Retrofit retrofit; private DouBanMovieService movieService; OkHttpClient.Builder builder; /** * 获取RetrofitHelper对象的单例 * */ private static class Singleton { private static final RetrofitHelper INSTANCE = new RetrofitHelper(); } public static RetrofitHelper getInstance() { return Singleton.INSTANCE; } public RetrofitHelper() { builder = new OkHttpClient.Builder(); builder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS); retrofit = new Retrofit.Builder() .client(builder.build()) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .baseUrl(DouBanMovieService.BASE_URL) .build(); movieService = retrofit.create(DouBanMovieService.class); } public void getMovies(Subscriber<Movie> subscriber, int start, int count) { movieService.getMovies(start, count) .map(new Func1<Response<List<Movie>>, List<Movie>>() { @Override public List<Movie> call(Response<List<Movie>> listResponse) { return listResponse.getSubjects(); } }) .flatMap(new Func1<List<Movie>, Observable<Movie>>() { @Override public Observable<Movie> call(List<Movie> movies) { return Observable.from(movies); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(subscriber); } }

    还有 entity 类,这里就不贴出来了。

    联系与交流

    微信公众号

    最后

    以上就是无聊西装最近收集整理的关于MVVM,RxJava和Retrofit的一次实践联系与交流的全部内容,更多相关MVVM,RxJava和Retrofit内容请搜索靠谱客的其他文章。

    本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
    点赞(73)

    评论列表共有 0 条评论

    立即
    投稿
    返回
    顶部