Android之Retrofit和RxJava的结合使用

转载请标明出处:
http://blog.csdn.net/hai_qing_xu_kong/article/details/53674161
本文出自:【顾林海的博客】

##前言
Retrofit,一个时尚的代名词,好像不知道Retrofit就不算Android开发工程师了,因此我也来时尚一把,写这篇文章旨在使广大开发者能根据这篇浅薄的文章来了解Retrofit,并将它用到我们的项目中去,当然Retrofit和RxJava结合起来用是非常酸爽的。文章开头会先去介绍Retrofit,并单独使用Retrofit,后面会将Retrofit和RxJava结合起来使用,最后会封装一个Retrofit和RxJava结合的请求框架。

##Retrofit介绍

Retrofit出自Square公司,是一个类型安全的REST安卓客户端请求库。这个库为网络认证、API请求以及用OkHttp发送网络请求提供了强大的框架 ,当然OkHttp也是出自这家公司。

在漫长的时间里,Retrofit经历了从1.x到2.1(最新版请参看这里“https://github.com/square/retrofit”),相比retrofit1.x来说,retrofit2.x更新了几个不错的功能点。

##实例一(单独使用Retrofit )

该实例只会讲解单纯使用 Retrofit的用法,源码位于底下github地址,实例一位于demo1包下。

使用 Retrofit 前我们需要做以下两件事:

  • 在app/build.gradle 中引入 Retrofit。
  • 在AndroidManifest中添加网络请求权限。

在gradle中引入 Retrofit:

compile 'com.squareup.retrofit2:retrofit:2.1.0'

在AndroidManifest中添加网络请求权限:

<uses-permission android:name="android.permission.INTERNET" />

下面正式我们的Retrofit使用之旅。

###步骤一:创建我们的请求API(Service)

这里的请求API其实就是我们的向服务端请求的接口地址。Retrofit2.x在定义Service时,已经不区分同步和异步之分了。可以直接看下面的代码,建议大家边看文章边动手撸下代码,代码中的接口地址替换成你们公司或是自己的服务器的地址。在Retrofit中使用注解的方式来区分请求类型.比如@GET("")表示一个GET请求,括号中的内容为请求的地址.

public interface APIService {

    @GET("getBrandBanner.php")
    Call<ResponseBody> getBanner(@Query("uid") String _uid, @Query("token") String _token);

    @GET("getHomePager.php")
    Call<ResponseBody> getHomePager();

    @FormUrlEncoded
    @POST("editUserInfo.php")
    Call<ResponseBody> postIP(@Field("name") String name, @Field("age") int age);
}

以上定义了两个请求方式,分别是Get和Post请求,其中Get请求分为无参和有参请求。至此接口地址已经创建完毕。

###步骤二:创建Retrofit实例

在请求接口的API定义完毕后,就需要使用Retrofit Builder类,来指定Service的baseUrl(也就是域名)。

在Activity中编写代码:

private static final String API_URL = "http://n1.glh.la/apps/";


Retrofit retrofit = new Retrofit.Builder().baseUrl(API_URL).build();
APIService apiService = retrofit.create(APIService.class);
Call<ResponseBody> responseBodyCall = apiService.getBanner("10915", "585234059ab68");

创建完Retrofit实例后,通过该实例创建APIService,接着通过APIService中定义的方法来获取Call对象。前置工作已经准备完毕,剩下的就是进行请求。 ###步骤三:请求(异步与同步)

一、同步请求

同步请求使用execute方法,但不能在UI线程中执行,否则会阻塞UI线程,引起NetwordOnMainThreadException异常。因此,这里使用handler+thread的方式来进行请求,在子线程中请求数据,并通过handler来刷新界面。

handler:

private static class MyHandler extends Handler {

    WeakReference<DemoActivity1> weakReference;

    public MyHandler(DemoActivity1 activity) {
        weakReference = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        DemoActivity1 activity = weakReference.get();
        if (activity != null) {
            String json = (String) msg.obj;
            activity.setData(json);
        }
        super.handleMessage(msg);
    }
}

Thead:

private static class MyThread extends Thread {
    private MyHandler myHandler;
    Call<ResponseBody> bodyResponse;

    public MyThread(Call<ResponseBody> responseBodyCall, MyHandler handler) {
        bodyResponse = responseBodyCall;
        myHandler = handler;
    }

    @Override
    public void run() {
        super.run();
        try {
            Response<ResponseBody> body = bodyResponse.execute();
            Message message = new Message();
            message.obj = body.body().string();
            myHandler.sendMessage(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用:

/**
 * 同步
 *
 * @param responseBodyCall
 */
private void synSendRequest(Call<ResponseBody> responseBodyCall) {
    MyHandler myHandler = new MyHandler(this);
    MyThread myThread = new MyThread(responseBodyCall, myHandler);
    myThread.start();
}

private void setData(String json) {
    tv_show.setText(json);
}

同步请求的整体流程大致就这样了。

二、异步请求

相比同步请求方式,异步比较简单,因此建议使用异步请求方式,异步请求方式使用enqueue方法,并通过回调Callback 泛型接口的两个方法:

/**
 * 异步
 *
 * @param responseBodyCall
 */
private void asySendRequest(Call<ResponseBody> responseBodyCall) {

    responseBodyCall.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            try {
                tv_show.setText(response.body().string());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            tv_show.setText("error");
        }
    });

}

###其它一些注意事项

service 的模式变成Call的形式的原因是为了让正在进行的事务可以被取消。要做到这点,你只需调用call.cancel()。

##实例二(Retrofit 与 Gson)

当然如果你想把json字符串解析成DAO(实体类对象),在Retrofit2.x中,Converter不再包含其中,因此需要我们把Gson Converter依赖添加进来,此实例源码在源文件中的demo2包下。

添加Gson Converter:

compile 'com.squareup.retrofit2:converter-gson:2.1.0'
demo1下的程序进行修改如下: ###修改一:接口请求
public interface APIService {

    @GET("getBrandBanner.php")
    Call<HttpResult> getBanner(@Query("uid") String _uid, @Query("token") String _token);

}

在定义接口请求时,我们传入了一个HttpResult类,它是我们从服务器返回的json串解析后的实体类。


###修改二:创建Retrofit实例

Retrofit retrofit = new Retrofit.Builder().baseUrl(API_URL).addConverterFactory(GsonConverterFactory.create()).build();
APIService apiService = retrofit.create(APIService.class);
Call<HttpResult> responseBodyCall = apiService.getBanner("10915", "58524bb42c9ca");
我们在通过Retrofit Builder类来构造Retrofit实例时插入了一个Converter(Gson Converter)。 ###修改三:请求 ####同步请求
/**
 * 同步
 *
 * @param responseBodyCall
 */
private void synSendRequest(Call<HttpResult> responseBodyCall) {
    MyHandler myHandler = new MyHandler(this);
    MyThread myThread = new MyThread(responseBodyCall, myHandler);
    myThread.start();
}

private void setData(String data) {
    tv_show.setText(data);
}

private static class MyHandler extends Handler {

    WeakReference<DemoActivity2> weakReference;

    public MyHandler(DemoActivity2 activity) {
        weakReference = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        DemoActivity2 activity = weakReference.get();
        if (activity != null) {
            HttpResult hotBean = (HttpResult) msg.obj;
            if (hotBean != null) {
                activity.setData(hotBean.data.pc.number);
            }
        }
        super.handleMessage(msg);
    }
}

private static class MyThread extends Thread {
    private MyHandler myHandler;
    Call<HttpResult> bodyResponse;

    public MyThread(Call<HttpResult> responseBodyCall, MyHandler handler) {
        bodyResponse = responseBodyCall;
        myHandler = handler;
    }

    @Override
    public void run() {
        super.run();
        try {
            Response<HttpResult> body = bodyResponse.execute();
            Message message = new Message();
            message.obj = body.body();
            myHandler.sendMessage(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

####异步请求

/**
 * 异步
 *
 * @param responseBodyCall
 */
private void asySendRequest(Call<HttpResult> responseBodyCall) {

    responseBodyCall.enqueue(new Callback<HttpResult>() {
        @Override
        public void onResponse(Call<HttpResult> call, Response<HttpResult> response) {
            HotBean hotBean = response.body().data;
            tv_show.setText(hotBean.pc.number);
        }

        @Override
        public void onFailure(Call<HttpResult> call, Throwable t) {
            tv_show.setText("error");
        }
    });

}

##实例三(Retrofit与RxJava )

经历了上面的两个例子,大家对Retrofit的使用已经有了充分的认识了吧,如果不满足于此,可以继续看下去,因为高潮马上来了,接下来会讲解Retrofit与RxJava的结合使用。

###RxJava介绍

想来想去对RxJava介绍的文章,网上多的是,当然我认为这篇文章《给Android开发者的RxJava讲解》("http://gank.io/post/560e15be2dca930e00da1083")还是很不错,所以啊,我就不介绍了,哈哈哈。。。。。,强烈建议大家将RxJava讲解这篇文章看看,当然,不看也没问题,除非你只是拿来就用,否则作为一个有"节操"的程序员,还是老老实实的研究下。

###正题

Retrofit2.x中有个机制,叫做CallAdapter,而Retrofit团队已经提供了RxJava的CallAdapter,这时就需要在app/build.gradle中引入以下依赖:

compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'io.reactivex:rxjava:1.1.0'
compile 'io.reactivex:rxandroid:1.1.0'

在之前例子中,为了使用同步请求方式,需要自己创建线程,过程比较繁琐,所以只能使用异步方式,基于此,使用RxJava来解决异步的问题,代码实例在demo3包下。

###步骤一:请求接口

在创建请求接口时,我们就可以将Service作为Observable返回:

public interface APIService {

    @GET("getBrandBanner.php")
    Observable<HttpResult> getBanner(@Query("uid") String _uid, @Query("token") String _token);

}

###步骤二:创建Retrofit实例

//step1
Retrofit retrofit = new Retrofit.Builder().baseUrl(API_URL).addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJavaCallAdapterFactory.create()).build();
APIService apiService = retrofit.create(APIService.class);
Observable<HttpResult> httpResultObservable = apiService.getBanner("10915", "58524bb42c9ca");

使用CallAdapter这种机制,可以在 Retrofit Builder 链中调用addCallAdapterFactory方法。

###步骤三:请求

httpResultObservable.subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<HttpResult>() {
    @Override
    public void onCompleted() {
        tv_show.append("请求结束");
    }

    @Override
    public void onError(Throwable e) {
        tv_show.append("请求错误");
    }

    @Override
    public void onNext(HttpResult httpResult) {
        tv_show.append(httpResult.data.pc.number);
    }

    @Override
    public void onStart() {
        tv_show.append("请求开始");
    }
});

上面指定subscribe发生在IO线程中,而指定的Subscriber的回调发生在Android的UI线程中。

呼~~~~,终于把上面的例子写完了,天已经黑了,看看还有什么要讲的,好吧,每次这样请求无意间代码量就增多了,而我们理想中的请求方式应该是这样的,在Activity中是这样的请求的:

private void getNews() {
    MainHttpRequest.getInstance().getBanner(new ListenerSubscriber<ArrayList<NewsBean>>(getNewsListener, DemoActivity4.this));
}
接着通过回调获取我们想要的数据:
private OnFunctionListener getNewsListener = new OnFunctionListener<ArrayList<NewsBean>>() {
    @Override
    public void success(ArrayList<NewsBean> o) {
        tv_show.setText(o.get(0).lname);
    }

    @Override
    public void fail(String message) {

    }

};
并且服务端返回的信息,我们应该是抽取其中有用的信息,而code 、message、success、client等等信息不是我们应该关心的,就拿下面的json串来说:
{
    "code":"200",
    "message":"数据返回成功",
    "success":true,
    "data":[
        {
            "id":"1",
            "lname":"香水合集 | 该换上适合秋天的味道啦~"
        },
        {
            "id":"3",
            "lname":"IOPE水滢多效气垫腮红"
        },
        {
            "id":"0",
            "lname":"单品小记∣毛孔收收收?痘痘消消消?"
        },
        {
            "id":"4",
            "lname":"雅诗兰黛肌透修护精萃蜜"
        }
    ]
}

我们应该是关心data节点下的json串,因此,不管是为了省代码量还是获取数据方便,我们都有必要对这些进行一定量的封装。 ##实例四:封装

还是拿上面的json串来说事,在这json串中我们业务层应该是只关心data节点下的数据,因此定义一个HttpResult类:

public class HttpResult<T> {

    private int code;
    private String message;
    private boolean success;

    private T data;


    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}


接着自定义一个Func1的类,用于变换时获取data部分的数据:
/**
 * 用来统一处理Http的resultCode,并将HttpResult的Data部分剥离出来返回给subscriber
 *
 * @param <T> Subscriber真正需要的数据类型,也就是Data部分的数据类型
 */
public class HttpResultFunc<T> implements Func1<HttpResult<T>, T> {

    @Override
    public T call(HttpResult<T> httpResult) {
        if (httpResult.isSuccess()) {
            Log.e("TAG", "response error");
        }
        return httpResult.getData();
    }
}
自定义一个Converter类用于Gson解析:
public class ResponseConvertFactory extends Converter.Factory {

    private final Gson gson;

    public static ResponseConvertFactory create() {
        return create(new Gson());
    }

    public static ResponseConvertFactory create(Gson gson) {
        return new ResponseConvertFactory(gson);
    }


    private ResponseConvertFactory(Gson gson) {
        if (gson == null) {
            throw new NullPointerException("gson == null");
        }
        this.gson = gson;
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
                                                            Retrofit retrofit) {
        return new GsonResponseBodyConverter<>(gson, type);
    }

}
``` class GsonResponseBodyConverter implements Converter

}

</br>
</br>
这样定义后,我们可以在底层根据返回数据的一些参数来判别一些错误类型,方便处理。

创建一个单例的Http类:

public class Http {

public static final String BASE_URL = "http://n1.glh.la/apps_T1/";

private static final int DEFAULT_TIMEOUT = 5;

private Retrofit retrofit;


//构造方法私有
private Http() {
    OkHttpClient.Builder builder = new OkHttpClient.Builder();
    builder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
    retrofit = new Retrofit.Builder()
            .client(builder.build())
            .addConverterFactory(ResponseConvertFactory.create())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .baseUrl(BASE_URL)
            .build();

}

private static class SingletonHolder {
    private static final Http INSTANCE = new Http();
}

public Retrofit getRetrofit() {
    return retrofit;
}

public static Http getInstance() {
    return SingletonHolder.INSTANCE;
}


public <T> void getData(Observable<T> o, Subscriber<T> s) {
    o.subscribeOn(Schedulers.io())
            .unsubscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(s);
}

}

</br>
</br>
最后自定义一个Subscriber类,用于请求监听,比如在这里显示一个加载框或者是对一些错误码的处理:


public class ListenerSubscriber extends Subscriber {

private OnFunctionListener mSubscriberOnNextListener;
private Context context;


public ListenerSubscriber(OnFunctionListener mSubscriberOnNextListener, Context context) {
    this.mSubscriberOnNextListener = mSubscriberOnNextListener;
    this.context = context;
}


@Override
public void onStart() {
}

@Override
public void onCompleted() {
}

@Override
public void onError(Throwable e) {
    if (mSubscriberOnNextListener != null) {
        mSubscriberOnNextListener.fail("error");
    }
}

/**
 * 将onNext方法中的返回结果交给Activity或Fragment自己处理
 *
 * @param t 创建Subscriber时的泛型类型
 */
@Override
public void onNext(T t) {
    if (mSubscriberOnNextListener != null) {
        mSubscriberOnNextListener.success(t);
    }
}

}

</br>
</br>

public interface OnFunctionListener {
void success(T t);
void fail(String message);
}


</br>
</br>
整体封装完毕后,再在业务层将上面请求的方式再次进行封装一遍:

/**

  • 首页相关请求
  • Created by glh on 2016-12-15.
    */
    public interface HomeCarouselService {
    @GET(“getHomeCarousel.php”)
    Observable<HttpResult<ArrayList>> getNews();
    }

/**

  • 首页相关的网络请求

  • Created by glh on 2016-12-14.
    */
    public class MainHttpRequest {

    private HomeCarouselService mHomeCarouselService;
    private Http mHttpMethods;

    private MainHttpRequest() {
    mHttpMethods = Http.getInstance();
    mHomeCarouselService = mHttpMethods.getRetrofit().create(HomeCarouselService.class);
    }

    private static class SingletonHolder {
    private static final MainHttpRequest INSTANCE = new MainHttpRequest();
    }

    //获取单例
    public static MainHttpRequest getInstance() {
    return SingletonHolder.INSTANCE;
    }

    /**

    • 获取广告
    • @param subscriber 由调用者传过来的观察者对象
      */
      public void getBanner(Subscriber<ArrayList> subscriber) {
      Observable observable = mHomeCarouselService.getNews()
      .map(new HttpResultFunc<ArrayList>());
      mHttpMethods.getData(observable, subscriber);
      }

}

</br>
</br>

创建实体类:


public class NewsBean {
public String id;
public String lname;
public String tid;
public String imgurl;
public String desc;
public String ptype;
public String url;
}



完整源码在源文件的demo4中。

<br>
<br>
##项目下载地址
<br>
> 以下是完整的github项目地址,欢迎多多star和fork。
> github项目源码地址:[点击【项目源码】](https://github.com/LinhaiGu/Retrofit_RxJava)
目前在做android的代码混淆,没有混淆之前,代码运行正常,当打开混淆之后,代码运行时出现如下差错: ``` 03-13 10:39:59.936 6285 6285 E AndroidRuntime: java.lang.IllegalArgumentException: Unable to create call adapter for class io.reactivex.b 03-13 10:39:59.936 6285 6285 E AndroidRuntime: for method RequestApis.getCameraInfo 03-13 10:39:59.936 6285 6285 E AndroidRuntime: at retrofit2.ServiceMethod$Builder.methodError() 03-13 10:39:59.936 6285 6285 E AndroidRuntime: at retrofit2.ServiceMethod$Builder.createCallAdapter() 03-13 10:39:59.936 6285 6285 E AndroidRuntime: at retrofit2.ServiceMethod$Builder.build() 03-13 10:39:59.936 6285 6285 E AndroidRuntime: at retrofit2.Retrofit.loadServiceMethod() 03-13 10:39:59.936 6285 6285 E AndroidRuntime: at retrofit2.Retrofit$1.invoke() 03-13 10:39:59.936 6285 6285 E AndroidRuntime: at java.lang.reflect.Proxy.invoke(Proxy.java:393) 03-13 10:39:59.936 6285 6285 E AndroidRuntime: at $Proxy0.getCameraInfo(Unknown Source) 03-13 10:39:59.936 6285 6285 E AndroidRuntime: at com.xxx.helper.RetrofitHelper.getCameraInfo() ...... 03-13 10:39:59.936 6285 6285 E AndroidRuntime: Caused by: java.lang.IllegalStateException: Flowable return type must be parameterized as Flowable<Foo> or Flowable<? extends Foo> 03-13 10:39:59.936 6285 6285 E AndroidRuntime: at retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory.get() 03-13 10:39:59.936 6285 6285 E AndroidRuntime: at retrofit2.Retrofit.nextCallAdapter() 03-13 10:39:59.936 6285 6285 E AndroidRuntime: at retrofit2.Retrofit.callAdapter() ``` ``` 其中proguard配置: # Retrofit -dontwarn retrofit2.** -dontwarn org.codehaus.mojo.** -keep class retrofit2.** { *; } -keepattributes Signature -keepattributes Exceptions -keepattributes *Annotation* # Platform calls Class.forName on types which do not exist on Android to determine platform. -dontnote retrofit2.Platform # Platform used when running on RoboVM on iOS. Will not be used at runtime. -dontnote retrofit2.Platform$IOS$MainThreadExecutor # Platform used when running on Java 8 VMs. Will not be used at runtime. -dontwarn retrofit2.Platform$Java8 # Retain generic type information for use by reflection by converters and adapters. -keepattributes Signature # Retain declared checked exceptions for use by a Proxy instance. -keepattributes Exceptions -dontwarn retrofit2.adapter.rxjava.CompletableHelper$** # https://github.com/square/retrofit/issues/2034 #To use Single instead of Observable in Retrofit interface -keepnames class rx.Single #Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and # EnclosingMethod is required to use InnerClasses. -keepattributes Signature, InnerClasses, EnclosingMethod # Retain service method parameters when optimizing. -keepclassmembers,allowobfuscation interface * { @retrofit2.http.* <methods>; } -keepattributes RuntimeVisibleAnnotations -keepattributes RuntimeInvisibleAnnotations -keepattributes RuntimeVisibleParameterAnnotations -keepattributes RuntimeInvisibleParameterAnnotations -keepattributes EnclosingMethod -keepclasseswithmembers interface * { @retrofit2.* <methods>; } -keepclasseswithmembers class * { @retrofit2.http.* <methods>; } # OkHttp3 -keepattributes Signature -keepattributes *Annotation* -keep class okhttp3.** { *; } -keep interface okhttp3.** { *; } -dontwarn okhttp3.** #OkHttp -keep class com.squareup.okhttp.** { *; } -keep interface com.squareup.okhttp.** { *; } -dontwarn com.squareup.okhttp.** -dontwarn okio.** ``` jar版本: adapter-rxjava2-2.4.0.jar retrofit-2.4.0.jar reactive-streams-1.0.2.jar rxjava-2.1.14.jar rxandroid-2.0.2.aar 有哪位大神帮忙看下,如何解决?
©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:上身试试 返回首页