Giter Club home page Giter Club logo

nohttp's Introduction

NoHttp

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 在什么情况下会请求超时?

下面介绍上方省略的其它配置的详情。

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

配置缓存位置为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的异步方法(AsyncRequestExecutorRequestQueue都是基于同步请求封装的),所以使用RxJavaAsyncTask等都可以很好的封装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封装的,开发者可以作为参考或者直接使用:

  1. IRequest(袁慎彬)
  2. NohttpRxUtils(李奇)

同步请求和异步请求

NoHttp的请求模块的核心其实就是同步请求:SyncRequestExecutorNoHttp的异步请求分为两个类型,一个是异步请求执行器: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主线程不允许发起网络请求。当然如果使用RxJavaAsyncTask等把同步请求封装一下也可以用在主线程,不过NoHttp提供了两种异步请求的方式,可以直接用在主线程中。

异步请求-AsyncRequestExecutor

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

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的业务,所以这里开发者可以尽情发挥:

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 <T> void request(int what, Request<T> request, SimpleResponseListener<T> listener) {
        queue.add(what, request, listener);
    }
    
    // 完全退出app时,调用这个方法释放CPU。
    public void stop() {
        queue.stop();
    }
}

注意:上面的出现的listener就是接受结果的回调interface,它实际上是OnResponseListener,它一种有四个方法需要实现,而有时候实现4个方法显得比较麻烦,所以NoHttp提供了一个默认实现类SimpleResponseListener,开发者可以仅仅实现自己需要实现的方法。

上面在添加Request到队列中时,出现了一个what参数,它相当于使用Handler时的Messagewhat一样,仅仅是用于当一个OnResponseListener接受多个Request的请求结果时区分是哪个Request的响应结果的。

其它特点和用法

下面将会介绍NoHttp默认的几种请求,比如StringBitmapJSONObject等,一般清情况下,一部分开发者都是直接请求String,然后进行解析成JSONXMLJavaBean等,无论使用任何网络框架,这都不是最好的办法,原因如下:

  1. 每一个请求都需要解析StringXMLJSON等,逻辑判断麻烦,代码冗余。
  2. 解析过程在主线程进行,数据量过大时解析过程必将耗时,会造成不好的用户体验(App假死)。

所以本人写了一片如何结合业务直接请求JavaBeanListMapProtobuf的博文:
http://blog.csdn.net/yanzhenjie1003/article/details/70158030

请求不同数据的几种Request

NoHttp请求什么样的数据是由Request决定的,NoHttp本身已经提供了请求StringBitmapJSONObjectJSONArrayRequest

// 请求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/<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了。

添加请求头

请求头支持添加各种类型,比如Stringintlongdoublefloat等等。

StringRequest request = new StringRequest(url, RequestMethod.POST);
   .addHeader("name", "yanzhenjie") // String类型。
   .addHeader("age", "18") // int类型。
   .setHeader("sex", "男") // setHeader将会覆盖已经存在的key。
   ...

添加参数

请求头支持添加各种类型,比如BinaryFileStringintlongdoublefloat等等。

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中添加以下几种类型的参数了:

StringFileBinaryList<String>、List<Binary>、List<File>、List<Object>

代码举例说明:

Map<String, Object> params = new HashMap<>();

params.put("name", "yanzhenjie");
params.put("head", new File(path));
params.put("logo", new FileBinary(file));
params.put("age", 18);
params.put("height", 180.5);

List<String> hobbies = new ArrayList<>();
hobbies.add("篮球");
hobbies.add("帅哥");
params.put("hobbies", hobbies);

List<File> goods = new ArrayList<>();
goods.add(file1);
goods.add(file2);
params.put("goods", goods);

List<Object> otherParams = new ArrayList<>();
otherParams.add("yanzhenjie");
otherParams.add(1);
otherParams.add(file);
otherParams.add(new FileBinary(file));

params.put("other", otherParams);

当然,真实开发中第三种和文件一起使用同一个key请求,几乎不会存在,但是难免会Stringint等使用同一个key请求。

文件上传有两种形式,第一种:以表单的形式上传,第二种:以request body的形式上传,下面先介绍第一种表单的形式:

  • 单个文件
StringRequest request = ...
request.add("file", new FileBinary(file));
  • 多文件,以不同的key上传不同的多个文件
    这里可以添加各种形式的文件,FileBitmapInputStreamByteArray
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

  • 1、Default模式,也是没有设置缓存模式时的默认模式 这个模式实现http协议中的内容,比如响应码是304时,当然还会结合E-Tag和LastModify等头。
