美文网首页
Dagger2+Retrofit+RxJava+Butterkn

Dagger2+Retrofit+RxJava+Butterkn

作者: 夜不见月光的蓝 | 来源:发表于2017-06-12 17:32 被阅读0次

    上一篇我们讲解了安卓中的MVP模型,这次我们将用查看学生列表作为例子,搭建一个MVP安卓项目。我们都知道不要重复造轮子,因此使用一些现成的框架会简化我们的操作。因此这篇文章将会重点介绍Dagger2+Retrofit+RxJava+Butterknife环境的搭建,以及如何使用。
    项目代码连接

    • Dagger2 - 依赖注入
    • Retrofit - 发送http请求, 一种 type-­safe REST client
    • RxJava - 实现异步操作
      (个人觉得还是很复杂的,需要透彻理解观察者模式。可以看这篇博客稍微了解一下这个框架)
    • Butterknife - 用来绑定Android views和方法

    添加依赖

    目前我使用的gradle版本是2.3.2,引入Dagger2的时候已经不再需要配置apt插件。另外,配置Retrofit的各种converter和adapter时版本一定要一致。

        compile 'com.squareup.retrofit2:retrofit:2.1.0'
        compile 'com.squareup.retrofit2:converter-gson:2.1.0'
        compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
        compile 'com.jakewharton:butterknife:8.4.0'
        compile 'com.google.dagger:dagger:2.4'
        compile 'io.reactivex:rxjava:1.0.14'
        compile 'io.reactivex:rxandroid:1.0.1'
        compile 'io.reactivex:rxjava-joins:0.22.0'
        //偷懒用的现成的底部导航栏,很少女很好看
        compile 'com.ashokvarma.android:bottom-navigation-bar:0.9.5' 
        annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
        annotationProcessor 'com.google.dagger:dagger-compiler:2.4'
    

    Model

    因为要展示学生列表,所以首先定义从服务器端接收过来的学生信息。
    服务器端提供接口:
    /students

    [
      {
        username:tj,
        name:"jenny",
        gender:"female",
        email:"test@163.com"
      }
    ]
    

    Student类:

    public class Student implements Serializable {
        @SerializedName("username")
        private String username;
        @SerializedName("name")
        private String name;
        @SerializedName("gender")
        private String gender;
        @SerializedName("email")
        private String email;
    }
    
    

    注意@SerializedName注解中要与服务器端提供的格式相符
    因为要访问网络,所以记得在AndroidManifest.xml中添加INTERNET permissions

    创建Retrofit实例

    为了发送网络请求,我们需要使用Retrofit Builder 类并配置base URL。为了接收服务器端的json格式的数据并转化成对象,我们需要添加GsonConverterFactory。为了支持RxJava,Retrofit本来默认返回的是Call<T>,我们需要修改为Observable<T>,我们需要添加RxJavaCallAdapterFactory。

    public class ApiClient {
        public static final String BASE_URL = "http://和谐掉/api/";
        private static Retrofit retrofit = null;
    
    
        public static Retrofit getClient() {
            if (retrofit == null) {
                retrofit = new Retrofit.Builder()
                        .baseUrl(BASE_URL)
                        .addConverterFactory(GsonConverterFactory.create())
                        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                        .build();
            }
            return retrofit;
        }
    }
    

    定义接收端口

    在接口内定义接收端口,使用特殊的Retrofit注解来对请求参数和请求方法进行编码。
    另外,服务器端获取信息的接口需要身份验证,所以需要在请求的Header中添加token。

    public interface ApiInterface {
        @GET("students")
        Observable<List<Student>> getStudents(@Header("Authorization") String token);
    }
    

    Retrofit的其他注解
    @Path - 用来代替API端口中的变量。如:

    @GET("group/{groupId}/students")
    Observable<List<User>> getStudents( @Path("groupId") int groupId);
    

    @Query - 使用注解参数的值指定查询键名称。
    @Body - POST 请求中的playload

    在Repository中调用

    public class StudentRepository {
        private static StudentRepository instance;
        protected ApiInterface apiService;
    
        private StudentRepository() {
            apiService = ApiClient.getClient().create(ApiInterface.class);
        }
    
        public static StudentRepository getInstance() {
            if (instance == null)
                instance = new TeacherRepository();
            return instance;
        }
    
        public Observable<List<Student>> getStudents(String username, String password) {
            return apiService.getStudents(getToken(username, password), groupId);
        }
    }
    

    Retrofit 会在后台的线程下载并转换数据。


    Presenter

    Presenter需要覆盖View的生命周期,如果我们需要跟踪Activity或者Fragment的生命周期,我们需要调用Presenter对应的方法。因此每个Presenter需要实现以下接口:

    public interface BasePresenter<T> {
        void onCreate();
    
        void onStart();
    
        void onStop();
    
        void onPause();
    }
    

    StudentListPresenter

    public class StudentListPresenter implements BasePresenter<BasicListView> {
        private Subscription subscription;
        private BasicListView view;
        private StudentRepository repository;
    
        public StudentListPresenter(BasicListView view, StudentRepository repository) {
            this.view = view;
            this.repository = repository;
        }
    
        public void getStudents(String username, String password) {
             subscription = repository.getStudents(username, password)
                    .subscribeOn(Schedulers.io())
                    .onErrorReturn(new Func1<Throwable, List<User>>() {
                        @Override
                        public List<User> call(Throwable throwable) {
                            throwable.printStackTrace();
                            view.showError();
                            return null;
                        }
                    })
                    .subscribe(new Action1<List<User>>() {
                        @Override
                        public void call(List<User> groups) {
                            if (groups != null) {
                                view.addStudents(groups);
                            }
                        }
                    });
        }
    
        @Override
        public void onStop() {
            if (subscription != null && !subscription.isUnsubscribed()) {
                subscription.unsubscribe();
            }
        }
    }
    
    

    我们使用StudentRepository来获得学生列表,Presenter并不在意它是如何获得的,它只是简单地订阅Repository返回的Observable,更新view。为了避免后台任务完成前Activity就被销毁,Presenter需持有一个Subscription的引用,在Activity调用onStop()方法时unsubscribe.


    View

    根据依赖倒置原则,在View和Presenter之间定义一个接口,Activity或者Fragment实现该接口,Presenter持有接口的引用。

    public interface BasicListView {
        void showError();
    
        void addStudents(List<User> list);
    }
    

    定义Activity或者Fragment实现该接口

    public class StudentsListFragment extends BaseFragment implements BasicListView {
        @BindView(R.id.list)
        ListView listView;
        @Inject
        StudentListPresenter presenter;
        private View view;
        private UserInfoActivity activity;
        private List<Map<String, Object>> studentMap;
    
        @Override
        public void showError() {
            Runnable myRunnable = new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(activity, R.string.network_error, Toast.LENGTH_LONG).show();
                }
            };
            activity.runOnUiThread(myRunnable);
        }
    
        @Override
        public void addStudents(List<User> list) {
             for (User user : list) {
                  Map<String, Object> map = new HashMap<>();
                  map.put("title", user.getName());
                  map.put("content", user.getNumber());
                  studentMap.add(map);
              }
              showList();
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            activity = (UserInfoActivity) getActivity();
            view = inflater.inflate(R.layout.student_list, container, false);
            ButterKnife.bind(this, view);
            DaggerActivityComponent.builder()
                    .mainModule(new MainModule(this))
                    .build()
                    .inject(this);
            presenter.getStudents(username, password);
            return view;
        }
    
        @Override
        public void onStop() {
            super.onStop();
            presenter.onStop();
        }
    
        private void showList() {
            Runnable myRunnable = new Runnable() {
                @Override
                public void run() {
                    String[] from = {"title", "content"};
                    int[] to = {R.id.title, R.id.content};
                    SimpleAdapter adapter = new SimpleAdapter(view.getContext(), studentMap, R.layout.short_student_info, from, to);
                    listView.setAdapter(adapter);
                }
            };
            activity.runOnUiThread(myRunnable);
        }
    
    }
    

    在StudentsListFragment中,我们使用到了@BindView注解,该注解可以通过id找到对应的view组件。Butterknife的功能不止于此,其官方文档写的非常清楚,感兴趣请戳Butter Knife.

    在MVP模型中,Activity或者Fragment持有Presenter的引用,并在Activity中实例化这个Presenter,即Activity依赖Presenter,Presenter又需要依赖View接口,从而更新UI。这样Activity与Presenter就耦合在了一起,因此我们这里使用到了Dagger2的依赖注入,来降低耦合。

    让我们来看一下代码。首先我们声明了一个StudentListPresenter,在声明的基础上加了一个注解@Inject,表明Presenter是需要注入到Fragment中。这里要注意的是,使用@Inject时,不能用private修饰符修饰类的成员属性。

    @Component(modules = MainModule.class)
    public interface ActivityComponent {
        void inject(StudentsListFragment fragment);
    }
    
    

    然而,StudentListPresenter的构造函数和StudentListFragment中声明的presenter并不会凭空建立起联系。Component是一个接口或者抽象类,用来将它们连接在一起。
    @Component注解标注,我们在这个接口中定义了一个inject()方法,参数是StudentListFragment。然后rebuild一下项目,会生成一个以Dagger为前缀的Component类,这里是DaggerActivityComponent.
    注意StudentListFragment中的这段代码:

     DaggerActivityComponent.builder()
                    .mainModule(new MainModule(this))
                    .build()
                    .inject(this);
    

    此时Component就将@Inject注解的presenter与其构造函数联系了起来。

    有时候,有的类没有构造函数,这些类无法用@Inject标注,比如第三方类库,系统类,以及上面示例的View接口。此时我们需要@Module标记的MainModlue类来提供依赖。
    MainModule

    @Module
    public class MainModule {
        private StudentsListFragment fragment;
    
        public MainModule(StudentsListFragment fragment) {
            this.fragment = fragment;
        }
    
        @Provides
        public StudentsListFragment provideFragment() {
            return fragment;
        }
    
        @Provides
        public StudentRepository provideRepository() {
            return StudentRepository.getInstance();
        }
    
        @Provides
        public StudentListPresenter getStuListPresenter(StudentsListFragment fragment, TeacherRepository repository) {
            return new StudentListPresenter(fragment, repository);
        }
    }
    

    Module要发挥作用,还是要依靠于Component类,一个Component类可以包含多个Module类,用来提供依赖。

    我们接着来看上面这段代码。这里通过new MainModule(this)将view传递到MainModule里。当去实例化StudentListPresenter时,发现构造函数有两个参数,此时会在Module里查找提供这个依赖的方法,即用@Provides标注的方法,将该View和Repository传递进去,这样就完成了presenter里View的注入。


    讲到这里项目就搭建完毕啦,也实现了一个小功能。这里就略去layout的编写了毕竟我也不会。

    相关文章

      网友评论

          本文标题:Dagger2+Retrofit+RxJava+Butterkn

          本文链接:https://www.haomeiwen.com/subject/djylqxtx.html