Rxjava 和 Retrofit 的封装使用

前言

三篇比较好的文章,分别简述RxJava、Retrofit2、RxJava&Retrofit2
给 Android 开发者的 RxJava 详解 作者:扔物线
Retrofit2 完全解析 探索与okhttp之间的关系 作者:Hongyang
RxJava 与 Retrofit 结合的最佳实践 作者:tough1985
这上面是我学习的时候主要看的一些博客,已经很全了,而且讲的也很好,这篇文章主要是记录开发中你可能会用到的,已经自己开发中的具体使用以及遇到的一些问题。

基本使用代码

gradle的配置

	compile 'io.reactivex:rxandroid:1.2.0'
    compile 'io.reactivex:rxjava:1.1.5'
    compile 'com.squareup.retrofit2:retrofit:2.0.2'
    compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
    compile 'com.squareup.retrofit2:converter-gson:2.0.2'
    compile 'com.squareup.okhttp3:okhttp:3.3.1'
    compile 'com.squareup.okio:okio:1.8.0'
    compile 'com.github.orhanobut:logger:1.12'

这里我用compile 'com.github.orhanobut:logger:1.12’代替了OKhttp的compile ‘com.squareup.okhttp3:logging-interceptor:(insert latest version)’,是为了方便看JSON,需要注意的是如果你使用logging-interceptor,那么你的版本号就需要和OKhttp的版本号一致。另外logger的拦截代码在后面会给出。

NetWork: Request参数获取 & Response缓存 & log日志拦截 & Header加密处理

第一步 缓存

private static final File cacheFile = new File(AppApplication.sInstance.getCacheDir().getAbsolutePath() + File.separator + "data" + File.separator + "NetCache");
	private static final Cache cache = new Cache(cacheFile, 1024 * 1024 * 50);
	private static final Interceptor cacheInterceptor = new Interceptor() {
		@Override
		public Response intercept(Chain chain) throws IOException {
			Request request = chain.request();
			if (!NetUtil.isConnected()) {
				request = request.newBuilder()
						.cacheControl(CacheControl.FORCE_CACHE)
						.build();
			}
			Response response = chain.proceed(request);
			if (NetUtil.isConnected()) {
				int maxAge = 0;
				// 有网络时, 不缓存, 最大保存时长为0
				response.newBuilder()
						.header("Cache-Control", "public, max-age=" + maxAge)
						.removeHeader("Pragma")
						.build();
			} else {
				// 无网络时,设置超时为4周
				int maxStale = 60 * 60 * 24 * 28;
				response.newBuilder()
						.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
						.removeHeader("Pragma")
						.build();
			}
			return response;
		}
	};

第二步 创建 okHttpClient & Header 加密处理 & Request 参数获取

大部分项目都会在header里面放一些验证签名的机制,所以你就需要一个共同的OKhttp,这里直接在创建的时候 addHeader就行了。
另外这里你每次调用网络请求的时候,Okhttp的addHeader等的方法每次都会调用,所以不用担心拿到的TimeStamp不是最新的。
当然,如果不想在Retrofit的接口里面动态添加@Header,那就直接放进addHeader里面就行,没有的情况下,给一个空的字符串就行了。

	private static final OkHttpClient okHttpClient = new OkHttpClient.Builder()
			.readTimeout(120, TimeUnit.SECONDS)
			.connectTimeout(120, TimeUnit.SECONDS)
			.writeTimeout(120,TimeUnit.SECONDS)
			.addNetworkInterceptor(cacheInterceptor)
			.addInterceptor(cacheInterceptor)
			.cache(cache)
			.addInterceptor(new MyLogInterceptor().setLevel(AppApplication.debug ? MyLogInterceptor.Level.BODY : MyLogInterceptor.Level.NONE))
			.addInterceptor(chain -> {
				Request request = chain.request();
				String sign_request_body = getOriginalSign(request);
				request = request
						.newBuilder()
						.addHeader("Version", getHeader(VERSION, ""))
						.addHeader("Timestamp", getHeader(TIMESTAMP, ""))
						.addHeader("Token", getHeader(TOKEN, ""))
						.addHeader("Sign", getHeader(SIGN, sign_request_body))
						.build();
				return chain.proceed(request);
			})
			.build();

