刚做完项目里的上传图片并回调进度的需求,可谓一波三折,我就从我的开发过程中先后尝试的不同方法来总结下吧。
先说下我们的需求:用base64编码上传图片,并实时更新上传的进度条。
第一阶段:
由于我们项目中用的网络框架是async-httpClient,所以刚开始肯定会考虑这个网络框架本身有没有上传图片并回调进度
的接口,当然我们android开发都是用封装好的网络框架,第三方肯定会留上传图片的进度回调接口。
下面是试图用async-httpClient上传图片的代码:
/**
* 作 者:**
* 描 述:上传图片请求
*/
private void requestUploadImage(String fineName, String filePath) {
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("filename", fineName); //图片名称
jsonObject.put("realfile", BitmapUtil.pathCompress2Base64(filePath)); //指定路径图片--Bitmap--压缩到2M以下--base64编码
} catch (Exception e) {
e.printStackTrace();
}
RestClient.post(URL, new Params(jsonObject), new MyAsyncHttpResponse(activity) {
@Override
public void onFailure(int arg0, Header[] arg1, byte[] arg2,
Throwable arg3) {
super.onFailure(arg0, arg1, arg2, arg3);
// TODO:
}
@Override
public void onProgress(final long bytesWritten, final long totalSize) {
super.onProgress(bytesWritten, totalSize);
LogUtils.i("bytesWritten:" + bytesWritten);
LogUtils.i("totalSize:" + totalSize);
viewHolder.tv_percent.setText((int) (bytesWritten * 100 / totalSize) + "%");
viewHolder.progress_bar.setProgress((int) (bytesWritten * 100 / totalSize));
}
@Override
public void onSuccess(int arg0, Header[] headers,
byte[] responseBody) {
super.onSuccess(arg0, headers, responseBody);
// TODO:
}
});
}
实验结果是让人崩溃的,直接说结论吧:async-httpClient中的onProgress方法的确是进度回调的方法,但是只有参数值File格式才会
实时的回调进度,如果是base64编码后的字符串格式是不起作用的,只有在整个请求体上传完成之后回传一下总的上传进度。就是说只有
如下的方式上传图片才可以:
jsonObject.put(“realfile”,new File(filePath));
但是我们上传图片通常都是需要压缩的,现在普通的android手机照片都是4M左右,而文件服务器的上传限制通常是2M,而我们对图片压缩
分的很细的话有三种方法(详细代码这里不贴了):
1.一种是质量压缩:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
2.一种是指定分辨率压缩:
InputStream is = new FileInputStream(new File(path));
bitmap = BitmapFactory.decodeStream(is,null,opts);//return BitmapFactory.decodeFile(path, options);
3.另一种就是这两种压缩方式的结合使用。
但是他们压缩之后得到的都是压缩后的bitmap,如果想要用文件形式上传就需要主动创建临时目录存储压缩之后的图片,这显然不太合适。更
何况,我们服务端接口就是要求以base64编码之后的字符串形式上传。
所以想要用字符串上传,用async-httpClient网络框架做图片上传是无法实现进度回调的,因为它内部已经封装好,只有用File格式上传图片,
才会实时回调进度。
第二阶段:
用第三方的网络框架不行,一般我们会想起来比较熟悉的HttpClient,通过HttpCient+AsyncTask这种方式做上传图片+进度回调:
网上可以搜到很多不错的代码可以实现,但是你需要注意下面这部分代码:
// 保存需上传文件信息
MultipartEntityBuilder entitys = MultipartEntityBuilder.create();
entitys.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
entitys.setCharset(Charset.forName(HTTP.UTF_8));
File file = params[0];
entitys.addPart("file", new FileBody(file));
HttpEntity httpEntity = entitys.build();
就算用HttpClient,想要实现进度实时回调,仍然需要以File格式上传图片。
第三阶段:
到这个时候真的有点蛋疼,现在就应该仔细想下网络上传的基本原理和图片进度的原理,
网络传输过程中无论你传输的是字符还是文件,传输过程中都是以byte字符传输的,
而文件传输的进度回调原理就是不断的把文件的输入流写入网络链接的输出流,连续不断
向网络中写入一个byte一个byte的文件流字符。正如用httpURLConnection去上传文件一样:
部分代码如下:
OutputStream outputSteam = conn.getOutputStream();
DataOutputStream dos = new DataOutputStream(outputSteam);
StringBuffer sb = new StringBuffer();
sb.append(PREFIX);
sb.append(BOUNDARY);
sb.append(LINE_END);
sb.append("Content-Disposition: form-data; name=\"img\"; filename=\""
+ file.getName() + "\"" + LINE_END);
sb.append("Content-Type: application/octet-stream; charset="
+ CHARSET + LINE_END);
sb.append(LINE_END);
dos.write(sb.toString().getBytes());
InputStream is = new FileInputStream(file);
byte[] bytes = new byte[1024];
int len = 0;
while ((len = is.read(bytes)) != -1) {
dos.write(bytes, 0, len);
}
is.close();
dos.write(LINE_END.getBytes());
byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINE_END)
.getBytes();
dos.write(end_data);
dos.flush();
dos.close();
从这里可以知道,上传文件之所以有进度就是因为把整个文件作为一个流不断写入网络,每写入一次就是1024个字节,
即1k。然后我们联想到用字符串上传图片的情况,我们是把字符串封装到整个请求体之中了,然后把整个请求体不断
的写入网络,所以我们可以监听这个请求体写入网络的过程来实现进度回调。代码如下:
public class MyAsyncTask extends AsyncTask<String, Integer, String> {
private long totalSize;
@Override
protected void onPreExecute() {
super.onPreExecute();
//TODO
}
@Override
protected String doInBackground(String... params) {
String fineName = params[0];
String filePath = params[1];
try {
URL url = new URL(GlobalConstants.SERVER_API + GlobalConstants.USER_SAVE_IMG);
HttpURLConnection uRLConnection = (HttpURLConnection) url.openConnection();
uRLConnection.setDoInput(true);
uRLConnection.setDoOutput(true);
uRLConnection.setRequestMethod("POST");
uRLConnection.setUseCaches(false);
uRLConnection.setInstanceFollowRedirects(false);
uRLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
uRLConnection.connect();
// 设置请求的超时时间
uRLConnection.setReadTimeout(5000);
uRLConnection.setConnectTimeout(10000);
//拼装请求数据
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("filename", fineName); //图片名称
jsonObject.put("realfile", BitmapUtil.pathCompress2Base64(filePath)); //指定路径图片--Bitmap--压缩到2M以下--base64编码
} catch (Exception e) {
e.printStackTrace();
}
String content = "params=" + jsonObject.toString(); //多个参数用“&”符号连接起来
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(content.getBytes());
//发送请求数据
DataOutputStream out = new DataOutputStream(uRLConnection.getOutputStream());
long total = content.getBytes().length;
LogUtils.i("文件大小total:" + total);
// 设置每次写入1024bytes
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = -1;
long progressBarLength = 0;
//从文件读取数据至缓冲区
while ((length = byteArrayInputStream.read(buffer)) != -1) {
//将请求体写入DataOutputStream中
out.write(buffer, 0, length);
//获取进度
progressBarLength += length;
publishProgress((int) ((progressBarLength / (float) total) * 100)); //调用此方法才能保证onProgressUpdate中能得到进度数据
}
out.flush();
out.close();
//获取响应数据
InputStream is = uRLConnection.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String response = "";
String readLine = null;
while ((readLine = br.readLine()) != null) {
//response = br.readLine();
response = response + readLine;
}
if (StringUtil.isNull(response)) {
return getResources().getString(R.string.data_exception);
}
is.close();
br.close();
uRLConnection.disconnect();
return response; //返回服务端的返回数据
} catch (Exception e) {
e.printStackTrace();
LogUtils.i("----异常----:" + e.toString());
return “网络异常”;
}
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
int progress = values[0];
LogUtils.i("进度回调progress:" + progress);
tv_progress_percent.setText(progress + "%");
progress_bar.setProgress(progress); //实时刷新进度
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
//TODO 解析并处理服务端返回的数据
}
}
总结:HttpURLConnection是java封装的基本的网络请求框架,请求参数写入网络都是不断的
向网络中写入byte字符。然而第三方的网络框架给我们封装的方法中,只有以File格式
上传的时候才能回调进度,因为不是文件上传根本就没有上传进度的需求嘛,所以当用普通的请求
体上传请求参数时候并没有做实时的进度回调,这也是无可厚非的,但是现在图片上传往往要求用
base64编码后再上传,虽然这样会使文件变大,但毕竟做了简单加密,更安全一些。所以要
想实现用字符创上传图片又要回调进度,我们只能用httpUrlConnection这种方
法去对请求体写入网络的过程做监听。当然如果感兴趣,还可以用socect去写上传,那样肯定实现进度监听是没有问题的,但是听说效率一般,本人没试过,只是道听途说,如有不对欢迎指正!