系列文章:
1-準(zhǔn)備工作
2-搭建項(xiàng)目框架
3-功能實(shí)現(xiàn)
4-加入網(wǎng)絡(luò)緩存
前文搭建了項(xiàng)目的大體架構(gòu), 本文以實(shí)現(xiàn)github中最常用的搜索repository功能為例, 講述下功能開發(fā)的整個(gè)過程以及關(guān)于MVP, Dagger2注入, Rx以及Retrofit, OkHttp等在準(zhǔn)備工作中提到的一系列開源庫的使用.
1, 功能梳理
基本功能類似于Github中的Search功能一致, 包含Repository/User/Issue/Code的搜索, 并可選排序方式(Best match, Most stars等等).
本文以搜索Repository為例, 默認(rèn)按照Most Stars來排序列舉搜索結(jié)果. 監(jiān)聽用戶輸入關(guān)鍵字, 當(dāng)超過600ms沒有輸入時(shí)自動(dòng)開始搜索, 按照stars數(shù)顯示搜索結(jié)果.
效果如下:

2, 從數(shù)據(jù)開始(M)
Github相關(guān)API
https://api.github.com/search/repositories?q=tetris+language:assembly&sort=stars&order=desc
根據(jù)API, 我們可以寫Retrofit的接口以及相關(guān)Model類:
public interface RepoService {
@GET("search/repositories")
Observable<SearchResultResp> searchRepo(@Query("q") String key, @Query("sort") String sort,
@Query("order") String order, @Query("page") int page,
@Query("per_page") int pageSize);
}
其中Repo這個(gè)Model類就不貼了, 太長.
建議大家可以使用Android Studio的插件GsonFormat來生成:

Retrofit/OkHttpClient設(shè)計(jì)
大家都知道Retrofit一般會(huì)是單例, 會(huì)設(shè)置一個(gè)baseUrl.
考慮到Github客戶端這個(gè)應(yīng)用, 我會(huì)加入Trending這個(gè)功能, 但是官方并沒有提供相關(guān)接口. 這個(gè)baseUrl就可以不同接口不一樣.
另外使用的OkHttpClient也可能針對不同接口使用不同的interceptor.
在此做了一個(gè)小框架, 以便擴(kuò)展(時(shí)刻牢記開閉原則).

- ApiEndpoint 接口提供baseUrl.
- BaseOkHttpClient 提供OkHttpClient實(shí)例.
- BaseRetrofit 依賴ApiEndpoint和BaseOkHttpClient, 提供Retrofit實(shí)例.
- GithubRepoRetrofit 是基于github的url的Retrofit實(shí)現(xiàn).
數(shù)據(jù)層整體結(jié)構(gòu)
綜上, 數(shù)據(jù)層的整體結(jié)構(gòu)如下:

- RepoApi 定義業(yè)務(wù)接口.
- RepoDataSource 實(shí)現(xiàn)RepoApi接口, 通過RepoService這個(gè)Retrofit格式的Restful接口取數(shù)據(jù)并可以在此做相應(yīng)處理.
3, 繪制界面(V)
這部分就不細(xì)說了, 參看代碼. 抽象了幾個(gè)Base類:

4, 數(shù)據(jù)關(guān)聯(lián)顯示(P)
Presenter作為M和V的橋梁, 如2-搭建項(xiàng)目框架所說, 其擁有V和M的實(shí)例:

