一、起因
APP想要从远程服务器下载一个文件,不想使用网络请求框架,想了解一下原生的实现。于是简单了解了一下URLconnection类的使用,加上参考了网络上的实现,简单实现了文件下载操作。
代码如下
long downloadLength = 0;
mDownloadFile = new File(filePath,fileName);
if (mDownloadFile.exists()){
downloadLength = mDownloadFile.length();
}
//下载文件
URL url = new URL(downloadUrl);
URLConnection connection = url.openConnection();
// 设置允许输入流输入数据到本地
connection.setDoInput(true);
// 设置允许输出流输出到服务器
connection.setDoOutput(true);
connection.connect();
mContentLength = connection.getContentLength();
PrintLogCat.printLogCat(CommonFunction.getClassNameAndMethodNameAndLineNumberInfo()+"contentLength--->"+mContentLength);
if (mContentLength == 0){
return DOWNLOAD_FAILED;
}else if (mContentLength == downloadLength){
return DOWNLOAD_FINISHED;
}
InputStream in = connection.getInputStream();
BufferedInputStream bis = new BufferedInputStream(in);
OutputStream os = new FileOutputStream(mDownloadFile);
//定义整型变量接收读取到文件的大小
int size ;
//定义整型变量用来存储累计读取文件长度(大小)
int length = 0;
byte[] buffer = new byte[1024];
// 从输入缓冲区中一次读取1024个字节的文件内容到buf对象中,并将读取大小赋值给size变量,当读取完毕后size=-1,结束循环读取
while ((size = bis.read(buffer))!= -1){
length += size;
//输入流写出数据到文件
os.write(buffer,0,size);
int progress =(int)(length*100/mContentLength);
publishProgress(progress);
}
os.close();
bis.close();
in.close();
只是截取了一下下载的核心代码。这里为了速度偷了懒部分程序使用了网上的,没有仔细检查网上程序的正确性。
于是在测试验证效果时收到了回报。APP中没有发起下载的操作(网络请求没有得到正确的响应)。当然如果使用网络请求框架的如okgo的会有出现问题会有比较清晰的日志信息。于是开始了寻找问题的旅程。通过添加log信息发现了问题 getContentLength()为-1。
二、问题分析
遇到问题对于菜鸟来说当然是去网上查询前辈大佬的解决方案了,于是开心的上网查找。得到了如下的分析:
1、最近在使用OKhttp下载文件的时候出现了一个奇怪的现象,responsebody.contentLength()获取到的值为-1经常抓包分析,发现服务器会随机的对下发的资源做GZip操作,而此时就没有相应的content-length,解决方法很简单,在Header中加入:Request.Builder().addHeader(“Accept-Encoding”, “identity”)这样强迫服务器不走压缩,问题就得到了解决。
2、 链接不正确或者服务器响应的问题 或者ip被屏蔽这个问题经确认,很快的排除了。
3、注意服务器支持的请求方式搜索无果于是使用浏览器输入资源地址,一切正常。于是使用fiddler抓了包。了解如何抓包可参考HTTPS 或手机 APP 接口数据的抓包配置步骤 抓包结果如下:
浏览器:
GET http://xxx/apk/xxx.apk HTTP/1.1
Host: 192.xxx.xxx.xxx:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
HTTP/1.1 200
Last-Modified: Thu, 27 Jun 2019 02:15:22 GMT
Accept-Ranges: bytes
Content-Type: application/vnd.android.package-archive
Content-Length: 95570633
Date: Thu, 27 Jun 2019 03:21:21 GMT
APP:
POST http://xxx/apk/xxx.apk HTTP/1.1
Content-Type: application/x-www-form-urlencoded
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1.1; PRO 6s Build/NMF26O)
Host: 192.xxx.xxx.xxx:8080
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 0
HTTP/1.1 405
Allow: GET, HEAD
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 27 Jun 2019 03:20:15 GMT
c2
{"timestamp":"2019-06-27T03:20:15.897+0000","status":405,"error":"Method Not Allowed","message":"Request method 'POST' not supported","path":"/apk/xxx.apk"}
0
可以看到APP端莫名其妙的变成了POST请求,但是服务器端(Allow: GET, HEAD)不支持POST。所以该请求响应体中没有contentlength属性,这也就是contentlength 返回-1 的原因
于是检查代码,发现添加了
// 设置允许输出流输出到服务器
connection.setDoOutput(true);
去掉后问题解决,通过查看URLconnection 类 上述语句设置为true 就可以获取到服务器的输出流,向服务器发送数据时就需要使用该输出流。于是明白了该语句设置为true就相当于使用了POST请求
三、总结
有空还是要认真理解原理,多动手,多使用工具辅助定位问题。网上查找的结果可作参考,因为多数情况下的异常错误不一定与搜索结果中的情况一致。实现某个功能时页最好能够理解原理,这样有时候能避免很多错误
就网络请求这块异常抓包是最有效的定位问题的方法。
另外大文件(大于100M的文件)没有尝试过,不知道会不会出现新的问题