Rxjava+retrofit 框架备忘
项目中使用了Rxjava+retrofit 框架来做Android端和服务器端的交互,这两个框架近些年使用频率很高,提笔备忘。
转载注明出处:https://blog.csdn.net/skysukai
1、单独使用Rxjava
项目中有这样一个场景,从服务器去下载一个文件列表。一般来说,我都会把下载操作放在线程中,使用Rxjava怎么达到这个功能呢?直接给出示例伪代码:
private void startDownLoad() {
Observer<String> observer = new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(String fileName) {
Log.i(TAG, String.format("文件%s已下载", fileName));
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "下载出错:" + e.getMessage());
}
@Override
public void onComplete() {
synchronized (mSynObject) {
if (mDownloadList.size() > 0) {
//队列中仍有需要下载的文件,继续进行下载
startDownLoad();
} else {
mIsDownloading = false;
}
}
}
};
Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
List<String> downloadList = currentCase.getDownLoadList();
for (String url : downloadList) {
//去掉文件中的网络地址信息,仅保留文件名
String[] parts = url.split("/");
String fileName = parts[parts.length - 1];
if (localFiles.containsKey(fileName)) {
//本地已下载过文件
String filePath = localFiles.get(fileName);
File file = new File(filePath);
if (!file.exists()) {
//本地文件已不存在,重新下载
S3Util.downLoadFile(file);
}
}
emitter.onNext(fileName);
}
emitter.onComplete();
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
有关Rxjava的扫盲贴传送门.简单说一下,获取了一个文件列表,将下载放到新线程中下载,完成后回调给UI主线程。
2、retrofit+Rxjava搭建的异步网络请求框架
有关retrofit的扫盲贴传送门,最重要的一点,retrofit将Http请求抽象成Java接口。
2.1 服务器返回数据结构
单个数据结构如下:
{
"result": {
"apkName": "string",
"appInfo": "string",
"appNam": "string",
"appUrl": "string",
},
"resultInfo": {
"resultCode": "string",
"resultMsg": "string"
}
}
数据结构列表如下:
{
"result": {
"count": 0,
"data": [
{
"apkName": "string",
"appInfo": "string",
"appNam": "string",
"appUrl": "string",
}
],
"pagenum": 0,
"pagesize": 0
},
"resultInfo": {
"resultCode": "string",
"resultMsg": "string"
}
}
2.2 返回数据定义
根据服务器返回的数据结构,可以统一封装返回的数据结构基类。具体请求结果只需继承基类。
public class BaseResult {
public ResultInfo resultInfo;
final public class ResultInfo {
public int resultCode;
public String resultMsg;
}
public BaseResult() {
this.resultInfo = new ResultInfo();
}
}
以登录接口为例,定义数据结构如下:
public class LoginResult extends BaseResult {
public UserInfo result;
}
其中,UserInfo定义:
public class UserInfo implements Serializable {
public String id;
public int role;
public String nickName;
public String avatar;
public String token;
}
2.3 retrofit实例
一般来说,我们会在项目里设置一个单例的retrofit实例,以供调用。
public class Network {
private HttpService mService;
private static Network sInstance;
private Network() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(new HttpHeaderInterceptor());
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(interceptor);
}
Cache cache = new Cache(UltrasoundApplication.getContext().getCacheDir(), 10_000_000);
builder.cache(cache);
builder.addNetworkInterceptor(new LocalCacheInterceptor());
String baseUrl = ……;
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(builder.build())
.addConverterFactory(GsonConverterFactory.create(JsonUtils.getGson()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
mService = retrofit.create(UltrasoundService.class);
}
static HttpService request() {
if (sInstance == null) {
sInstance = new Network();
}
return sInstance.mService;
}
……
}
2.4 请求接口统一封装
以登录接口为例,给出示例代码:
public interface HttpService {
@POST("user/v1/login")
Observable<LoginResult> login(@Query("phone") String phone, @Query("password") String password);
}
其中,@POST
规定了请求方式,后面user/v1/login
给出了请求的具体URL,会和HttpService的baseurl拼接起来。两个请求参数phone及password,以及他们的请求方式。
2.5 请求包装类统一封装
public class RequestHelper {
public static void login(Context context, String phone, String passwd, RequestListener<LoginResult> listener) {
toSubscribe(Network.request().login(phone, passwd),
new RequestObserver<LoginResult>(context, true) {
@Override
public void onSuccess(LoginResult result) {
listener.onSuccess(result);
}
@Override
public void onFailure(LoginResult result) {
listener.onFailure(result);
}
});
}
@SuppressWarnings("WeakerAccess, unchecked")
public static void toSubscribe(Observable observable, Observer observer) {
observable.compose(SchedulerProvider.request()).retry(RETRY_TIME)
.subscribe(observer);
}
private RequestHelper() {
throw new AssertionError("No instance!");
}
……
}
在网络请求包装类中,只需把具体的请求操作放在这里。如果有其他接口,需同步更新HttpService
定义请求参数和这个RequestHelper
包装类即可。
2.6 发起请求的具体实现
在登录界面,只需处理请求成功和请求失败的回调即可。
RequestHelper.login(this, mName, mPasswd, new RequestListener<LoginResult>() {
@Override
public void onSuccess(LoginResult result) {
UserManager.setUserInfo(result.result);
UserManager.setToken(result.result.token);
……
}
@Override
public void onFailure(LoginResult result) {
……
}
});
3、其他
遇到一个请求接口,需要这样携带数据:
https:……/api/v1/cfg/log/event_log_query?log_type=0&username=test&date_from=03/17/2021%2000:00:00&date_to=03/24/2021%2013:59:59&page=1&page_size=20&_=1616564341402
关键是这两个字段:date_from=03/17/2021%2000:00:00
date_to=03/24/2021%2013:59:59
其中%20
是空格对应的Unicode编码,/
会被转义成Unicode编码0x2F
,date_from
会变成030x2F170x2F2021%2000:00:00
,对应的请求接口写成这样就好了:
@POST("/api/v1/cfg/log/event_log_query")
NetworkCall<LoginHistoryResponseData> getLoginHistory(@Query("log_type") int logType, @Query("username") String userName,
@Query(value = "date_from", encoded = true) String dateFrom,
@Query(value = "date_to", encoded = true) String dateTo,
@Query("page") int page, @Query("page_size") int pageSize);
将date_from
、date_to
预先编码encoded = true
即可。
有关请求中的各个参数,可以参考这篇文章传送门。
4、结语
这套封装了Rxjava和retrofit的网络请求框架应该是经过了一次又一次的重构及实践。在实际的使用过程中易于增加请求接口,系统稳定性高。
相关参考:https://blog.csdn.net/gxflh/article/details/81183985
相关参考:https://www.jianshu.com/p/45cb536be2f4
相关参考:https://blog.csdn.net/weixin_28774815/article/details/80960779
相关参考:https://www.jianshu.com/p/162b36a84e8a