# NoHttp
**Repository Path**: mirrors_yanzhenjie/NoHttp
## Basic Information
- **Project Name**: NoHttp
- **Description**: :lemon: Android实现Http标准协议框架,支持多种缓存模式,底层可动态切换OkHttp、URLConnection。
- **Primary Language**: Unknown
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2020-08-19
- **Last Updated**: 2025-10-11
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# NoHttp
QQ技术交流群:[46505645](https://jq.qq.com/?_wv=1027&k=5ImVHCl)
**特别说明**:强烈建议开发者切换到另一个网络框架[Kalle](https://github.com/yanzhenjie/Kalle),Kalle在架构设计上、Api设计上、功能实现上都更加健壮和完善,文档也比较全面。
Kalle开源地址:[https://github.com/yanzhenjie/Kalle](https://github.com/yanzhenjie/Kalle)
Kalle文档地址:[http://yanzhenjie.github.io/Kalle](http://yanzhenjie.github.io/Kalle)
**NoHttp依旧正常维护**,正在使用和即将要使用的同学可以放心使用。
## 添加依赖
如果使用HttpURLConnection作为网络层
```groovy
implementation 'com.yanzhenjie.nohttp:nohttp:1.1.11'
```
如果要使用OkHttp作为网络层,请再依赖
```groovy
implementation 'com.yanzhenjie.nohttp:okhttp:1.1.11'
```
### 一般初始化
直接初始化后,一切采用默认设置。
```java
NoHttp.initialize(this);
```
### 高级初始化
```java
InitializationConfig config = InitializationConfig.newBuilder(context)
// 其它配置。
...
.build();
NoHttp.initialize(config);
```
关于超时,很多人都没有彻底理解或理解有误差,本人在知乎上写过一个答案,请参考:
[HTTP 在什么情况下会请求超时?](https://www.zhihu.com/question/21609463/answer/160100810)
下面介绍上方省略的**其它配置**的详情。
```java
InitializationConfig config = InitializationConfig.newBuilder(context)
// 全局连接服务器超时时间,单位毫秒,默认10s。
.connectionTimeout(30 * 1000)
// 全局等待服务器响应超时时间,单位毫秒,默认10s。
.readTimeout(30 * 1000)
// 配置缓存,默认保存数据库DBCacheStore,保存到SD卡使用DiskCacheStore。
.cacheStore(
// 如果不使用缓存,setEnable(false)禁用。
new DBCacheStore(context).setEnable(true)
)
// 配置Cookie,默认保存数据库DBCookieStore,开发者可以自己实现CookieStore接口。
.cookieStore(
// 如果不维护cookie,setEnable(false)禁用。
new DBCookieStore(context).setEnable(true)
)
// 配置网络层,默认URLConnectionNetworkExecutor,如果想用OkHttp:OkHttpNetworkExecutor。
.networkExecutor()
// 全局通用Header,add是添加,多次调用add不会覆盖上次add。
.addHeader()
// 全局通用Param,add是添加,多次调用add不会覆盖上次add。
.addParam()
.sslSocketFactory() // 全局SSLSocketFactory。
.hostnameVerifier() // 全局HostnameVerifier。
.retry(x) // 全局重试次数,配置后每个请求失败都会重试x次。
.build();
```
**说明**:
1. 上方配置可以全部配置,也可以只配置其中一个或者几个。
2. addHeader()、addParam()可以调用多次,且值不会被覆盖。
3. 使用`DiskCacheStore()`时默认缓存到`context.getCacheDir()`目录,使用`DiskCacheStore(path)`指定缓存目录为`path`,不过要注意SD卡的读写权限和运行时权限:[AndPermission](https://github.com/yanzhenjie/AndPermission)。
配置缓存位置为SD卡示例:
```java
InitializationConfig config = InitializationConfig.newBuilder(context)
.cacheStore(
new DiskCacheStore(context) // 保存在context.getCahceDir()文件夹中。
// new DiskCacheStore(path) // 保存在path文件夹中,path是开发者指定的绝对路径。
)
.build();
```
添加全局请求头、参数示例:
```java
InitializationConfig config = InitializationConfig.newBuilder(context)
.addHeader("Token", "123") // 全局请求头。
.addHeader("Token", "456") // 全局请求头,不会覆盖上面的。
.addParam("AppVersion", "1.0.0") // 全局请求参数。
.addParam("AppType", "Android") // 全局请求参数。
.addParam("AppType", "iOS") // 全局请求参数,不会覆盖上面的两个。
.build();
```
## 需要的权限
```xml
```
## 调试模式
```java
Logger.setDebug(true);// 开启NoHttp的调试模式, 配置后可看到请求过程、日志和错误信息。
Logger.setTag("NoHttpSample");// 打印Log的tag。
```
开启NoHttp的调试模式后可看到请求过程、日志和错误信息,基本不用抓包。可以看到请求头、请求数据、响应头、Cookie等,而且打印出的Log非常整齐。
所以说,如果开发者使用过程中遇到什么问题了,开启调试模式,一切妖魔鬼怪都会现形的。
## 第三方异步框架
`NoHttp`的核心就是同步请求方法,`NoHttp`的异步方法(`AsyncRequestExecutor`、`RequestQueue`都是基于同步请求封装的),所以使用`RxJava`、`AsyncTask`等都可以很好的封装`NoHttp`,一个请求`String`的示例:
```
StringRequest request = new String(url, RequestMethod.GET);
Response response = SyncRequestExecutor.INSTANCE.execute(request);
if (response.isSucceed()) {
// 请求成功。
} else {
// 请求失败,拿到错误:
Exception e = response.getException();
}
```
下面是两个项目群里的基友基于RxJava + NoHttp封装的,开发者可以作为参考或者直接使用:
1. [IRequest](https://github.com/yuanshenbin/IRequest)(袁慎彬)
2. [NohttpRxUtils](https://github.com/LiqiNew/NohttpRxUtils)(李奇)
# 同步请求和异步请求
`NoHttp`的请求模块的核心其实就是同步请求:`SyncRequestExecutor`;`NoHttp`的异步请求分为两个类型,一个是异步请求执行器:`AsyncRequestExecutor`,另一个是请求队列:`RequestQueue`。
## 同步请求
一个请求`String`的示例:
```java
StringRequest req = new String("http://api.nohttp.net", RequestMethod.POST);
Response response = SyncRequestExecutor.INSTANCE.execute(req);
if (response.isSucceed()) {
// 请求成功。
} else {
// 请求失败,拿到错误:
Exception e = response.getException();
}
```
当然同步请求只适合在**子线程**中使用,因为Android主线程不允许发起网络请求。当然如果使用`RxJava`、`AsyncTask`等把同步请求封装一下也可以用在主线程,不过NoHttp提供了两种异步请求的方式,可以直接用在主线程中。
## 异步请求-AsyncRequestExecutor
```java
StringRequest request = new StringRequest("http://api.nohttp.net");
Cancelable cancel = AsyncRequestExecutor.INSTANCE.execute(0, request, new SimpleResponseListener() {
@Override
public void onSucceed(int what, Response response) {
// 请求成功。
}
@Override
public void onFailed(int what, Response response) {
// 请求失败。
}
});
// 如果想取消请求:
cancel.cancel();
// 判断是否取消:
boolean isCancelled = cancel.isCancelled();
```
这种方式是基于线程池的,它没有队列的优先级的特点了。
## 异步请求-RequestQueue
```java
RequestQueue queue = NoHttp.newRequestQueue(); // 默认三个并发,此处可以传入并发数量。
...
// 发起请求:
queue.add(what, request, listener);
...
// 使用完后需要关闭队列释放CPU:
queue.stop();
```
也可以自己建立队列:
```java
// 也可以自己建立队列:
RequestQueue queue = new RequestQueue(5);
queue.start(); // 开始队列。
...
// 发起请求:
queue.add(what, request, listener);
...
// 使用完后需要关闭队列:
queue.stop();
```
很多同学有一个习惯就是每发起一个请求就new一个队列,**这是绝对错误的用法**,例如某同学封装的一个方法:
```java
public void request(Request request, SampleResponseListener listener) {
RequestQueue queue = NoHttp.newRequestQueue(5);
queue.add(0, request, listener);
}
```
再次声明一下,**上面的这段用法是错误的**。
对于想直接调用队列就能请求的开发者,`NoHttp`也提供了一个单例模式的用法:
```java
// 比如请求队列单例模式:
NoHttp.getRequestQueueInstance().add...
...
// 比如下载队列单例模式:
NoHttp.getDownloadQueueInstance().add...
```
当然开发者可以直接使用上面讲到的异步请求执行器:`AsyncRequestExecutor`,这个是比较推荐的。
### 队列的正确用法
队列正确的用法有两种,一种是每一个页面使用一个队列,在页面退出时调用`queue.stop()`停止队列;另一种是全局使用同一个队列,在App退出时调用`queue.stop()`停止队列。本人比较推荐第二种方法,即全局使用同一个`RequestQueue`。
用法一,开发者可以写一个`BaseActivity`,在`onCreate()`方法中建立`RequestQueue`,在`onDestory()`中销毁队列:
```java
public class BaseActivity extends Activity {
private RequestQueue queue;
@Override
public void onCreate(Bundle savedInstanceState) {
queue = NoHttp.newRequestQueue();
}
// 提供给子类请求使用。
public void request(int what, Request request, SimpleResponseListener listener) {
queue.add(what, request, listener);
}
@Override
public void onDestory() {
queue.stop();
}
}
```
用法二,使用单例模式封装一个全局专门负责请求的类,使全局仅仅保持一个`RequestQueue`:
```java
StringRequest request = new StringRequest("http://api.nohttp.net", RequestMethod.POST);
CallServer.getInstance().request(0, request, listener);
```
上面的`CallServer`不是`NoHttp`提供的,而是需要开发者自己封装,因为这里可以写自己App的业务,所以这里开发者可以尽情发挥:
```java
public class CallServer {
private static CallServer instance;
public static CallServer getInstance() {
if (instance == null)
synchronized (CallServer.class) {
if (instance == null)
instance = new CallServer();
}
return instance;
}
private RequestQueue queue;
private CallServer() {
queue = NoHttp.newRequestQueue(5);
}
public void request(int what, Request request, SimpleResponseListener listener) {
queue.add(what, request, listener);
}
// 完全退出app时,调用这个方法释放CPU。
public void stop() {
queue.stop();
}
}
```
**注意**:上面的出现的`listener`就是接受结果的回调`interface`,它实际上是`OnResponseListener`,它一种有四个方法需要实现,而有时候实现4个方法显得比较麻烦,所以`NoHttp`提供了一个默认实现类`SimpleResponseListener`,开发者可以仅仅实现自己需要实现的方法。
> 上面在添加Request到队列中时,出现了一个`what`参数,它相当于使用`Handler`时的`Message`的`what`一样,仅仅是用于当一个`OnResponseListener`接受多个Request的请求结果时区分是哪个`Request`的响应结果的。
# 其它特点和用法
下面将会介绍`NoHttp`默认的几种请求,比如`String`、`Bitmap`、`JSONObject`等,一般清情况下,一部分开发者都是直接请求`String`,然后进行解析成`JSON`、`XML`、`JavaBean`等,无论使用任何网络框架,这都不是最好的办法,原因如下:
1. 每一个请求都需要解析`String`成`XML`、`JSON`等,逻辑判断麻烦,代码冗余。
2. 解析过程在主线程进行,数据量过大时解析过程必将耗时,会造成不好的用户体验(App假死)。
所以本人写了一片如何结合业务直接请求`JavaBean`、`List`、`Map`、`Protobuf`的博文:
[http://blog.csdn.net/yanzhenjie1003/article/details/70158030](http://blog.csdn.net/yanzhenjie1003/article/details/70158030)
## 请求不同数据的几种Request
`NoHttp`请求什么样的数据是由`Request`决定的,`NoHttp`本身已经提供了请求`String`、`Bitmap`、`JSONObject`、`JSONArray`的`Request`:
```java
// 请求String:
StringRequest request = new StringRequest(url, method);
// 请求Bitmap:
ImageRequest request = new ImageRequest(url, method);
// 请求JSONObject:
JsonObjectRequest request = new JsonObjectRequest(url, method);
// 请求JSONArray:
JsonArrayRequest request = new JsonArrayRequest(url, method);
```
## 拼装URL
这个能力是在1.1.3开始增加的,也是本次升级的一个亮点,增加拼装URL的方法,比如服务器是RESTFUL风格的API,请求用户信息时可能是这样一个URL:
```
http://api.nohttp.net/rest//userinfo
```
这里的``就是用户名或者用户id,需要开发者动态替换,然后获取用户信息。以前是这样做的:
```
String userName = AppConfig.getUserName();
String url = "http://api.nohttp.net/rest/%1$s/userinfo";
url = String.format(Locale.getDefault(), url, userName);
StringRequest request = new StringRequest(url);
...
```
现在可以这样做:
```
String url = "http://api.nohttp.net/rest/";
StringRequest request = new StringRequest(url)
request.path(AppConfig.getUserName())
request.path("userinfo")
...
```
也就是说开发者可以动态拼装URL了。
## 添加请求头
请求头支持添加各种类型,比如`String`、`int`、`long`、`double`、`float`等等。
```java
StringRequest request = new StringRequest(url, RequestMethod.POST);
.addHeader("name", "yanzhenjie") // String类型。
.addHeader("age", "18") // int类型。
.setHeader("sex", "男") // setHeader将会覆盖已经存在的key。
...
```
## 添加参数
请求头支持添加各种类型,比如`Binary`、`File`、`String`、`int`、`long`、`double`、`float`等等。
```java
StringRequest request = new StringRequest(url, RequestMethod.POST);
.add("name", "严振杰") // String类型
.add("age", 18) // int类型
.add("age", "20") // add方法不会覆盖已经存在key,所以age将会有两个值:18, 20。
.set("sex", "女") // set会覆盖已存在的key。
.set("sex", "男") // 比如最终sex就只有一个值:男。
// 添加File
.add("head", file)
.add("head", new FileBinary(file))
// 添加Bitmap
.add("head", new BitmapBinary(bitmap))
// 添加ByteArray
.add("head", new ByteArrayBinary(byte[]))
// 添加InputStream
.add("head", new InputStreamBinary(inputStream));
```
另外需要说明原来的`Request#add(Map)`更新为`Request#add(Map)`,这样做的好处是喜欢使用`Map`封装参数的同学,可以在`Map`中添加以下几种类型的参数了:
```java
String、File、Binary、List、List、List、List