关于retrofit也是一个有点老掉牙的话题了,百度各种文章都有,不过本人项目中使用的很少,使用的时候也是简单的copy使用一下,有点尴尬。写此文整理一下实际使用中得一些基本操作,加深一下自己的印象,更方便以后使用的时候借鉴。
本文章基于retrofit2.0+okhttp3,也欢迎看到此文章的朋友,不足之处和有问题的地方可以随便喷~~~哈哈,正所谓不骂不进步嘛!
使用步骤:
一、在根build.gradle中配置
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven { url "https://jitpack.io" }
}
}
在主工程可以引用的build.gradle中依赖
api 'com.google.code.gson:gson:2.2.4'
api 'com.squareup.okhttp3:okhttp:3.12.1'
api 'com.squareup.okhttp3:logging-interceptor:3.6.0'
api 'com.squareup.retrofit2:retrofit:2.4.0'
api('com.squareup.retrofit2:converter-gson:2.4.0') {
exclude group: 'com.google.code.gson'
}
注意:1、retrofit本身和volley类似是一个网路请求框架,默认请求方式使用okhttp,本文使用okhttp3
2、 api 'com.squareup.okhttp3:logging-interceptor:3.6.0'是打印日志需要的,不需要不集成,一般是要log的!
3、 { exclude group: 'com.google.code.gson' } 防止冲突,很有必要!
4、权限配置文件的网络请求权限、6.0版本的动态申请文件读写权限不再啰嗦,自己注意!
二、单例的Retrofit和Okhttp管理类
import android.content.Context;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.File;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.concurrent.TimeUnit;
import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* Function : <br/>
* Date: 2017-03-10 10:54<br>
*
* @author ReiChin_
*/
public class HttpsClientUtil {
private static final String BASE_API = Constants.API_BASE;
// timeout for seconds
private static long TIMEOUT_DEFAULT = 3 * 60L;
private Context mAppContext;
private static HttpsClientUtil mInstance = null;
private HttpsClientUtil() {
}
public static HttpsClientUtil newInstance() {
if (null == mInstance) {
synchronized (HttpsClientUtil.class) {
if (null == mInstance) {
mInstance = new HttpsClientUtil();
}
}
}
return mInstance;
}
public void init(Context appContext) {
this.mAppContext = appContext;
}
/**
* 通过默认的OkHttpClient构建Retrofit实例
*
* @return
*/
public Retrofit buildRetrofit() {
OkHttpClient okHttpClient = buildOkHttpClient();
return buildRetrofit(okHttpClient);
}
/**
* 通过自定义的OkHttpClient构建Retrofit实例
*
* @param okHttpClient
* @return
*/
public Retrofit buildRetrofit(OkHttpClient okHttpClient) {
Gson gson = new GsonBuilder()
// .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.setDateFormat("yyyy-MM-dd")
.create();
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.baseUrl(BASE_API)
.build();
return retrofit;
}
/**
* 构建一个Https请求的OkHttpClient实例
*
* @return
*/
public OkHttpClient buildOkHttpClient()/* throws CertificateException,
NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException */ {
if (null == mAppContext) {
throw new NullPointerException("Context 没有初始化");
}
// SSLParser.SSLParams sslParams = SSLParser.getSSLParams(mAppContext);
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.addNetworkInterceptor(httpLoggingInterceptor)
.connectTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS)
.readTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS)
.writeTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS)
.addInterceptor(new HeaderInterceptor()) //添加header
// .retryOnConnectionFailure(true) //失败重连
// .addInterceptor(new NetCacheInterceptor()); //添加网络缓存
// .sslSocketFactory(sslParams.sslSocketFactory, sslParams.trustManager)
/* .hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
})*/;
// addLogIntercepter(builder); //日志拦截器
// setCacheFile(builder);//网络缓存
OkHttpClient okHttpClient = builder.build();
return okHttpClient;
}
/**
* 设置缓存文件路径
*/
private void setCacheFile(OkHttpClient.Builder builder) {
//设置缓存文件
File cacheFile = new File(FileHelper.LOG_PATH);
//缓存大小为10M
int cacheSize = 10 * 1024 * 1024;
Cache cache = new Cache(cacheFile,cacheSize);
builder.cache(cache);
}
/**
* 调试模式下加入日志拦截器
* @param builder
*/
private void addLogIntercepter(OkHttpClient.Builder builder) {
if (Constants.isDebug) {
builder.addInterceptor(new LoggingInterceptor());
}
}
/**
* 获取对应的Service
* @param service
* @param <T>
* @return
*/
public <T> T create(Class<T> service){
return buildRetrofit().create(service);
}
/**
*统一添加header的拦截器
*/
public class HeaderInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
builder.addHeader("token", MyApplication.getInstance().getToken());
return chain.proceed(builder.build());
}
}
/**
* 网络拦截器进行网络缓存:
*/
public class NetCacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
int onlineCacheTime = 60;
return response.newBuilder()
.header("Cache-Control", "public, max-age="+onlineCacheTime)
.removeHeader("Pragma")
.build();
}
}
/**
* Okhttp返回数据日志拦截器
*/
public class LoggingInterceptor implements Interceptor {
private final int byteCount = 1024*1024;
@Override
public Response intercept(Chain chain) throws IOException {
//chain里面包含了request和response,按需获取
Request request = chain.request();
Response response = chain.proceed(request);
LogUtils.d(String.format("发送请求 %s",request.url()));
ResponseBody responseBody = response.peekBody(byteCount);
LogUtils.d(String.format("接收响应 %s", responseBody.string()));
return response;
}
}
}
- https对应的ssl证书代码已注释
-
GsonConverterFactory可以自行定义,比如下载文件的时候(文末引用文章链接中有)
-
createRemoteRealService可以定义多个或者不定义
三、创建 Callback的过滤类,为了适应各种网络状态和事件的统一处理,比如处理统一的错误状态、单点登录等等
import android.support.annotation.NonNull;
import java.io.IOException;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public abstract class NetworkCallback<T> implements Callback<T> {
@Override
public void onResponse(@NonNull Call<T> call, @NonNull Response<T> response) {
int code = response.code();
if (code >= 200 && code < 300) {
onSucceed(response.body());
} else if (code >= 500 && code < 600) {
onFailed(ExceptionCode.SERVER_ERROR);
} else {
onFailed(ExceptionCode.UNKNOWN_ERROR);
}
}
@Override
public void onFailure(@NonNull Call call, @NonNull Throwable t) {
int code = ExceptionCode.UNKNOWN_ERROR;
if (t instanceof IOException) {
code = ExceptionCode.NETWORK_ERROR;
}
onFailed(code);
}
public abstract void onSucceed(T response);
public abstract void onFailed(int errorCode);
}
其中错误状态使用接口(或者枚举类型更显得有学问,,,)统一定义
public interface ExceptionCode {
int NETWORK_ERROR = 1;
int SERVER_ERROR = 2;
int UNKNOWN_ERROR = 3;
}
四、请求声明
import java.util.Map;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
import retrofit2.http.PartMap;
import retrofit2.http.Query;
import retrofit2.http.Url;
public interface RemoteRealService {
/**
* 登录
* @param username
* @param password
* @param clientType
* @return
*/
@POST("login")
Call<BobyBean<UserBean>> login(@Query("username") String username, @Query("password") String password, @Query("clientType") String clientType);
/**
* 单个文件上传
* @param tableName
* @param file
* @return
*/
@Multipart
@POST("fjwj/fileUpload")
Call<BobyBean<FileBean>> fjwj_fileUpload_only_retrofit(@Part("tableName") RequestBody tableName, @Part MultipartBody.Part file);
/**
* 多文件上传
* @param tableName
* @param params
* @return
*/
@Multipart
@POST("fjwj/fileUpload")
Call<BobyBean<FileBean>> fjwj_fileUpload_retrofit(@Part("tableName") RequestBody tableName, @PartMap Map<String, RequestBody> params);
/**
* 文件下载
* @param url
* @return
*/
@GET
Call<ResponseBody> loadFile(@Url String url);
}
- 请求声明举例典型类型,各种参数标识不一一列举,可以需要的时候再百度或者意会-----程序猿的意会是件好事情
- post和get传递参数有很多类似的地方,快速开发可能只用一个post类型就可以了,其他get、delelte、put基本不用的,你懂得
- 单文件上传和多文件上传、文件下载还是必须要知道的呀
- 里面的bean类,请自行脑补,根据实际情况自己定义
- 更多请求方式和参数,参考博客: https://blog.youkuaiyun.com/guohaosir/article/details/78942485
五、请求实体类
import android.content.Context;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
/**
* @author hahaliu
* @Description 所有接口统一
*/
public class Http implements IRemoteService {
/**
* 登录
*
* @param activity
* @param account
* @param pwd
* @param callback
*/
public static void doLogin(String username, String password, NetworkCallback<BobyBean<UserBean>> callback) {
RemoteRealService momentRealService = HttpsClientUtil.newInstance().create(RemoteRealService.class);
Call<BobyBean<UserBean>> call = momentRealService.login(username, password, "app");
call.enqueue(callback);
}
/**
* 单文件上传 retrofit
* @param tableName
* @param file
* @param callback
注意:
MultipartBody.Part.createFormData("file", file.getName(), photoRequestBody);的file是根据后台参数定义的
*/
public static void fjwj_fileUpload_only_retrofit(String tableName, File file, NetworkCallback<BobyBean<FileBean>> callback) {
RemoteRealService service = HttpsClientUtil.newInstance().create(RemoteRealService.class);
RequestBody tableBoay = RequestBody.create(MediaType.parse("text/plain"), tableName);
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
MultipartBody.Part photo = MultipartBody.Part.createFormData("file", file.getName(), photoRequestBody);
Call<BobyBean<FileBean>> call = service.fjwj_fileUpload_only_retrofit(tableBoay, photo);
call.enqueue(callback);
}
/**
* 多文件统一上传 retrofit
* @param tableName
* @param files
* @param callback
注意:
params.put("file\"; filename=\"" + file.getName(), photoRequestBody);中得file是根据后台定义的
*/
public static void fjwj_fileUpload_retrofit(String tableName, List<File> files, NetworkCallback<BobyBean<FileBean>> callback) {
RemoteRealService service = HttpsClientUtil.newInstance().create(RemoteRealService.class);
RequestBody tableBoay = RequestBody.create(MediaType.parse("text/plain"), tableName);
Map<String, RequestBody> params = new HashMap<>();
for (File file : files) {
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
params.put("file\"; filename=\"" + file.getName(), photoRequestBody);
}
Call<BobyBean<FileBean>> call = service.fjwj_fileUpload_retrofit(tableBoay, params);
call.enqueue(callback);
}
/**
* 文件下载
* @param context
* @param url
* @param fileDes 文件夹存储的命名
* @param position 回调状态--本方法中为文件列表的下表
* @param listener 自定义的回调监听 方法onItemClickListener的两个int类型参数 type事件类型 position回调状态
注意:
AppConfig.getAppCacheFilePath自定义文件应用缓存路径 的公共方法
AppConfig.openFile自定义使用手机自带应用打开文件的 公共方法
*/
public static void loadFile(final Context context, String url, final String fileDes, final int position, final BaseRecyclerBottomSpaceAdapter.OnItemCommonClickListener listener) {
RemoteRealService service = HttpsClientUtil.newInstance().create(RemoteRealService.class);
Call<ResponseBody> call = service.loadFile(url);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
boolean writeToDisk = Http.writeFileToSDCard(context, fileDes, response.body());
if (writeToDisk) {
AppConfig.openFile(context, new File(AppConfig.getAppCacheFilePath(context), fileDes));
listener.onItemClickListener(1, position);
} else {
listener.onItemClickListener(-1, -1);
}
} else {
listener.onItemClickListener(-1, -1);
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
listener.onItemClickListener(-1, -1);
}
});
}
/**
* 下载文件
* @param context
* @param fileDes 文件路径
* @param body
* @return
*/
private static boolean writeFileToSDCard(Context context, String fileDes, ResponseBody body) {
try {
File futureStudioIconFile = new File(AppConfig.getAppCacheFilePath(context), fileDes);
InputStream inputStream = null;
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
long fileSize = body.contentLength();
long fileSizeDownloaded = 0;
inputStream = body.byteStream();
outputStream = new FileOutputStream(futureStudioIconFile);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
Log.d("", "file download: " + fileSizeDownloaded + " of " + fileSize);
}
outputStream.flush();
return true;
} catch (IOException e) {
return false;
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
return false;
}
}
}
基本上大工高成,可以满足基本快速开发使用了,,,,优化的话,请各位多多指教
另外附录问件路径和打开文件公共方法
public final class AppConfig {
/**
* 应用缓存路径 文件
*/
public static String getAppCacheFilePath(Context context) {
File filePath = new File(context.getApplicationContext().getExternalCacheDir().getPath() + File.separator + "file" + File.separator);
if (!filePath.exists()) {
filePath.mkdirs();
}
return filePath.getPath();
}
/**
* 打开文件
*
* @param context
* @param f
注意:FileProvider适配不再详述,不理解的可以使用https://github.com/hongyangAndroid/FitAndroid7 文件权限框架
*/
public static void openFile(Context context, File f) {
try {
Intent myIntent = new Intent(Intent.ACTION_VIEW);
Uri fileUri = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//区别于 FLAG_GRANT_READ_URI_PERMISSION 跟 FLAG_GRANT_WRITE_URI_PERMISSION, URI权限会持久存在即使重启,直到明确的用 revokeUriPermission(Uri, int) 撤销。 这个flag只提供可能持久授权。但是接收的应用必须调用ContentResolver的takePersistableUriPermission(Uri, int)方法实现
myIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
fileUri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".FileProvider", f);
} else {
fileUri = Uri.fromFile(f);
}
String extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(fileUri.toString());
String mimetype = android.webkit.MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
LogUtils.e("extension--->" + extension + " mimetype--->" + mimetype);
myIntent.setDataAndType(fileUri, mimetype);
myIntent.addCategory(Intent.CATEGORY_DEFAULT);
myIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(myIntent);
} catch (Exception e) {
ToastUtils.showToast(context, e.toString());
Toast.makeText(context, "手机没有支持打开此文件的应用!", Toast.LENGTH_SHORT).show();
}
}
}
最后谢谢几个同事的代码,做了很多参考
参考文章地址
https://blog.youkuaiyun.com/qq_34161388/article/details/78403171
https://www.jianshu.com/p/251b91efb6c9
https://mp.weixin.qq.com/s/-PWoVxd8SSvJzRPQ8mpktA
参数详解:https://blog.youkuaiyun.com/xieluoxixi/article/details/80092582
https://blog.youkuaiyun.com/guohaosir/article/details/78942485
文件下载做的比较粗糙,上面链接还说明了大文件下载注意事项,如果GsonConverterFactory转换器进行配置下载文件可能更显得高大上,参考以下文章
https://blog.youkuaiyun.com/xinlangren88/article/details/79561504