1、为何用Retrofit
一、由于Okhttp很牛逼二、由于RxJava很热
由于Retrofit封装了okhttp,又由于RxJava和Retrofit的关系就像下雨天和巧克力。因此,折腾Retrofit。RxJava参考连接
2、Retrofit初步应用
build.gradle引入相关库java
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'com.google.code.gson:gson:2.7'react
开始最简单的demo
咱们要请求网络数据了,那么来一个接口,get请求天气数据。
http://apistore.baidu.com/microservice/weather?citypinyin=beijing
典型的的Json返回类型:
{
code:0,
msg:"success"
data:{...}
}
实际返回:
{
"errNum": 0,
"errMsg": "success",
"retData": {
"city": "接口已经停用",
"pinyin": "apistore.baidu.com",
"citycode": "000000000",
"date": "201616-05-12",
"time": "10:00",
"postCode": "0000000",
"longitude": 0,
"latitude": 0,
"altitude": "0",
"weather": "多云",
"temp": "0",
"l_tmp": "0",
"h_tmp": "0",
"WD": "北风",
"WS": "接口已经停用,请访问APIStore.baidu.com查找对应接口",
"sunrise": "00:00",
"sunset": "00:00"
}
}
(虽显停用,无碍使用)
弄一个实体Bean吧,为了方便起见,public修饰
public class WeatherBean {
public int errNum;
public String errMsg;
public RetDataBean retData;android
public static class RetDataBean {
public String city;
public String pinyin;
public String citycode;
public String date;
public String time;
public String postCode;
public int longitude;
public int latitude;
public String altitude;
public String weather;
public String temp;
public String l_tmp;
public String h_tmp;
public String WD;
public String WS;
public String sunrise;
public String sunset;
}
}
权限勿忘
<uses-permission android:name="android.permission.INTERNET"/>
准备工做完毕
来来来,解析解析:
准备一个WeatherApi 接口
public interface WeatherApi {
// 发送get请求,光是这段地址确定不完整,待会咱们还在在别的地方补上 baseurl 。 这里直接把后面的请求给写死了(这样确定很差)
@GET("/microservice/weather?citypinyin=beijing")
Call<WeatherBean> getWeather();
}
MainActivity代码
public class MainActivity extends AppCompatActivity {git
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getData();
}api
private void getData() {
String baseUrl = "http://apistore.baidu.com/";
// 建立一个 retrofit ,baseUrl必须有
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create()) // 用到了 com.squareup.retrofit2:adapter-rxjava:2.1.0'
.build();
// 利用 retrofit 建立一个接口
WeatherApi apiService = retrofit.create(WeatherApi.class);数组
// 利用接口建立一个Call对象
Call<WeatherBean> weatherBeanCall = apiService.getWeather();
// 请求入队,回调请求成功或者失败
weatherBeanCall.enqueue(new Callback<WeatherBean>() {
@Override
public void onResponse(Call<WeatherBean> call, Response<WeatherBean> response) {
System.out.println("====请求成功:"+response.body().retData.weather);
}
@Override
public void onFailure(Call<WeatherBean> call, Throwable t) {
System.out.println("====请求失败");
}
});
}
}
..运行打印结果:
====请求成功:多云
第一次就这么跑下来了。
代码分析
(数据和bean的就不说了,只从Retroift提及)服务器
第一步、准备一个接口,好比名为WeatherApi,接口里面经过注解的方式说明请求方式,好比这里咱们指定为GET。public interface WeatherApi {
// 发送get请求,光是这段地址确定不完整,待会咱们还在在别的地方补上 baseurl 。 这里直接把后面的请求给写死了(这样确定很差)
@GET("/microservice/weather?citypinyin=beijing")
Call<WeatherBean> getWeather();
}
(GET固然能够传参,咱们这里先经过一个不规范的行为写死了,后面会补充会有更好的方式)网络
第二步,建立一个retrofit 对象,传入对象baseUrl,指定数据将解析为什么种对象。.baseUrl(baseUrl),好理解.addConverterFactory(GsonConverterFactory.create()) 将数据解析成Gson对象.build就成功建立 retrofit 对象了app
第三步,利用retrofit对象获得一个接口实例。异步
第四步,利用接口实例获得Call对象,让Call对象异步入队。入队支队就会有两个回调方法 ,成功仍是失败。
大概流程就是这么走的。
get传参
咱们改一下代码
在WeatherApi里面,利用@Query能够传参
public interface APIService {
// 发送get请求,光是这段地址确定不完整,待会咱们还在在别的地方补上 baseurl 。 这里直接把后面的请求给写死了(这样确定很差)
// 请求仍是Get
@GET("/microservice/weather")
Call<WeatherBean> getWeather(@Query("citypinyin") String city); // 使用了@Query,后面的 citypinyin 是参数名
}
get方式的url里面,问号不用写,@Query("citypinyin")是key,String city是值类型和参数名。
MainActivity也须要修改下
private void getData() {
String baseUrl = "http://apistore.baidu.com/";
// 建立一个 retrofit ,baseUrl必须有
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create()) // 用到了 com.squareup.retrofit2:adapter-rxjava:2.1.0'
.build();
// 利用 retrofit 建立一个接口
WeatherApi apiService = retrofit.create(WeatherApi.class);
// 利用接口建立一个Call对象
Call<WeatherBean> weatherBeanCall = apiService.getWeather("beijing");
// 请求入队,回调请求成功或者失败
weatherBeanCall.enqueue(new Callback<WeatherBean>() {
@Override
public void onResponse(Call<WeatherBean> call, Response<WeatherBean> response) {
System.out.println("====请求成功:"+response.body().retData.weather);
}
@Override
public void onFailure(Call<WeatherBean> call, Throwable t) {
System.out.println("====请求失败");
}
});
}
改动的只有一行代码
Call<WeatherBean> weatherBeanCall = apiService.getWeather("beijing");
打印结果一致。
除了@Query传参,还由@Path等,请求方式里面有Get,Post等也是支持的,这里就不演示了。
2、Retrofit + RxJava
引入多两个库
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'com.google.code.gson:gson:2.7'
compile 'io.reactivex:rxjava:1.1.6'
compile 'io.reactivex:rxandroid:1.2.1'
简单结合
既然引入RxJava,那么就应该是 观察者 和 被观察者。
咱们先看看在原有的代码里面观察者和被观察者的角色分别是谁在扮演
Paste_Image.png
被观察者
在上面的代码中,咱们的 apiService 扮演者 被观察者 的角色,因此在接口里面的返回值咱们就不能够用Call,而应该用 Observable
public interface WeatherApi {
@GET("/microservice/weather")
Observable<WeatherBean> getWeather(@Query("citypinyin") String city);
}
观察者
在上面的分析图里面,new Callback<WeatherBean> 扮演的是 观察者 的角色,因此要作一个调整
以下
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getData();
}
private void getData() {
String baseUrl = "http://apistore.baidu.com/";
// 建立一个 retrofit ,baseUrl必须有
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // //添加 RxJava 适配器
.build();
// 利用 retrofit 建立一个接口
WeatherApi apiService = retrofit.create(WeatherApi.class);
// 被观察者
Observable observable = apiService.getWeather("beijing")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
// 观察者
Subscriber<WeatherBean> subscriber = new Subscriber<WeatherBean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
System.out.println("====请求失败");
}
@Override
public void onNext(WeatherBean weatherBean) {
System.out.println("====请求成功:" + weatherBean.retData.weather);
}
};
// 产生订阅关系
observable.subscribe(subscriber);
}
}
在上面 观察者 和 被观察者 只要熟悉RxJava的能够理解。
RxJavaCallAdapterFactory
可是须要注意的是,retrofit 的配置里面要加多一句 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // //添加 RxJava 适配器这个很重要
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // //添加 RxJava 适配器
.build();
CallAdapter.Factory 是 Retrofit 这个库中的接口,用来给咱们自定义去解析咱们本身想要的类型用的。
举个栗子:
@GET("/aaa")Observable<QuestionListData> getQuestionNewestList();
好比这么一个接口, retrofit自己是没法识别 Observable<QuestionListData>而后去工做的,若是没有这个适配器就根本没法工做,所以咱们的适配器的做用,就是生成咱们想要的 Observable 。
3、封装请求
在上面的代码中,每次请求都须要new 一个Retrofit实例,这样确定是很差的。因此咱们弄一个单例,把请求封装起来。
借鉴 RxJava 与 Retrofit 结合的最佳实践
封装代码:
public class HttpMethods {
public static final String BASE_URL = "http://apistore.baidu.com/";
private static final int DEFAULT_TIMEOUT = 5;
private Retrofit retrofit;
private WeatherApi weatherApi;
//构造方法私有
private HttpMethods() {
//手动建立一个OkHttpClient并设置超时时间
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
retrofit = new Retrofit.Builder()
.client(httpClientBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();
weatherApi = retrofit.create(WeatherApi.class);
}
//在访问HttpMethods时建立单例
private static class SingletonHolder{
private static final HttpMethods INSTANCE = new HttpMethods();
}
//获取单例
public static HttpMethods getInstance(){
return SingletonHolder.INSTANCE;
}
public void getWeather(Subscriber<WeatherBean> subscriber, String city){
weatherApi.getWeather(city)
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
}
接下来ManActivity本身要这么作就好
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getData();
}
private void getData() {
// 观察者
Subscriber<WeatherBean> subscriber = new Subscriber<WeatherBean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
System.out.println("====请求失败");
}
@Override
public void onNext(WeatherBean weatherBean) {
System.out.println("====请求成功:" + weatherBean.retData.weather);
}
};
// 产生订阅关系
HttpMethods.getInstance().getWeather(subscriber,"beijing");
}
}
几乎一行代码完成。
4、服务器返回数据统一处理
好比服务器返回的数据是统一是以下格式:{code:0,msg:"success"data:{...}}
code和msg是固定格式int和String,data不固定,多是数组,多是一个对象。那么这个data就是泛型T
而后在Activity和Fragment里面我不关心code和msg,咱们只关心data。对于code咱们能够作一个预处理,若是预处理code不等于0,那么就直接执行onError了。
咱们把WeatherBean删掉,实体Bean咱们能够拆分红这样
Paste_Image.png
这么作以后,天然不少报错,那么来解决。
第一处修改
Observable参数类型换成 GeneralResults<RetDataBean>
Paste_Image.png
public interface WeatherApi {
@GET("/microservice/weather")
Observable<GeneralResults<RetDataBean>> getWeather(@Query("citypinyin") String city);
}
第二处
Paste_Image.png
public class HttpMethods {
public static final String BASE_URL = "http://apistore.baidu.com/";
private static final int DEFAULT_TIMEOUT = 5;
private Retrofit retrofit;
private WeatherApi weatherApi;
//构造方法私有
private HttpMethods() {
//手动建立一个OkHttpClient并设置超时时间
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
retrofit = new Retrofit.Builder()
.client(httpClientBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();
weatherApi = retrofit.create(WeatherApi.class);
}
//在访问HttpMethods时建立单例
private static class SingletonHolder{
private static final HttpMethods INSTANCE = new HttpMethods();
}
//获取单例
public static HttpMethods getInstance(){
return SingletonHolder.INSTANCE;
}
public void getWeather(Subscriber<GeneralResults<RetDataBean>> subscriber, String city){
weatherApi.getWeather(city)
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
}
第三处:
Paste_Image.png
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getData();
}
private void getData() {
// 观察者
Subscriber<GeneralResults<RetDataBean>> subscriber = new Subscriber<GeneralResults<RetDataBean>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
System.out.println("====请求失败");
}
@Override
public void onNext(GeneralResults<RetDataBean> weatherBean) {
System.out.println("====请求成功:" + weatherBean.retData.weather);
}
};
// 产生订阅关系
HttpMethods.getInstance().getWeather(subscriber,"beijing");
}
}
依然结果不变,你能够说,这不就是把相关的5个原先的WeatherBean换成GeneralResults<RetDataBean>吗,是的没错,有这个认识挺好的。
咱们先来想一下,这5个地方的类型须要彻底一致吗?对于 被观察者 来讲,也就是WeatherApi里面的返回值Observable,这里的参数类型须要跟返回的Json数据对应,这样才靠谱对于 观察者 Subscriber来讲,他的类型是不须要跟着Observable同样的,可是直接写不同的类型确定不能够,可是咱们能够经过map变换等把 被观察者 传入的数据类型 转换成 观察者 想要的其余类型。
咱们知道,被观察者须要的是 GeneralResults<RetDataBean> ,可是观察者须要的是 RetDataBean 而已。
也就是说,咱们能够把 被观察者 传入的GeneralResults<RetDataBean> 变换成 观察者 想要的 RetDataBean。
根据上面的代码,咱们接着来。
利用变换统一处理返回的数据类型。
被观察者须要的是 GeneralResults<RetDataBean> ,可是观察者须要的是 RetDataBean 而已。
这里咱们用map进行变换GeneralResults<RetDataBean>变RetDataBean 是一个一对一的操做。若是data是一个数组或者集合,那么咱们能够用flatMap,平铺变换,这个支持一对多。
Paste_Image.png
上代码:WeatherApi
public interface WeatherApi {
@GET("/microservice/weather")
Observable<GeneralResults<RetDataBean>> getWeather(@Query("citypinyin") String city);
}
上图中,HttpMethods 采用的是map变换,下面的实际代码中咱们为了演示多一种用法,咱们采用flatMap(虽然在当前状况下不须要flatMap,可是flatMap依然能够良好地完成工做)
HttpMethods
public class HttpMethods {
public static final String BASE_URL = "http://apistore.baidu.com/";
private static final int DEFAULT_TIMEOUT = 5;
private Retrofit retrofit;
private WeatherApi weatherApi;
//构造方法私有
private HttpMethods() {
//手动建立一个OkHttpClient并设置超时时间
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
retrofit = new Retrofit.Builder()
.client(httpClientBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();
weatherApi = retrofit.create(WeatherApi.class);
}
//在访问HttpMethods时建立单例
private static class SingletonHolder{
private static final HttpMethods INSTANCE = new HttpMethods();
}
//获取单例
public static HttpMethods getInstance(){
return SingletonHolder.INSTANCE;
}
public void getWeather(Subscriber<RetDataBean> subscriber, String city){
weatherApi.getWeather(city)
.flatMap(new Func1<GeneralResults<RetDataBean>, Observable<RetDataBean>>() {
@Override
public Observable<RetDataBean> call(GeneralResults<RetDataBean> retDataBeanGeneralResults) {
return flatResult(retDataBeanGeneralResults);
}
})
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
static <T> Observable<T> flatResult(final GeneralResults<T> result) {
return Observable.create(new Observable.OnSubscribe<T>() {
@Override
public void call(Subscriber<? super T> subscriber) {
if (result.errNum!= 0) {
subscriber.onError(new ApiException(ApiException.USER_NOT_EXIST));
} else{
subscriber.onNext(result.retData);
}
subscriber.onCompleted();
}
});
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getData();
}
private void getData() {
// 观察者
Subscriber<RetDataBean> subscriber = new Subscriber<RetDataBean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
System.out.println("====请求失败");
}
@Override
public void onNext(RetDataBean weatherBean) {
System.out.println("====请求成功:" + weatherBean.weather);
}
};
// 产生订阅关系
HttpMethods.getInstance().getWeather(subscriber,"beijing");
}
}
WeatherApi
public interface WeatherApi {
@GET("/microservice/weather")
Observable<GeneralResults<RetDataBean>> getWeather(@Query("citypinyin") String city);
}
ApiException
public class ApiException extends RuntimeException {
public static final int USER_NOT_EXIST = 100;
public static final int WRONG_PASSWORD = 101;
public ApiException(int resultCode) {
this(getApiExceptionMessage(resultCode));
}
public ApiException(String detailMessage) {
super(detailMessage);
}
/**
* 因为服务器传递过来的错误信息直接给用户看的话,用户未必可以理解
* 须要根据错误码对错误信息进行一个转换,在显示给用户
* @param code
* @return
*/
private static String getApiExceptionMessage(int code){
String message = "";
switch (code) {
case USER_NOT_EXIST:
message = "该用户不存在";
break;
case WRONG_PASSWORD:
message = "密码错误";
break;
default:
message = "未知错误";
}
return message;
}
}
运行效果一致。
5、如何去掉调用
若是没有使用Rxjava,那么Service返回的是一个Call,而这个Call对象有一个cancel方法能够用来取消Http请求。那么用了Rxjava以后,如何来取消一个请求呢
咱们在Activity或者Fragment中建立subscriber对象,想要取消请求的时候调用subscriber的unsubscribe方法就能够了。
6、加载时的Dialog
Paste_Image.png
加载时,dilalog弹出完成、错误时dialog消失
dialog跟订阅关系同步
ProgressCancelListener
public interface ProgressCancelListener {
void onCancelProgress();
}
..ProgressDialogHandler
public class ProgressDialogHandler extends Handler {
public static final int SHOW_PROGRESS_DIALOG = 1;
public static final int DISMISS_PROGRESS_DIALOG = 2;
private ProgressDialog pd;
private Context context;
private boolean cancelable;
private ProgressCancelListener mProgressCancelListener;
public ProgressDialogHandler(Context context, ProgressCancelListener mProgressCancelListener,
boolean cancelable) {
super();
this.context = context;
this.mProgressCancelListener = mProgressCancelListener;
this.cancelable = cancelable;
}
private void initProgressDialog(){
if (pd == null) {
pd = new ProgressDialog(context);
pd.setCancelable(cancelable);
if (cancelable) {
pd.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
mProgressCancelListener.onCancelProgress();
}
});
}
if (!pd.isShowing()) {
pd.show();
}
}
}
private void dismissProgressDialog(){
if (pd != null) {
pd.dismiss();
pd = null;
}
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_PROGRESS_DIALOG:
initProgressDialog();
break;
case DISMISS_PROGRESS_DIALOG:
dismissProgressDialog();
break;
}
}
}
..
SubscriberOnNextListener
public interface SubscriberOnNextListener<T> {
void onNext(T t);
}
..ProgressSubscriber
public class ProgressSubscriber<T> extends Subscriber<T> implements ProgressCancelListener {
private SubscriberOnNextListener mSubscriberOnNextListener;
private ProgressDialogHandler mProgressDialogHandler;
private Context context;
public ProgressSubscriber(SubscriberOnNextListener mSubscriberOnNextListener, Context context) {
this.mSubscriberOnNextListener = mSubscriberOnNextListener;
this.context = context;
mProgressDialogHandler = new ProgressDialogHandler(context, this, true);
}
private void showProgressDialog() {
if (mProgressDialogHandler != null) {
mProgressDialogHandler.obtainMessage(ProgressDialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget();
}
}
private void dismissProgressDialog() {
if (mProgressDialogHandler != null) {
mProgressDialogHandler.obtainMessage(ProgressDialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget();
mProgressDialogHandler = null;
}
}
@Override
public void onStart() {
showProgressDialog();
}
@Override
public void onCompleted() {
dismissProgressDialog();
Toast.makeText(context, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(Throwable e) {
dismissProgressDialog();
Toast.makeText(context, "error:" + e.getMessage(), Toast.LENGTH_SHORT).show();
}
@Override
public void onNext(T t) {
mSubscriberOnNextListener.onNext(t);
}
@Override
public void onCancelProgress() {
if (!this.isUnsubscribed()) {
this.unsubscribe();
}
}
}
最后,MainActivity
public class MainActivity extends AppCompatActivity {
private TextView mTvGet;
private TextView mTvResult;
private SubscriberOnNextListener getWeatherNext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvGet = (TextView) findViewById(R.id.mTvGet);
mTvResult = (TextView) findViewById(R.id.mTvResult);
mTvGet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
HttpMethods.getInstance().getWeather(new ProgressSubscriber(getWeatherNext, MainActivity.this), "beijing");
}
});
getWeatherNext = new SubscriberOnNextListener<RetDataBean>() {
@Override
public void onNext(RetDataBean retDataBean) {
mTvResult.setText("==========:"+retDataBean.weather);
}
};
}
}
原文连接:http://www.jianshu.com/p/c1b2af70f24b
1、为何用Retrofit
一、由于Okhttp很牛逼二、由于RxJava很热
由于Retrofit封装了okhttp,又由于RxJava和Retrofit的关系就像下雨天和巧克力。因此,折腾Retrofit。RxJava参考连接
2、Retrofit初步应用
build.gradle引入相关库
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'com.google.code.gson:gson:2.7'
开始最简单的demo
咱们要请求网络数据了,那么来一个接口,get请求天气数据。
http://apistore.baidu.com/microservice/weather?citypinyin=beijing
典型的的Json返回类型:
{
code:0,
msg:"success"
data:{...}
}
实际返回:
{
"errNum": 0,
"errMsg": "success",
"retData": {
"city": "接口已经停用",
"pinyin": "apistore.baidu.com",
"citycode": "000000000",
"date": "201616-05-12",
"time": "10:00",
"postCode": "0000000",
"longitude": 0,
"latitude": 0,
"altitude": "0",
"weather": "多云",
"temp": "0",
"l_tmp": "0",
"h_tmp": "0",
"WD": "北风",
"WS": "接口已经停用,请访问APIStore.baidu.com查找对应接口",
"sunrise": "00:00",
"sunset": "00:00"
}
}
(虽显停用,无碍使用)
弄一个实体Bean吧,为了方便起见,public修饰
public class WeatherBean {
public int errNum;
public String errMsg;
public RetDataBean retData;
public static class RetDataBean {
public String city;
public String pinyin;
public String citycode;
public String date;
public String time;
public String postCode;
public int longitude;
public int latitude;
public String altitude;
public String weather;
public String temp;
public String l_tmp;
public String h_tmp;
public String WD;
public String WS;
public String sunrise;
public String sunset;
}
}
权限勿忘
<uses-permission android:name="android.permission.INTERNET"/>
准备工做完毕
来来来,解析解析:
准备一个WeatherApi 接口
public interface WeatherApi {
// 发送get请求,光是这段地址确定不完整,待会咱们还在在别的地方补上 baseurl 。 这里直接把后面的请求给写死了(这样确定很差)
@GET("/microservice/weather?citypinyin=beijing")
Call<WeatherBean> getWeather();
}
MainActivity代码
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getData();
}
private void getData() {
String baseUrl = "http://apistore.baidu.com/";
// 建立一个 retrofit ,baseUrl必须有
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create()) // 用到了 com.squareup.retrofit2:adapter-rxjava:2.1.0'
.build();
// 利用 retrofit 建立一个接口
WeatherApi apiService = retrofit.create(WeatherApi.class);
// 利用接口建立一个Call对象
Call<WeatherBean> weatherBeanCall = apiService.getWeather();
// 请求入队,回调请求成功或者失败
weatherBeanCall.enqueue(new Callback<WeatherBean>() {
@Override
public void onResponse(Call<WeatherBean> call, Response<WeatherBean> response) {
System.out.println("====请求成功:"+response.body().retData.weather);
}
@Override
public void onFailure(Call<WeatherBean> call, Throwable t) {
System.out.println("====请求失败");
}
});
}
}
..运行打印结果:
====请求成功:多云
第一次就这么跑下来了。
代码分析
(数据和bean的就不说了,只从Retroift提及)
第一步、准备一个接口,好比名为WeatherApi,接口里面经过注解的方式说明请求方式,好比这里咱们指定为GET。public interface WeatherApi {
// 发送get请求,光是这段地址确定不完整,待会咱们还在在别的地方补上 baseurl 。 这里直接把后面的请求给写死了(这样确定很差)
@GET("/microservice/weather?citypinyin=beijing")
Call<WeatherBean> getWeather();
}
(GET固然能够传参,咱们这里先经过一个不规范的行为写死了,后面会补充会有更好的方式)
第二步,建立一个retrofit 对象,传入对象baseUrl,指定数据将解析为什么种对象。.baseUrl(baseUrl),好理解.addConverterFactory(GsonConverterFactory.create()) 将数据解析成Gson对象.build就成功建立 retrofit 对象了
第三步,利用retrofit对象获得一个接口实例。
第四步,利用接口实例获得Call对象,让Call对象异步入队。入队支队就会有两个回调方法 ,成功仍是失败。
大概流程就是这么走的。
get传参
咱们改一下代码
在WeatherApi里面,利用@Query能够传参
public interface APIService {
// 发送get请求,光是这段地址确定不完整,待会咱们还在在别的地方补上 baseurl 。 这里直接把后面的请求给写死了(这样确定很差)
// 请求仍是Get
@GET("/microservice/weather")
Call<WeatherBean> getWeather(@Query("citypinyin") String city); // 使用了@Query,后面的 citypinyin 是参数名
}
get方式的url里面,问号不用写,@Query("citypinyin")是key,String city是值类型和参数名。
MainActivity也须要修改下
private void getData() {
String baseUrl = "http://apistore.baidu.com/";
// 建立一个 retrofit ,baseUrl必须有
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create()) // 用到了 com.squareup.retrofit2:adapter-rxjava:2.1.0'
.build();
// 利用 retrofit 建立一个接口
WeatherApi apiService = retrofit.create(WeatherApi.class);
// 利用接口建立一个Call对象
Call<WeatherBean> weatherBeanCall = apiService.getWeather("beijing");
// 请求入队,回调请求成功或者失败
weatherBeanCall.enqueue(new Callback<WeatherBean>() {
@Override
public void onResponse(Call<WeatherBean> call, Response<WeatherBean> response) {
System.out.println("====请求成功:"+response.body().retData.weather);
}
@Override
public void onFailure(Call<WeatherBean> call, Throwable t) {
System.out.println("====请求失败");
}
});
}
改动的只有一行代码
Call<WeatherBean> weatherBeanCall = apiService.getWeather("beijing");
打印结果一致。
除了@Query传参,还由@Path等,请求方式里面有Get,Post等也是支持的,这里就不演示了。
2、Retrofit + RxJava
引入多两个库
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'com.google.code.gson:gson:2.7'
compile 'io.reactivex:rxjava:1.1.6'
compile 'io.reactivex:rxandroid:1.2.1'
简单结合
既然引入RxJava,那么就应该是 观察者 和 被观察者。
咱们先看看在原有的代码里面观察者和被观察者的角色分别是谁在扮演
Paste_Image.png
被观察者
在上面的代码中,咱们的 apiService 扮演者 被观察者 的角色,因此在接口里面的返回值咱们就不能够用Call,而应该用 Observable
public interface WeatherApi {
@GET("/microservice/weather")
Observable<WeatherBean> getWeather(@Query("citypinyin") String city);
}
观察者
在上面的分析图里面,new Callback<WeatherBean> 扮演的是 观察者 的角色,因此要作一个调整
以下
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getData();
}
private void getData() {
String baseUrl = "http://apistore.baidu.com/";
// 建立一个 retrofit ,baseUrl必须有
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // //添加 RxJava 适配器
.build();
// 利用 retrofit 建立一个接口
WeatherApi apiService = retrofit.create(WeatherApi.class);
// 被观察者
Observable observable = apiService.getWeather("beijing")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
// 观察者
Subscriber<WeatherBean> subscriber = new Subscriber<WeatherBean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
System.out.println("====请求失败");
}
@Override
public void onNext(WeatherBean weatherBean) {
System.out.println("====请求成功:" + weatherBean.retData.weather);
}
};
// 产生订阅关系
observable.subscribe(subscriber);
}
}
在上面 观察者 和 被观察者 只要熟悉RxJava的能够理解。
RxJavaCallAdapterFactory
可是须要注意的是,retrofit 的配置里面要加多一句 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // //添加 RxJava 适配器这个很重要
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // //添加 RxJava 适配器
.build();
CallAdapter.Factory 是 Retrofit 这个库中的接口,用来给咱们自定义去解析咱们本身想要的类型用的。
举个栗子:
@GET("/aaa")Observable<QuestionListData> getQuestionNewestList();
好比这么一个接口, retrofit自己是没法识别 Observable<QuestionListData>而后去工做的,若是没有这个适配器就根本没法工做,所以咱们的适配器的做用,就是生成咱们想要的 Observable 。
3、封装请求
在上面的代码中,每次请求都须要new 一个Retrofit实例,这样确定是很差的。因此咱们弄一个单例,把请求封装起来。
借鉴 RxJava 与 Retrofit 结合的最佳实践
封装代码:
public class HttpMethods {
public static final String BASE_URL = "http://apistore.baidu.com/";
private static final int DEFAULT_TIMEOUT = 5;
private Retrofit retrofit;
private WeatherApi weatherApi;
//构造方法私有
private HttpMethods() {
//手动建立一个OkHttpClient并设置超时时间
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
retrofit = new Retrofit.Builder()
.client(httpClientBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();
weatherApi = retrofit.create(WeatherApi.class);
}
//在访问HttpMethods时建立单例
private static class SingletonHolder{
private static final HttpMethods INSTANCE = new HttpMethods();
}
//获取单例
public static HttpMethods getInstance(){
return SingletonHolder.INSTANCE;
}
public void getWeather(Subscriber<WeatherBean> subscriber, String city){
weatherApi.getWeather(city)
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
}
接下来ManActivity本身要这么作就好
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getData();
}
private void getData() {
// 观察者
Subscriber<WeatherBean> subscriber = new Subscriber<WeatherBean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
System.out.println("====请求失败");
}
@Override
public void onNext(WeatherBean weatherBean) {
System.out.println("====请求成功:" + weatherBean.retData.weather);
}
};
// 产生订阅关系
HttpMethods.getInstance().getWeather(subscriber,"beijing");
}
}
几乎一行代码完成。
4、服务器返回数据统一处理
好比服务器返回的数据是统一是以下格式:{code:0,msg:"success"data:{...}}
code和msg是固定格式int和String,data不固定,多是数组,多是一个对象。那么这个data就是泛型T
而后在Activity和Fragment里面我不关心code和msg,咱们只关心data。对于code咱们能够作一个预处理,若是预处理code不等于0,那么就直接执行onError了。
咱们把WeatherBean删掉,实体Bean咱们能够拆分红这样
Paste_Image.png
这么作以后,天然不少报错,那么来解决。
第一处修改
Observable参数类型换成 GeneralResults<RetDataBean>
Paste_Image.png
public interface WeatherApi {
@GET("/microservice/weather")
Observable<GeneralResults<RetDataBean>> getWeather(@Query("citypinyin") String city);
}
第二处
Paste_Image.png
public class HttpMethods {
public static final String BASE_URL = "http://apistore.baidu.com/";
private static final int DEFAULT_TIMEOUT = 5;
private Retrofit retrofit;
private WeatherApi weatherApi;
//构造方法私有
private HttpMethods() {
//手动建立一个OkHttpClient并设置超时时间
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
retrofit = new Retrofit.Builder()
.client(httpClientBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();
weatherApi = retrofit.create(WeatherApi.class);
}
//在访问HttpMethods时建立单例
private static class SingletonHolder{
private static final HttpMethods INSTANCE = new HttpMethods();
}
//获取单例
public static HttpMethods getInstance(){
return SingletonHolder.INSTANCE;
}
public void getWeather(Subscriber<GeneralResults<RetDataBean>> subscriber, String city){
weatherApi.getWeather(city)
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
}
第三处:
Paste_Image.png
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getData();
}
private void getData() {
// 观察者
Subscriber<GeneralResults<RetDataBean>> subscriber = new Subscriber<GeneralResults<RetDataBean>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
System.out.println("====请求失败");
}
@Override
public void onNext(GeneralResults<RetDataBean> weatherBean) {
System.out.println("====请求成功:" + weatherBean.retData.weather);
}
};
// 产生订阅关系
HttpMethods.getInstance().getWeather(subscriber,"beijing");
}
}
依然结果不变,你能够说,这不就是把相关的5个原先的WeatherBean换成GeneralResults<RetDataBean>吗,是的没错,有这个认识挺好的。
咱们先来想一下,这5个地方的类型须要彻底一致吗?对于 被观察者 来讲,也就是WeatherApi里面的返回值Observable,这里的参数类型须要跟返回的Json数据对应,这样才靠谱对于 观察者 Subscriber来讲,他的类型是不须要跟着Observable同样的,可是直接写不同的类型确定不能够,可是咱们能够经过map变换等把 被观察者 传入的数据类型 转换成 观察者 想要的其余类型。
咱们知道,被观察者须要的是 GeneralResults<RetDataBean> ,可是观察者须要的是 RetDataBean 而已。
也就是说,咱们能够把 被观察者 传入的GeneralResults<RetDataBean> 变换成 观察者 想要的 RetDataBean。
根据上面的代码,咱们接着来。
利用变换统一处理返回的数据类型。
被观察者须要的是 GeneralResults<RetDataBean> ,可是观察者须要的是 RetDataBean 而已。
这里咱们用map进行变换GeneralResults<RetDataBean>变RetDataBean 是一个一对一的操做。若是data是一个数组或者集合,那么咱们能够用flatMap,平铺变换,这个支持一对多。
Paste_Image.png
上代码:WeatherApi
public interface WeatherApi {
@GET("/microservice/weather")
Observable<GeneralResults<RetDataBean>> getWeather(@Query("citypinyin") String city);
}
上图中,HttpMethods 采用的是map变换,下面的实际代码中咱们为了演示多一种用法,咱们采用flatMap(虽然在当前状况下不须要flatMap,可是flatMap依然能够良好地完成工做)
HttpMethods
public class HttpMethods {
public static final String BASE_URL = "http://apistore.baidu.com/";
private static final int DEFAULT_TIMEOUT = 5;
private Retrofit retrofit;
private WeatherApi weatherApi;
//构造方法私有
private HttpMethods() {
//手动建立一个OkHttpClient并设置超时时间
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
retrofit = new Retrofit.Builder()
.client(httpClientBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();
weatherApi = retrofit.create(WeatherApi.class);
}
//在访问HttpMethods时建立单例
private static class SingletonHolder{
private static final HttpMethods INSTANCE = new HttpMethods();
}
//获取单例
public static HttpMethods getInstance(){
return SingletonHolder.INSTANCE;
}
public void getWeather(Subscriber<RetDataBean> subscriber, String city){
weatherApi.getWeather(city)
.flatMap(new Func1<GeneralResults<RetDataBean>, Observable<RetDataBean>>() {
@Override
public Observable<RetDataBean> call(GeneralResults<RetDataBean> retDataBeanGeneralResults) {
return flatResult(retDataBeanGeneralResults);
}
})
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
static <T> Observable<T> flatResult(final GeneralResults<T> result) {
return Observable.create(new Observable.OnSubscribe<T>() {
@Override
public void call(Subscriber<? super T> subscriber) {
if (result.errNum!= 0) {
subscriber.onError(new ApiException(ApiException.USER_NOT_EXIST));
} else{
subscriber.onNext(result.retData);
}
subscriber.onCompleted();
}
});
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getData();
}
private void getData() {
// 观察者
Subscriber<RetDataBean> subscriber = new Subscriber<RetDataBean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
System.out.println("====请求失败");
}
@Override
public void onNext(RetDataBean weatherBean) {
System.out.println("====请求成功:" + weatherBean.weather);
}
};
// 产生订阅关系
HttpMethods.getInstance().getWeather(subscriber,"beijing");
}
}
WeatherApi
public interface WeatherApi {
@GET("/microservice/weather")
Observable<GeneralResults<RetDataBean>> getWeather(@Query("citypinyin") String city);
}
ApiException
public class ApiException extends RuntimeException {
public static final int USER_NOT_EXIST = 100;
public static final int WRONG_PASSWORD = 101;
public ApiException(int resultCode) {
this(getApiExceptionMessage(resultCode));
}
public ApiException(String detailMessage) {
super(detailMessage);
}
/**
* 因为服务器传递过来的错误信息直接给用户看的话,用户未必可以理解
* 须要根据错误码对错误信息进行一个转换,在显示给用户
* @param code
* @return
*/
private static String getApiExceptionMessage(int code){
String message = "";
switch (code) {
case USER_NOT_EXIST:
message = "该用户不存在";
break;
case WRONG_PASSWORD:
message = "密码错误";
break;
default:
message = "未知错误";
}
return message;
}
}
运行效果一致。
5、如何去掉调用
若是没有使用Rxjava,那么Service返回的是一个Call,而这个Call对象有一个cancel方法能够用来取消Http请求。那么用了Rxjava以后,如何来取消一个请求呢
咱们在Activity或者Fragment中建立subscriber对象,想要取消请求的时候调用subscriber的unsubscribe方法就能够了。
6、加载时的Dialog
Paste_Image.png
加载时,dilalog弹出完成、错误时dialog消失
dialog跟订阅关系同步
ProgressCancelListener
public interface ProgressCancelListener {
void onCancelProgress();
}
..ProgressDialogHandler
public class ProgressDialogHandler extends Handler {
public static final int SHOW_PROGRESS_DIALOG = 1;
public static final int DISMISS_PROGRESS_DIALOG = 2;
private ProgressDialog pd;
private Context context;
private boolean cancelable;
private ProgressCancelListener mProgressCancelListener;
public ProgressDialogHandler(Context context, ProgressCancelListener mProgressCancelListener,
boolean cancelable) {
super();
this.context = context;
this.mProgressCancelListener = mProgressCancelListener;
this.cancelable = cancelable;
}
private void initProgressDialog(){
if (pd == null) {
pd = new ProgressDialog(context);
pd.setCancelable(cancelable);
if (cancelable) {
pd.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
mProgressCancelListener.onCancelProgress();
}
});
}
if (!pd.isShowing()) {
pd.show();
}
}
}
private void dismissProgressDialog(){
if (pd != null) {
pd.dismiss();
pd = null;
}
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_PROGRESS_DIALOG:
initProgressDialog();
break;
case DISMISS_PROGRESS_DIALOG:
dismissProgressDialog();
break;
}
}
}
..
SubscriberOnNextListener
public interface SubscriberOnNextListener<T> {
void onNext(T t);
}
..ProgressSubscriber
public class ProgressSubscriber<T> extends Subscriber<T> implements ProgressCancelListener {
private SubscriberOnNextListener mSubscriberOnNextListener;
private ProgressDialogHandler mProgressDialogHandler;
private Context context;
public ProgressSubscriber(SubscriberOnNextListener mSubscriberOnNextListener, Context context) {
this.mSubscriberOnNextListener = mSubscriberOnNextListener;
this.context = context;
mProgressDialogHandler = new ProgressDialogHandler(context, this, true);
}
private void showProgressDialog() {
if (mProgressDialogHandler != null) {
mProgressDialogHandler.obtainMessage(ProgressDialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget();
}
}
private void dismissProgressDialog() {
if (mProgressDialogHandler != null) {
mProgressDialogHandler.obtainMessage(ProgressDialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget();
mProgressDialogHandler = null;
}
}
@Override
public void onStart() {
showProgressDialog();
}
@Override
public void onCompleted() {
dismissProgressDialog();
Toast.makeText(context, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(Throwable e) {
dismissProgressDialog();
Toast.makeText(context, "error:" + e.getMessage(), Toast.LENGTH_SHORT).show();
}
@Override
public void onNext(T t) {
mSubscriberOnNextListener.onNext(t);
}
@Override
public void onCancelProgress() {
if (!this.isUnsubscribed()) {
this.unsubscribe();
}
}
}
最后,MainActivity
public class MainActivity extends AppCompatActivity {
private TextView mTvGet;
private TextView mTvResult;
private SubscriberOnNextListener getWeatherNext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvGet = (TextView) findViewById(R.id.mTvGet);
mTvResult = (TextView) findViewById(R.id.mTvResult);
mTvGet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
HttpMethods.getInstance().getWeather(new ProgressSubscriber(getWeatherNext, MainActivity.this), "beijing");
}
});
getWeatherNext = new SubscriberOnNextListener<RetDataBean>() {
@Override
public void onNext(RetDataBean retDataBean) {
mTvResult.setText("==========:"+retDataBean.weather);
}
};
}
}
原文连接:http://www.jianshu.com/p/c1b2af70f24b
推荐查看: http://gank.io/post/56e80c2c677659311bed9841?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io