StringRequest request = new StringRequest(url, method);
request.setCacheMode(CacheMode.DEFAULT);
  • 2、 当请求服务器失败的时候,读取缓存 请求服务器成功则返回服务器数据,如果请求服务器失败,读取缓存数据返回。
StringRequest request = new StringRequest(url, method);
request.setCacheMode(CacheMode.REQUEST_NETWORK_FAILED_READ_CACHE);
  • 3、如果发现有缓存直接成功,没有缓存才请求服务器 ImageLoader的核心除了内存优化外,剩下一个就是发现把内地有图片则直接使用,没有则请求服务器。

请求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.setCacheMode(CacheMode.IF_NONE_CACHE_REQUEST_NETWORK);
  • 4、仅仅请求网络 无论如何也只会请求网络,也不支持http 304这种默认行为。
ImageRequest request = new ImageRequest(url, method);
request.setCacheMode(CacheMode.ONLY_REQUEST_NETWORK);
...
  • 5、仅仅读取缓存 无论如何仅仅读取缓存,不会请求网络和其它操作。
Request<Bitmap> request = NoHttp.createImageRequest(imageUrl);
request.setCacheMode(CacheMode.ONLY_READ_CACHE);

注意:如果开发者想先得到缓存再请求网络,开发者可以先发起一个仅仅读取缓存的Request,然后发起一个仅仅请求网络的Request不过本人已经在准备NoHttp2.0了,到时候将会以一个全新的面貌和开发者们见面。

缓存模式支持缓存任何数据,因为NoHttp保存数据是转为byte[],读取数据时是把byte[]转为开发者想要的数据,因此NoHttp的缓存可以支持任何自定义的Request

自定义请求

NoHttp的所有自带请求都是继承RestRequest类,所以自定义请求也需要继承RestRequest,泛型写自己想要请求的数据类型,最后在parseResponse()方法中解析服务器数据成自己自己想要的数据类型即可。

  • FastJsonRequest
public class FastJsonRequest extends RestRequestor<JSONObject> {

    public FastJsonRequest(String url) {
	    this(url, RequestMethod.GET);
    }

    public FastJsonRequest(String url, RequestMethod requestMethod) {
	    super(url, requestMethod);
    }

    @Override
    public JSONObject parseResponse(Headers header, byte[] body) throws Throwable {
	    String result = StringRequest.parseResponseString(headers, body);
	    return JSON.parseObject(result);
    }
}

这只是一个自定义请求的演示,比如开发者还可以结合业务封装Request,可以直接请求到业务的JavaBeanList等复杂数据,具体请参考这篇博文:
http://blog.csdn.net/yanzhenjie1003/article/details/70158030

文件下载

因为下载文件代码比较多,这里贴关键部分,具体的请参考demo。

NoHttp的下载模块的核心也是同步请求:SyncDownloadExecutorNoHttp的异步异步下载只有下载队列一种方式:DownloadQueue,当然也可以使用SyncDownloadExecutor结合RxJavaAsyncTask封装其它形式的异步下载。

同步下载-SyncDownloadExecutor

DownloadRequest request = new DownloadRequest(url, RequestMethod.GET, fileFolder, true, true);
    SyncDownloadExecutor.INSTANCE.execute(0, request, new SimpleDownloadListener() {
        @Override
        public void onStart(int what, boolean resume, long range, Headers headers, long size) {
            // 开始下载,回调的时候说明文件开始下载了。
            // 参数1:what。
            // 参数2:是否是断点续传,从中间开始下载的。
            // 参数3:如果是断点续传,这个参数非0,表示之前已经下载的文件大小。
            // 参数4:服务器响应头。
            // 参数5:文件总大小,可能为0,因为服务器可能不返回文件大小。
        }

        @Override
        public void onProgress(int what, int progress, long fileCount, long speed) {
            // 进度发生变化,服务器不返回文件总大小时不回调,因为没法计算进度。
            // 参数1:what。
            // 参数2:进度,[0-100]。
            // 参数3:文件总大小,可能为0,因为服务器可能不返回文件大小。
            // 参数4:下载的速度,含义为1S下载的byte大小,计算下载速度时:
            //        int xKB = (int) speed / 1024; // 单位:xKB/S
            //        int xM = (int) speed / 1024 / 1024; // 单位:xM/S
        }

        @Override
        public void onFinish(int what, String filePath) {
            // 下载完成,参数2为保存在本地的文件路径。
        }
});