Request 请求参数的获取:

private static String getOriginalSign(Request request) {
		String url = request.url() + "";
		String sign_request_body = "";
		String method = request.method();

		if ("POST".equals(method))
			sign_request_body = bodyToString(request);
		else {
			String[] body = url.split("\\?");
			if (body.length > 1)
				sign_request_body = body[1];
		}
		return sign_request_body;
	}

private static String bodyToString(final Request request) {

		try {
			final Request copy = request.newBuilder().build();
			final Buffer buffer = new Buffer();
			copy.body().writeTo(buffer);
			return buffer.readUtf8();
		} catch (final IOException e) {
			return "did not work";
		}
	}

Retrofit 的创建 & Service 的创建

private static final CallAdapter.Factory rxJavaCallAdapterFactory = RxJavaCallAdapterFactory.create();
public static Retrofit getRetrofit() {
		if (retrofit == null) {
			retrofit = new Retrofit.Builder()
					.callFactory(okHttpClient)
					.baseUrl(ApiUrls.BASE_URL)
					.addConverterFactory(GsonConverterFactory.create())
					.addCallAdapterFactory(rxJavaCallAdapterFactory)
					.build();
		}
		return retrofit;
	}

	public static <S> S createService(Class<S> serviceClass) {
		return getRetrofit().create(serviceClass);
	}

NetWork 的完整代码

import com.facebook.stetho.okhttp3.StethoInterceptor;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

import okhttp3.Cache;
import okhttp3.CacheControl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import okio.Buffer;
import retrofit2.CallAdapter;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

public class NetWork {

	private static final String OKHTTP = "okhttp";

	private NetWork() {
	}

	public static void init() {
		getRetrofit();
	}

	private static final File cacheFile = new File(AppApplication.sInstance.getCacheDir().getAbsolutePath() + File.separator + "data" + File.separator + "NetCache");
	private static final Cache cache = new Cache(cacheFile, 1024 * 1024 * 50);
	private static final Interceptor cacheInterceptor = new Interceptor() {
		@Override
		public Response intercept(Chain chain) throws IOException {
			Request request = chain.request();
			if (!NetUtil.isConnected()) {
				request = request.newBuilder()
						.cacheControl(CacheControl.FORCE_CACHE)
						.build();
			}
			Response response = chain.proceed(request);
			if (NetUtil.isConnected()) {
				int maxAge = 0;
				// 有网络时, 不缓存, 最大保存时长为0
				response.newBuilder()
						.header("Cache-Control", "public, max-age=" + maxAge)
						.removeHeader("Pragma")
						.build();
			} else {
				// 无网络时,设置超时为4周
				int maxStale = 60 * 60 * 24 * 28;
				response.newBuilder()
						.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
						.removeHeader("Pragma")
						.build();
			}
			return response;
		}
	};


	private static final int VERSION = 1;
	private static final int TIMESTAMP = VERSION + 1;
	private static final int TOKEN = TIMESTAMP + 1;
	private static final int SIGN = TOKEN + 1;


	private static Retrofit retrofit;
	private static final CallAdapter.Factory rxJavaCallAdapterFactory = RxJavaCallAdapterFactory.create();
	private static final OkHttpClient okHttpClient = new OkHttpClient.Builder()
			.readTimeout(120, TimeUnit.SECONDS)
			.connectTimeout(120, TimeUnit.SECONDS)
			.writeTimeout(120,TimeUnit.SECONDS)
			.addNetworkInterceptor(cacheInterceptor)
			.addNetworkInterceptor(new StethoInterceptor())
			.addInterceptor(cacheInterceptor)
			.cache(cache)
			.addInterceptor(new MyLogInterceptor().setLevel(Application.debug ? MyLogInterceptor.Level.BODY : MyLogInterceptor.Level.NONE))
			.addInterceptor(chain -> {
				Request request = chain.request();
				String sign_request_body = getOriginalSign(request);
				request = request
						.newBuilder()
						.addHeader("Version", getHeader(VERSION, ""))
						.addHeader("Timestamp", getHeader(TIMESTAMP, ""))
						.addHeader("Token", getHeader(TOKEN, ""))
						.addHeader("Sign", getHeader(SIGN, sign_request_body))
						.build();
				return chain.proceed(request);
			})
			.build();

