上一篇我們講解了安卓中的MVP模型,這次我們將用查看學(xué)生列表作為例子唁影,搭建一個MVP安卓項目。我們都知道不要重復(fù)造輪子,因此使用一些現(xiàn)成的框架會簡化我們的操作晾剖。因此這篇文章將會重點介紹Dagger2+Retrofit+RxJava+Butterknife環(huán)境的搭建,以及如何使用梯嗽。
項目代碼連接
- Dagger2 - 依賴注入
- Retrofit - 發(fā)送http請求齿尽, 一種 type--safe REST client
-
RxJava - 實現(xiàn)異步操作
(個人覺得還是很復(fù)雜的,需要透徹理解觀察者模式灯节⊙罚可以看這篇博客稍微了解一下這個框架) - Butterknife - 用來綁定Android views和方法
添加依賴
目前我使用的gradle版本是2.3.2,引入Dagger2的時候已經(jīng)不再需要配置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'
//偷懶用的現(xiàn)成的底部導(dǎo)航欄形入,很少女很好看
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
因為要展示學(xué)生列表全跨,所以首先定義從服務(wù)器端接收過來的學(xué)生信息。
服務(wù)器端提供接口:
/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注解中要與服務(wù)器端提供的格式相符
因為要訪問網(wǎng)絡(luò)亿遂,所以記得在AndroidManifest.xml中添加INTERNET permissions
創(chuàng)建Retrofit實例
為了發(fā)送網(wǎng)絡(luò)請求螟蒸,我們需要使用Retrofit Builder 類并配置base URL。為了接收服務(wù)器端的json格式的數(shù)據(jù)并轉(zhuǎn)化成對象崩掘,我們需要添加GsonConverterFactory七嫌。為了支持RxJava,Retrofit本來默認(rèn)返回的是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;
}
}
定義接收端口
在接口內(nèi)定義接收端口,使用特殊的Retrofit注解來對請求參數(shù)和請求方法進行編碼绍赛。
另外蔓纠,服務(wù)器端獲取信息的接口需要身份驗證,所以需要在請求的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
- 使用注解參數(shù)的值指定查詢鍵名稱。
@Body
- POST 請求中的playload
在Repository中調(diào)用
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 會在后臺的線程下載并轉(zhuǎn)換數(shù)據(jù)蚯妇。
Presenter
Presenter需要覆蓋View的生命周期敷燎,如果我們需要跟蹤Activity或者Fragment的生命周期,我們需要調(diào)用Presenter對應(yīng)的方法箩言。因此每個Presenter需要實現(xiàn)以下接口:
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來獲得學(xué)生列表硬贯,Presenter并不在意它是如何獲得的,它只是簡單地訂閱Repository返回的Observable陨收,更新view饭豹。為了避免后臺任務(wù)完成前Activity就被銷毀,Presenter需持有一個Subscription的引用务漩,在Activity調(diào)用onStop()方法時unsubscribe.
View
根據(jù)依賴倒置原則拄衰,在View和Presenter之間定義一個接口,Activity或者Fragment實現(xiàn)該接口饵骨,Presenter持有接口的引用翘悉。
public interface BasicListView {
void showError();
void addStudents(List<User> list);
}
定義Activity或者Fragment實現(xiàn)該接口
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找到對應(yīng)的view組件。Butterknife的功能不止于此包吝,其官方文檔寫的非常清楚饼煞,感興趣請戳Butter Knife.
在MVP模型中,Activity或者Fragment持有Presenter的引用诗越,并在Activity中實例化這個Presenter砖瞧,即Activity依賴Presenter,Presenter又需要依賴View接口嚷狞,從而更新UI块促。這樣Activity與Presenter就耦合在了一起,因此我們這里使用到了Dagger2的依賴注入床未,來降低耦合竭翠。
讓我們來看一下代碼。首先我們聲明了一個StudentListPresenter薇搁,在聲明的基礎(chǔ)上加了一個注解@Inject斋扰,表明Presenter是需要注入到Fragment中。這里要注意的是,使用@Inject時传货,不能用private修飾符修飾類的成員屬性屎鳍。
@Component(modules = MainModule.class)
public interface ActivityComponent {
void inject(StudentsListFragment fragment);
}
然而,StudentListPresenter
的構(gòu)造函數(shù)和StudentListFragment
中聲明的presenter
并不會憑空建立起聯(lián)系问裕。Component是一個接口或者抽象類逮壁,用來將它們連接在一起。
用@Component
注解標(biāo)注粮宛,我們在這個接口中定義了一個inject()
方法,參數(shù)是StudentListFragment
窥淆。然后rebuild一下項目,會生成一個以Dagger為前綴的Component類窟勃,這里是DaggerActivityComponent
.
注意StudentListFragment
中的這段代碼:
DaggerActivityComponent.builder()
.mainModule(new MainModule(this))
.build()
.inject(this);
此時Component就將@Inject
注解的presenter與其構(gòu)造函數(shù)聯(lián)系了起來祖乳。
有時候,有的類沒有構(gòu)造函數(shù)秉氧,這些類無法用@Inject
標(biāo)注眷昆,比如第三方類庫,系統(tǒng)類汁咏,以及上面示例的View接口亚斋。此時我們需要@Module
標(biāo)記的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要發(fā)揮作用攘滩,還是要依靠于Component類帅刊,一個Component類可以包含多個Module類,用來提供依賴漂问。
我們接著來看上面這段代碼赖瞒。這里通過new MainModule(this)
將view傳遞到MainModule里。當(dāng)去實例化StudentListPresenter
時蚤假,發(fā)現(xiàn)構(gòu)造函數(shù)有兩個參數(shù)栏饮,此時會在Module里查找提供這個依賴的方法,即用@Provides
標(biāo)注的方法磷仰,將該View和Repository傳遞進去袍嬉,這樣就完成了presenter里View的注入。
講到這里項目就搭建完畢啦灶平,也實現(xiàn)了一個小功能伺通。這里就略去layout的編寫了畢竟我也不會。