Android中封装OkHttp,处理网络请求
Android中常用的组合是OkHttp+Retrofit。
OKHttp
- 添加权限
<uses-permission android:name="android.permission.INTERNET" />
- 添加依赖
//region 请求网络相关
//提示:region这种语法是最新的,推荐使用这种,也更容易阅读,不建议在同一个文件同时使用
//因为可能会显示出错
//okhttp
//https://github.com/square/okhttp
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
//用来打印okhttp请求日志
//当然也可以自定义
implementation("com.squareup.okhttp3:logging-interceptor:4.9.3")
//retrofit
//https://github.com/square/retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
//使用gson解析json
//https://github.com/google/gson
implementation 'com.google.code.gson:gson:2.9.0'
//适配retrofit使用gson解析
//版本要和retrofit一样
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
//适配retrofit支持rxjava
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
//使用了Android响应式编程
//RxJava和RxAndroid区别?
//简单来说:就是RxAndroid在RxJava的基础上
//优化了一些功能
//增强了Android特有的功能
//https://github.com/ReactiveX/RxAndroid
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
//endregion
//自动释放RxJava相关资源
//https://github.com/uber/AutoDispose
implementation "com.uber.autodispose2:autodispose-androidx-lifecycle:2.1.1"
- 添加全局配置
android {
//配置不同的环境
productFlavors {
//本地开发环境
local {
//API端点
buildConfigField('String', "ENDPOINT", '"http://192.168.2.108:8080/"')
//资源端点
buildConfigField 'String', 'RESOURCE_ENDPOINT', '"http://course-music-dev.ixuea.com/%s"'
dimension = minSdkVersion
buildFeatures{
buildConfig = true
}
}
//开发环境
dev {
//API端点
buildConfigField('String', "ENDPOINT", '"http://my-cloud-music-api-sp3-dev.ixuea.com/"')
//资源端点
buildConfigField 'String', 'RESOURCE_ENDPOINT', '"http://course-music-dev.ixuea.com/%s"'
dimension = minSdkVersion
buildFeatures{
buildConfig = true
}
}
//正式环境
prod {
//API端点
buildConfigField 'String', 'ENDPOINT', '"http://my-cloud-music-api-sp3.ixuea.com/"'
//资源端点
buildConfigField 'String', 'RESOURCE_ENDPOINT', '"http://course-music.ixuea.com/%s"'
dimension = minSdkVersion
buildFeatures{
buildConfig = true
}
}
}
}
- 创建配置文件
package com.ixuea.courses.mymusic.config;
import com.ixuea.courses.mymusic.BuildConfig;
/**
* 配置文件
* <p>
* 例如:API地址,QQ等第三方服务配置信息等
*/
public class Config {
/**
* 默认延时时间
*/
public static final long SPLASH_DEFAULT_DELAY_TIME = 1500;
/**
* 是否是调试模式
*/
public static final boolean DEBUG = BuildConfig.DEBUG;
/**
* 端点
*/
public static String ENDPOINT = BuildConfig.ENDPOINT;
/**
* 资源端点
*/
public static String RESOURCE_ENDPOINT = BuildConfig.RESOURCE_ENDPOINT;
/**
* 网络缓存目录大小
* 100M
*/
public static final long NETWORK_CACHE_SIZE = 1024 * 1024 * 100;
}
- 使用OkHttp请求网络
private void testGet() {
OkHttpClient client = new OkHttpClient();
String url = Config.ENDPOINT + "v1/songs";
Request request = new Request.Builder()
.url(url)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
Log.e(TAG, "onFailure: "+e.getLocalizedMessage());
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
Log.d(TAG, "onResponse: "+response.body().string());
}
});
}
Retrofit
- 创建网络相关依赖提供类NetworkModule
添加了chucker实现应用内显示网络请求信息拦截器,参考官网文档:chucker:🔎 An HTTP inspector for Android & OkHTTP (like Charles but on device) - GitCode
package com.ixuea.courses.mymusic.component.api;
import android.util.Log;
import com.chuckerteam.chucker.api.ChuckerInterceptor;
import com.ixuea.courses.mymusic.AppContext;
import com.ixuea.courses.mymusic.config.Config;
import com.ixuea.courses.mymusic.util.JSONUtil;
import java.util.concurrent.TimeUnit;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* 网络相关依赖提供类
* <p>
* 例如:OkHttp,retrofit依赖
*/
public class NetworkModule {
/**
* 提供OkHttpClient
*/
public static OkHttpClient provideOkHttpClient(){
//初始化okhttp
OkHttpClient.Builder okhttpClientBuilder = new OkHttpClient.Builder();
//配置缓存
Cache cache = new Cache(AppContext.getInstance().getCacheDir(), Config.NETWORK_CACHE_SIZE);
okhttpClientBuilder.cache(cache);
okhttpClientBuilder.connectTimeout(10, TimeUnit.SECONDS)//连接超时时间
.writeTimeout(10,TimeUnit.SECONDS)//写,也就是将数据发送到服务端的超时时间
.readTimeout(10,TimeUnit.SECONDS);//读,也就是将服务端的数据下载到本地的超时时间
if(Config.DEBUG){
//调试模式
//创建okhttp日志拦截器
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
//设置日志等级
loggingInterceptor.level(HttpLoggingInterceptor.Level.BASIC);
//添加到网络架构中
okhttpClientBuilder.addInterceptor(loggingInterceptor);
//添加chucker实现应用内显示网络请求信息拦截器
okhttpClientBuilder.addInterceptor(new ChuckerInterceptor.Builder(AppContext.getInstance()).build());
}
return okhttpClientBuilder.build();
}
/**
* 提供Retrofit实例
*
* @param okHttpClient
* @return
*/
// @Provides
// @Singleton
public static Retrofit provideRetrofit(OkHttpClient okHttpClient) {
return new Retrofit.Builder()
//让retrofit使用okhttp
.client(okHttpClient)
//api地址
.baseUrl(Config.ENDPOINT)
//适配rxjava
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
//使用gson解析json
//包括请求参数和响应
.addConverterFactory(GsonConverterFactory.create(JSONUtil.createGson()))
//创建retrofit
.build();
}
}
- 创建Service
package com.ixuea.courses.mymusic.component.api;
import com.ixuea.courses.mymusic.component.ad.model.Ad;
import com.ixuea.courses.mymusic.component.comment.model.Comment;
import com.ixuea.courses.mymusic.component.input.model.CodeRequest;
import com.ixuea.courses.mymusic.component.login.model.Session;
import com.ixuea.courses.mymusic.component.sheet.model.Sheet;
import com.ixuea.courses.mymusic.component.song.model.Song;
import com.ixuea.courses.mymusic.component.user.model.User;
import com.ixuea.courses.mymusic.model.Base;
import com.ixuea.courses.mymusic.model.BaseId;
import com.ixuea.courses.mymusic.model.response.DetailResponse;
import com.ixuea.courses.mymusic.model.response.ListResponse;
import java.util.Map;
import io.reactivex.rxjava3.core.Observable;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.PATCH;
import retrofit2.http.POST;
import retrofit2.http.Path;
import retrofit2.http.Query;
import retrofit2.http.QueryMap;
/**
* 默认远程数据源
*/
public interface DefaultService {
/**
* 歌单列表
*
* @return
*/
@GET("v1/sheets")
Observable<ListResponse<Sheet>> sheets(@Query(value = "category") String category, @Query(value = "size") int size);
/**
* 歌单详情
*
* @param testHeader 可以通过@Header这种方式针对单个请求传递请求头,这里就是测试,无实际作用
* @param id
* @return
*/
@GET("v1/sheets/{id}")
Observable<DetailResponse<Sheet>> sheetDetail(@Header("testHeader") String testHeader, @Path("id") String id);
/**
* 获取用户创建的歌单
*
* @param userId
* @return
*/
@GET("v1/users/{userId}/create")
Observable<ListResponse<Sheet>> createSheets(@Path("userId") String userId);
}
- 创建Repository
package com.ixuea.courses.mymusic.repository;
import com.ixuea.courses.mymusic.component.ad.model.Ad;
import com.ixuea.courses.mymusic.component.api.DefaultService;
import com.ixuea.courses.mymusic.component.api.NetworkModule;
import com.ixuea.courses.mymusic.component.input.model.CodeRequest;
import com.ixuea.courses.mymusic.component.login.model.Session;
import com.ixuea.courses.mymusic.component.sheet.model.Sheet;
import com.ixuea.courses.mymusic.component.song.model.Song;
import com.ixuea.courses.mymusic.component.user.model.User;
import com.ixuea.courses.mymusic.model.Base;
import com.ixuea.courses.mymusic.model.BaseId;
import com.ixuea.courses.mymusic.model.response.DetailResponse;
import com.ixuea.courses.mymusic.model.response.ListResponse;
import com.ixuea.courses.mymusic.util.Constant;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
/**
* 本项目默认仓库
* 主要是从网络,数据库获取数据
* 目前项目中大部分操作都在这里
* <p>
* 如果项目每个模块之间有明显的区别,例如:有商城,有歌单,那可以放到对应模块的Repository
*/
public class DefaultRepository {
private static DefaultRepository instance;
private final DefaultService service;
public DefaultRepository() {
//虽然当前类是单例设计模式,但因为直接调用provideRetrofit这样的方法
//所以虽然代码是和MVVM架构模块那边(商城)复用了,但他们不是一个单例对象
service = NetworkModule.provideRetrofit(NetworkModule.provideOkHttpClient()).create(DefaultService.class);
}
/**
* 返回当前对象的唯一实例
* 单例设计模式
* 由于移动端很少有高并发
* 所以这个就是简单判断
*
* @return
*/
public static DefaultRepository getInstance() {
if (instance == null) {
instance = new DefaultRepository();
}
return instance;
}
/**
* 广告列表
*
* @return
*/
public Observable<ListResponse<Ad>> ads(int position) {
return service.ads(position)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
/**
* 首页banner界面广告
*
* @return
*/
public Observable<ListResponse<Ad>> bannerAd() {
return ads(Constant.VALUE0);
}
}
- 封装自动处理请求错误
/**
* 网络请求Observer
*/
public abstract class HttpObserver<T> extends ObserverAdapter<T> {
private static final String TAG = "HttpObserver";
/**
* 无参构造方法
*/
public HttpObserver() {
}
/**
* 请求成功
*
* @param data
*/
public abstract void onSucceeded(T data);
/**
* 请求失败
*
* @param data
* @param e
* @return true:自己处理;false:框架处理
*/
public boolean onFailed(T data, Throwable e) {
return false;
}
/**
* 请求结束,成功失败都会调用(调用前调用),使用在这里隐藏加载提示
*/
public void onEnd(){
}
@Override
public void onSubscribe(Disposable d) {
super.onSubscribe(d);
}
@Override
public void onNext(T t) {
super.onNext(t);
onEnd();
if (isSucceeded(t)) {
//请求正常
onSucceeded(t);
} else {
//请求出错了
handlerRequest(t, null);
}
}
@Override
public void onError(Throwable e) {
super.onError(e);
onEnd();
//处理错误
handlerRequest(null, e);
}
/**
* 网络请求是否成功了
*
* @param t
* @return
*/
private boolean isSucceeded(T t) {
if (t instanceof Response) {
//retrofit里面的响应对象
//获取响应对象
Response response = (Response) t;
//获取响应码
int code = response.code();
//判断响应码
if (code >= 200 && code <= 299) {
//网络请求正常
return true;
}
} else if (t instanceof BaseResponse) {
//判断具体的业务请求是否成功
BaseResponse response = (BaseResponse) t;
return response.isSucceeded();
}
return false;
}
/**
* 处理错误网络请求
*
* @param data
* @param error
*/
private void handlerRequest(T data, Throwable error) {
if (onFailed(data, error)) {
//回调了请求失败方法
//并且该方法返回了true
//返回true就表示外部手动处理错误
//那我们框架内部就不用做任何事情了
} else {
HttpUtil.handlerRequest(data, error);
}
}
}
- 使用
DefaultRepository.getInstance()
.userDetail(sp.getUserId())
.to(autoDisposable(AndroidLifecycleScopeProvider.from(this)))
.subscribe(new HttpObserver<DetailResponse<User>>() {
@Override
public void onSucceeded(DetailResponse<User> data) {
showData(data.getData());
}
@Override
public boolean onFailed(DetailResponse<User> data, Throwable e) {
return true;
}
});