android 通用Http网络请求管理工具开发思维
前言
嘿嘿
目前我接触的大部分项目都是走Http的协议,行业大概也是如此。所以在规划整个项目的时候,网络请求的数据流通应该如何管理才能提高拓展性比较关键。目前圈子里也有很多开源框架可以直接使用,也比较好用,出名的有OkHttp这种,也有xUtils这种包罗万象的,这些都可以使用毕竟已经经过好多前辈的验证,非常成熟。
但是,最好不要在我们项目的主包里直接使用,后期如果遇到什么问题需要更换网络架构或升级网络架构,会改动太大了。我们需要做一个中间件,随时更换网络依赖都不会对业务出现影响。
图中是我自己现在在用的架构,所有业务活动都不能直接用到第三方的依赖方法和类,需要中间件进行沟通维护,这样子,下层依赖的替换就不会影响到上层的事务逻辑,维护起来会更加的方便。
1、业务活动只能使用一种方法,那就是去数据管理缓存区伸手拿数据。
2、数据管理缓存区决定是否需要去进行网络请求,如果需要就给业务活动返回一个状态值,告诉业务活动我现在去帮你拿数据,你等一下。否则,直接返回数据即可
3、网络请求中间件进行网络请求管理,利用线程池合理安排线程,返回网络请求结果。
而这一次我主要讲解的就是如何写一个 “网络请求中间件”
上代码
首先在工程中new一个Module,选择Android library,名称就填network。
HttpManager
新建一个文件HttpManager,搞上单例模式
public class HttpManager {
private static HttpManager instance;
/**
* 单例模式
*
* @return 单例
*/
public static HttpManager getInstance(){
if (null == instance){
synchronized (HttpManager.class){
if (null == instance){
instance = new HttpManager();
}
}
}
return instance;
}
}
HttpException
再新建一个文件夹exception,在其中新建一个HttpException;自定义异常信息:
/**
* 自定义异常消息
* @author dlong
* created at 2019/4/12 10:28 AM
*/
public class HttpException extends RuntimeException {
public HttpException(String detailMessage) {
super(detailMessage);
}
public HttpException(Throwable throwable) {
super(throwable);
}
public HttpException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
}
RequestMode
我们的http请求可以分为4种类型
1、get
2、post
3、upload
4、download
所以新建一个文件夹bean,新建一个RequestMode
/**
* 请求模式
*
* @author D10NG
* @date on 2019-04-22 10:39
*/
public class RequestMode {
/** get */
public static final int MODE_GET = 1;
/** post */
public static final int MODE_POST = 2;
/** 上传 */
public static final int MODE_UPLOAD = 3;
/** 下载 */
public static final int MODE_DOWNLOAD = 4;
}
OnResultListener
新建文件夹method,新建一个OnResultListener接口
/**
* 结果回调接口
*
* @author D10NG
* @date on 2019-04-22 10:25
*/
public interface OnResultListener {
/**
* 请求过程中的失败
* @param what tag
* @param e 失败信息
*/
void onRequestFail(int what, String e);
/**
* 请求结果失败
* @param what tag
* @param code 失败码
*/
void onResultFail(int what, int code);
/**
* 请求成功
* @param what tag
* @param mode 模式
* @param result 成功字符串
*/
void onSuccess(int what, int mode, byte[] result);
}
HttpGet
在method文件夹里新建HttpGet
/**
* get请求
*
* @author D10NG
* @date on 2019-04-22 10:49
*/
public class HttpGet {
private static OnResultListener onResultListener;
/**
* 请求
* @param what tag
* @param requestUrl 链接地址
* @param resultListener 结果监听
*/
public static void call(int what, String requestUrl,
OnResultListener resultListener){
onResultListener = resultListener;
if (null == onResultListener) {
throw new HttpException("OnResultListener can not be null");
}
if (null == requestUrl || requestUrl.equals("")){
onResultListener.onRequestFail(what, "requestUrl is null or empty");
}
try {
// 新建一个URL对象
URL url = new URL(requestUrl);
// 打开一个HttpURLConnection连接
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
// 设置连接主机超时时间
urlConn.setConnectTimeout(Config.CONNECT_TIME_OUT);
//设置从主机读取数据超时
urlConn.setReadTimeout(Config.READ_TIME_OUT);
// 设置是否使用缓存 默认是true
urlConn.setUseCaches(true);
// 设置为Post请求
urlConn.setRequestMethod("GET");
//urlConn设置请求头信息
//设置请求中的媒体类型信息。
urlConn.setRequestProperty("Content-Type", Config.CONTENT_TYPE);
//设置客户端与服务连接类型
urlConn.addRequestProperty("Connection", "Keep-Alive");
// 开始连接
urlConn.connect();
// 判断请求是否成功
if (urlConn.getResponseCode() == 200) {
// 获取返回的数据
byte[] result = ConversionTool.streamTobytes(urlConn.getInputStream());
onResultListener.onSuccess(what, RequestMode.MODE_GET, result);
} else {
onResultListener.onResultFail(what, urlConn.getResponseCode());
}
// 关闭连接
urlConn.disconnect();
} catch (Exception e) {
onResultListener.onRequestFail(what, e.getMessage());
}
}
}
HttpPost
在method文件夹里新建HttpPost
/**
* post请求
*
* @author D10NG
* @date on 2019-04-22 13:51
*/
public class HttpPost {
private static OnResultListener onResultListener;
/**
* 请求
* @param what tag
* @param requestUrl 链接地址
* @param paramsMap 参数携带
* @param resultListener 结果监听
*/
public static void call(int what, String requestUrl, HashMap<String, String> paramsMap,
OnResultListener resultListener) {
onResultListener = resultListener;
if (null == onResultListener) {
throw new HttpException("OnResultListener can not be null");
}
if (null == requestUrl || requestUrl.equals("")){
onResultListener.onRequestFail(what, "requestUrl is null or empty");
}
try {
//合成参数
StringBuilder tempParams = new StringBuilder();
int pos = 0;
for (String key : paramsMap.keySet()) {
if (pos > 0) {
tempParams.append("&");
}
tempParams.append(String.format("%s=%s",
key, URLEncoder.encode(paramsMap.get(key),"utf-8")));
pos++;
}
String params = tempParams.toString();
// 请求的参数转换为byte数组
byte[] postData = params.getBytes();
// 新建一个URL对象
URL url = new URL(requestUrl);
// 打开一个HttpURLConnection连接
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
// 设置连接超时时间
urlConn.setConnectTimeout(Config.CONNECT_TIME_OUT);
//设置从主机读取数据超时
urlConn.setReadTimeout(Config.READ_TIME_OUT);
// Post请求必须设置允许输出 默认false
urlConn.setDoOutput(true);
//设置请求允许输入 默认是true
urlConn.setDoInput(true);
// Post请求不能使用缓存
urlConn.setUseCaches(false);
// 设置为Post请求
urlConn.setRequestMethod("POST");
//设置本次连接是否自动处理重定向
urlConn.setInstanceFollowRedirects(true);
// 配置请求Content-Type
urlConn.setRequestProperty("Content-Type", Config.CONTENT_TYPE);
// 开始连接
urlConn.connect();
// 发送请求参数
DataOutputStream dos = new DataOutputStream(urlConn.getOutputStream());
dos.write(postData);
dos.flush();
dos.close();
// 判断请求是否成功
if (urlConn.getResponseCode() == 200) {
// 获取返回的数据
byte[] result = ConversionTool.streamTobytes(urlConn.getInputStream());
onResultListener.onSuccess(what, RequestMode.MODE_POST, result);
} else {
onResultListener.onResultFail(what, urlConn.getResponseCode());
}
// 关闭连接
urlConn.disconnect();
} catch (Exception e) {
onResultListener.onRequestFail(what, e.getMessage());
}
}
}
HttpUpload
在method文件夹里新建HttpUpload
/**
* 上传
*
* @author D10NG
* @date on 2019-04-22 14:00
*/
public class HttpUpload {
private static OnResultListener onResultListener;
/**
* 请求
* @param what tag
* @param requestUrl 链接地址
* @param filePath 文件路径
* @param resultListener 结果监听
*/
public static void call(int what, String requestUrl, String filePath,
OnResultListener resultListener) {
onResultListener = resultListener;
if (null == onResultListener) {
throw new HttpException("OnResultListener can not be null");
}
if (null == requestUrl || requestUrl.equals("")){
onResultListener.onRequestFail(what, "requestUrl is null or empty");
}
if (null == filePath || filePath.equals("")){
onResultListener.onRequestFail(what, "filePath is null or empty");
}
try {
File file = new File(filePath);
//新建url对象
URL url = new URL(requestUrl);
//通过HttpURLConnection对象,向网络地址发送请求
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
//设置该连接允许读取
urlConn.setDoOutput(true);
//设置该连接允许写入
urlConn.setDoInput(true);
//设置不能适用缓存
urlConn.setUseCaches(false);
//设置连接超时时间
urlConn.setConnectTimeout(Config.CONNECT_TIME_OUT);
//设置读取超时时间
urlConn.setReadTimeout(Config.READ_TIME_OUT);
//设置连接方法post
urlConn.setRequestMethod("POST");
//设置维持长连接
urlConn.setRequestProperty("connection", "Keep-Alive");
//设置文件字符集
urlConn.setRequestProperty("Accept-Charset", "UTF-8");
//设置文件类型
urlConn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + "*****");
String name = file.getName();
DataOutputStream requestStream = new DataOutputStream(urlConn.getOutputStream());
requestStream.writeBytes("--" + "*****" + "\r\n");
//发送文件参数信息
StringBuilder tempParams = new StringBuilder();
tempParams.append("Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + name + "\"; ");
tempParams.append("\r\n");
tempParams.append("Content-Type: application/octet-stream\r\n");
tempParams.append("\r\n");
String params = tempParams.toString();
requestStream.writeBytes(params);
//发送文件数据
FileInputStream fileInput = new FileInputStream(file);
int bytesRead;
byte[] buffer = new byte[1024];
DataInputStream in = new DataInputStream(new FileInputStream(file));
while ((bytesRead = in.read(buffer)) != -1) {
requestStream.write(buffer, 0, bytesRead);
}
requestStream.writeBytes("\r\n");
requestStream.flush();
requestStream.writeBytes("--" + "*****" + "--" + "\r\n");
requestStream.flush();
fileInput.close();
int statusCode = urlConn.getResponseCode();
if (statusCode == 200) {
// 获取返回的数据
byte[] result = ConversionTool.streamTobytes(urlConn.getInputStream());
onResultListener.onSuccess(what, RequestMode.MODE_UPLOAD, result);
} else {
onResultListener.onResultFail(what, urlConn.getResponseCode());
}
} catch (IOException e) {
onResultListener.onRequestFail(what, e.getMessage());
}
}
}
HttpDownload
在method文件夹里新建HttpDownload
/**
* 下载
*
* @author D10NG
* @date on 2019-04-22 14:15
*/
public class HttpDownload {
private static OnResultListener onResultListener;
/**
* 请求
* @param what tag
* @param requestUrl 连接地址
* @param filePath 存储文件地址
* @param resultListener 结果监听
*/
public static void call(int what, String requestUrl, String filePath,
OnResultListener resultListener){
onResultListener = resultListener;
if (null == onResultListener) {
throw new HttpException("OnResultListener can not be null");
}
if (null == requestUrl || requestUrl.equals("")){
onResultListener.onRequestFail(what, "requestUrl is null or empty");
}
if (null == filePath || filePath.equals("")){
onResultListener.onRequestFail(what, "filePath is null or empty");
}
try {
// 新建一个URL对象
URL url = new URL(requestUrl);
// 打开一个HttpURLConnection连接
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
// 设置连接主机超时时间
urlConn.setConnectTimeout(Config.CONNECT_TIME_OUT);
//设置从主机读取数据超时
urlConn.setReadTimeout(Config.READ_TIME_OUT);
// 设置是否使用缓存 默认是true
urlConn.setUseCaches(true);
// 设置为get请求
urlConn.setRequestMethod("GET");
//urlConn设置请求头信息
//设置请求中的媒体类型信息。
urlConn.setRequestProperty("Content-Type", Config.CONTENT_TYPE);
//设置客户端与服务连接类型
urlConn.addRequestProperty("Connection", "Keep-Alive");
// 开始连接
urlConn.connect();
// 判断请求是否成功
if (urlConn.getResponseCode() == 200) {
File descFile = new File(filePath);
FileOutputStream fos = new FileOutputStream(descFile);
byte[] buffer = new byte[1024];
int len;
InputStream inputStream = urlConn.getInputStream();
while ((len = inputStream.read(buffer)) != -1) {
// 写到本地
fos.write(buffer, 0, len);
}
onResultListener.onSuccess(what, RequestMode.MODE_DOWNLOAD, null);
} else {
onResultListener.onResultFail(what, urlConn.getResponseCode());
}
// 关闭连接
urlConn.disconnect();
} catch (Exception e) {
onResultListener.onRequestFail(what, e.getMessage());
}
}
}
ConversionTool
在method里再写一个数据转换工具
/**
* 数据转换工具
*
* @author D10NG
* @date on 2019-04-22 11:15
*/
class ConversionTool {
/**
* 将输入流转换成byte数组
*
* @param is 从网络获取的输入流
* @return byte[]
*/
public static byte[] streamTobytes(InputStream is) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
baos.close();
is.close();
return baos.toByteArray();
} catch (Exception e) {
return null;
}
}
}
Config
写一个配置类,所有的配置信息存放在这里,我们可以根据需要进行修改
/**
* 配置文件
*
* @author D10NG
* @date on 2019-04-22 11:24
*/
public class Config {
/** ------------------------------------- 工作线程池 ---------------------------------------- */
/** 能同时运行的请求数 */
public static final int CORE_POOL_SIZE = 3;
/** 超出队列后的非核心线程数 */
public static final int MAXINUM_POOL_SIZE = 5;
/** 保持空闲不被回收的时间 */
public static final int KEEP_ALIVE_TIME = 1;
/** 保持空闲不被回收的时间单位 */
public static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
/** 排队队列数量 */
public static final int QUEUE_SIZE = 15;
/** ------------------------------------- 工作线程池 ---------------------------------------- */
/** ------------------------------------- Http请求 ----------------------------------------- */
/** 连接超时设定 */
public static final int CONNECT_TIME_OUT = 5000;
/** 读取超时设定 */
public static final int READ_TIME_OUT = 5000;
/** 读取超时设定 */
public static final String CONTENT_TYPE = "application/x-www-form-urlencoded";
}
OnCallBack
数据回调接口,返回给中间件使用者,也就是数据缓存区
/**
* 整个Http框架回调
* @author D10NG
* @date on 2019-04-22 15:28
*/
public interface OnCallBack extends OnResultListener {
/**
* 已经有了相同的参数
* @param what tag
*/
void onHaveSameWhat(int what);
}
完善HttpManager逻辑
写完其他的类,这里就是我们的主要逻辑的编写地方。
1、利用线程池进行业务排序;
2、将操作后的结果通过接口回调;
/**
* 总管理
*
* @author D10NG
* @date on 2019-04-22 14:30
*/
public class HttpManager {
private static HttpManager instance;
private OnCallBack onCallBack;
/** 线程池 */
private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(Config.CORE_POOL_SIZE,
Config.MAXINUM_POOL_SIZE, Config.KEEP_ALIVE_TIME, Config.KEEP_ALIVE_TIME_UNIT,
new LinkedBlockingQueue<Runnable>(Config.QUEUE_SIZE));
/** 存储what与Runnable */
private final SparseArray<Runnable> taskByWhat = new SparseArray<>();
/**
* 单例模式
*
* @return 单例
*/
public static HttpManager getInstance(){
if (null == instance){
synchronized (HttpManager.class){
if (null == instance){
instance = new HttpManager();
}
}
}
return instance;
}
/**
* 初始化结果监听
* @param callBack 结果监听
*/
public void init(OnCallBack callBack){
onCallBack = callBack;
}
/**
* 检查是否设置了监听
*/
private void checkCallBack(){
if (null == onCallBack){
throw new HttpException("OnCallBack is null");
}
}
/**
* 检查what是否存在
* @param what
* @return boolean
*/
private boolean checkWhat(int what, boolean sendBack){
if (null != taskByWhat.get(what)){
if (sendBack){
onCallBack.onHaveSameWhat(what);
}
return true;
}
return false;
}
/**
* 请求监听
*/
private OnResultListener onResultListener = new OnResultListener() {
@Override
public void onRequestFail(int what, String e) {
checkCallBack();
onCallBack.onRequestFail(what, e);
if (checkWhat(what, false)){
taskByWhat.remove(what);
}
}
@Override
public void onResultFail(int what, int code) {
checkCallBack();
onCallBack.onResultFail(what, code);
if (checkWhat(what, false)){
taskByWhat.remove(what);
}
}
@Override
public void onSuccess(int what, int mode, byte[] result) {
checkCallBack();
onCallBack.onSuccess(what, mode, result);
if (checkWhat(what, false)){
taskByWhat.remove(what);
}
}
};
/**
* get
* @param what tag
* @param requestUrl 连接地址
*/
public void executeGet (final int what, final String requestUrl){
checkCallBack();
if (checkWhat(what, true)) return;
Runnable runnable = new Runnable() {
@Override
public void run() {
HttpGet.call(what, requestUrl, onResultListener);
}
};
taskByWhat.put(what, runnable);
threadPoolExecutor.execute(runnable);
}
/**
* post
* @param what tag
* @param requestUrl 连接地址
* @param paramsMap 参数集合
*/
public void executePost (final int what, final String requestUrl,
final HashMap<String, String> paramsMap){
checkCallBack();
if (checkWhat(what, true)) return;
Runnable runnable = new Runnable() {
@Override
public void run() {
HttpPost.call(what, requestUrl, paramsMap, onResultListener);
}
};
taskByWhat.put(what, runnable);
threadPoolExecutor.execute(runnable);
}
/**
* 上传
* @param what tag
* @param requestUrl 连接地址
* @param filePath 文件路径
*/
public void executeUpload (final int what, final String requestUrl,
final String filePath){
checkCallBack();
if (checkWhat(what, true)) return;
Runnable runnable = new Runnable() {
@Override
public void run() {
HttpUpload.call(what, requestUrl, filePath, onResultListener);
}
};
taskByWhat.put(what, runnable);
threadPoolExecutor.execute(runnable);
}
/**
* 下载
* @param what tag
* @param requestUrl 连接地址
* @param filePath 下载保存的文件路径
*/
public void executeDownload (final int what, final String requestUrl,
final String filePath){
checkCallBack();
if (checkWhat(what, true)) return;
Runnable runnable = new Runnable() {
@Override
public void run() {
HttpDownload.call(what, requestUrl, filePath, onResultListener);
}
};
taskByWhat.put(what, runnable);
threadPoolExecutor.execute(runnable);
}
/**
* 取消任务
* @param what tag
* @return boolean
*/
public boolean cancleTask(int what){
Runnable runnable = taskByWhat.get(what);
if (null == runnable){
return true;
}
return threadPoolExecutor.remove(runnable);
}
}
使用方法
添加依赖
implementation project(path: ':network')
数据缓存区
新建文件夹http,新建文件HttpHandler
/**
* Http回复分发
* @author D10NG
* @date on 2019-04-22 16:43
*/
public class HttpHandler {
private static final String TAG = "HttpHandler";
/**
* 初始化
* 这里的监听结果可以使用EventBus进行数据的传递
* 分发数据
*/
public static void init(){
HttpManager.getInstance().init(new OnCallBack() {
@Override
public void onHaveSameWhat(int what) {
Log.e(TAG, "已经存在该请求" + what);
}
@Override
public void onRequestFail(int what, String e) {
Log.e(TAG, what + "请求失败!" + e);
}
@Override
public void onResultFail(int what, int code) {
Log.e(TAG, what + "请求结果不正确!" + code);
}
@Override
public void onSuccess(int what, int mode, byte[] result) {
Log.e(TAG, what + "请求成功!" + mode);
Log.e(TAG, "result => " + new String(result));
}
});
}
/**
* 获得管理者
* @return HttpManager
*/
public static HttpManager getManager(){
return HttpManager.getInstance();
}
}
新建文件HttpWhat
/**
* Http请求的tag标记列表
*
* 全部的http请求都应该把tag放在这里统一管理;
*
* @author D10NG
* @date on 2019-04-22 16:40
*/
public class HttpWhat {
public static final int APP_UPDATA_0 = 10;
public static final int APP_UPDATA_1 = 11;
public static final int APP_UPDATA_2 = 12;
}
初始化
在Application中初始化
/**
* @author D10NG
* @date on 2019-04-22 16:37
*/
public class app extends Application {
@Override
public void onCreate() {
super.onCreate();
// 初始化
HttpHandler.init();
}
}
点击事件
再在点击事件中触发请求。
public void Click(View view){
HashMap<String, String> params = new HashMap<>();
params.put("appid", "1234567890");
HttpHandler.getManager().executePost(HttpWhat.APP_UPDATA_0, url, params);
}
完事
demo : D10NGYANG/DL10HttpManager
业务逻辑这里因为我懒,所以没有写出来缓存区的缓存的工作方法,也挺简单的,用一个Map存起来,key就是what,而value就是一个object。
业务活动过来拿数据就是根据what来取。
那缓存区怎么通知业务活动有新的消息来了呢?
就是用EventBus,但是只传递what,这样能减少EventBus携带的信息量,也更好管理信息去向与来源,然后监听同样what的业务活动来缓存区拿数据即可。
这样子应该比较清晰了,如果还有问题可以评论联系我。