Android网络功能开发——文件下载和上传
Posted nanoage
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android网络功能开发——文件下载和上传相关的知识,希望对你有一定的参考价值。
本篇介绍使用HTTP协议实现文件下载和上传。在客户端和服务器的通信过程中,可能有些多媒体或数据文件需要下载或上传,可以通过HTTP协议实现。
首先看使用HTTP协议下载文件的原理:客户端发送一个HTTP GET请求,并且在消息中用URL指出要下载的文件。
Web服务器都实现了对文件下载请求的响应,响应的消息头中包含文件的基本信息,消息体中包含文件的具体内容,文件内容是二进制格式的。
客户端用HTTP GET实现文件下载的流程和用HTTP GET从服务器获取数据的流程是一致的,区别在于对返回的响应消息的处理。在用HTTP GET从服务器上获取数据时,对于返回的消息体中的内容是直接当作数据来处理的。用HTTP GET下载文件时,对于返回的消息体中的内容要当作文件内容来保存。具体来说,就是获取消息体输入流、读取数据保存到文件、关闭流。
对应的代码是这样的,获取输入流对应的是connection.getInputStream方法,从流中读取数据用的是输入流的read方法,写到文件用的是输出流的write方法,最后用close关闭流。代码的整体框架和用HTTP GET从服务器上获取数据是一样的。
String download(String urlStr, File f)
String result = null;
HttpURLConnection connection = null;
try
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(5000);
int statusCode = connection.getResponseCode();
if(statusCode==200)
InputStream in = connection.getInputStream();
if(f.exists()) f.delete();
f.createNewFile();
FileOutputStream out = new FileOutputStream(f);
byte[] buf = new byte[1024];
int j = 0;
while( (j=in.read(buf))!=-1) out.write(buf, 0, j);
out.flush();
out.close();
in.close();
result = "Download OK.\\nFrom: " + urlStr + "\\nTo: " + f.getPath();
catch (Exception e)
e.printStackTrace();
finally
if(connection!=null) connection.disconnect();
return result;
文件下载也需要用AsyncTask异步执行。
在前一篇BBS的例子中,我们继续添加文件下载和上传功能。关于文件下载是这样设计的,当点击Download按钮时,就会用AsyncTask异步下载上面URL中的文件。该文件保存到外部存储根目录下,因为需要向外部存储写文件,所以需要android.permission.WRITE_EXTERNAL_STORAGE权限。Android6.0以后采用了动态权限管理,这个权限是危险权限,所以最好在执行之前先检查用户是否允许了,如果没有允许则询问,具体代码如下:
if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED)
requestPermissions(new String[]Manifest.permission.WRITE_EXTERNAL_STORAGE, 1);
return;
此外,Android 10(API29)引入了分区存储(scoped storage)的功能,不允许应用直接读写外部存储根目录下的文件。要想让本例子在Android11上运行,可以将文件存放到应用在外部存储上的私有目录下,该目录可以用方法getExternalFilesDir(null)来获得,其实际位置为:Android/data/<package-name>/files/。
检查完权限后,就可以执行异步下载,具体代码是这样的:
异步执行的代码中先准备好本地文件,然后下载文件。这个文件必须事先放在服务器的WebContent目录下,或相应子目录中。下载完成后,会在下面的文本框中显示下载文件的信息,内容是是否下载成功,从哪个链接下载到本地哪个文件。
如果文件下载需要的时间比较长,就需要显示下载进度。下面,我们用一个ProgressDialog进度对话框来显示文件下载进度,界面就像下面这样。
下载进度等于已下载字节数 / 文件总长度,文件总长度从消息头中得到,用connection对象的getContentLength方法获得,已下载字节数在读取文件内容的循环中统计。具体代码是在基本的文件下载流程的基础上,添加统计进度的代码,也就是红色代码部分。每读取一次数据,就更新下下载进度。
下载进度要显示在进度对话框中,但文件下载代码是在后台线程中执行的,不能直接操作进度对话框。所以更新进度时要调用AsyncTask的publishProgress方法,把进度传递到在主线程执行的onProgressUpdate方法,在这个方法中更新进度对话框。
接下来看文件上传的原理。客户端发送HTTP POST请求,用URL指出服务器端接收上传文件的程序,比如JavaEE中的Servlet。另外消息头中一般要包含文件类型,以便服务器端生成文件后缀。文件的内容以二进制格式放在消息体中。
Web服务器一般不会自带接收上传文件的功能,需要开发者自己编写。接收文件时,服务器端一般是按照文件上传时间生成一个文件名,不用客户端文件名是为了防止不同客户端上传同名文件互相覆盖。然后把POST消息体中的文件内容保存到该文件中。再在响应消息中把文件名或链接返回给客户端,客户端可以在其它Post中使用该文件名或链接作为参数。
客户端用HTTP上传文件的流程和用HTTP上传数据的流程是一致的,区别是在消息头中添加文件类型描述,在消息体中写入的是文件内容。服务器端流程是获取文件类型、生成文件名、获取请求的输入流,读取数据写入文件,返回文件名。
客户端上传文件的代码是这样的:
String upload(String urlStr, File f)
String result = null;
HttpURLConnection connection = null;
try
URL url = new URL(etUrl.getText().toString());
connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setUseCaches(false);
connection.setRequestMethod("POST");
connection.setRequestProperty("Charset", "UTF-8");
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("Content-Type", "binary/" + FileUtils.getExtName(f));
OutputStream os = connection.getOutputStream();
FileInputStream fis = new FileInputStream(f);
byte[] buf = new byte[1024];
int j = 0;
while( (j=fis.read(buf))!=-1)
os.write(buf, 0, j);
fis.close();
os.flush();
os.close();
int statusCode = connection.getResponseCode();
if(statusCode==200)
InputStream in = connection.getInputStream();
result = readInputStream(in);
in.close();
catch (Exception e)
e.printStackTrace();
finally
if(connection!=null) connection.disconnect();
return result;
添加文件类型用的是setRequestProperty方法,在消息头中添加一行Content-Type属性,值是binary/文件扩展名。然后用getOutputStream打开HttpURLConnection连接的输出流,把文件内容用write方法一块一块写进去。
服务器端是用一个Servlet实现的,具体代码在Servlet的doPost方法中。
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
String contentType = request.getContentType();
int separate = contentType.lastIndexOf("/");
String fileType = contentType.substring(separate+1);
String uploadPath = "/upload";
String filePath = this.getServletContext().getRealPath(uploadPath);
String fileName = Long.toString(System.currentTimeMillis()) + "." + fileType;
// 必须创建upload文件夹
File f = new File(filePath + "/" + fileName);
if(!f.getParentFile().exists()) f.getParentFile().mkdir();
System.out.println("File upload: " + f.getPath());
InputStream in = request.getInputStream();
FileOutputStream fos = new FileOutputStream(f);
byte[] buffer = new byte[1024];
int j = 0;
while( (j=in.read(buffer))!=-1 ) fos.write(buffer, 0, j);
fos.flush();
fos.close();
in.close();
response.setCharacterEncoding("utf-8");
response.getWriter().print(fileName);
先获取文件扩展名,再用系统当前时间生成文件名,在加上路径,从而生成上传文件的名称和位置。然后从request获得输入流,从输入流读取数据保存到该文件。最后,返回该文件名。
文件上传也需要用AsyncTask异步执行。在这个例子中,关于文件上传是这样设计的,当点击Upload按钮时,就会用AsyncTask异步上传一个本地文件到URL指定的程序,具体代码是这样的。
异步执行的代码中先准备好本地文件,这个文件就是前面下载到本地的某个文件,然后上传文件。上传完成后,会在下面的文本框中显示上传文件的信息,内容是该文件上传到服务器上后的新文件名。
如果文件上传需要的时间比较长,就需要显示上传进度。下面,我们用一个ProgressDialog进度对话框来显示文件上传进度,界面就像下面这样。
上传进度等于已上传字节数 / 文件总长度,文件总长度从文件输入流得到,已上传字节数在上传文件内容的循环中统计。具体代码是在基本的文件上传流程的基础上,添加统计进度的代码,也就是红色代码部分。每上传一块数据,就更新一下上传进度。上传进度要显示在进度对话框中,也是通过AsyncTask的publishProgress方法,把进度传递到在主线程执行的onProgressUpdate方法,在这个方法中更新进度对话框。
Android网络请求库RetrofitUtils
RetrofitUtils
项目介绍
Retrofit+Okhttp辅助类的简单封装,vesion 1.0.X 实现了Get,Post-Form、Post-Json
三种形式的网络请求,后续版本会实现文件上传下载and各类raw的请求方式。
功能
- Get
- Post表单
- PostJson
后续版本待实现功能
- 文件上传下载(开发中...)
- 拦截器
- RxJava+Retrofit+Okhttp
- 考虑去掉loading(丑且无用),是否把所用到的第三方库打包进库中
使用说明
1. 引用
implementation ‘cn.cyq.net:retrofitutils:1.0.3‘
<!--library中引用了下面五个库,我没有打包进去了,避免版本冲突,比如七牛云的okio okhttp冲突-->
//网络请求依赖
implementation ‘com.squareup.okio:okio:1.14.0‘
implementation ‘com.squareup.okhttp3:okhttp:3.10.0‘
implementation ‘com.squareup.retrofit2:retrofit:2.4.0‘
implementation ‘com.squareup.retrofit2:converter-scalars:2.3.0‘
//Loader依赖
implementation ‘com.wang.avi:library:2.1.3‘
2. 初始化
在Application的onCreate()初始化
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
RestClient.init(getApplicationContext(), "baseUrl address");
}
}
3. 具体请求
Get
RestClient.buider()
.loader(this)//可以不添加
.url(url)
.success(new ISuccess() {
@Override
public void onSuccess(String response) {
Log.i("test", "GET请求:" + response);
}
})
.failure(new IFailure() {
@Override
public void onFailure() {
Log.i("test", "失败");
}
})
.error(new IError() {
@Override
public void onError(int code, String msg) {
Log.i("test", "错误");
}
})
.build()
.get();
Post表单
RestClient.buider()
.loader(this)
.url(url)
.params("key1", "value1")
.params("key2", "value2")
.success(new ISuccess() {
@Override
public void onSuccess(String response) {
Log.i("test", "POST请求:" + response);
}
})
.failure(new IFailure() {
@Override
public void onFailure() {
Log.i("test", "失败");
}
})
.error(new IError() {
@Override
public void onError(int code, String msg) {
Log.i("test", "错误");
}
})
.build()
.post();
Post Json
String jsonStr = "{"username":"张三","age":16}";
RestClient.buider().loader(this)
.url("http://192.168.0.1:8080/service/jsontest.html")
.raw(jsonStr)
.success(new ISuccess() {
@Override
public void onSuccess(String response) {
Toast.makeText(MainActivity.this, response, Toast.LENGTH_LONG).show();
Log.i("test", "Post Row请求:" + response);
}
})
.error(new IError() {
@Override
public void onError(int code, String msg) {
Log.i("test", "Post Ro请求失败");
}
})
.build()
.post();
说明
- vesion 1.0.X 是初期版本,不建议在正式项目使用,后续会完善...
引用的库及版本
ps:2018-07-26[最新]
- com.squareup.retrofit2:retrofit:2.4.0
- com.squareup.okhttp3:okhttp:3.10.0
- com.wang.avi:library:2.1.3
- com.squareup.retrofit2:converter-scalars:2.3.0
- com.squareup.okio:okio:1.14.0
以上是关于Android网络功能开发——文件下载和上传的主要内容,如果未能解决你的问题,请参考以下文章
Android实战简易教程-第二十八枪(基于Bmob实现头像图片设置和网络上传功能!)
iOS开发-AFNetworking参数和多文件同时上传多文件上传
iOS开发之网络编程--5NSURLSessionUploadTask+NSURLSessionDataDelegate代理上传