Github
項(xiàng)目的起因
Github上一個(gè)看起來很漂亮的Github客戶端
成功引起了我的注意
但是,它不開源
作者在貼了一堆截圖后留下了自己的商務(wù)合作郵箱
我不開心所以也想做一個(gè)
本文的目的
- 如果你是青銅玩家:希望本文提供的一些資料(如API,架構(gòu),三方,功能實(shí)現(xiàn)思路等)可以給你一些參考價(jià)值
- 如果你是王者玩家:歡迎指出問題和建議,尤其是優(yōu)化和架構(gòu)方面
架構(gòu)和三方
基于 google-android-architecture-mvp-rxjava 的RxJava + Retrofit + Mvp架構(gòu),該架構(gòu)會(huì)在后面詳細(xì)講解,三方庫如下
Rx
快速開發(fā)工具
- butterknife 大名鼎鼎的黃油刀,讓你不再findById
- android_dbinspector 不需要root就可以查看真機(jī)上數(shù)據(jù)庫內(nèi)容
- fastjson 最快的json解析工具,阿里巴巴出品
網(wǎng)絡(luò)相關(guān)
- Retrofit 新一代網(wǎng)絡(luò)請求神器
- OkHttp logging interceptor 請求日志攔截器
圖片加載
- Glide 圖片加載框架
UI
- MaterialSearchView Material Design風(fēng)格的搜索
- CircleImageview 圓形頭像
- BaseRecyclerViewAdapterHelper 強(qiáng)大的RecyclerView萬能適配器
- FloatingActionButton Material Design風(fēng)格的浮動(dòng)按鈕
- spots-dialog 閃爍的loding進(jìn)度條
- materialish-progress 旋轉(zhuǎn)的菊花圈
- MarkdownView 將markdown格式的字符串顯示成漂亮的html頁面
- WaitingDots 閃爍的loding動(dòng)畫
- CodeView 將代碼顯示成漂亮的樣式
- material-dialogs Material風(fēng)格的Dialog
功能介紹
Explore
- 瀏覽Repository和User,使用選項(xiàng)卡切換,并且將瀏覽過的數(shù)據(jù)緩存在本地(本應(yīng)用所有數(shù)據(jù)都做了緩存,并且緩存時(shí)間可由用戶定制)
- 支持關(guān)鍵字搜索Repository和User,可以選擇排序方式(Most star,Best match,Most fork,Rencent update等),可以按標(biāo)簽換語言分類(排序方式和標(biāo)簽可用用戶定制)
RepositoryDetail
- Repository簡要信息查看,并且可以進(jìn)行star,unstar,fork操作
- 異步加載ReadMe,按markdown格式顯示ReadMe
- 顯示code樹,查看代碼內(nèi)容,因?yàn)榫彺娴脑颍c(diǎn)擊加載過的節(jié)點(diǎn)可以秒加載
UserDetail
- User簡要信息查看,并且可以進(jìn)行follow,unfollow操作
RepositoryList
- 查詢自己的Repository
- 查詢自己Star過的Repository
- 查詢RepositoryDetail被fork過的Repository
- 查詢UserDetail被擁有的Repository
UserList
- 查詢自己,以及其他User的following和follower
- 查詢RepositoryDetail的Contributors和Stargazers
Event
- 可以查詢,自己,User,Repository的Event
Setting
- 設(shè)置Explore頁面首先查詢的語言,右下方應(yīng)該有幾個(gè)語言選項(xiàng),默認(rèn)的排序方式
架構(gòu)分析
MVP
谷歌去年在github上發(fā)布一整套的它推薦的Android架構(gòu)Demo,todo-mvp-rxjava 是之中用來示范rxjava的sample
關(guān)于它的這套架構(gòu),我畫了一個(gè)栩栩如生的草圖,嗯,栩栩如生
是不是已經(jīng)被我的美術(shù)功底震驚的說不出話來,就沖這圖你不給Star一個(gè)?

