如果你对开源项目分析感兴趣,欢迎加入我们的android-open-source-project-cracking
原创地址:http://www.jianshu.com/p/47e72693a302

零、背景

比起阅读枯燥的技术文档,独自苦苦摸索新技术的基本用法,还有一种更好更快速也更有效的提高自身技术的方法,那就是阅读学习优质的开源项目,通过仿写、练习最终达到理解,潜移默化提升自身编程技能。

《带你学开源项目》系列将带领你深入阅读及分析当前流行的一些开源项目,并针对其中采用的新技术与精妙之处进行细致的阐述,以期让你快速掌握Android开发中的多种强大技能点。

一、本期开源项目Meizhi Android

本次的开源项目选择了Meizhi Android,本文主要介绍该项目中采用的RxJava、Retrofit两种技术,这二者在Android开发者中非常流行,不仅能够优美地处理异步回调,而且能提高代码的性能和稳定性。而Meizhi Android中较好的覆盖了二者的多种应用场景,能够给多数开发者一个全面的学习。

下面本人会对原项目的代码进行详细的介绍,同时为了读者看的清楚其中的逻辑关系,可能会做一定调整以帮助读者理解,比如把lambda表达式还原成普通java函数形式,以避免很多读者对lambda并不熟悉。

二、原项目分析

  1. clone项目到本地
    第一步当然是把项目clone下来,编译,运行。有兴趣的同学可以执行这一步。

  2. 添加Stetho抓包工具
    首先,由于我们要分析retrofit,所以为了查看app的网络请求,有兴趣的同学可以手动在代码里添加Stetho。Stetho是Facebook推出的一款黑科技,能够在chrome里轻松查看app所有的网络请求,比起iOS需要装个Charles查看http请求方便多咯。

  3. Retrofit结构
    从下图我们可以看到,首页里有很多card,每一个card里有两个元素:妹纸图片, 描述文字,具体UI实现我们不在乎,只要明白一点,这两个元素数据是来自于两个不同的api。其中,妹纸图片来自于http://gank.io/api/data/福利/10;描述文字来自于http://gank.io/api/data/休息视频/10。

app中为了请求网络数据,采用了Retrofit。具体关于retrofit如何配置请各位参考官网,这里只讲解如何使用Retrofit。

该项目中主要创建了以下几个类来实现Retrofit结构,大家可以作为参考用于自己的项目中。

i. GankApi:这个类用来定义相关的http接口,这是符合retrofit规范的定义形式,每一个api返回的为Observable格式结果,方便RxJava进行进一步处理。

@GET("/data/福利/{page}") Observable<MeizhiList> getMeizhiList(@Path("page") int page);
@GET("/data/休息视频/{page}") Observable<GankVideoList> getGankVideoList(@Path("page") int page);

ii. DrakeetRetrofit:这个类用来对Retrofit进行相关配置并生成GankApi实例gankApi

OkHttpClient client = new OkHttpClient();
RestAdapter.Builder builder = new RestAdapter.Builder();
builder.setClient(new OkClient(client)).setLogLevel(RestAdapter.LogLevel.FULL) .setEndpoint("http://gank.io/api").setConverter(new GsonConverter(gson));
RestAdapter gankRestAdapter = builder.build();
GankApi gankApi = gankRestAdapter.create(GankApi.class);public GankApi getGankApi() {    return gankApi;
}

iii. DrakeetFactory: 这个类用来对外生成单例GankApi实例,为确保GankApi实例只生成一次。

public static GankApi getGankApi() {    synchronized (monitor) {        if (sGankApi == null) {            sGankApi = new DrakeetRetrofit().getGankApi();        }       return sGankApi;    }
}

所以,在实际应用场景中,比如我们想要发起一个http请求来获取福利数据,那么我们可以采用以下方式:

GankApi gankApi = DrakeetFactory.getGankApi();
Observable<MeizhiList> meizhiList = gankApi. getMeizhiList(10);

  1. 首页的RxJava的实现
    既然我们已经把网络框架搭建好了,那么可以开始从服务器获取数据并显示了。我们首先看首页的数据。下面,我来对首页数据进行分析,一步步推出所需要的RxJava表达式。

