:lemon: Android 实现了 Http 标准协议框架,支持多种缓存模式,底层可动态切换 OkHttp 和URLConnection。
:lemon: Android 实现了 Http 标准协议框架,支持多种缓存模式,底层可动态切换 OkHttp 和URLConnection。
QQ技术交流群:46505645
特别说明:强烈建议开发者切换到另一个网络框架Kalle,Kalle在架构设计上、Api设计上、功能实现上都更加健壮和完善,文档也比较全面。
Kalle开源地址:https://github.com/yanzhenjie/Kalle
Kalle文档地址:http://yanzhenjie.github.io/Kalle
NoHttp依旧正常维护,正在使用和即将要使用的同学可以放心使用。
如果使用HttpURLConnection作为网络层
implementation 'com.yanzhenjie.nohttp:nohttp:1.1.11'
如果要使用OkHttp作为网络层,请再依赖
implementation 'com.yanzhenjie.nohttp:okhttp:1.1.11'
直接初始化后,一切采用默认设置。
NoHttp.initialize(this);
InitializationConfig config = InitializationConfig.newBuilder(context)
// 其它配置。
...
.build();
NoHttp.initialize(config);
关于超时,很多人都没有彻底理解或理解有误差,本人在知乎上写过一个答案,请参考:
HTTP 在什么情况下会请求超时?
下面介绍上方省略的其它配置的详情。
…
说明:
DiskCacheStore()时默认缓存到context.getCacheDir()目录,使用DiskCacheStore(path)指定缓存目录为path,不过要注意SD卡的读写权限和运行时权限:AndPermission。配置缓存位置为SD卡示例:
InitializationConfig config = InitializationConfig.newBuilder(context)
.cacheStore(
new DiskCacheStore(context) // 保存在context.getCahceDir()文件夹中。
// new DiskCacheStore(path) // 保存在path文件夹中,path是开发者指定的绝对路径。
)
.build();
添加全局请求头、参数示例:
InitializationConfig config = InitializationConfig.newBuilder(context)
.addHeader("Token", "123") // 全局请求头。
.addHeader("Token", "456") // 全局请求头,不会覆盖上面的。
.addParam("AppVersion", "1.0.0") // 全局请求参数。
.addParam("AppType", "Android") // 全局请求参数。
.addParam("AppType", "iOS") // 全局请求参数,不会覆盖上面的两个。
.build();
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
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<String> response = SyncRequestExecutor.INSTANCE.execute(request);
if (response.isSucceed()) {
// 请求成功。
} else {
// 请求失败,拿到错误:
Exception e = response.getException();
}
下面是两个项目群里的基友基于RxJava + NoHttp封装的,开发者可以作为参考或者直接使用:
NoHttp的请求模块的核心其实就是同步请求:SyncRequestExecutor;NoHttp的异步请求分为两个类型,一个是异步请求执行器:AsyncRequestExecutor,另一个是请求队列:RequestQueue。
一个请求String的示例:
StringRequest req = new String("http://api.nohttp.net", RequestMethod.POST);
Response<String> response = SyncRequestExecutor.INSTANCE.execute(req);
if (response.isSucceed()) {
// 请求成功。
} else {
// 请求失败,拿到错误:
Exception e = response.getException();
}
当然同步请求只适合在子线程中使用,因为Android主线程不允许发起网络请求。当然如果使用RxJava、AsyncTask等把同步请求封装一下也可以用在主线程,不过NoHttp提供了两种异步请求的方式,可以直接用在主线程中。
StringRequest request = new StringRequest("http://api.nohttp.net");
Cancelable cancel = AsyncRequestExecutor.INSTANCE.execute(0, request, new SimpleResponseListener<String>() {
@Override
public void onSucceed(int what, Response<String> response) {
// 请求成功。
}
@Override
public void onFailed(int what, Response<String> response) {
// 请求失败。
}
});
// 如果想取消请求:
cancel.cancel();
// 判断是否取消:
boolean isCancelled = cancel.isCancelled();
这种方式是基于线程池的,它没有队列的优先级的特点了。
RequestQueue queue = NoHttp.newRequestQueue(); // 默认三个并发,此处可以传入并发数量。
...
// 发起请求:
queue.add(what, request, listener);
...
// 使用完后需要关闭队列释放CPU:
queue.stop();
也可以自己建立队列:
// 也可以自己建立队列:
RequestQueue queue = new RequestQueue(5);
queue.start(); // 开始队列。
...
// 发起请求:
queue.add(what, request, listener);
...
// 使用完后需要关闭队列:
queue.stop();
很多同学有一个习惯就是每发起一个请求就new一个队列,这是绝对错误的用法,例如某同学封装的一个方法:
public <T> void request(Request<T> request, SampleResponseListener<T> listener) {
RequestQueue queue = NoHttp.newRequestQueue(5);
queue.add(0, request, listener);
}
再次声明一下,上面的这段用法是错误的。
对于想直接调用队列就能请求的开发者,NoHttp也提供了一个单例模式的用法:
// 比如请求队列单例模式:
NoHttp.getRequestQueueInstance().add...
...
// 比如下载队列单例模式:
NoHttp.getDownloadQueueInstance().add...
当然开发者可以直接使用上面讲到的异步请求执行器:AsyncRequestExecutor,这个是比较推荐的。
队列正确的用法有两种,一种是每一个页面使用一个队列,在页面退出时调用queue.stop()停止队列;另一种是全局使用同一个队列,在App退出时调用queue.stop()停止队列。本人比较推荐第二种方法,即全局使用同一个RequestQueue。
用法一,开发者可以写一个BaseActivity,在onCreate()方法中建立RequestQueue,在onDestory()中销毁队列:
public class BaseActivity extends Activity {
private RequestQueue queue;
@Override
public void onCreate(Bundle savedInstanceState) {
queue = NoHttp.newRequestQueue();
}
// 提供给子类请求使用。
public <T> void request(int what, Request<T> request, SimpleResponseListener<T> listener) {
queue.add(what, request, listener);
}
@Override
public void onDestory() {
queue.stop();
}
}
用法二,使用单例模式封装一个全局专门负责请求的类,使全局仅仅保持一个RequestQueue:
StringRequest request = new StringRequest("http://api.nohttp.net", RequestMethod.POST);
CallServer.getInstance().request(0, request, listener);
上面的CallServer不是NoHttp提供的,而是需要开发者自己封装,因为这里可以写自己App的业务,所以这里开发者可以尽情发挥:
…
注意:上面的出现的listener就是接受结果的回调interface,它实际上是OnResponseListener,它一种有四个方法需要实现,而有时候实现4个方法显得比较麻烦,所以NoHttp提供了一个默认实现类SimpleResponseListener,开发者可以仅仅实现自己需要实现的方法。
上面在添加Request到队列中时,出现了一个
what参数,它相当于使用Handler时的Message的what一样,仅仅是用于当一个OnResponseListener接受多个Request的请求结果时区分是哪个Request的响应结果的。
下面将会介绍NoHttp默认的几种请求,比如String、Bitmap、JSONObject等,一般清情况下,一部分开发者都是直接请求String,然后进行解析成JSON、XML、JavaBean等,无论使用任何网络框架,这都不是最好的办法,原因如下:
String成XML、JSON等,逻辑判断麻烦,代码冗余。所以本人写了一片如何结合业务直接请求JavaBean、List、Map、Protobuf的博文:
http://blog.csdn.net/yanzhenjie1003/article/details/70158030
NoHttp请求什么样的数据是由Request决定的,NoHttp本身已经提供了请求String、Bitmap、JSONObject、JSONArray的Request:
// 请求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);
这个能力是在1.1.3开始增加的,也是本次升级的一个亮点,增加拼装URL的方法,比如服务器是RESTFUL风格的API,请求用户信息时可能是这样一个URL:
http://api.nohttp.net/rest/<userid>/userinfo
这里的<userid>就是用户名或者用户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等等。
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等等。
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<String, String>)更新为Request#add(Map<String, Object>),这样做的好处是喜欢使用Map封装参数的同学,可以在Map中添加以下几种类型的参数了:
String、File、Binary、List<String>、List<Binary>、List<File>、List<Object>
代码举例说明:
…
当然,真实开发中第三种和文件一起使用同一个key请求,几乎不会存在,但是难免会String、int等使用同一个key请求。
文件上传有两种形式,第一种:以表单的形式上传,第二种:以request body的形式上传,下面先介绍第一种表单的形式:
StringRequest request = ...
request.add("file", new FileBinary(file));
key上传不同的多个文件File、Bitmap、InputStream、ByteArray。StringRequest request = ...
request.add("file1", new FileBinary(File));
request.add("file2", new FileBinary(File));
request.add("file3", new InputStreamBinary(InputStream));
request.add("file4", new ByteArrayBinary(byte[]));
request.add("file5", new BitmapBinary(Bitmap));
key上传相同的多个文件StringRequest request = ...
fileList.add("image", new FileBinary(File));
fileList.add("image", new InputStreamBinary(InputStream));
fileList.add("image", new ByteArrayBinary(byte[]));
fileList.add("image", new BitmapBinary(Bitmap));
或者:
StringRequest request = ...;
List<Binary> fileList = ...;
fileList.add(new FileBinary(File));
fileList.add(new InputStreamBinary(InputStream));
fileList.add(new ByteArrayBinary(byte[]));
fileList.add(new BitmapStreamBinary(Bitmap));
request.add("file_list", fileList);
第二种request body的形式是多种多样的,同时不仅可以提交文件,也可以提交任何流的数据,详情看下面提交请求包体的内容。
提交Body分为提交Json、提交String、提交Xml、提交流等,其实最终都是转成流提交的,所以开发者可以用这种方式提交文件。
具体用法如下:
// 提交普通String
request.setDefineRequestBody(String, ContentType);
// 提交json字符串
request.setDefineRequestBodyForJson(JsonString)
// 提交jsonObject对象,其实还是json字符串
request.setDefineRequestBodyForJson(JSONObject)
// 提交xml字符串
request.setDefineRequestBodyForXML(XmlString)
// 提交字体Body,比如File(这跟表单上传不一样的),可以转为InputStream来提交
request.setDefineRequestBody(InputStream, ContentType)
举一个提交文件的例子:
File file = ...;
FileInputStream fileStream = new FileInputStream(file);
StringRequest request = new StringRequest(url, RequestMethod.POST);
request.setDefineRequestBody(fileStream, Headers.HEAD_VALUE_CONTENT_TYPE_OCTET_STREAM);
NoHttp支持缓存到数据库、缓存到SD卡等,并且不论缓存在数据库或者SD,NoHttp都把数据进行了加密,需要在初始化的时候配置缓存的位置。
需要注意的是,在6.0以上的手机中如果要缓存在SD卡,需要在请求之前,需要请求运行时权限,如果开发者不懂运行时权限,可以看这篇文章Android 6.0 运行时权限管理最佳实践,本人推荐使用这个运行时权限管理框架:AndPermission。
Default模式,也是没有设置缓存模式时的默认模式
这个模式实现http协议中的内容,比如响应码是304时,当然还会结合E-Tag和LastModify等头。StringRequest request = new StringRequest(url, method);
request.setCacheMode(CacheMode.DEFAULT);
StringRequest request = new StringRequest(url, method);
request.setCacheMode(CacheMode.REQUEST_NETWORK_FAILED_READ_CACHE);
请求String,缓存String:
StringRequest request = new StringRequest(url, method);
// 非标准Http协议,改变缓存模式为IF_NONE_CACHE_REQUEST_NETWORK
request.setCacheMode(CacheMode.IF_NONE_CACHE_REQUEST_NETWORK);
请求图片,缓存图片:
ImageRequest request = new ImageRequest(url, method);
request.setCacheMo
暂无开放 Issues,或尚未同步最近议题。