	private static String getOriginalSign(Request request) {
		String url = request.url() + "";
		String sign_request_body = "";
		String method = request.method();

		if ("POST".equals(method))
			sign_request_body = bodyToString(request);
		else {
			String[] body = url.split("\\?");
			if (body.length > 1)
				sign_request_body = body[1];
		}

//		sign_request_body = sortRequestBody(sign_request_body);
		return sign_request_body;
	}

	private static String sortRequestBody(String sign_request_body) {
		TLog.d(OKHTTP + " SORTREQUESTBODY ", sign_request_body);
		String[] bodys = sign_request_body.split("&");
		sign_request_body = "";
		if (bodys.length > 0) {
			Arrays.sort(bodys);
			TLog.d(OKHTTP + " sort ", Arrays.toString(bodys));

			for (int i = 0; i < bodys.length; i++) {
				if (!TextUtils.isEmpty(bodys[i]))
					sign_request_body += bodys[i] + ":";
			}

		}
		TLog.d(OKHTTP + " sign_request_body ", sign_request_body);
		return sign_request_body;
	}

	private static String bodyToString(final Request request) {

		try {
			final Request copy = request.newBuilder().build();
			final Buffer buffer = new Buffer();
			copy.body().writeTo(buffer);
			return buffer.readUtf8();
		} catch (final IOException e) {
			return "did not work";
		}
	}

	private static String getHeader(int type, String sign_request_body) {
		String s = "";
		switch (type) {
			case VERSION:
                s = CommonUtil.getVersion();
				TLog.d(OKHTTP, "header Version: " + s);
				break;

			case TIMESTAMP:
				s = TimeUtils.getTime(System.currentTimeMillis());
				TLog.d(OKHTTP, "header Timestamp: " + s);
				break;

			case TOKEN:
				s = SPUtils.getInstance().getString(SPUtils.TOKEN,"");
//                s = "77b23a227a86cdde4a29d1ace10bec34";
				TLog.d(OKHTTP, "header Token: " + s);
				break;

			case SIGN:
				s = MD5.md5(sign_request_body + "salt=" + SPUtils.getInstance().getString(SPUtils.SALT));
//                TLog.d("md5 test", MD5.md5("gender=0:position=2:sort=0:type_id=1:salt=04fe9cac95").equals("9cfe7c434f0ec54b1a853731a1c7ca11") + "");
				TLog.d(OKHTTP, "header Sign: " + s);
				break;
		}

		return s;
	}

	public static Retrofit getRetrofit() {
		if (retrofit == null) {
			retrofit = new Retrofit.Builder()
					.callFactory(okHttpClient)
					.baseUrl(ApiUrls.BASE_URL)
					.addConverterFactory(GsonConverterFactory.create())
					.addCallAdapterFactory(rxJavaCallAdapterFactory)
					.build();
		}
		return retrofit;
	}

	public static <S> S createService(Class<S> serviceClass) {
		return getRetrofit().create(serviceClass);
	}

}

Warning
这里说一个比较蠢的事情:
看下这段代码:

public static Gson buildGson() {
        if (gson == null) {
            gson = new GsonBuilder()
                    .setDateFormat("yyyy-MM-dd HH:mm:ss")
                    .setLenient()
                    .create();
        }
        return gson;
    }

我是用的是 google 的 gson,然后出了这个错误:com.google.gson.stream.MalformedJsonException,然后我在上面代码中设置了时间的解析和规定宽松的json格式,但是还是不行,最后找出来是我为了偷懒,想着要上传文件,于是在 header里面添加了addHeader("Content-type", "gizp"),好吧传过去倒是没什么,问题来了,返回值它默认也是这个格式,解析出来一堆乱码,坑了自己一把。引以为戒!!!