上面已经介绍过,每一个card里有两部分数据:妹纸图片(红色方框)和描述文本(绿色方框)。

  • 妹纸图片数据来自于”/data/福利/{page}”这个api,该api会返回妹纸图片的url;
  • 描述文本来自于”/data/休息视频/{page}”这个api,该api会返回休息视频及相关描述信息,card里会把描述信息显示出来;
  • 两个api均可以携带page字段,即一次请求可以获得多个数据。如我们在”/data/福利/{page}”里设置page=10,那么我们一次请求可以得到10条福利数据,即10张妹纸图片url;
  • 由于我们一次可以获得多张妹纸图片url和多个视频信息,那我们就需要把二者进行合并,即单拎出来一张妹纸图片和一个视频信息组装成一个card。然后按这种方式生成其他的card。

小结一下,根据以上描述,假如我们把两个api的page都设置为10,那么两个请求同时发出去后,我们能得到10张妹纸图片url(如http://img.com/1.png, http://img.com/2.png, …)和10个视频信息(如舌尖上的中国, 星际穿越, …),然后我们将二者组装成10个card所需要的数据,放入每个card里显示即可。

好,终于可以开始动手写代码了。上面的分析看似复杂,然后只要你学会了如何分析,很快就能写出对应的RxJava代码。下面我结合RxJava的数据流思想和具体操作符来介绍实现代码。

i. 在网络请求数据之前,我们要创建几个数据entry对象来将获取回来的json字符串转化为object

public class Meizhi {public String url;public Date publishDate;
} //这是一个Meizhi对象,存储妹纸图片的url,图片描述信息和创建日期public class Video {public String desc;public Date publishDate;
} //这是一个视频对象,存储视频描述信息和创建日期public class MeizhiList {public List<Meizhi> meizhiList;
} //由于我们一次请求能获取到10个(根据`page`设置),所以我们用MeizhiList来存储结果public class VideoList {public List<Video> videoList;
} //原理同上,存储多个video对象public class MeizhiWithVideo {public String url;public String desc;public Date publishDate;
}//将video信息合并入meizhi对象中public class MeizhiWithVideoList {public List<MeizhiWithVideoList> data;
}

ii. zip: 将两个retrofit接口请求后得到的两个数据源Observable Observable进行合并
我们需要把这两个数据源的数据拼接起来,所以我们可以考虑使用zip操作符,该操作符可以将两个数据源发射出来的数据依次组装在一起。

比如一个Observable数据源依次发射出1, 3, 5, 7, 另一个Observable数据源依次发射出a, b, c, d,那么zip操作符组装后会对外发射出1a, 3b, 5c, 7d这样的数据。

而我们需要的正是这样。

Observable一次对外发射一个MeizhiList对象,Observable一次对外发射一个VideoList对象,我们将二者合并成一个MeizhiWithVideoList对象。然后把MeizhiWithVideoList对象拿给UI去进行显示即可。

所以,我们可以得到:

Observable<MeizhiList> meizhiListObservable = gankApi.getMeizhiList(10);
Observable<VideoList> videoListObservable = gankApi.getVideoList(10);
Observable<MeizhiWithVideoList> meizhiWithVideoListObservable =
Observable.zip(meizhiListObservable, videoListObservable, this::mergeVideoWithMeizhi)

其中mergeVideoWithMeizhi是一个合并函数,把video信息与meizhi信息合并成新的MeizhiWithVideo对象。