必须要介绍一下DownloadListener,它是文件下载状态的监听器,NoHttp提供了一个默认实现,就是上面看到的SimpleDownloadListenerDownloadListener的完成实现如下:

private DownloadListener downloadListener = new DownloadListener() {
	@Override
	public void onStart(int what, boolean resume, long preLenght, Headers header, long count) {
	    // 下载开始。
	}

	@Override
	public void onProgress(int what, int progress, long downCount, long speed) {
		// 更新下载进度和下载网速。
	}

 	@Override
	public void onFinish(int what, String filePath) {
	    // 下载完成。
 	}

	@Override
	public void onDownloadError(int what, StatusCode code, CharSequence message) {
	    // 下载发生错误。
	    // 参数2:错误类型,是枚举值,每一个枚举的具体请看javaDoc或者demo。
	    // 参数三:错误信息。
	}

	@Override
	public void onCancel(int what) {
	    // 下载被取消或者暂停。
	}
};

异步下载-DownloadQueue

DownloadQueue queue = NoHttp.newDownloadQueue(); // 默认三个并发,此处可以传入并发数量。

...
// 发起下载请求:
queue.add(what, request, listener);

...
// 使用完后需要关闭队列释放CPU:
queue.stop();

当然开发者可以自己建立队列:

// 也可以自己建立队列:
RequestQueue queue = new RequestQueue(5);
queue.start(); // 开始队列。

...
// 发起下载请求:
queue.add(what, request, listener);

...
// 使用完后需要关闭队列释放CPU:
queue.stop();

其它的使用方法和封装和上面的RequestQueue相同,请参考上面RequestQueue用法。

创建请求

NoHttp提供了两种构造下载请求的方法,第一种:手动指定下载文件名;第二种:由NoHttp根据服务器响应头、URL等自动确定文件名。

方式一:指定文件名

如果指定文件名,就会使用开发者指定的文件名去命名下载的文件(推荐):

DownloadRequest req = new DownloadRequest(url, method, folder, filename, range, deleteOld);
// 参数1,文件的url。
// 参数2,请求方法,一般为GET。
// 参数3,要保存的文件名路径,须是绝对路径。
// 参数4,文件最终的文件名,最终会用这个文件命名下载好的文件。
// 参数5,是否断点续传,比如之前已经下载了50%,是否继续从50%处开始下载,否则从0开始下载。
// 参数6,下载前检测到已存在你指定的相同文件名的文件时,是否删除重新下载,否则直接回调下载成功。

示例:

String url = "http://...";
String folder = ...;
String filename = "xx.apk";
DownloadRequest req = new DownloadRequest(url, RequestMethod.GET, folder, filename, true, true);

方式一:不指定文件名

NoHttp会根据url或者服务器响应头的Content-Disposition自动命名文件:

DownloadRequest req = new DownloadRequest(url, method, folder, range, deleteOld);
// 参数含义同上。

注意:两种方式都是支持断点续传的。如果开发者设置了使用断点续传,但是文件服务器不支持,那么NoHttp会先尝试以断点的请求一次,如果请求失败,则再以普通下载的方式请求下载。

暂停、继续、取消下载

特别注意:Http下载其实没有暂停下载一说,其本质就是取消下载,继续下载其实利用的就是上面说的断点续传技术,断点续传需要服务器支持,一般tomcatapachenginxiis都是支持的。

Nohttp暂停下载继续下载原理介绍

NoHttp的demo中演示了暂停下载,继续下载等功能,其实就是下载到中途,暂停下载时调用取消下载,然后继续下载时重新建一个DownloadRequest并且使用断点续传下载,此时服务器就会从客户端上次取消下载时客户端已经接受的byte数处开始写出文件,客户端也从上次已经接受的byte数处开始接受并写入文件。

示例:

DownloadRequest request;
String url = "http://...";

// 开始或者继续一个下载。
public void startDownload() {
    if(request != null)
        request = new DownloadRequest(url, RequestMethod.GET, "/sdcard/", "xx.apk", true, true);
    // 注意第5个参数,true表示断点续传。
}

// 暂停或者取消一个下载。
public void stopDownload() {
    if(downloadRequest != null)
        downloadRequest.cancel();
}

更多的使用请参考sample。

代码混淆

如果你没有使用Https,NoHttp可以随意混淆,如果使用了Https,请添加如下混淆规则:

-keepclassmembers class ** {
    private javax.net.ssl.SSLSocketFactory delegate;
}

关于我

😄关注一下我的微信公众号支持我一波
微信二维码

License

Copyright 2015 Yan Zhenjie

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