Logger

import android.util.Log;

import com.orhanobut.logger.Logger;

import java.io.EOFException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.concurrent.TimeUnit;

import okhttp3.Connection;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.Platform;
import okhttp3.internal.http.HttpEngine;
import okio.Buffer;
import okio.BufferedSource;

import static okhttp3.internal.Platform.INFO;

/**
 * Created by chenjiahuan on 16/6/16.
 */
public class MyLogInterceptor implements Interceptor {

    private static final String TAG = "OKhttp";

    private static final Charset UTF8 = Charset.forName("UTF-8");

    public enum Level {
        /**
         * No logs.
         */
        NONE,
        /**
         * Logs request and response lines.
         * 

*

Example: *

{@code
         * --> POST /greeting http/1.1 (3-byte body)
         *
         * <-- 200 OK (22ms, 6-byte body)
         * }
*/
BASIC, /** * Logs request and response lines and their respective headers. *

*

Example: *

{@code
         * --> POST /greeting http/1.1
         * Host: example.com
         * Content-Type: plain/text
         * Content-Length: 3
         * --> END POST
         *
         * <-- 200 OK (22ms)
         * Content-Type: plain/text
         * Content-Length: 6
         * <-- END HTTP
         * }
*/
HEADERS, /** * Logs request and response lines and their respective headers and bodies (if present). *

*

Example: *

{@code
         * --> POST /greeting http/1.1
         * Host: example.com
         * Content-Type: plain/text
         * Content-Length: 3
         *
         * Hi?
         * --> END GET
         *
         * <-- 200 OK (22ms)
         * Content-Type: plain/text
         * Content-Length: 6
         *
         * Hello!
         * <-- END HTTP
         * }
*/
BODY } public interface Logger { void log(String message); /** * A {@link Logger} defaults output appropriate for the current platform. */ Logger DEFAULT = new Logger() { @Override public void log(String message) { Platform.get().log(INFO, message, null); } }; } public MyLogInterceptor() { this(Logger.DEFAULT); } public MyLogInterceptor(Logger logger) { this.logger = logger; } private final Logger logger; private volatile Level level = Level.NONE; /** * Change the level at which this interceptor logs. */ public MyLogInterceptor setLevel(Level level) { if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead."); this.level = level; return this; } public Level getLevel() { return level; } @Override public Response intercept(Chain chain) throws IOException { Level level = this.level; Request request = chain.request(); if (level == Level.NONE) { return chain.proceed(request); } boolean logBody = level == Level.BODY; boolean logHeaders = logBody || level == Level.HEADERS; RequestBody requestBody = request.body(); boolean hasRequestBody = requestBody != null; Connection connection = chain.connection(); Protocol protocol = connection != null ? connection.protocol() : Protocol.HTTP_1_1; String requestStartMessage = "--> " + request.method() + ' ' + request.url() + ' ' + protocol; if (!logHeaders && hasRequestBody) { requestStartMessage += " (" + requestBody.contentLength() + "-byte body)"; } logger.log(requestStartMessage); if (logHeaders) { if (hasRequestBody) { // Request body headers are only present when installed as a network interceptor. Force // them to be included (when available) so there values are known. if (requestBody.contentType() != null) { logger.log("Content-Type: " + requestBody.contentType()); } if (requestBody.contentLength() != -1) { logger.log("Content-Length: " + requestBody.contentLength()); } } Headers headers = request.headers(); for (int i = 0, count = headers.size(); i < count; i++) { String name = headers.name(i); // Skip headers from the request body as they are explicitly logged above. if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) { logger.log(name + ": " + headers.value(i)); } } if (!logBody || !hasRequestBody) { logger.log("--> END " + request.method()); } else if (bodyEncoded(request.headers())) { logger.log("--> END " + request.method() + " (encoded body omitted)"); } else { Buffer buffer = new Buffer(); requestBody.writeTo(buffer); Charset charset = UTF8; MediaType contentType = requestBody.contentType(); if (contentType != null) { charset = contentType.charset(UTF8); } logger.log(""); if (isPlaintext(buffer)) { logger.log(buffer.readString(charset)); logger.log("--> END " + request.method() + " (" + requestBody.contentLength() + "-byte body)"); } else { logger.log("--> END " + request.method() + " (binary " + requestBody.contentLength() + "-byte body omitted)"); } } } long startNs = System.nanoTime(); Response response; try { response = chain.proceed(request); } catch (Exception e) { logger.log("<-- HTTP FAILED: " + e); throw e; } long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs); ResponseBody responseBody = response.body(); long contentLength = responseBody.contentLength(); String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length"; logger.log("<-- " + response.code() + ' ' + response.message() + ' ' + response.request().url() + " (" + tookMs + "ms" + (!logHeaders ? ", " + bodySize + " body" : "") + ')'); if (logHeaders) { Headers headers = response.headers(); for (int i = 0, count = headers.size(); i < count; i++) { logger.log(headers.name(i) + ": " + headers.value(i)); } if (!logBody || !HttpEngine.hasBody(response)) { logger.log("<-- END HTTP"); } else if (bodyEncoded(response.headers())) { logger.log("<-- END HTTP (encoded body omitted)"); } else { BufferedSource source = responseBody.source(); source.request(Long.MAX_VALUE); // Buffer the entire body. Buffer buffer = source.buffer(); Charset charset = UTF8; MediaType contentType = responseBody.contentType(); if (contentType != null) { try { charset = contentType.charset(UTF8); } catch (UnsupportedCharsetException e) { logger.log(""); logger.log("Couldn't decode the response body; charset is likely malformed."); logger.log("<-- END HTTP"); return response; } } if (!isPlaintext(buffer)) { logger.log(""); logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)"); return response; } if (contentLength != 0) { logger.log(""); logger.log(buffer.clone().readString(charset)); com.orhanobut.logger.Logger.json(buffer.clone().readString(charset)); } logger.log("<-- END HTTP (" + buffer.size() + "-byte body)"); } } return response; } /** * Returns true if the body in question probably contains human readable text. Uses a small sample * of code points to detect unicode control characters commonly used in binary file signatures. */ static boolean isPlaintext(Buffer buffer) throws EOFException { try { Buffer prefix = new Buffer(); long byteCount = buffer.size() < 64 ? buffer.size() : 64; buffer.copyTo(prefix, 0, byteCount); for (int i = 0; i < 16; i++) { if (prefix.exhausted()) { break; } int codePoint = prefix.readUtf8CodePoint(); if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { return false; } } return true; } catch (EOFException e) { return false; // Truncated UTF-8 sequence. } } private boolean bodyEncoded(Headers headers) { String contentEncoding = headers.get("Content-Encoding"); return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity"); } }

这个log的代码全部都是COPY的 HttpLoggingInterceptor这个类的代码,大家可以把我上面的代码copy下来,在260行我添加了Logger的代码,当然最好也保留原来的打印,原因很简单,Logger.json()这个方法只会打印json,如果是错误的字符串,就没有log了。

至此,使用前的准备步骤完毕:创建&扩展&调试Log,接下来,就是使用了,当然,是经过封装加工过的

具体使用

HttpResult的定义 & SuccessCallBack & ErrorCallBack

Json的开发标准而言,基本格式如下:

{
         “status”: // 接口访问成功或者失败的状态码

         “message”:// 接口访问错误的时候返回的错误提示文字,访问成功的时候为空字符串

         “result”:{    // 服务端实际返回的数据
    }
}

这里我们定义泛型,不然用的时候太累赘了

HttpResult

public class HttpResult<T> {

    public int code;

    public String message;

    public T result;

}

ISuccessCallBack

public interface ISuccessCallBack<T> {

	void success(T data);
}

NetUtil

import android.app.Activity;
import android.app.Application;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;

import com.google.gson.JsonSyntaxException;

import java.net.ConnectException;
import java.net.SocketTimeoutException;

public class NetUtil {
	public static void checkHttpException(Throwable mThrowable) {
		if (!NetUtil.isConnected()) {
			ToastUtil.showToast("没有网络");
			return;
		}
		if ((mThrowable instanceof UnknownHostException)) {
			ToastUtil.showToast("网络异常");
		} else if (mThrowable instanceof JsonSyntaxException) {
			ToastUtil.showToast("数据异常");
		} else if (mThrowable instanceof SocketTimeoutException) {
			ToastUtil.showToast("连接超时");
		} else if (mThrowable instanceof ConnectException) {
			ToastUtil.showToast("连接服务器失败");
		} else if (mThrowable instanceof TipException) {
			ToastUtil.showToast(((TipException) mThrowable).tip);
		} else {
			ToastUtil.showToast("操作失败");
		}/* else if (mThrowable instanceof RuntimeException) {
            ToastUtil.showToast(mContext,"程序出错");
        } else {
            ToastUtil.showToast(mContext,"网络异常");
        }*/

		TLog.e("okhttp", mThrowable.toString());
	}
}

简单的使用一下:

第一步 创建接口

import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Query;
import rx.Observable;

public interface CommonApi {

	/**
	 * 获取新版本
	 * @param version
	 * @return
	 */
	@GET(ApiUrls.CORE_APPVERSION_GETVERSION)
	Observable<HttpResult<UpdateAppBean>> getNewVersion(@Query("version") String version);

	/**
	 * 上报地理位置信息
	 * @param longitude
	 * @param latitude
	 * @return
	 */
	@FormUrlEncoded
	@POST(ApiUrls.COMMON_LOCATION_REPORT)
	Observable<HttpResult> updateLocation(@Field("longitude") String longitude,
	                                      @Field("latitude") String latitude);
}

第二步 调用接口

public class CommonApiHelper {
    private static final CommonApi commonApi = NetWork.createService(CommonApi.class);
	
	public void getVersion(String version, ISuccessCallBack callBack){
		commonApi.getNewVersion(CommonUtil.getVersion())
				.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
				.subscribe(updateAppBean -> 
				callBack.success(updateAppBean)
				, NetUtil::checkHttpException));
	}
}

基本使用就到这里吧,对于RxJava和Retrofit的基本使用我就不介绍了,网上一搜一片,而且开篇我就介绍了很好的文章,很详细了,接下来是封装的部分了。


返回值中 code 的统一处理

在开发时,定然需要对服务器返回的某个值做特殊处理,或者是退出,或者是补全个人信息,大部分时候很多接口都需要做相同的处理,于是,统一的回调就显得尤为重要,不单单是减少代码量的问题,也需要考虑维护的成本,以及为了优雅的编码~
HttpFun

public class HttpFunc<T> implements Func1<HttpResult<T>, T> {

	@Override
	public T call(HttpResult<T> httpResult) {
		if (httpResult.code == HttpResult.SUCCESS) {
			return httpResult.result;
		} else if (httpResult.code == ApiErrorCode.FORCE_UPDATE) {
			EventBus.getDefault().post(new ForceUpdateEvent());
			throw new TipException("请更新app后操作");
		} else {
			throw new TipException(httpResult.message);
		}
	}
}

上面的代码是一个例子,HttpFun 继承自 RxJava 的 Func1,在 call() 方法中,对特殊的CODE做特殊的处理。

HttpFun 的实际使用

就以之前的简单使用为例:

public void getVersion(String version, ISuccessCallBack callBack){
		commonApi.getNewVersion(CommonUtil.getVersion())
				.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .map(new HttpFunc<>())
				.subscribe(updateAppBean -> 
				callBack.success(updateAppBean)
				, NetUtil::checkHttpException));
	}

所有的请求都需要调用 map(new HttpFunc<>()),那么,必然就会有统一的处理。

RxUtil

就上面而言,统一的处理已经完成,然而,代码还有优化的余地,所有的请求都会有这三行代码:

.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .map(new HttpFunc<>())

这个时候,就是RxJava大显神威的时候了,尽管你可以自定义方法,把Observeable传进去,但是那样就打断了流式API,工具类 :

public class RxUtil {

    public interface OnClick {
        void onClick();
    }

    /**
     * 统一线程处理
     *
     * @param 
     * @return
     */
    public static <T> Observable.Transformer<T, T> rxSchedulerHelper() {    //compose简化线程
        return observable -> observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .unsubscribeOn(Schedulers.io());
    }

    public static <T> Observable.Transformer<HttpResult, T> applySchedulers() {    //compose简化线程
        return observable -> observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .map(new HttpFunc())
                .unsubscribeOn(Schedulers.io());
    }

}

于是上面的请求就变成了:

public void getVersion(String version, ISuccessCallBack callBack){
		commonApi.getNewVersion(CommonUtil.getVersion())
				.compose(RxUtil.<T>applySchedulers())
				.subscribe(updateAppBean -> 
				callBack.success(updateAppBean)
				, NetUtil::checkHttpException));
	}

从RxJava + Retrofit 的使用上而言,其实这篇文章该结束了,但是想了想,还是把一个参数传递的类贡献出来,我个人觉得方便了,见仁见智吧。

统一形式的参数传递 ParamsMapHelper

我们正常开发的时候,有 get/post,有普通的请求,有上传的操作,就我个人而言还是挺乱的,不管怎么先把代码贴出来:

import android.text.TextUtils;

import java.io.File;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import okhttp3.MediaType;
import okhttp3.RequestBody;

public class FileMapHelper {


