我是靠谱客的博主 无聊西装,最近开发中收集的这篇文章主要介绍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

    直接上代码。

    依赖的第三方类库

        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

    android {
        ......
    
        dataBinding {
            enabled = true
        }
    }
    
    

    工程目录结构

    MVVM 之 View

    MainActivity.java

    getFragmentManager().beginTransaction().add(R.id.movie_fragment, MovieFragment.getInstance()).commit();
    
    

    MovieFragment.java

    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

    <?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

    <?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

    <?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

    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**

    public interface CompletedListener {
        void onCompleted();
    }
    

    MVVM 之 ViewModel

    MainViewModel.java

    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

    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

    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

    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的一次实践联系与交流所遇到的程序开发问题。

    如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

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

    评论列表共有 0 条评论

    立即
    投稿
    返回
    顶部