public MeizhiWithVideoList
mergeVideoWithMeizhi(MeizhiList meizhiList, VideoList videoList) {//省略...}

iii. 对MeizhiWithVideo对象进行排序。
在上面,我们通过合并,得到了 Observable数据源,这个数据源对外发射出一个MeizhiWithVideoList对象,这个对象里有10个MeizhiWithVideo数据,我们可以对这10个数据利用它们的发布日期进行排序。

所以我们要实现以下几步:

  • 先把Observable<MeizhiWithVideoList>数据源转化为Observable< List<
    MeizhiWithVideo>>,从对外发一个MeizhiWithVideoList对象变成对外发射一个List< MeizhiWithVideo>对象;
  • 再把Observable< List< MeizhiWithVideo>>转化为Observable< MeizhiWithVideo>数据源,变成了对外发射出10个MeizhiWithVideo对象;
  • 对这10个MeizhiWithVideo对象基于publishDate进行排序;
  • 其中比较操作很耗cpu,所以我们放在Schedulers.computation()线程中做

代码实现:

meizhiWithVideoListObservable.map(new Func1<MeizhiWithVideoList, List<MeizhiWithVideo>>() {    @Override    public List<Meizhi> call(MeizhiList meizhiList) {                return MeizhiWithVideoList.data;    }
})
.flatMap(new Func1<List<MeizhiWithVideo>, Observable<MeizhiWithVideo>>() {    @Override    public Observable<MeizhiWithVideo> call(List<MeizhiWithVideo> meizhiWithVideos) {        return Observable.from(meizhiWithVideos);    }
})
.toSortedList(new Func2<MeizhiWithVideo, MeizhiWithVideo, Integer>() {    @Override    public Integer call(MeizhiWithVideo meizhiWithVideo1, MeizhiWithVideo meizhiWithVideo2) {        return meizhiWithVideo2.publishedAt.compareTo(meizhiWithVideo1.publishedAt);    }
})
.subscribeOn(Schedulers.computation());

iv. 排序后,我们得到Observable< List>数据源,传给adapter去更新UI
上面的toSortedList(xxx)方法会把Observable< MeizhiWithVideo>排序后重新组装成Observable< List< MeizhiWithVideo>>对象sortedMVListObservable,该对象对外发射一个有序的List< MeizhiWithVideo>。我们将该数据源提供给adapter供显示。

代码如下:

sortedMVListObservable.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<MeizhiWithVideo>>() {    @Override    public void onCompleted() {            setRefresh(false); // stop refreshing data.                 }    @Override    public void onError(Throwable e) {    }@Override    public void onNext(List<MeizhiWithVideo> meizhiWithVideoList) {    adapter.setData(meizhiWithVideoList);adapter.notifyDataSetChanged(); // update UI}
})
  1. 利用Subscription来管理异步处理与Activity生命周期
    对于异步我们知道一直存在一个问题,假设一个页面要同时发出很多个http请求,如http1, http2, http3…,然后这些请求会被放在一个队列里依次发出,而且每个请求发出后需要等待一段时间才能得到返回数据。

那么问题就来了,假设在A页面发出了多个网络请求,在这些网络请求还在等待响应时用户就跳转到了B页面,在以前的情况下是,A页面的网络请求仍然进行直到所有数据返回,而且当数据返回时会尝试去调用A页面的UI进行修改,而此时已经进入了B页面,所以,这不仅造成了网络资源的浪费,也存在一定的风险。

有了RxJava,我们可以把每一个网络请求转化为一个Subscription对象,这个Subscription对象可以被手动unsubscribe,即停止订阅所请求的数据源,这样就可以暂定数据请求,而且即使数据返回回来,由于我已经取消订阅了,所以不会再接收到这些数据了。

代码实现:
在BaseActivity中,创建一个CompositeSubscription对象来进行管理

`BaseActivity`
private CompositeSubscription mCompositeSubscription;
protected void addSubscription(Subscription s) {   if (this.mCompositeSubscription == null) {                this.mCompositeSubscription = new CompositeSubscription();    }    this.mCompositeSubscription.add(s);
}@Override
protected void onDestroy() {    super.onDestroy();    if (this.mCompositeSubscription != null) {                this.mCompositeSubscription.unsubscribe();    }
}

在实际的Activity中的网络请求:

public class MyActivity extends BaseActivity {private void loadData() {Subscription s = gankApi.getMeizhiList(10)                           .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(...);addSubscription(s);}
}
三、改进及总结

本文通过对开源项目Meizhi Android进行分析,了解了Retrofit,RxJava的实际应用场景,也对于二者有了更加深入的认识。

不过本人认为该项目还有一些可以改善的地方,比如Retrofit中利用DrakeetFactory工厂来生成GankApi的单例,但是new DrakeetRetrofit().getGankApi();也是一个可以生成GankApi的方法,而且是public的,那么如果新的开发者忘记调用DrakeetFactory来生成GankApi的实例,而是采用后者,那么工厂模式就达不到预期的目的了。我认为可以把new DrakeetRetrofit().getGankApi();这个操作内容放在DrakeetFactory工厂内部,并且设置为private属性,这样的话如果想要获得GankApi实例,就必须依靠DrakeetFactory来生成,从而真正保证了单例的优势。

带你学开源项目:Meizhi Android之RxJava Retrofit最佳实践相关推荐

  1. 带你学开源项目:RxLifecycle-当Activity被destory时自动暂停网络请求

    版权声明:本文原创发布于公众号 wingjay,转载请务必注明出处! https://www.jianshu.com/p/62cc6d692af0 大幅提高自身技术实力最有效的途径之一就是学习世界级优 ...

  2. Android开发---RxJava+Retrofit封装

    本文内容是基于Retrofit + RxJava做的一些巧妙的封装.参考了很多文章加入了一些自己的理解,请多指教.源码地址https://github.com/Hemumu/RxSample 先放出b ...

  3. 开源,才是科技巨头的最佳实践!| 技术头条

    开源,曾经有多少企业避之不及,此后就有多少公司奋勇拥抱.经过这么多年的发展,开源现状究竟如何了?对于科技巨头而言,在拥抱开源之后,他们又有了哪些不同?接下来,本文将通过New Stack和Linux基 ...

  4. Android应用性能优化最佳实践.

    移动开发 Android应用性能优化最佳实践 罗彧成 著 图书在版编目(CIP)数据 Android应用性能优化最佳实践 / 罗彧成著. -北京:机械工业出版社,2017.1 (移动开发) ISBN ...

  5. 熔断降级与限流在开源SpringBoot/SpringCloud微服务框架的最佳实践

    目录导读 熔断降级与限流在开源SpringBoot/SpringCloud微服务框架的最佳实践 1. 开源代码整体架构设计 2. 微服务逻辑架构设计 3. 微服务熔断降级与限流规划 3.1 微服务熔断 ...

  6. 使用ConnectBot开源项目在android设备上管理你的linux系统

    最近,工作中有使用android通过ssh管理linux系统的需求,找到了这个ConnectBot这个开源项目https://github.com/connectbot/connectbot.http ...

  7. 提升Android开发效率的最佳实践

    本文属于Android入门与最佳实践系列,有兴趣的可以围观笔者的前一篇关于Android实践建议的文章:2016里一些Android最佳实践列表--Opinionated 原文地址 软件工程师的工作效 ...

  8. 基于lis3dh的简易倾角仪c源码_开源网关apisix源码阅读和最佳实践

    大家应该都接手过这种项目,前人找一个开源软件改一改,发上线. 我这里便曾经遇到过类似的问题. 随着需求的增加,各种维护人员东改改西改改,原来的开源项目被改的面目全非,再也无法和上游合并. 甚至TLS协 ...

  9. 360开源的类Redis存储系统Pika的最佳实践

    360DBA及基础架构团队根据360内部的Pika使用经验及社区用户的问题反馈,整理了以下这些Pika的最佳实践分享给大家. Pika最佳实践之一 在群里提问主动带上版本号能大幅度加快问题解决速度.( ...

最新文章

  1. android 代码设置inputtype,android – 如何正确设置EditText的InputType?
  2. java报错误设置属性值_java – 设置属性值时出错;嵌套异常是org.springframework.beans.NotWritablePropertyException:...
  3. boost::detail::reference_content的用法测试程序
  4. ZooKeeper3.4.5-最基本API开发
  5. play 应用商店_如何在Play商店中发布应用程序
  6. oracle使一个字段自增,Oracle数据库实现一个字段自增
  7. iOS: 彻底理解position与anchorPoint
  8. LeetCode 74. Search a 2D Matrix
  9. Android Design与Holo Theme详解
  10. 做开发十年,我总结出了这些开发经验
  11. mysql datasource property_spring配置datasource三种方式
  12. SAP 的总账和明细账
  13. SSM6==spring体系回顾,纯XML配置springMVC,纯注解配置springMVC
  14. 1卡路里(kcal)=4.184千焦(kJ)
  15. 【解决】SX1308无法升压、升压后接上负载电压就被拉低解决办法
  16. 闭环系统的零极点图判定稳定性_《自动控制原理》课后习题答案.doc
  17. 编译原理:LL(1)文法的First集跟Follow集
  18. 【监控利器Prometheus】——Prometheus+Grafana监控服务器资源
  19. 大智慧L2实时api接口的逐笔委托功能执行代码分享
  20. vue引入echarts中国地图

热门文章

  1. JPA EntityManager 获取关联对象
  2. GoEasy,适用于多种前端框架,即时通讯技术
  3. uml学习过程6-其他不能理解的(弱化,过于专业)
  4. 【考研数据结构题型分类讲解练习】6-3 排序--应用题、实做题
  5. 自己写富文本编辑器jss_JSS选择器和语法规则
  6. 数据库存储图片路径并显示到前端
  7. 为什么大多数知名互联网公司都加班
  8. 3月“ChatGPT”相关热门论文-AMiner
  9. ICML2018论文研讨会记录
  10. supervised——>self-supervised