    private static final String TAG = "fileMapHelper";

    private Map<String, RequestBody> map;

    public FileMapHelper() {
        this.map = new TreeMap<>(new FileComparator());
    }

    public Map<String, RequestBody> getMap() {
        return map;
    }

    public void putText(String key, String value) {
        if (!TextUtils.isEmpty(value)) {
            RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), value);
            map.put(key, requestBody);
        }
    }

    public void putAudio(String key, String path) {
        File file = new File(path);
        FileNameMap fileNameMap = URLConnection.getFileNameMap();
        String contentTypeFor = fileNameMap.getContentTypeFor(path);
        RequestBody requestBody =
                RequestBody.create(MediaType.parse(contentTypeFor), file);
        map.put(key + "\";filename=\"" + file.getName(), requestBody);
    }

    public void putPic(String key, List<File> fileList) {
        for (int i = 0; i < fileList.size(); i++) {
            RequestBody requestBody = RequestBody.create(MediaType.parse("image/jpg"), fileList.get(i));
            map.put(key + "[" + i + "]" + "\";filename=\"" + fileList.get(i).getName(), requestBody);
        }
    }

    public void putSinglePic(String key, File file) {
        RequestBody requestBody = RequestBody.create(MediaType.parse("image/jpg"), file);
        map.put(key + "\";filename=\"" + file.getName(), requestBody);
    }


    public void putGif(String key, File file) {
        RequestBody requestBody = RequestBody.create(MediaType.parse("image/gif"), file);
        map.put(key + "\";filename=\"" + file.getName(), requestBody);
    }

    public void putVideo(String key, String path) {
        File file = new File(path);
        RequestBody requestBody = RequestBody.create(MediaType.parse("video/mp4"), file);
        map.put(key + "\";filename=\"" + file.getName(), requestBody);
    }

    class FileComparator implements Comparator<String> {

        @Override
        public int compare(String lhs, String rhs) {
            if (lhs.contains("image") && rhs.contains("image")) {
                int start = lhs.indexOf("[");
                int end = lhs.indexOf("]", start);

                String substring = lhs.substring(start + 1, end);
                String substring1 = rhs.substring(start + 1, end);
                if (isNumeric(substring)) {
                    Integer integer = Integer.valueOf(substring);
                    Integer integer1 = Integer.valueOf(substring1);
                    return integer - integer1;
                }
            }
            return -1;
        }

        private boolean isNumeric(String str) {
            for (int i = str.length(); --i >= 0; ) {
                if (!Character.isDigit(str.charAt(i))) {
                    return false;
                }
            }
            return true;
        }
    }
}

