Github
项目的起因
Github上一个看起来很漂亮的Github客户端
成功引起了我的注意
但是,它不开源
作者在贴了一堆截图后留下了自己的商务合作邮箱
我不开心所以也想做一个
本文的目的
- 如果你是青铜玩家:希望本文提供的一些资料(如API,架构,三方,功能实现思路等)可以给你一些参考价值
- 如果你是王者玩家:欢迎指出问题和建议,尤其是优化和架构方面
架构和三方
基于 google-android-architecture-mvp-rxjava 的RxJava + Retrofit + Mvp架构,该架构会在后面详细讲解,三方库如下
Rx
快速开发工具
- butterknife 大名鼎鼎的黄油刀,让你不再findById
- android_dbinspector 不需要root就可以查看真机上数据库内容
- fastjson 最快的json解析工具,阿里巴巴出品
网络相关
- Retrofit 新一代网络请求神器
- OkHttp logging interceptor 请求日志拦截器
图片加载
- Glide 图片加载框架
UI
- MaterialSearchView Material Design风格的搜索
- CircleImageview 圆形头像
- BaseRecyclerViewAdapterHelper 强大的RecyclerView万能适配器
- FloatingActionButton Material Design风格的浮动按钮
- spots-dialog 闪烁的loding进度条
- materialish-progress 旋转的菊花圈
- MarkdownView 将markdown格式的字符串显示成漂亮的html页面
- WaitingDots 闪烁的loding动画
- CodeView 将代码显示成漂亮的样式
- material-dialogs Material风格的Dialog
功能介绍
Explore
- 浏览Repository和User,使用选项卡切换,并且将浏览过的数据缓存在本地(本应用所有数据都做了缓存,并且缓存时间可由用户定制)
- 支持关键字搜索Repository和User,可以选择排序方式(Most star,Best match,Most fork,Rencent update等),可以按标签换语言分类(排序方式和标签可用用户定制)
RepositoryDetail
- Repository简要信息查看,并且可以进行star,unstar,fork操作
- 异步加载ReadMe,按markdown格式显示ReadMe
- 显示code树,查看代码内容,因为缓存的原因,点击加载过的节点可以秒加载
UserDetail
- User简要信息查看,并且可以进行follow,unfollow操作
RepositoryList
- 查询自己的Repository
- 查询自己Star过的Repository
- 查询RepositoryDetail被fork过的Repository
- 查询UserDetail被拥有的Repository
UserList
- 查询自己,以及其他User的following和follower
- 查询RepositoryDetail的Contributors和Stargazers
Event
- 可以查询,自己,User,Repository的Event
Setting
- 设置Explore页面首先查询的语言,右下方应该有几个语言选项,默认的排序方式
架构分析
MVP
谷歌去年在github上发布一整套的它推荐的Android架构Demo,todo-mvp-rxjava 是之中用来示范rxjava的sample
关于它的这套架构,我画了一个栩栩如生的草图,嗯,栩栩如生
是不是已经被我的美术功底震惊的说不出话来,就冲这图你不给Star一个?
只看图可能容易蒙蔽,用代码来解释一下
先看接口类
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的实现类,持有了view的对象和repository的对象,分别用来加载数据和展现数据,这里偷懒了没有切换线程,后面用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的实现类,也就是Activity,持有一个Presenter的对象,并且在创建该对象的时候将自身和数据仓库类传了过去
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());//得到数据仓库
mPresenter = new PrensenterImpl(this, repository); //将自身和数据仓库类传了过去
mPresenter.loadList();
}
public void setPresenter(Contract.Presenter presenter) {
mPresenter = presenter;
}
@Override
public void showLoading() {
// 展示进度条
}
@Override
public void hideLoading() {
// 加载成功隐藏进度条
}
@Override
public void showError() {
// 加载失败
}
@Override
public void showEmpty() {
// 数据是空的
}
@Override
public void showList(List list) {
// 加载成功并且有数据耶,展示起来
}
}
最后数据仓库先这么简单的写,后面再补充
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();//从网络加载数据
}
}
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();
// ....让两个数据源同时去加载数据,谁先加载完成就返回谁的
return null;
}
}
所以整个MVP的请求逻辑如下
- 用户到达View后开始请求数据
- MainActivity将请求委托给Presenter去处理
- Presenter通过Repository去请求数据,根据结果的不同分发回View
从来实现了数据的展示,请求,分发三层分离,时序图如下图:
RxJava
上一节中有两个地方是十足的伪代码,mPresenter.loadList和Repository的具体实现,使用RxJava可以很容易的完成这两个部分的实现
对RxJava还没有概念的请看 给Android 开发者的 RxJava 详解
这里直接展示用法
添加依赖
compile 'io.reactivex:rxjava:1.0.8'
compile 'io.reactivex:rxandroid:1.2.1'
- 使用RxJava将两个数据源的结果合并返回,返回类型改成了Observable,使用了concat操作符来合并两个数据源,使用first操作符来返回第一个结果
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;//从网络加载数据
}
}
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();//让本地缓存先读取,网络拉去后执行,谁先拿到数据就返回谁
}
}
- 使用RxJava实现mPresenter.loadList中的跳转逻辑,避免了使用八百个回调来切换线程
public class PrensenterImpl implements Contract.Presenter {
...
@Override
public void loadList() {
//UI 线程
mView.showLoading();
mRepository.loadList()
.subscribeOn(Schedulers.io()) //指定上游在IO线程执行
.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);
}
}
});
}
}