Retrofit2 是目前Android开发主流的网络库,RxJava2也是目前开发者使用的比较多用来更优雅实现异步的库,因为最近业务需求有用到这两个库,就简单分享下它的一个实际使用场景—上传文件
集成RxJava2和Retrofit2
// Rx
compile rootProject.ext.dependencies["rxjava"]
compile rootProject.ext.dependencies["rxandroid"]
compile rootProject.ext.dependencies["rxpermissions"]
// network
compile rootProject.ext.dependencies["retrofit"]
compile rootProject.ext.dependencies["retrofit-converter-gson"]
compile rootProject.ext.dependencies["retrofit-adapter-rxjava2"]
compile rootProject.ext.dependencies["logging-interceptor"]
上面我将依赖统一抽取出来了,也建议大家这样做。
具体配置文件在根目录下的config.gradle
ext {
android = [
compileSdkVersion: 25,
buildToolsVersion: '25.0.3',
applicationId : "com.tencent.bugly",
minSdkVersion : 16,
targetSdkVersion : 25,
javaVersion : JavaVersion.VERSION_1_7,
versionCode : 1,
versionName : "1.0.0"
]
def dependVersion = [
rxJava : "2.0.7",
rxandroid : "2.0.1",
rxpermissions : "0.9.3@aar",
retrofit : "2.2.0",
okhttp3 : "3.4.1",
]
dependencies = [
// rx
"rxjava" : "io.reactivex.rxjava2:rxjava:${dependVersion.rxJava}",
"rxandroid" : "io.reactivex.rxjava2:rxandroid:${dependVersion.rxandroid}",
"rxpermissions" : "com.tbruyelle.rxpermissions2:rxpermissions:${dependVersion.rxpermissions}",
// network
"retrofit" : "com.squareup.retrofit2:retrofit:${dependVersion.retrofit}",
"retrofit-converter-gson" : "com.squareup.retrofit2:converter-gson:${dependVersion.retrofit}",
"retrofit-adapter-rxjava2" : "com.squareup.retrofit2:adapter-rxjava2:${dependVersion.retrofit}",
// 网络日志拦截
"logging-interceptor" : "com.squareup.okhttp3:logging-interceptor:${dependVersion.okhttp3}",
]
}
这是依赖的部分,集成之后会从maven仓库中将我们需要的库下载到本地,这样我就可以使用了 ,不用说,这些大家都懂。
封装OkHttpManager类
/**
* OkHttp管理类.
*
* @author devilwwj
* @since 2017/7/12
*/
public class OkHttpManager {
private static OkHttpClient okHttpClient;
/**
* 获取OkHttp单例,线程安全.
*
* @return 返回OkHttpClient单例
*/
public static OkHttpClient getInstance() {
if (okHttpClient == null) {
synchronized (OkHttpManager.class) {
if (okHttpClient == null) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (BuildConfig.DEBUG) {
// 拦截okHttp的日志,如果开启了会导致上传回调被调用两次
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(interceptor);
}
// 超时时间
builder.connectTimeout(15, TimeUnit.SECONDS);// 15S连接超时
builder.readTimeout(20, TimeUnit.SECONDS);// 20s读取超时
builder.writeTimeout(20, TimeUnit.SECONDS);// 20s写入超时
// 错误重连
builder.retryOnConnectionFailure(true);
okHttpClient = builder.build();
}
}
}
return okHttpClient;
}
}
这个类主要是获取OkHttpClient示例,设置它的一些参数,比如超时时间,拦截器等等.
封装RetrofitClient类
/**
* RetrofitClient.
*
* @author devilwwj
* @since 2017/7/12
*/
public class RetrofitClient {
private static RetrofitClient mInstance;
private static Retrofit retrofit;
private RetrofitClient() {
retrofit = RetrofitBuilder.buildRetrofit();
}
/**
* 获取RetrofitClient实例.
*
* @return 返回RetrofitClient单例
*/
public static synchronized RetrofitClient getInstance() {
if (mInstance == null) {
mInstance = new RetrofitClient();
}
return mInstance;
}
private <T> T create(Class<T> clz) {
return retrofit.create(clz);
}
/**
* 单上传文件的封装.
*
* @param url 完整的接口地址
* @param file 需要上传的文件
* @param fileUploadObserver 上传回调
*/
public void upLoadFile(String url, File file,
FileUploadObserver<ResponseBody> fileUploadObserver) {
UploadFileRequestBody uploadFileRequestBody =
new UploadFileRequestBody(file, fileUploadObserver);
create(UploadFileApi.class)
.uploadFile(url, MultipartBuilder.fileToMultipartBody(file,
uploadFileRequestBody))
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(fileUploadObserver);
}
/**
* 多文件上传.
*
* @param url 上传接口地址
* @param files 文件列表
* @param fileUploadObserver 文件上传回调
*/
public void upLoadFiles(String url, List<File> files,
FileUploadObserver<ResponseBody> fileUploadObserver) {
create(UploadFileApi.class)
.uploadFile(url, MultipartBuilder.filesToMultipartBody(files,
fileUploadObserver))
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(fileUploadObserver);
}
}
这个是Retrofit客户端类,获取它的单例然后去调用它的上传文件的方法,可以看到我这里封装了两个方法,uploadFile是上传单个文件,uploadFiles方法上传多个文件.
因为我们需要构造一个Retrofit对象,所以这里有一个RetrofitBuilder类:
/**
* Retrofit构造器.
*
* @author devilwwj
* @since 2017/7/13
*/
public class RetrofitBuilder {
private static Retrofit retrofit;
public static synchronized Retrofit buildRetrofit() {
if (retrofit == null) {
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
GsonConverterFactory gsonConverterFactory = GsonConverterFactory.create(gson);
retrofit = new Retrofit.Builder().client(OkHttpManager.getInstance())
.baseUrl(AppConfig.HTTP_SERVER)
.addConverterFactory(gsonConverterFactory)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
return retrofit;
}
}
可以看到,想构造Retrofit对象是需要获取OkhttpClient实例的。
定义上传文件接口
/**
* 上传API.
*
* @author devilwwj
* @since 2017/7/12
*/
public interface UploadFileApi {
String UPLOAD_FILE_URL = AppConfig.HTTP_SERVER + "file/upload";
@POST
Observable<ResponseBody> uploadFile(@Url String url, @Body MultipartBody body);
}
这里就是Retrofit定义接口的形式,通过注解来表示各个参数,@POST表示发起post请求,@Url表示这是个请求地址,@Body表示这是请求体,关于Retrofit的各种注解的使用这里不多说,大家可以自行了解。
构造MultipartBody
上一步定义好了上传的接口,我们最终是要去构造MultipartBody,这一块就需要跟后台同学进行沟通了,根据接口定义来实现,这里是我们的实现:
/**
* MultipartBuilder.
*
* @author devilwwj
* @since 2017/7/13
*/
public class MultipartBuilder {
/**
* 单文件上传构造.
*
* @param file 文件
* @param requestBody 请求体
* @return MultipartBody
*/
public static MultipartBody fileToMultipartBody(File file, RequestBody requestBody) {
MultipartBody.Builder builder = new MultipartBody.Builder();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("fileName", file.getName());
jsonObject.addProperty("fileSha", Utils.getFileSha1(file));
jsonObject.addProperty("appId", "test0002");
builder.addFormDataPart("file", file.getName(), requestBody);
builder.addFormDataPart("params", jsonObject.toString());
builder.setType(MultipartBody.FORM);
return builder.build();
}
/**
* 多文件上传构造.
*
* @param files 文件列表
* @param fileUploadObserver 文件上传回调
* @return MultipartBody
*/
public static MultipartBody filesToMultipartBody(List<File> files,
FileUploadObserver<ResponseBody> fileUploadObserver) {
MultipartBody.Builder builder = new MultipartBody.Builder();
JsonArray jsonArray = new JsonArray();
Gson gson = new Gson();
for (File file : files) {
UploadFileRequestBody uploadFileRequestBody =
new UploadFileRequestBody(file, fileUploadObserver);
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("fileName", file.getName());
jsonObject.addProperty("fileSha", Utils.getFileSha1(file));
jsonObject.addProperty("appId", "test0002");
jsonArray.add(jsonObject);
LogUtil.d(jsonObject.toString());
builder.addFormDataPart("file", file.getName(), uploadFileRequestBody);
}
builder.addFormDataPart("params", gson.toJson(jsonArray));
LogUtil.d(gson.toJson(jsonArray));
builder.setType(MultipartBody.FORM);
return builder.build();
}
}
自定义RequestBody
构造MultipartBody是需要去创建每个文件对应的ReqeustBody,但我们这边需要监听到文件上传成功、失败和进度的状态,所以需要去自定义:
/**
* 上传文件请求body.
*
* @author devilwwj
* @since 2017/7/12
*/
public class UploadFileRequestBody extends RequestBody {
private RequestBody mRequestBody;
private FileUploadObserver<ResponseBody> fileUploadObserver;
public UploadFileRequestBody(File file, FileUploadObserver<ResponseBody> fileUploadObserver) {
this.mRequestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
this.fileUploadObserver = fileUploadObserver;
}
@Override
public MediaType contentType() {
return mRequestBody.contentType();
}
@Override
public long contentLength() throws IOException {
return mRequestBody.contentLength();
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
CountingSink countingSink = new CountingSink(sink);
BufferedSink bufferedSink = Okio.buffer(countingSink);
// 写入
mRequestBody.writeTo(bufferedSink);
// 刷新
// 必须调用flush,否则最后一部分数据可能不会被写入
bufferedSink.flush();
}
/**
* CountingSink.
*/
protected final class CountingSink extends ForwardingSink {
private long bytesWritten = 0;
public CountingSink(Sink delegate) {
super(delegate);
}
@Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
bytesWritten += byteCount;
if (fileUploadObserver != null) {
fileUploadObserver.onProgressChange(bytesWritten, contentLength());
}
}
}
}
这里有个RxJava2的Observer的抽象类,主要是用来收到Rxjava2的事件:
/**
* 上传文件的RxJava2回调.
*
* @author devilwwj
* @since 2017/7/12
*
* @param <T> 模板类
*/
public abstract class FileUploadObserver<T> extends DefaultObserver<T> {
@Override
public void onNext(T t) {
onUploadSuccess(t);
}
@Override
public void onError(Throwable e) {
onUploadFail(e);
}
@Override
public void onComplete() {
}
// 上传成功的回调
public abstract void onUploadSuccess(T t);
// 上传失败回调
public abstract void onUploadFail(Throwable e);
// 上传进度回调
public abstract void onProgress(int progress);
// 监听进度的改变
public void onProgressChange(long bytesWritten, long contentLength) {
onProgress((int) (bytesWritten * 100 / contentLength));
}
}
ok,到现在完整的代码实现已经说完。
具体使用方法
RetrofitClient.getInstance().upLoadFiles(UploadFileApi.UPLOAD_FILE_URL, files,
new FileUploadObserver<ResponseBody>() {
@Override
public void onUploadSuccess(ResponseBody responseBody) {
if (responseBody == null) {
LogUtil.e("responseBody null");
return;
}
try {
JSONObject jsonObject = new JSONObject(responseBody.string());
ArrayList<String> fileIds = new ArrayList<String>();
fileIds.add(jsonObject.getString("fileId"));
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void onUploadFail(Throwable e) {
}
@Override
public void onProgress(int progress) {
LogUtil.d(String.valueOf(progress));
}
});
笔者这里是上传到文件服务器,成功会返回对应的fileId。
总结
通篇代码实现很多,但可以看到使用Retrofit2和RxJava2的结合起来使用还是挺方便的,再也不用自己去控制线程的切换了,也不用去关注http的具体实现,少写了不少代码,实现起来也优雅不少,希望这篇文章能帮助到大家。
网友评论