我现在使用的代码就变成了这样:

Interface

public interface ChatApi {


    /**
     * 聊天里发送视频
     * video	binary	视频
     * image	binary	视频封面
     *
     * @return
     */
    @Multipart
    @POST(ApiUrls.CHAT_UPLOAD_VIDE)
    Observable<HttpResult<ChatVideoResult>> uploadChatVideo(@PartMap Map<String, RequestBody> map);

    /**
     * 聊天里发送图片
     * video	binary	视频
     * image	binary	视频封面
     *
     * @return
     */
    @Multipart
    @POST(ApiUrls.CHAT_UPLOAD_IMAGE)
    Observable<HttpResult<ChatImageResult>> uploadChatImage(@PartMap Map<String, RequestBody> map);


    /**
     * 漂流瓶-随机获取一个用户的环信id
     *
     * @return
     */
    @FormUrlEncoded
    @GET(ApiUrls.DRIFTING_BOTTLE_THORW)
    Observable<HttpResult<DriftBottleResult>> throwBottle();


    @Multipart
    @POST(ApiUrls.CHAT_UPLOAD_AUDIO)
    Observable<HttpResult<ChatAudioInfo>> uploadChatAudio(@PartMap Map<String, RequestBody> map);

}

Helper

public class ChatApiHelper {
    private static final ChatApi chatApi = NetWork.createService(ChatApi.class);

