- 前言
本篇将使用OkHttp实现文件的上传和下载,以及下载实现断点续传功能。因为是基本的使用,此系列文章主要是以大家会用为主而写的。当然,只要会用了,后面的优化、封装等等就不难了。
- 下载
使用OkHttp完成下载功能,实现断点续传,并附带进度条显示下载进度。
文件下载的交互过程:
下载的流程:
- 文件下载的代码:
- public class DownloadActivity extends AppCompatActivity {
- private ProgressBar mProgressBar;
- //准备下载
- public static final int BEGIN = 0;
- //正在下载
- public static final int DOWNLOADING = 1;
- //结束下载
- public static final int END = 2;
- //下载的进度
- private static int progress;
- //是否停止下载
- private boolean cancel ;
- OkHttpClient okHttpClient = new OkHttpClient();
- MyHandler mHandler = new MyHandler(this);
- private ImageView mShowImage;
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_download);
- mProgressBar = (ProgressBar) findViewById(R.id.down_progress_bar);
- mShowImage = (ImageView) findViewById(R.id.down_image);
- }
- public void click2(View view) {
- cancel = true;
- }
- public void click(View view) {
- cancel = false;
- new Thread(new Runnable() {
- @Override
- public void run() {
- //实例化Builder对象
- Request.Builder builder = new Request.Builder();
- //设置Url
- builder.url(Config.IMAGE_URL);
- //获取已经下载的大小
- int size = baos.size();
- //size表示已经下载的大小。如果不为0,则进行断点续传。
- if (size > 0) {
- //设置断点续传的开始位置,格式bytes=123456-
- builder.header("Range", "bytes=" + size + "-");
- //设置ProgressBar的当前进度从停止位置开始
- progress = size;
- }
- //创建Request对象
- Request request = builder.build();
- try {
- //执行下载请求,并获得Response对象
- Response response = okHttpClient.newCall(request).execute();
- //请求成功
- if (response.isSuccessful()) {
- //从Response对象中获取输入流对象
- InputStream inputStream = response.body().byteStream();
- //size==0表示第一次下载,非断点续传
- if (size == 0) {
- //获取文件的大小
- int contentLength = (int) response.body().contentLength();
- //将文件总大小通过Handler传递到UI线程,设置ProgressBar的总进度值
- mHandler.obtainMessage(BEGIN,contentLength,0).sendToTarget();
- }
- int len = 0;
- byte[] buffer = new byte[1024];
- //循环读取文件流,开始下载
- while((len = inputStream.read(buffer)) != -1) {
- if (cancel) {
- //如果点击了停止按钮,cancel为true。则结束循环
- break;
- }
- //将流写入缓存
- baos.write(buffer,0,len);
- baos.flush();
- //发送下载进度
- mHandler.obtainMessage(DOWNLOADING,len,0).sendToTarget();
- }
- //下载完成,结束请求,关闭body
- response.body().close();
- //将字节转成Bitmap对象
- byte[] bytes = baos.toByteArray();
- Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
- //下载完成通知更新试图
- mHandler.obtainMessage(END,bitmap).sendToTarget();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }).start();
- }
- static class MyHandler extends Handler {
- private WeakReference<DownloadActivity> activityWeakReference;
- public MyHandler(DownloadActivity activity) {
- this.activityWeakReference = new WeakReference<DownloadActivity>(activity);
- }
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case BEGIN:
- activityWeakReference.get().mProgressBar.setMax(msg.arg1);
- break;
- case DOWNLOADING:
- progress += msg.arg1;
- activityWeakReference.get().mProgressBar.setProgress(progress);
- break;
- case END:
- progress = 0;
- activityWeakReference.get().mShowImage.setImageBitmap((Bitmap)msg.obj);
- break;
- }
- }
- }
- }
复制代码
以上是文件下的代码。实现了断点续传,其中能进行断点续传的关键代码为builder.header("Range", "bytes=" + size + "-");代码中都有注释,结合上面的流程图应该不难理解。
代码解释:源码的效果是点击开始按钮出发click事件开始下载,点击停止按钮触发click2事件中断下载。下载的进度监听是在while循环中,通过Handler进行的进度更新。
- 文件上传
本案例中,实现带参数的文件上传功能-----同时完成参数传递和文件上传。
代码如下:
public class UploadActivity extends AppCompatActivity {
public static final int GET_PIC = 1;
private ImageView mShowImage;
private Uri uri;
OkHttpClient okHttpClient = new OkHttpClient();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_upload);
mShowImage = (ImageView) findViewById(R.id.upload_show_image);
}
public void click(View view) {
switch (view.getId()) {
case R.id.upload_choose_file:
choosePic();//选择图片
break;
case R.id.upload_start:
upload();//上传
break;
}
}
/**
* 上传
*/
private void upload() {
if (uri == null) {
Toast.makeText(UploadActivity.this, "请先选择文件", Toast.LENGTH_SHORT).show();
return;
}
//设置文件的媒体类型,image/*表示匹配所有的图片文件
MediaType mediaType = MediaType.parse("image/*");
MultipartBody.Builder builder = new MultipartBody.Builder();
//文件上传,此处是关键,设置媒体类型为multipart/form-data,表示多种格式的表单数据上传
builder.setType(MultipartBody.FORM);
//添加上传的参数username=androidxx
builder.addFormDataPart("username","androidxx");
//添加上传的文件。文件是从相册读取的文件流。
try {
//获得需要上传的文件流
InputStream inputStream = getContentResolver().openInputStream(uri);
int len = 0;
byte[] buffer = new byte[1024];
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while((len = inputStream.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
inputStream.close();
/*
* 添加文件到Builder中。如果要实现多文件同时上传,可以添加多个addFormDataPart。
* 注意:
* 参数一:上传的文件的标示,同username。也就是可以在服务器端通过upload找到对应的文件流
* 参数二:文件的名称。上传到服务器之后以此名称命名文件
* 参数三:需要上传的文件。包含在RequestBody中
* RequestBody.create方法有多个重载的方法,可以选择不同的数据源。此处选择的是字节形式(baos.toByteArray())的数据眼。
*/
builder.addFormDataPart("upload", "test.jpg", RequestBody.create(mediaType, baos.toByteArray()));
} catch (IOException e) {
e.printStackTrace();
}
//创建MultipartBody对象,MultipartBody是RequestBody的子类,用于文件上传。
MultipartBody multipartBody = builder.build();
Request request = new Request.Builder()
.url("http://192.168.3.4:8080/WebServer/upload.do")//上传的服务器地址
.post(multipartBody)
.build();
//开始上传。采用Post异步请求的方式
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d("androidxx.cn","--" + e.getMessage());
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//接受到成功的返回结果
if (response.isSuccessful()) {
Log.d("androidxx.cn","-上传成功-");
} else {
Log.d("androidxx.cn","-失败--" + response.body().string());
}
}
});
}
/**
* 打开相册,选择文件后返回
*/
private void choosePic() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent,GET_PIC);
}
/**
* 接收选择的图片
* @param requestCode
* @param resultCode
* @param data
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_CANCELED) {
return;
}
//获得图片的URI
uri = data.getData();
//通过ContentResolver获得图片对象
ContentResolver contentResolver = getContentResolver();
InputStream inputStream = null;
try {
inputStream = contentResolver.openInputStream(uri);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//将流转换成图片,显示到ImageView中
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
mShowImage.setImageBitmap(bitmap);
}
}
复制代码如上代码,第37行和第59行是上传能成功的重点。
- 多文件长传
实现多文件上传,只需将上面单文件上传中的builder.addFormDataPart("upload", "test.jpg", RequestBody.create(mediaType, baos.toByteArray()));这一句执行多次,即使用多个addFormDataPart方法添加多个文件,然后就可以同时上传多个文件了。
- 总结
1、文件上传和下载的过程其实就是一种特殊的Post和Get请求。总体的过程与Post请求和Get请求方式一样。
2、下载相当于一个特殊的Get请求,只是服务器返回的数据格式是文件流。我们也只能通过读取流来获得数据。
3、上传相当于一个特殊的Post请求,前面我们说过,Post请求就是传参数比较特殊和多样化。文件上传就是一种特殊的参数传递----参数是一个文件。
大家在看如上代码的时候,不要觉得陌生,其实代码的流程和逻辑同Post和Get请求一样,只是多了几行代码。
本案例的android端源码:Github
本案例的上传服务器配置方式【点击查看】,上传服务端源码:Github 服务器端代码请阅读Github中的readme.md文件。服务器端代码导入工程没有错之后,可以将代码加载到服务器,之后启动服务器就可以运行。本案例使用的是Tomcat服务器。
备注:对于android程序员如果想运行服务器代码,按照readme.md文档。然后有不懂的,可以留言。
转载请注明:androidxx.cn
最后附上下载效果(注意点击按钮开始和停止后下载进度条的变化,实现断点续传)。