通過BaseMvpPresenter中的attachView來獲取View實(shí)例, 在其構(gòu)造函數(shù)中關(guān)聯(lián)RepoApi這個(gè)M層接口.
在此抽象了一個(gè)RxMvpPresenter的基類, 主要用來解決Observable的unsubsribe問題.
5, 將MVP注入起來(Dagger2)
上面介紹完M, V, P這三個(gè)角色, 但是他們是怎么關(guān)聯(lián)起來的呢? 或者說怎么實(shí)例化的呢? 有請Dagger2出場.
具體的Dagger2的使用請自行Google~, 基本作用如下幾點(diǎn). 我后續(xù)也會(huì)抽時(shí)間整理Dagger2的使用經(jīng)驗(yàn), 敬請期待.
下面簡單說下我們這個(gè)功能中的注入情況:s
使用@Module和@Providers來對外提供依賴
搭建項(xiàng)目框架我們提到di的基本框架中, 就寫了一個(gè)ApplicationModule用來對外提供Application級別的依賴, 在此我們可以將Retrofit Service放在這里:
@Module
public class ApplicationModule {
protected final Application mApplication;
public ApplicationModule(Application application) {
mApplication = application;
}
@Provides
Application provideApplication() {
return mApplication;
}
@Provides
@ApplicationContext
Context provideContext() {
return mApplication;
}
@Provides
@Singleton
RepoService provideRepoService(GithubRepoRetrofit retrofit) {
return retrofit.get().create(RepoService.class);
}
}
可以看到我們使用的是GithubRepoRetrofit這個(gè)Retrofit示例來創(chuàng)建Service的.
那么這個(gè)GithubRepoRetrofit是怎么實(shí)例化的呢? 往下看...
構(gòu)造函數(shù)上的@Inject
當(dāng)用@Inject來注解一個(gè)構(gòu)造函數(shù)時(shí), Dagger2會(huì)根據(jù)其構(gòu)造參數(shù)來生成一個(gè)該實(shí)例. 這樣做是為了避免定義太多的@Provides.
例如:
@Inject
public GithubRepoRetrofit(NormalHttpClient mHttpClient) {
this.mHttpClient = mHttpClient;
}
@Inject
public NormalHttpClient() {
}
@Inject
public SearchPresenter(RepoApi api) {
this.mRepoApi = api;
}
變量上的@Inject
上面兩個(gè)原則用來提供實(shí)例, 這個(gè)就是用來請求實(shí)例的.
例如在SearchFragment里面我們需要一個(gè)SearchPresenter的實(shí)例:
@Inject
SearchPresenter mPresenter;
如上即可, 我們無需關(guān)注這個(gè)Presenter怎么實(shí)例化的. 這個(gè)給了我們很多想象空間, 我們可以隨意替換這個(gè)Presenter的實(shí)現(xiàn), 都不需要改動(dòng)View的代碼.
@Component的作用
以上講了實(shí)例的產(chǎn)生和消費(fèi), 那么他們是怎么關(guān)聯(lián)的呢? 這就需要@Component了, Component用來做實(shí)例關(guān)聯(lián)和inject.
所有的@Component類都會(huì)在編譯時(shí)生成一個(gè)Dagger開頭的類, 里面根據(jù)Component定義來提供實(shí)例, 和注入關(guān)系.
例如我們的Component:
@PerActivity
@Component(
dependencies = ApplicationComponent.class,
modules = {ActivityModule.class, RepoModule.class, TrendingRepoModule.class})
public interface MainComponent extends ActivityComponent {
void inject(MostStarFragment mostStarFragment);
void inject(TrendingFragment trendingFragment);
void inject(SearchFragment searchFragment);
}
可以看到MainComponent依賴ApplicationComponent, 并從ActivityModule, RepoModule, TrendingRepoModule這三個(gè)Module中獲取依賴實(shí)例.
另外提供inject關(guān)系到MostStarFragment, TrendingFragment, SearchFragment這三個(gè)使用者身上, 要使用這個(gè)inject關(guān)系, 還需在onCreate中inject一下:
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getComponent(MainComponent.class).inject(this);
mPresenter.attachView(this);
}
具體大家可以查看源碼以及編譯后生成的DaggerMainComponent.
6, RxBinding/RxJava的使用
在這個(gè)功能中, 使用RxBinding對EditText的Text Change事件做了事件流處理, 以便可以很方便的做延時(shí), 過濾, 與查詢接口合并等. 具體RxBinding的功能不在此細(xì)說了.
功能使用如下:
public void bindSearchView(EditText searchTextView) {
mCompositeSubscription.add(RxTextView.textChanges(searchTextView)
.subscribeOn(AndroidSchedulers.mainThread())
.filter(new Func1<CharSequence, Boolean>() {
@Override
public Boolean call(CharSequence charSequence) {
return charSequence.length() > 0;
}
})
.debounce(600, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.switchMap(new Func1<CharSequence, Observable<ArrayList<Repo>>>() {
@Override
public Observable<ArrayList<Repo>> call(CharSequence charSequence) {
String key = charSequence.toString();
return mRepoApi.searchMostStarredRepo(key, mLanguage)
.onErrorResumeNext(new Func1<Throwable, Observable<? extends ArrayList<Repo>>>() {
@Override
public Observable<? extends ArrayList<Repo>> call(Throwable throwable) {
getMvpView().showError(throwable);
AppLog.d("searchMostStarredRepo onErrorResumeNext:" + throwable);
return Observable.empty();
}
})
.doOnSubscribe(new Action0() {
@Override
public void call() {
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
getMvpView().showLoading();
}
});
}
})
.doOnTerminate(new Action0() {
@Override
public void call() {
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
getMvpView().dismissLoading();
}
});
}
});
}
})
.observeOn(AndroidSchedulers.mainThread())
.onErrorResumeNext(new Func1<Throwable, Observable<? extends ArrayList<Repo>>>() {
@Override
public Observable<? extends ArrayList<Repo>> call(Throwable throwable) {
AppLog.d("onErrorResumeNext:" + throwable);
getMvpView().showError(throwable);
return Observable.empty();
}
})
.subscribe(new ResponseObserver<ArrayList<Repo>>() {
@Override
public void onSuccess(ArrayList<Repo> repos) {
getMvpView().showContent(repos);
}
@Override
public void onError(Throwable e) {
AppLog.d("onError:" + e);
getMvpView().showError(e);
}
}));
}
看起來好長, 實(shí)際就幾步:
1, 字符串長度過濾.
2, debounce操作, 即僅在過了一段指定的時(shí)間還沒發(fā)射數(shù)據(jù)時(shí)才發(fā)射一個(gè)數(shù)據(jù)
3, 與查詢接口做switchMap操作.
7, 結(jié)語
至此, 一個(gè)基于Dagger2, RxJava, Retrofit, OkHttp等library的MVP架構(gòu)的實(shí)現(xiàn)已經(jīng)完成. 這幾篇文章只是提供給大家一個(gè)架構(gòu)的方向, 歡迎大家fork.
另外GithubApp這個(gè)工程還會(huì)持續(xù)更新, 包括功能上和架構(gòu), 以及一些新的主流庫的使用也會(huì)在這個(gè)工程上體現(xiàn), 歡迎關(guān)注star.