上一篇博客智能厨房重构-MVP架构中我们简单谈了一下MVP的使用方法,使用MVP接口的方式让我们的代码已经很简单明了了,但是我告诉你还不够,使用了本篇的Retrofit和RxJava后让你的代码美如画。对,就是美如画。
1.Retrofit的简单介绍
Retrofit是square公司推出的一种使用接口方式进行网络请求的网络请求库,里面实际上用的还是okhttp的网络请求模块,不过他进行了封装,使用起来更加方便。
1.1 环境配置
compile 'io.reactivex:rxjava:1.0.14'
compile 'io.reactivex:rxandroid:1.0.1'
compile 'com.squareup.retrofit2:retrofit:2.0.0'
compile 'com.squareup.retrofit2:converter-gson:2.0.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0'
compile 'com.google.code.gson:gson:2.4'
compile 'com.squareup.okhttp3:okhttp:3.3.1'
compile 'com.squareup.retrofit2:converter-scalars:2.0.0'
首先将上面的依赖库添加到你的appgradle中,上面还有RxJava我一并给出了。
1.2 基本语法
首先我们看一下Retrofit的配置,它使用了Builder建造者模式来进行配置自己。
/**
* 作者:GXL on 2016/8/3 0003
* 博客: http://blog.csdn.net/u014316462
*/
public class RetrofitWrapper {
public static RetrofitWrapper instance;
public static Retrofit retrofit;
private RetrofitWrapper() {
retrofit = new Retrofit.Builder()
.baseUrl(Constant.BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
public static RetrofitWrapper getInstance() {
if (instance == null) {
synchronized (RetrofitWrapper.class) {
instance = new RetrofitWrapper();
}
}
return instance;
}
public <T> T create(Class<T> service) {
return retrofit.create(service);
}
public class Constant {
public static final String BASE_URL = "http://www.meishij.net/";
}
}
上面是我对Retrofit配置的一个简单包裹,我们看一下需要配置的东西:
(1) baseUrl 这个使我们需要访问的网络地址
(2) addConverterFactory()这个参数特别重要,在这里你要选择你需要的转化器类型。square给我们提供了下面这几种
Gson :用来解析Json格式的数据。假如你请求的数据是Json的,就用这个。
scalars:用来解析字符串格式的数据,假如你请求的数据是字符串的话,就用这个。
别的我目前还没用过,上面这两个实际使用的比较多,你选择哪一个将上面哪一个的依赖添加到gradle里面啊。
(3) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 这个一看就是知道使用了RxJava作为我们的回调库,RxJava就是将你从回调中解放出来的神器。
重要的配置就是这么多了。现在我们来看一下接口请求的地方的参数。
/**
* 作者:GXL on 2016/8/3 0003
* 博客: http://blog.csdn.net/u014316462
* 作用:该接口用来请求美食杰网页数据
*/
public interface FoodService {
/**
*
* @param sortby 最新update和最热renqi两种
* @param lm 食品的种类
* @param page 当前的页码
* @return 当前html页面
*/
@GET("list.php")
Observable<String> getFoodList(@Query("sortby") String sortby, @Query("lm") int lm, @Query("page") int page);
/**
*
* @param foodname 食品名
* @return 当前html页面
*/
@GET("zuofa/{foodname}.html")
Observable<String> getDetailFood(@Path("foodname") String foodname);
/**
* 获取滚动展示的集合,假如参数为空,要在里面加一个空格
* @return 当前html页面
*/
@GET(" ")
Observable<String> getSliderShowFood();
}
是不是很简捷啊,一个函数代表一种网络请求,还是来看一下里面的参数:
(1) @GET 这个对应我们网络请求中的GET请求,里面的参数就是网络请求URL剩下的一部分,BASE_URL+list.php=http://www.meishij.net/list.php,这才是我们最终的请求网址。
假如BASE_URL就是最后的请求地址那就是直接在里面打一个空格。
再来看一下里面的参数可以有哪些配置
@Path
@GET("zuofa/{foodname}.html")
Observable<String> getDetailFood(@Path("foodname") String foodname);
@Path variable substitution for the API endpoint (i.e. username will be swapped for {username} in the URL endpoint).
这是官网的描述,个人理解就是替代块,在URL中用{}包含一个参数名未知需要在实际使用中传入,注意名称相同。
@Query
@GET("list.php")
Observable<String> getFoodList(@Query("sortby") String sortby, @Query("lm") int lm, @Query("page") int page);
@Query specifies the query key name with the value corresponding to the value of that annotated parameter.
这个对应着get请求后面的请求参数,上面请求代码就对应着下面的URL,http://www.meishij.net/list.php?sortby=update&lm=369&page=1。
(2) @POST 这个对应我们网络请求中的POST请求,Post请求相对于get请求来说更加安全,因为他的参数不会暴露在url中,并且post也没有url长度的限制。可以查看这篇博文http://blog.chinaunix.net/uid-25311424-id-3959591.html
@Field
@FormUrlEncoded
@POST("/newfind/index_ask")
Observable<Response> getDaJia(@Field("page") int page,
@Field("pageSize") int size,
@Field("tokenMark") long tokenMark,
@Field("token") String token
);
比如下面这一种,都将参数存放到了field里面,用键值对形式去请求数据。
FieldMap
@FormUrlEncoded
@POST("FundPaperTrade/AppUserLogin")
Observable<Response> getTransData(@FieldMap Map<String,String> map);
假如数据过多,可以使用键值对表的形式。
@FormUrlEncoded
注明是表单提交,使用field就需要使用该参数
@Headers()
请求头设置
@Headers("Cache-Control: max-age=640000")
@GET("/widget/list")
List<Widget> widgetList();
例如上述代码就给http请求添加了请求头。注意所有头都不会覆盖,同名的会一并提交。所以假如我们想在所有的请求中添加一样的请求头可以使用下面的办法。
RequestInterceptor requestInterceptor = new RequestInterceptor() {
@Override
public void intercept(RequestFacade request) {
request.addHeader("User-Agent", "Retrofit-Sample-App");
}
};
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://api.github.com")
.setRequestInterceptor(requestInterceptor)
.build();
大致常用的参数就这么多,还有好多上传文件用到的,后面我在慢慢整理,看了这么多是不是晕晕乎乎的啊,赶快看看RxJava放松一下。
2.RxJava的简单介绍
RxJava之前我写过相关的博文简单介绍过,请看Rxjava操作步骤
3.实例
终于将前面的一些皮毛讲解完了,下面我们就要用他们来干点事情了。实战出真知嘛。
还是我们上一篇的需求。
先说一下需求,假如我们现在要动态解析http://www.meishij.net/list.php?sortby=update&lm=369&page=1
美食杰往上面的数据,如何加载到我们的App上。
变成这样。
想说一下思路:
1.想要把这个网页的html文件拿到手,就是字符串格式的。好,来干。
配置Retrofit
/**
* 作者:GXL on 2016/8/3 0003
* 博客: http://blog.csdn.net/u014316462
*/
public class RetrofitWrapper {
public static RetrofitWrapper instance;
public static Retrofit retrofit;
private RetrofitWrapper() {
retrofit = new Retrofit.Builder()
.baseUrl(Constant.BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
public static RetrofitWrapper getInstance() {
if (instance == null) {
synchronized (RetrofitWrapper.class) {
instance = new RetrofitWrapper();
}
}
return instance;
}
public <T> T create(Class<T> service) {
return retrofit.create(service);
}
public class Constant {
public static final String BASE_URL = "http://www.meishij.net/";
}
}
定义请求接口
/**
* 作者:GXL on 2016/8/3 0003
* 博客: http://blog.csdn.net/u014316462
* 作用:该接口用来请求美食杰网页数据
*/
public interface FoodService {
/**
*
* @param sortby 最新update和最热renqi两种
* @param lm 食品的种类
* @param page 当前的页码
* @return 当前html页面
*/
@GET("list.php")
Observable<String> getFoodList(@Query("sortby") String sortby, @Query("lm") int lm, @Query("page") int page);
}
2.经过retrofit的奋战,String已经拿到了,但是现在还不能用,需要编程实体类,这时候RxJava也不能闲着了。
/**
* 作者:GXL on 2016/8/3 0003
* 博客: http://blog.csdn.net/u014316462
* 作用:Food请求model
*/
public class FoodModel implements FoodModelImpl {
@Override
public void getGeneralFoodsItem(String sortby, int type, int page, final BaseListener listener) {
FoodService service = RetrofitWrapper.getInstance().create(FoodService.class);
service.getFoodList(sortby,type,page).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.map(new Func1<String, List<FoodGeneralItem>>(){
@Override
public List<FoodGeneralItem> call(String s) {
return HtmlParser.parserHtml(s);
}
}).subscribe(new Subscriber<List<FoodGeneralItem>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
listener.getFailure();
}
@Override
public void onNext(List<FoodGeneralItem> s) {
listener.getSuccess(s);
}
});
}
}
下面这段代码是解析的核心,将String对象转化成了List<FoodGeneralItem>>
``` lasso
.map(new Func1<String, List<FoodGeneralItem>>(){
@Override
public List<FoodGeneralItem> call(String s) {
return HtmlParser.parserHtml(s);
}
})
<div class="se-preview-section-delimiter"></div>
HtmlParser.parserHtml(s); 这个函数使用Jsoup解析html的节点。下面给出代码,自己研究一下啊。
/**
* 作者:GXL on 2016/8/3 0003
* 博客: http://blog.csdn.net/u014316462
* 作用:解析html获得实体对象
*/
public class HtmlParser {
/**
* 获得大概的美食集合
* @param msg
* @return
*/
public static List<FoodGeneralItem> parserHtml(String msg) {
String content_personame = null;
List<FoodGeneralItem> foodsItemList=new ArrayList<>();
org.jsoup.nodes.Document doc = Jsoup.parse(msg);
Elements units = doc.getElementsByClass("listtyle1");
Elements detail_units = doc.getElementsByClass("c1");
Elements date_units = doc.getElementsByClass("c2");
for (int i = 0; i < units.size(); i++) {
FoodGeneralItem foodsItem = new FoodGeneralItem();
Element detail_ele = units.get(i);
Elements links = detail_ele.getElementsByTag("a");
String link = links.get(0).attr("href");
String title = links.get(0).attr("title");
Element imagelink_ele = detail_ele.getElementsByTag("img").get(0);
String imagelink = imagelink_ele.attr("src");
Element content_ele = detail_units.get(i);
Elements content_pinglus = detail_ele.getElementsByTag("span");
String content_pinglun = content_pinglus.get(0).text();
Elements content_pername = detail_ele.getElementsByTag("em");
if(content_pername.size()!=0) {
content_personame = content_pername.get(0).text();
}
Element date_ele = date_units.get(i);
Elements date_shijian = detail_ele.getElementsByTag("ul");
String content_bushu = date_shijian.get(0)
.getElementsByClass("li1").text();
String content_weidao = date_shijian.get(0)
.getElementsByClass("li2").text();
String Personname = content_personame;
String Date = content_bushu;
foodsItem.setTitle(title);
foodsItem.setLink(link);
foodsItem.setWriter(Personname);
foodsItem.setTaste(content_weidao);
foodsItem.setDate(Date);
foodsItem.setDiscuss(content_pinglun);
foodsItem.setImgLink(imagelink);
foodsItemList.add(foodsItem);
}
return foodsItemList;
}
/**
* 解析手机端的Html获得详细的做菜步骤
* @param canshu
* @return
*/
public static FoodDetailTeachItem parserHtmlToDetailInMobile(String canshu) {
FoodDetailTeachItem detailTeachList=new FoodDetailTeachItem();
ArrayList<FoodAccessories> accessoriesList=new ArrayList<>();
ArrayList<FoodTeachStep> teachStepList=new ArrayList<>();
Document doc = Jsoup.parse(canshu);
String food_title = doc.getElementsByClass("fade_topbar").get(0)
.getElementsByTag("h2").get(0).text();
Log.i("food_title", food_title);
detailTeachList.setFoodTitle(food_title);
String showfood_text = doc.getElementsByClass("cp_main").attr("style");
Log.i("showfood_text", showfood_text);
detailTeachList.setFoodIntroduction(showfood_text);
String NewStr = showfood_text.substring(showfood_text.indexOf("(") + 1,
showfood_text.lastIndexOf(")"));
Log.i("showfood_text", NewStr);
detailTeachList.setFoodImage(NewStr);
String show_writer_name_text = doc.getElementsByClass("con_main")
.get(0).getElementsByTag("a").get(0).getElementsByTag("span")
.text();
detailTeachList.setWriteName(show_writer_name_text);
String show_writer_image_text = doc.getElementsByClass("con_main")
.get(0).getElementsByTag("a").get(0).getElementsByTag("img")
.attr("src");
detailTeachList.setWritePhoto(show_writer_image_text);
/*
* 下面解析出做该菜要用到的食材和辅料
*/
if (doc.select("div.material_coloum_type1").size() > 0) {
Elements units = doc.getElementsByClass("material_coloum_type1");
Elements units_zl = units.get(0).getElementsByClass(
"material_coloum");
for (int i = 0; i < units_zl.size(); i++) {
FoodAccessories accessories = new FoodAccessories();
Element content_zl_name = units_zl.get(i)
.getElementsByTag("span").get(0);
String content_name = content_zl_name.text();
Element content_zl_shuliang = units_zl.get(i)
.getElementsByTag("em").get(0);
String content_shuliang = content_zl_shuliang.text();
accessories.setName(content_name);
accessories.setNumber(content_shuliang);
Log.i("content_name", content_name);
Log.i("content_shuliang", content_shuliang);
accessoriesList.add(accessories);
}
}
if (doc.select("div.material_coloum_type2").size() > 0) {
Elements units_fuliao = doc
.getElementsByClass("material_coloum_type2");
Elements Units_fuliao = units_fuliao.get(0).getElementsByClass(
"material_coloum");
Log.i("content_fuliao_shuliang", Units_fuliao.size() + "");
for (int i = 0; i < Units_fuliao.size(); i++) {
FoodAccessories fuliao = new FoodAccessories();
Element content_fuliao = Units_fuliao.get(i)
.getElementsByTag("span").get(0);
String content_fuliao_name = content_fuliao.text();
Element content_zl_shuliang = Units_fuliao.get(i)
.getElementsByTag("em").get(0);
String content_fuliao_shuliang = content_zl_shuliang.text();
Log.i("content_fuliao_name", content_fuliao_name);
Log.i("content_fuliao_shuliang", content_fuliao_shuliang);
fuliao.setName(content_fuliao_name);
fuliao.setNumber(content_fuliao_shuliang);
accessoriesList.add(fuliao);
}
}
/**
* 下面解析出详细的做菜方法
*/
if (doc.select("div.cp_step").size() > 0) {
Elements units_teach = doc.getElementsByClass("cp_step").get(0)
.getElementsByTag("h2");
String teachtext;
String imagelink;
for (int i = 0; i < units_teach.size() - 1; i++) {
FoodTeachStep foodcontent = new FoodTeachStep();
Log.i("teachtext", units_teach.get(i).text());
if (units_teach.get(i).nextElementSibling()
.getElementsByTag("img").size() > 0) {
imagelink = units_teach.get(i).nextElementSibling()
.getElementsByTag("img").get(0).attr("src");
teachtext = units_teach.get(i).nextElementSibling()
.nextElementSibling().text();
Log.i("imagelink", imagelink);
Log.i("teachtext", teachtext);
showfood_text = imagelink;
} else {
imagelink = "noimagelink";
teachtext = units_teach.get(i).nextElementSibling().text();
Log.i("imagelink", imagelink);
Log.i("teachtext", teachtext);
}
foodcontent.setNum(units_teach.get(i).text());
foodcontent.setImagelink(imagelink);
foodcontent.setTeachtext(teachtext);
teachStepList.add(foodcontent);
}
}
detailTeachList.setAccessoriesList(accessoriesList);
detailTeachList.setStepList(teachStepList);
return detailTeachList;
}
/**
* 解析电脑端的Html获得详细的做菜步骤
* @param canshu
* @return
*/
public static FoodDetailTeachItem parserHtmlToDetailInPc(String canshu) {
FoodDetailTeachItem detailTeachList=new FoodDetailTeachItem();
ArrayList<FoodAccessories> accessoriesList=new ArrayList<>();
ArrayList<FoodTeachStep> teachStepList=new ArrayList<>();
Document doc = Jsoup.parse(canshu);
String food_title = doc.getElementsByClass("info1").get(0)
.getElementsByTag("a").get(0).text();
Log.i("food_title",food_title);
detailTeachList.setFoodTitle(food_title);
String showfood_image = doc.getElementsByClass("cp_headerimg_w").get(0)
.getElementsByTag("img").get(0).attr("src");
detailTeachList.setFoodImage(showfood_image);
String showdetail_text = doc.getElementsByClass("materials").get(0)
.getElementsByTag("p").get(0).text();
detailTeachList.setFoodIntroduction(showdetail_text);
String show_writer_name_text = doc.getElementsByClass("user").get(0)
.getElementsByClass("info").get(0).getElementsByTag("h4")
.get(0).getElementsByTag("a").get(0).text();
detailTeachList.setWriteName(show_writer_name_text);
String show_writer_image_text = doc.getElementsByClass("user").get(0)
.getElementsByTag("a").get(0).getElementsByTag("img").get(0)
.attr("src");
detailTeachList.setWritePhoto(show_writer_image_text);
String show_writer_date_text = doc.getElementsByClass("user").get(0)
.getElementsByClass("info").get(0).getElementsByTag("strong")
.get(0).text();
show_writer_date_text="创建于"+show_writer_date_text.substring(0,show_writer_date_text.lastIndexOf('/')-1);
detailTeachList.setWriteDate(show_writer_date_text);
Log.i("tishi", food_title);
/*
* 下面解析出做该菜要用到的食材和辅料
*/
if (doc.select("div.zl").size() > 0) {
Elements units = doc.getElementsByClass("zl");
Elements units_zl = units.get(0).getElementsByTag("li");
for (int i = 0; i < units_zl.size(); i++) {
FoodAccessories fuliao = new FoodAccessories();
Element content_zl_name = units_zl.get(i)
.getElementsByClass("c").get(0).getElementsByTag("h4")
.get(0).getElementsByTag("a").get(0);
String content_name = content_zl_name.text();
Element content_zl_shuliang = units_zl.get(i)
.getElementsByClass("c").get(0).getElementsByTag("h4")
.get(0).getElementsByTag("span").get(0);
String content_shuliang = content_zl_shuliang.text();
fuliao.setName(content_name);
fuliao.setNumber(content_shuliang);
Log.i("content_name", content_name);
Log.i("content_shuliang", content_shuliang);
accessoriesList.add(fuliao);
}
}
if (doc.select("div.fuliao").size() > 0) {
Elements units_fuliao = doc.getElementsByClass("fuliao");
Elements Units_fuliao = units_fuliao.get(0).getElementsByTag("li");
Log.i("content_fuliao_shuliang", Units_fuliao.size() + "");
for (int i = 0; i < Units_fuliao.size(); i++) {
FoodAccessories fuliao = new FoodAccessories();
Element content_fuliao = Units_fuliao.get(i)
.getElementsByTag("h4").get(0).getElementsByTag("a")
.get(0);
String content_fuliao_name = content_fuliao.text();
Element content_zl_shuliang = Units_fuliao.get(i)
.getElementsByTag("span").get(0);
String content_fuliao_shuliang = content_zl_shuliang.text();
Log.i("content_fuliao_name", content_fuliao_name);
Log.i("content_fuliao_shuliang", content_fuliao_shuliang);
fuliao.setName(content_fuliao_name);
fuliao.setNumber(content_fuliao_shuliang);
accessoriesList.add(fuliao);
}
}
/**
* 下面解析出详细的做菜方法
*/
if (doc.select("div.content").size() > 0) {
Log.i("1111", "1111");
Elements units_teach = doc.getElementsByClass("measure").get(0)
.getElementsByClass("edit").get(0)
.getElementsByClass("content");
Log.i("units_teach", units_teach.size() + "");
String teachtext;
String imagelink;
for (int i = 0; i < units_teach.size(); i++) {
FoodTeachStep foodcontent = new FoodTeachStep();
if (units_teach.get(i).getElementsByClass("c").get(0)
.getElementsByTag("p").size() == 2) {
teachtext = units_teach.get(i).getElementsByClass("c")
.get(0).getElementsByTag("p").get(0).text();
imagelink = units_teach.get(i).getElementsByClass("c")
.get(0).getElementsByTag("p").get(1)
.getElementsByTag("img").get(0).attr("src");
} else {
if (units_teach.get(i).getElementsByClass("c").get(0)
.getElementsByTag("p").get(0).hasAttr("src")) {
teachtext = "";
imagelink = units_teach.get(i).getElementsByClass("c")
.get(0).getElementsByTag("p").get(0)
.getElementsByTag("img").get(0).attr("src");
} else {
teachtext = units_teach.get(i).getElementsByClass("c")
.get(0).getElementsByTag("p").get(0).text();
imagelink = "noimagelink";
}
}
Log.i("teachtext", teachtext);
Log.i("imagelink", imagelink);
foodcontent.setNum(String.valueOf(i + 1));
foodcontent.setImagelink(imagelink);
foodcontent.setTeachtext(teachtext);
teachStepList.add(foodcontent);
}
} else {
Elements units_teach = doc.getElementsByClass("measure").get(0)
.getElementsByClass("edit").get(0).getElementsByTag("p");
Log.i("units_teach", units_teach.size() + "");
Log.i("units_teach_texy", units_teach.get(0).text());
int num = 0;
for (int i = 0, j = 0; i < units_teach.size() - 1; i++, j++) {
FoodTeachStep foodcontent = new FoodTeachStep();
String teachtext = null;
String imagelink = null;
if (units_teach.get(i).getElementsByTag("em").size() > 0) {
teachtext = units_teach.get(i).text();
int flag = i + 1;
if (units_teach.get(flag).getElementsByClass("conimg")
.size() > 0) {
imagelink = units_teach.get(flag)
.getElementsByClass("conimg").get(0)
.attr("src");
i++;
} else {
imagelink = "noimagelink";
}
} else if (units_teach.get(i).getElementsByTag("img").size() > 0) {
teachtext = "";
imagelink = units_teach.get(i).getElementsByClass("conimg")
.get(0).attr("src");
} else {
continue;
}
Log.i("teachtext", teachtext);
Log.i("imagelink", imagelink);
foodcontent.setNum(String.valueOf(++num));
foodcontent.setImagelink(imagelink);
foodcontent.setTeachtext(teachtext);
teachStepList.add(foodcontent);
}
}
detailTeachList.setAccessoriesList(accessoriesList);
detailTeachList.setStepList(teachStepList);
return detailTeachList;
}
public static List<SlideShowView.SliderShowViewItem> parserHtmlToSliderShow(String msg) {
ArrayList<SlideShowView.SliderShowViewItem> list=new ArrayList<>();
SlideShowView.SliderShowViewItem viewItem = null;
org.jsoup.nodes.Document doc = Jsoup.parse(msg);
Elements content = doc.getElementsByClass("zzw_item_2");
Elements links = content.get(0).getElementsByTag("li");
Log.i("msg", msg);
Log.i("content.size()", content.size() + "");
Log.i("links.size()", links.size() + "");
for (int i = 0; i < links.size(); i++) {
viewItem = new SlideShowView.SliderShowViewItem();
Elements links_a = links.get(i).getElementsByTag("a");
String link = links_a.get(0).attr("href");
Element imagelink_ele = links_a.get(0).getElementsByTag("img")
.get(0);
String imagelink = imagelink_ele.attr("src");
String foodname = links_a.get(0).attr("title");
viewItem.setImgLink(imagelink);
viewItem.setLink(link);
viewItem.setName(foodname);
Log.i("link", link);
Log.i("imagelink", imagelink);
Log.i("foodname", foodname);
list.add(viewItem);
}
return list;
}
}
这样RxJava+Retrofit的实战就干完了,思路非常清晰,代码特别简捷,加上MVP简直逆天。
,项目代码地址:https://github.com/gxl1240779189/ReIntelligentKitchen