android 文件上传主要有两种方式,HttpUrlConnection上传和Socket上传,
下面贴出实现代码:
public class HttpUploadFileHelper {
private final static String TAG = HttpUploadFileHelper.class.getSimpleName();
/**
* http 请求消息体中的上传文件边界标识
*/
private static final String BOUNDARY = UUID.randomUUID().toString();
/**
* 文件类型
*/
private static final String CONTENT_TYPE = "multipart/form-data";
private static final String PREFIX = "--";
/**
* http 请求消息体中的回车换行
*/
private static final String CRLF = "\r\n";
private static final String CHARSET_UTF_8 = "UTF-8";
/**
* 表单名
*/
private static final String FORM_NAME = "upload_file";
private HttpUploadFileHelper() {
}
/**
* 使用HttpUrlConnection来向服务器上传文件,在上传大文件时,会造成内存溢出
*
* @param url
* @param filePath
* @param listener
*/
public static void sendByHttpUrlConnection(final String url, final String filePath, final UploadResultListener listener) {
if (TextUtils.isEmpty(url) || TextUtils.isEmpty(filePath)) {//校验上传路径和文件
return;
}
final File uploadFile = new File(filePath);
if (uploadFile.exists() && uploadFile.isFile()) {
new AsyncTask() {
@Override
protected Boolean doInBackground(Void... params) {
try {
StringBuffer headBuffer = new StringBuffer(); //构建文件头部信息
headBuffer.append(PREFIX);
headBuffer.append(BOUNDARY);
headBuffer.append(CRLF);
headBuffer.append("Content-Disposition: form-data; name=\"" + FORM_NAME + "\"; filename=\"" + uploadFile.getName() + "\"" + CRLF);//模仿web上传文件提交一个form表单给服务器,表单名随意起
headBuffer.append("Content-Type: application/octet-stream" + CRLF);//若服务器端有文件类型的校验,必须明确指定Content-Type类型
headBuffer.append(CRLF);
Log.i(TAG, headBuffer.toString());
byte[] headBytes = headBuffer.toString().getBytes();
StringBuffer endBuffer = new StringBuffer();//构建文件结束行
endBuffer.append(CRLF);
endBuffer.append(PREFIX);
endBuffer.append(BOUNDARY);
endBuffer.append(PREFIX);
endBuffer.append(CRLF);
byte[] endBytes = endBuffer.toString().getBytes();
URL remoteUrl = new URL(url);
HttpURLConnection httpURLConnection = (HttpURLConnection) remoteUrl.openConnection();
httpURLConnection.setDoOutput(true);//打开输出流
httpURLConnection.setDoInput(true);//打开输入流
httpURLConnection.setUseCaches(false);
httpURLConnection.setRequestMethod("POST");//上传文件必须要POST请求
httpURLConnection.setRequestProperty("Charset", CHARSET_UTF_8);//设置编码
httpURLConnection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);//设置http消息头部的Content-Type
String contentLength = String.valueOf(headBytes.length + endBytes.length + uploadFile.length());
httpURLConnection.setRequestProperty("Content-Length", contentLength);//设置内容长度
OutputStream outputStream = httpURLConnection.getOutputStream();
outputStream.write(headBytes);//输出文件头部
FileInputStream fileInputStream = new FileInputStream(uploadFile);
byte[] buffer = new byte[1024];
int length;
while ((length = fileInputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);//输出文件内容
}
fileInputStream.close();
outputStream.write(endBytes);//输出结束行
outputStream.close();
if (httpURLConnection.getResponseCode() == 200) {//发送成功
return true;
} else {
return false;
}
} catch (MalformedURLException e) {
e.printStackTrace();
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (listener != null) {
if (result) {
listener.onSuccess();
} else {
listener.onFailure();
}
}
}
}.execute();
}
}
/**
* 使用Socket向服务器上传文件,上传大文件时建议使用Socket,才不会造成内存溢出
*
* @param url
* @param filePath
* @param listener
*/
public static void sendBySocket(final String url, String filePath, final UploadResultListener listener) {
if (TextUtils.isEmpty(url) || TextUtils.isEmpty(filePath)) {
return;
}
final File uploadFile = new File(filePath);
if (uploadFile.exists() && uploadFile.isFile()) {
new AsyncTask() {
@Override
protected Boolean doInBackground(Void... params) {
try {
StringBuffer headBuffer = new StringBuffer(); //构建文件头部信息
headBuffer.append(PREFIX);
headBuffer.append(BOUNDARY);
headBuffer.append(CRLF);
headBuffer.append("Content-Disposition: form-data; name=\"" + FORM_NAME + "\"; filename=\"" + uploadFile.getName() + "\"" + CRLF);//模仿web上传文件提交一个form表单给服务器,表单名随意起
headBuffer.append("Content-Type: application/octet-stream" + CRLF);//若服务器端有文件类型的校验,必须明确指定Content-Type类型
headBuffer.append(CRLF);
Log.i(TAG, headBuffer.toString());
byte[] headBytes = headBuffer.toString().getBytes();
StringBuffer endBuffer = new StringBuffer();//构建文件结束行
endBuffer.append(CRLF);
endBuffer.append(PREFIX);
endBuffer.append(BOUNDARY);
endBuffer.append(PREFIX);
endBuffer.append(CRLF);
byte[] endBytes = endBuffer.toString().getBytes();
URL remoteUrl = new URL(url);
Socket socket = new Socket(remoteUrl.getHost(), remoteUrl.getPort());
OutputStream outputStream = socket.getOutputStream();
PrintStream printStream = new PrintStream(outputStream, true, CHARSET_UTF_8);
//输出请求头,用println输出可以省了后面的换行
printStream.println("POST " + url + " HTTP/1.1");
printStream.println("Content-Type: multipart/form-data; boundary=" + BOUNDARY);
String contentLength = String.valueOf(headBytes.length + endBytes.length + uploadFile.length());
printStream.println("Content-Length: " + contentLength);
printStream.println();//根据 HTTP 协议,空行将结束头信息
outputStream.write(headBytes);//输出文件头部
FileInputStream fileInputStream = new FileInputStream(uploadFile);
byte[] buffer = new byte[1024];
int length;
while ((length = fileInputStream.read(buffer)) != -1) {//输出文件内容
outputStream.write(buffer, 0, length);
}
fileInputStream.close();
outputStream.write(endBytes);//输出结束行
outputStream.close();
return true;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (listener != null) {
if (result) {
listener.onSuccess();
} else {
listener.onFailure();
}
}
}
}.execute();
}
}
/**
* 监听上传结果
*/
public static interface UploadResultListener {
/**
* 上传成功
*/
public void onSuccess();
/**
* 上传失败
*/
public void onFailure();
}
}
对于文件上传需要注意的是,如果是大文件就要采用Socket上传,以免发生OOM,而大文件的上传也可以实现为断点上传,需要服务端配合实现,和断点下载实现类似,也采用RandomAccessFile来做文件移动,具体的断点上传实现代码如下:
客户端:
/**
* 上传文件
* @param uploadFile
*/
private void uploadFile(final File uploadFile) {
new Thread(new Runnable() {
@Override
public void run() {
try {
uploadbar.setMax((int)uploadFile.length());
String souceid = logService.getBindId(uploadFile);
String head = "Content-Length="+ uploadFile.length() + ";filename="+ uploadFile.getName() + ";sourceid="+
(souceid==null? "" : souceid)+"\r\n";
Socket socket = new Socket("192.168.1.78",7878);
OutputStream outStream = socket.getOutputStream();
outStream.write(head.getBytes());
PushbackInputStream inStream = new PushbackInputStream(socket.getInputStream());
String response = StreamTool.readLine(inStream);
String[] items = response.split(";");
String responseid = items[0].substring(items[0].indexOf("=")+1);
String position = items[1].substring(items[1].indexOf("=")+1);
if(souceid==null){//代表原来没有上传过此文件,往数据库添加一条绑定记录
logService.save(responseid, uploadFile);
}
RandomAccessFile fileOutStream = new RandomAccessFile(uploadFile, "r");
fileOutStream.seek(Integer.valueOf(position));
byte[] buffer = new byte[1024];
int len = -1;
int length = Integer.valueOf(position);
while(start&&(len = fileOutStream.read(buffer)) != -1){
outStream.write(buffer, 0, len);
length += len;
Message msg = new Message();
msg.getData().putInt("size", length);
handler.sendMessage(msg);
}
fileOutStream.close();
outStream.close();
inStream.close();
socket.close();
if(length==uploadFile.length()) logService.delete(uploadFile);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
服务端代码如下:
public SocketServer(int port) {
this.port = port;
// 初始化线程池
executorService = Executors.newFixedThreadPool(Runtime.getRuntime()
.availableProcessors() * 50);
}
// 启动服务
public void start() throws Exception {
ss = new ServerSocket(port);
while (!quit) {
Socket socket = ss.accept();// 接受客户端的请求
// 为支持多用户并发访问,采用线程池管理每一个用户的连接请求
executorService.execute(new SocketTask(socket));// 启动一个线程来处理请求
}
}
// 退出
public void quit() {
this.quit = true;
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
SocketServer server = new SocketServer(7878);
server.start();
}
private class SocketTask implements Runnable {
private Socket socket;
public SocketTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
System.out.println("accepted connenction from "
+ socket.getInetAddress() + " @ " + socket.getPort());
PushbackInputStream inStream = new PushbackInputStream(
socket.getInputStream());
// 得到客户端发来的第一行协议数据:Content-Length=143253434;filename=xxx.3gp;sourceid=
// 如果用户初次上传文件,sourceid的值为空。
String head = StreamTool.readLine(inStream);
System.out.println(head);
if (head != null) {
// 下面从协议数据中读取各种参数值
String[] items = head.split(";");
String filelength = items[0].substring(items[0].indexOf("=") + 1);
String filename = items[1].substring(items[1].indexOf("=") + 1);
String sourceid = items[2].substring(items[2].indexOf("=") + 1);
Long id = System.currentTimeMillis();
FileLog log = null;
if (null != sourceid && !"".equals(sourceid)) {
id = Long.valueOf(sourceid);
log = find(id);//查找上传的文件是否存在上传记录
}
File file = null;
int position = 0;
if(log==null){//如果上传的文件不存在上传记录,为文件添加跟踪记录
String path = new SimpleDateFormat("yyyy/MM/dd/HH/mm").format(new Date());
File dir = new File(uploadPath+ path);
if(!dir.exists()) dir.mkdirs();
file = new File(dir, filename);
if(file.exists()){//如果上传的文件发生重名,然后进行改名
filename = filename.substring(0, filename.indexOf(".")-1)+ dir.listFiles().length+ filename.substring(filename.indexOf("."));
file = new File(dir, filename);
}
save(id, file);
}else{// 如果上传的文件存在上传记录,读取上次的断点位置
file = new File(log.getPath());//从上传记录中得到文件的路径
if(file.exists()){
File logFile = new File(file.getParentFile(), file.getName()+".log");
if(logFile.exists()){
Properties properties = new Properties();
properties.load(new FileInputStream(logFile));
position = Integer.valueOf(properties.getProperty("length"));//读取断点位置
}
}
}
OutputStream outStream = socket.getOutputStream();
String response = "sourceid="+ id+ ";position="+ position+ "\r\n";
//服务器收到客户端的请求信息后,给客户端返回响应信息:sourceid=1274773833264;position=0
//sourceid由服务生成,唯一标识上传的文件,position指示客户端从文件的什么位置开始上传
outStream.write(response.getBytes());
RandomAccessFile fileOutStream = new RandomAccessFile(file, "rwd");
if(position==0) fileOutStream.setLength(Integer.valueOf(filelength));//设置文件长度
fileOutStream.seek(position);//移动文件指定的位置开始写入数据
byte[] buffer = new byte[1024];
int len = -1;
int length = position;
while( (len=inStream.read(buffer)) != -1){//从输入流中读取数据写入到文件中
fileOutStream.write(buffer, 0, len);
length += len;
Properties properties = new Properties();
properties.put("length", String.valueOf(length));
FileOutputStream logFile = new FileOutputStream(new File(file.getParentFile(), file.getName()+".log"));
properties.store(logFile, null);//实时记录文件的最后保存位置
logFile.close();
}
if(length==fileOutStream.length()) delete(id);
fileOutStream.close();
inStream.close();
outStream.close();
file = null;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if(socket != null && !socket.isClosed()) socket.close();
} catch (IOException e) {}
}
}
}
最后是目前使用的OKHTTP上传文件代码:
//通过“addFormDataPart”可以添加多个上传的文件。
public class OkHttpCallBackWrap {
public void post(String url) throws IOException{
File file = new File("D:/app/dgm/3.mp4");
RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("application/octet-stream", "1.mp4", fileBody)
.build();
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
final okhttp3.OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();
OkHttpClient okHttpClient = httpBuilder
//设置超时
.connectTimeout(100, TimeUnit.SECONDS)
.writeTimeout(150, TimeUnit.SECONDS)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println(response.body().string());
}
@Override
public void onFailure(Call arg0, IOException e) {
// TODO Auto-generated method stub
System.out.println(e.toString());
}
});
}
}