nohttp's People

Contributors

y0landa avatar yanzhenjie avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nohttp's Issues

支持缓存自定义key吗?

因为在客户端请求的时候 经常会有参数是动态的!okhttp默认是url+参数 md5生产的key!造成带动态参数的缓存就没有意义了!

android studio

android studio 为什么不能直接添加依赖,我搜索不到nohttp

文件下载出现问题

你好,我在你的demo的单个文件下载界面修改了一下,我要多个url顺序下载,我先把这些url创建DownloadRequest并保存到list里面,然后一个下载完成后就进行下一个下载,但是当我点击暂停时,就不能恢复下载了,我debug发现好像在点cancel的时候,blockquene没有把这个移除掉。希望帮忙确认一下是什么问题,谢谢。

咨询关于httplistener

在daemon中看到实现了一个接口HttpListener, 但是在nohttp源码中看到有OnResponseListener, 为什么不直接用OnResponseListener? 这样有什么好处吗?

在教程中首先注明要先初始化

我首次使用该框架,按照网上的教程写了个简单的发送get的请求,在运行时报错才知道需要先在Application中先初始化,然而几乎在所有的教程中都没有写明这个一点,希望作者能加上,免得下次新手在使用的过程中才发现需要先初始化

您好,怎么解决中文乱码的问题

您好,目标网页是gbk编码的,用get方法获取网页内容,在succeed里取出中文的时候乱码,请问怎么解决呢?

    public void onSucceed(int what, Response<String> response) {
        String result =  response.get()
        TextView tvRet = (TextView) findViewById(R.id.tvRet);
        tvRet.setText(result);
    }

hi

前排

CallServer

怎么没有CallServer这个方法?
CallServer.getDownloadInstance().add(0, downloadRequest, downloadListener);
CallServer 报错

多個文件上傳問題?

開發者大大您好,我想上傳多個檔案,以下是我的部分代碼

File file1 = new File(Environment.getExternalStorageDirectory().getPath()+"/xxx.txt");
File file2 = new File(Environment.getExternalStorageDirectory().getPath()+"/yyy.txt");

BasicBinary binary1 = new FileBinary(file1);
BasicBinary binary2 = new FileBinary(file2);

request.add("file", binary1);
request.add("file", binary2);

requestQueue.add(NOHTTP_WHAT_TEST, request, onResponseListener);

php端接收:

600 * 1024){ echo "file size invalid, size > 600kb"; }else{ //如果出错就输出错误信息 if($_FILES["file"]["error"] > 0){ echo "upload file error !"; }else{ //没有错误,就拷贝上传的文件到指定的目录 $name = $_FILES["file"]["name"]; $dir = "upload/"; //如果目录不存在,就创建 if(!file_exists($dir)){ mkdir("upload/"); } //判断文件是否已存在 if(file_exists("upload/" . $name)){ echo "file exist"; }else{ //指定目录下不存在文件,则拷贝文件 move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]); echo "upload file complete"; } } } ``` ?>

上傳之後只會有 yyy.txt ,xxx.txt好像不會被上傳,是哪邊代碼有少打嗎?

還請大神賜教。

java.net.SocketException: sendto failed: EPIPE (Broken pipe)

java.net.SocketException: sendto failed: EPIPE (Broken pipe)
at libcore.io.IoBridge.maybeThrowAfterSendto(IoBridge.java:546)
at libcore.io.IoBridge.sendto(IoBridge.java:515)
at java.net.PlainSocketImpl.write(PlainSocketImpl.java:504)
at java.net.PlainSocketImpl.access$100(PlainSocketImpl.java:37)
at java.net.PlainSocketImpl$PlainSocketOutputStream.write(PlainSocketImpl.java:266)
at okio.Okio$1.write(Okio.java:78)
...
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) 
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) 
at java.lang.Thread.run(Thread.java:818) 

sendto failed: EPIPE (Broken pipe),swankjesse的回答:That's working as designed. Sometimes you lose network connectivity.

Please see here: square/okhttp#1775

问问有关架构的东西

达哥 最近看了nohttp的源码 我怎么感觉你的架构 和Volley的架构很相似 是不是你在开发的时候 参考了Volley的东西

有提问

添加到队列

RequestQueue requestQueue = NoHttp.newRequestQueue();
// 或者传一个并发值,允许三个请求同时并发
// RequestQueue requestQueue = NoHttp.newRequestQueue(3);