    public static void uploadChatVideo(String video_key, String video_path, String image_key, String image_path, ISuccessCallBack<ChatVideoResult> iSuccessCallBack) {
        FileMapHelper fileMapHelper = new FileMapHelper();
        fileMapHelper.putVideo(video_key, video_path);
        fileMapHelper.putSinglePic(image_key, new File(image_path));
        chatApi.uploadChatVideo(fileMapHelper.getMap())
                .compose(RxUtil.<ChatVideoResult>applySchedulers())
                .subscribe(chatVideoResult -> iSuccessCallBack.success(chatVideoResult), NetUtil::checkHttpException);
    }

    public static void uploadChatImage(String key, String path, ISuccessCallBack<ChatImageResult> iSuccessCallBack) {
        FileMapHelper fileMapHelper = new FileMapHelper();
        fileMapHelper.putSinglePic(key, new File(path));
        chatApi.uploadChatImage(fileMapHelper.getMap())
                .compose(RxUtil.<ChatImageResult>applySchedulers())
                .subscribe(chatImageResult -> iSuccessCallBack.success(chatImageResult), NetUtil::checkHttpException);
    }

    public static void uploadChatAudio(String key, String path, ISuccessCallBack<ChatAudioInfo> iSuccessCallBack) {
        FileMapHelper fileMapHelper = new FileMapHelper();
        fileMapHelper.putAudio(key, path);
        chatApi.uploadChatAudio(fileMapHelper.getMap())
                .compose(RxUtil.<ChatAudioInfo>applySchedulers())
                .subscribe(chatAudioInfo -> iSuccessCallBack.success(chatAudioInfo), NetUtil::checkHttpException);
    }

    public static void getRandomHXId(ISuccessCallBack<DriftBottleResult> iSuccessCallBack) {
        chatApi.throwBottle()
                .compose(RxUtil.<DriftBottleResult>applySchedulers())
                .subscribe(driftbottleResult -> iSuccessCallBack.success(driftbottleResult), NetUtil::checkHttpException);
    }
}

上面的例子中基本都是要么纯上传,要么就是纯字段(或者没有字段),当然也可以同时都有,只需要直接调用方法就行了。


结束语:
花了一天的时间把之前的博客推倒重来,一个是补充,其实主要的就是,之前写的博客写的太乱了,完全不知道在讲什么,至少我现在能看懂这篇文章在说什么了。当然,也有缺陷,在阅读文章之前,你需要有 lamda 和 RxJava 的基础,不然,代码读起来有点懵,希望对大家有所帮助。

你可能感兴趣的:(开发日常)