只看圖可能容易蒙蔽,用代碼來解釋一下
先看接口類
public interface Contract {
interface View {
void showLoading();
void hideLoading();
void showError();
void showEmpty();
void showList(List list);
void setPresenter(Contract.Presenter presenter);
}
interface Presenter {
void loadList();
}
}
然后是Presenter的實(shí)現(xiàn)類,持有了view的對象和repository的對象,分別用來加載數(shù)據(jù)和展現(xiàn)數(shù)據(jù),這里偷懶了沒有切換線程,后面用RxJava完成
public class PrensenterImpl implements Contract.Presenter {
private Contract.View mView;
private Repository mRepository;
public PrensenterImpl(Contract.View view, Repository repository) {
mView = view;
mRepository = repository;
mView.setPresenter(this);
}
@Override
public void loadList() {
//UI 線程
mView.showLoading();
try{
//IO 線程
List list = mRepository.loadList();
//切回UI 線程
if (list.isEmpty()){
mView.showEmpty();
}else {
mView.showList(list);
}
}catch (Exception e){
mView.showError();
}finally {
mView.hideLoading();
}
}
再看View的實(shí)現(xiàn)類,也就是Activity,持有一個(gè)Presenter的對象,并且在創(chuàng)建該對象的時(shí)候?qū)⒆陨砗蛿?shù)據(jù)倉庫類傳了過去
public class MainActivity extends AppCompatActivity implements Contract.View {
private Contract.Presenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Repository repository = new Repository(new RemoteDataSource(), new LocalDataSource());//得到數(shù)據(jù)倉庫
mPresenter = new PrensenterImpl(this, repository); //將自身和數(shù)據(jù)倉庫類傳了過去
mPresenter.loadList();
}
public void setPresenter(Contract.Presenter presenter) {
mPresenter = presenter;
}
@Override
public void showLoading() {
// 展示進(jìn)度條
}
@Override
public void hideLoading() {
// 加載成功隱藏進(jìn)度條
}
@Override
public void showError() {
// 加載失敗
}
@Override
public void showEmpty() {
// 數(shù)據(jù)是空的
}
@Override
public void showList(List list) {
// 加載成功并且有數(shù)據(jù)耶,展示起來
}
}
最后數(shù)據(jù)倉庫先這么簡單的寫,后面再補(bǔ)充
public interface DataSource {
List loadList();
}
public class LocalDataSource implements DataSource{
@Override
public List loadList() {
return new ArrayList();//從本地讀取緩存
}
}
public class RemoteDataSource implements DataSource{
@Override
public List loadList() {
return new ArrayList();//從網(wǎng)絡(luò)加載數(shù)據(jù)
}
}
public class Repository implements DataSource{
private DataSource mRemoteDataSource;
private DataSource mLocalDataSource;
public Repository(DataSource remoteDataSource,DataSource lemoteDataSource){
mRemoteDataSource = remoteDataSource;
mLocalDataSource = lemoteDataSource;
}
@Override
public List loadList() {
List remote = mRemoteDataSource.loadList();
List local = mLocalDataSource.loadList();
// ....讓兩個(gè)數(shù)據(jù)源同時(shí)去加載數(shù)據(jù),誰先加載完成就返回誰的
return null;
}
}
所以整個(gè)MVP的請求邏輯如下
- 用戶到達(dá)View后開始請求數(shù)據(jù)
- MainActivity將請求委托給Presenter去處理
- Presenter通過Repository去請求數(shù)據(jù),根據(jù)結(jié)果的不同分發(fā)回View
從來實(shí)現(xiàn)了數(shù)據(jù)的展示,請求,分發(fā)三層分離,時(shí)序圖如下圖:
RxJava
上一節(jié)中有兩個(gè)地方是十足的偽代碼,mPresenter.loadList和Repository的具體實(shí)現(xiàn),使用RxJava可以很容易的完成這兩個(gè)部分的實(shí)現(xiàn)
對RxJava還沒有概念的請看 給Android 開發(fā)者的 RxJava 詳解
這里直接展示用法
添加依賴
compile 'io.reactivex:rxjava:1.0.8'
compile 'io.reactivex:rxandroid:1.2.1'
- 使用RxJava將兩個(gè)數(shù)據(jù)源的結(jié)果合并返回,返回類型改成了Observable,使用了concat操作符來合并兩個(gè)數(shù)據(jù)源,使用first操作符來返回第一個(gè)結(jié)果
public interface DataSource {
Observable loadList();
}
public class LocalDataSource implements DataSource {
@Override
public Observable loadList() {
return Observable.create(new Observable.OnSubscribe<List>() {
@Override
public void call(Subscriber<? super List> subscriber) {
List list = new ArrayList();//從本地讀取的緩存
subscriber.onNext(list);
}
});//從本地讀取緩存
}
}
public class RemoteDataSource implements DataSource{
@Override
public Observable loadList() {
return null;//從網(wǎng)絡(luò)加載數(shù)據(jù)
}
}
public class Repository implements DataSource{
private DataSource mRemoteDataSource;
private DataSource mLocalDataSource;
public Repository(DataSource remoteDataSource,DataSource lemoteDataSource){
mRemoteDataSource = remoteDataSource;
mLocalDataSource = lemoteDataSource;
}
@Override
public Observable loadList() {
Observable localTask = mLocalDataSource.loadList();
Observable remoteTask = mRemoteDataSource.loadList();
return Observable.concat(localTask, remoteTask).first();//讓本地緩存先讀取,網(wǎng)絡(luò)拉去后執(zhí)行,誰先拿到數(shù)據(jù)就返回誰
}
}
- 使用RxJava實(shí)現(xiàn)mPresenter.loadList中的跳轉(zhuǎn)邏輯,避免了使用八百個(gè)回調(diào)來切換線程
public class PrensenterImpl implements Contract.Presenter {
...
@Override
public void loadList() {
//UI 線程
mView.showLoading();
mRepository.loadList()
.subscribeOn(Schedulers.io()) //指定上游在IO線程執(zhí)行
.observeOn(AndroidSchedulers.mainThread()) //指定下游在UI線程
.subscribe(new Observer<List>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
mView.hideLoading();
mView.showError();
}
@Override
public void onNext(List list) {
mView.hideLoading();
if (list.isEmpty()) {
mView.showEmpty();
} else {
mView.showList(list);
}
}
});
}
}