// 发起请求
requestQueue.add(what, request, responseListener);
  上面添加到队列时有一个what,这个what会在responseLisetener响应时回调给开发者,所以我们可以用一个responseLisetener接受多个请求的响应,用what来区分结果。而不用像有的框架一样,每一个请求都要new一个回调。

这个而不用像有的框架一样,每一个请求都要new一个回调。 我觉得还是每次请求都new 一个比较好。
这样不得把很多request 结果处理写到一个listener里头啊

上传文件时,调用request.cancle()无法取消上传

已测试,确实存在上传无法取消的问题,复现方法很简单,只需在sample项目中的上传代码中增加取消下载功能即可,经测试,在上传过程中,调用Request.cancle(),Request.cancelBySign(),RequestQueue.cancelBySign()以及RequestQueue.cancelAll()均无法停止上传,OnUploadListener中的回调方法依然会被调用,跪求 @Y0Landa 大神解决。测试系统为Android5.1.1,测试设备为Google Nexus 9。

thread interrupted错误

android 6.0 RequestQueue 去 start() 一个 stringRequest 时,报thread interrupted无法连接上网络,能帮忙分析一下错误吗?

java.io.InterruptedIOException: thread interrupted
at com.android.okhttp.okio.Timeout.throwIfReached(Timeout.java:145)
at com.android.okhttp.okio.Okio$1.write(Okio.java:73)
at com.android.okhttp.okio.AsyncTimeout$1.write(AsyncTimeout.java:155)
at com.android.okhttp.okio.RealBufferedSink.flush(RealBufferedSink.java:221)
at com.android.okhttp.internal.http.HttpConnection.flush(HttpConnection.java:141)
at com.android.okhttp.internal.http.HttpTransport.finishRequest(HttpTransport.java:52)
at com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:903)
at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:789)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:443)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:388)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:501)
at com.yolanda.nohttp.BasicConnection.getConnection(BasicConnection.java:88)
at com.yolanda.nohttp.rest.HttpRestConnection.sendRequestHandleRetry(HttpRestConnection.java:135)
at com.yolanda.nohttp.rest.HttpRestConnection.requestNetwork(HttpRestConnection.java:84)
at com.yolanda.nohttp.rest.HttpRestParser.parserRequest(HttpRestParser.java:51)
at com.yolanda.nohttp.rest.RequestDispatcher.run(RequestDispatcher.java:101)

RequestMethod.class的问题

public boolean allowRequestBody() {
boolean allowRequestBody = this == POST || this == PUT || this == PATCH || this == DELETE;
if (Build.VERSION.SDK_INT < AndroidVersion.LOLLIPOP)
allowRequestBody = allowRequestBody && this != DELETE;
return allowRequestBody;
}
目前在安卓6.0上delete方法服务器端拿不到参数,最终追到这里,不知道版主这个方法为什么这样写

几个小问题

  • 我看您在给出的 demo 里把 RequestQueue 的获取弄成单例了,但是这样是不是并发请求数就固定了,如果有特殊需要,还要自己再获取另一个可以指定并发请求数的 RequestQueue 呢?
  • 还有就是在首页 StartActivity 退出时执行了三步操作:先取消所有请求,然后停止所有请求队列,最后杀死进程,请问这样是必要的也必须按照这个顺序来吗?

怎么获取nohttp的错误消息

比如超时、网络错误等情况,我需要在 APP 中针对每种情况进行处理。请问如何能获取 nohttp 中这类错误消息?

能傳入JsonObject嗎?

開發者大大您好,傳入資料的時候,我想用JsonObject,但是在request.add( object );的時候,好像只能add字串,不能add(jsonObject)嗎?

因為在後台php程式碼裡面,我想把傳入的json解析,這樣實作上比較方便。

JSONObject object = new JSONObject();
try 
{
    object.put("member_account","test");
    object.put("member_password","test");
    object.put("member_email","[email protected]");
}
catch(JSONException e)
{
    e.printStackTrace();
}

request.add( object );

response.isSucceed()这个方法的逻辑有问题

response.isSucceed()这个方法好像并不能用来判断请求是否成功啊. 我试了, 明明状态已经是404了, response.isSucceed()返回的却是true, 我觉得应该根据code是否在200到300之间来判断是否是success

Status Code 302

对于Status Code 为 302 的这类访问,会自动转向目标URL,但是我只想要当前URL的Cookie这个怎么解决。
比如放问AURL,Status Code为302 跳转到BURL,但是我只想要AURL响应头里的Cookie。
求解决 谢谢!

请教一个问题

使用nohttp,如何将网络请求单独写在一个类里面,然后回调到对应的activity或fragment里面

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.