android中okhttp實(shí)現(xiàn)斷點(diǎn)上傳示例
前言
之前項(xiàng)目需要上傳大文件的功能,上傳大文件經(jīng)常遇到上傳一半由于網(wǎng)絡(luò)或者其他一些原因上傳失敗。然后又得重新上傳(很麻煩),所以就想能不能做個(gè)斷點(diǎn)上傳的功能。于是網(wǎng)上搜索,發(fā)現(xiàn)市面上很少有斷點(diǎn)上傳的案例,有找到一個(gè)案例也是采用SOCKET作為上傳方式(大文件上傳,不適合使用POST,GET形式)。由于大文件夾不適合http上傳的方式,所以就想能不能把大文件切割成n塊小文件,然后上傳這些小文件,所有小文件全部上傳成功后再在服務(wù)器上進(jìn)行拼接。這樣不就可以實(shí)現(xiàn)斷點(diǎn)上傳,又解決了http不適合上傳大文件的難題了嗎?。。?/p>
原理分析
Android客戶端
首先,android端調(diào)用服務(wù)器接口1,參數(shù)為filename(服務(wù)器標(biāo)識(shí)判斷是否上傳過)
如果存在filename,說明之前上傳過,則續(xù)傳;如果沒有,則從零開始上傳。
然后,android端調(diào)用服務(wù)器接口2,傳入?yún)?shù)name,chunck(傳到第幾塊),chuncks(總共多少塊)

服務(wù)器端
接口一:根據(jù)上傳文件名稱filename 判斷是否之前上傳過,沒有則返回客戶端chunck=1,有則讀取記錄chunck并返回。
接口二:上傳文件,如果上傳塊數(shù)chunck=chuncks,遍歷所有塊文件拼接成一個(gè)完整文件。
服務(wù)端源代碼
服務(wù)器接口1
@WebServlet(urlPatterns = { "/ckeckFileServlet" })
public class CkeckFileServlet extends HttpServlet {
private FileUploadStatusServiceI statusService;
String repositoryPath;
String uploadPath;
@Override
public void init(ServletConfig config) throws ServletException {
ServletContext servletContext = config.getServletContext();
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
statusService = (FileUploadStatusServiceI) context.getBean("fileUploadStatusServiceImpl");
repositoryPath = FileUtils.getTempDirectoryPath();
uploadPath = config.getServletContext().getRealPath("datas/uploader");
File up = new File(uploadPath);
if (!up.exists()) {
up.mkdir();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
String fileName = new String(req.getParameter("filename"));
//String chunk = req.getParameter("chunk");
//System.out.println(chunk);
System.out.println(fileName);
resp.setContentType("text/json; charset=utf-8");
TfileUploadStatus file = statusService.get(fileName);
try {
if (file != null) {
int schunk = file.getChunk();
deleteFile(uploadPath + schunk + "_" + fileName);
//long off = schunk * Long.parseLong(chunkSize);
resp.getWriter().write("{\"off\":" + schunk + "}");
} else {
resp.getWriter().write("{\"off\":1}");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
服務(wù)器接口2
@WebServlet(urlPatterns = { "/uploaderWithContinuinglyTransferring" })
public class UploaderServletWithContinuinglyTransferring extends HttpServlet {
private static final long serialVersionUID = 1L;
private FileUploadStatusServiceI statusService;
String repositoryPath;
String uploadPath;
@Override
public void init(ServletConfig config) throws ServletException {
ServletContext servletContext = config.getServletContext();
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
statusService = (FileUploadStatusServiceI) context.getBean("fileUploadStatusServiceImpl");
repositoryPath = FileUtils.getTempDirectoryPath();
System.out.println("臨時(shí)目錄:" + repositoryPath);
uploadPath = config.getServletContext().getRealPath("datas/uploader");
System.out.println("目錄:" + uploadPath);
File up = new File(uploadPath);
if (!up.exists()) {
up.mkdir();
}
}
@SuppressWarnings("unchecked")
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setCharacterEncoding("UTF-8");
Integer schunk = null;// 分割塊數(shù)
Integer schunks = null;// 總分割數(shù)
String name = null;// 文件名
BufferedOutputStream outputStream = null;
if (ServletFileUpload.isMultipartContent(request)) {
try {
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(1024);
factory.setRepository(new File(repositoryPath));// 設(shè)置臨時(shí)目錄
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("UTF-8");
upload.setSizeMax(5 * 1024 * 1024 * 1024);// 設(shè)置附近大小
List<FileItem> items = upload.parseRequest(request);
// 生成新文件名
String newFileName = null;
for (FileItem item : items) {
if (!item.isFormField()) {// 如果是文件類型
name = newFileName;// 獲得文件名
if (name != null) {
String nFname = newFileName;
if (schunk != null) {
nFname = schunk + "_" + name;
}
File savedFile = new File(uploadPath, nFname);
item.write(savedFile);
}
} else {
// 判斷是否帶分割信息
if (item.getFieldName().equals("chunk")) {
schunk = Integer.parseInt(item.getString());
//System.out.println(schunk);
}
if (item.getFieldName().equals("chunks")) {
schunks = Integer.parseInt(item.getString());
}
if (item.getFieldName().equals("name")) {
newFileName = new String(item.getString());
}
}
}
//System.out.println(schunk + "/" + schunks);
if (schunk != null && schunk == 1) {
TfileUploadStatus file = statusService.get(newFileName);
if (file != null) {
statusService.updateChunk(newFileName, schunk);
} else {
statusService.add(newFileName, schunk, schunks);
}
} else {
TfileUploadStatus file = statusService.get(newFileName);
if (file != null) {
statusService.updateChunk(newFileName, schunk);
}
}
if (schunk != null && schunk.intValue() == schunks.intValue()) {
outputStream = new BufferedOutputStream(new FileOutputStream(new File(uploadPath, newFileName)));
// 遍歷文件合并
for (int i = 1; i <= schunks; i++) {
//System.out.println("文件合并:" + i + "/" + schunks);
File tempFile = new File(uploadPath, i + "_" + name);
byte[] bytes = FileUtils.readFileToByteArray(tempFile);
outputStream.write(bytes);
outputStream.flush();
tempFile.delete();
}
outputStream.flush();
}
response.getWriter().write("{\"status\":true,\"newName\":\"" + newFileName + "\"}");
} catch (FileUploadException e) {
e.printStackTrace();
response.getWriter().write("{\"status\":false}");
} catch (Exception e) {
e.printStackTrace();
response.getWriter().write("{\"status\":false}");
} finally {
try {
if (outputStream != null)
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
android端源碼
UploadTask 上傳線程類
package com.mainaer.wjoklib.okhttp.upload;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
* 上傳線程
*
* @author hst
* @date 2016/9/6 .
*/
public class UploadTask implements Runnable {
private static String FILE_MODE = "rwd";
private OkHttpClient mClient;
private SQLiteDatabase db;
private UploadTaskListener mListener;
private Builder mBuilder;
private String id;// task id
private String url;// file url
private String fileName; // File name when saving
private int uploadStatus;
private int chunck, chuncks;//流塊
private int position;
private int errorCode;
static String BOUNDARY = "----------" + System.currentTimeMillis();
public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("multipart/form-data;boundary=" + BOUNDARY);
private UploadTask(Builder builder) {
mBuilder = builder;
mClient = new OkHttpClient();
this.id = mBuilder.id;
this.url = mBuilder.url;
this.fileName = mBuilder.fileName;
this.uploadStatus = mBuilder.uploadStatus;
this.chunck = mBuilder.chunck;
this.setmListener(mBuilder.listener);
// 以kb為計(jì)算單位
}
@Override
public void run() {
try {
int blockLength = 1024 * 1024;
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator +fileName);
if (file.length() % blockLength == 0) {
chuncks = (int) file.length() / blockLength;
} else {
chuncks = (int) file.length() / blockLength + 1;
}
while (chunck <= chuncks&&uploadStatus!= UploadStatus.UPLOAD_STATUS_PAUSE&&uploadStatus!= UploadStatus.UPLOAD_STATUS_ERROR)
{
uploadStatus = UploadStatus.UPLOAD_STATUS_UPLOADING;
Map<String, String> params = new HashMap<String, String>();
params.put("name", fileName);
params.put("chunks", chuncks + "");
params.put("chunk", chunck + "");
final byte[] mBlock = FileUtils.getBlock((chunck - 1) * blockLength, file, blockLength);
MultipartBody.Builder builder = new MultipartBody.Builder()
.setType(MultipartBody.FORM);
addParams(builder, params);
RequestBody requestBody = RequestBody.create(MEDIA_TYPE_MARKDOWN, mBlock);
builder.addFormDataPart("mFile", fileName, requestBody);
Request request = new Request.Builder()
.url(url+ "uploaderWithContinuinglyTransferring")
.post(builder.build())
.build();
Response response = null;
response = mClient.newCall(request).execute();
if (response.isSuccessful()) {
onCallBack();
chunck++;
/* if (chunck <= chuncks) {
run();
}*/
}
else
{
uploadStatus = UploadStatus.UPLOAD_STATUS_ERROR;
onCallBack();
}
}
} catch (IOException e) {
uploadStatus = UploadStatus.UPLOAD_STATUS_ERROR;
onCallBack();
e.printStackTrace();
}
}
/* *//**
* 刪除數(shù)據(jù)庫文件和已經(jīng)上傳的文件
*//*
public void cancel() {
if (mListener != null)
mListener.onCancel(UploadTask.this);
}*/
/**
* 分發(fā)回調(diào)事件到ui層
*/
private void onCallBack() {
mHandler.sendEmptyMessage(uploadStatus);
// 同步manager中的task信息
//UploadManager.getInstance().updateUploadTask(this);
}
Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
int code = msg.what;
switch (code) {
// 上傳失敗
case UploadStatus.UPLOAD_STATUS_ERROR:
mListener.onError(UploadTask.this, errorCode,position);
break;
// 正在上傳
case UploadStatus.UPLOAD_STATUS_UPLOADING:
mListener.onUploading(UploadTask.this, getDownLoadPercent(), position);
// 暫停上傳
break;
case UploadStatus.UPLOAD_STATUS_PAUSE:
mListener.onPause(UploadTask.this);
break;
}
}
};
private String getDownLoadPercent() {
String baifenbi = "0";// 接受百分比的值
if (chunck >= chuncks) {
return "100";
}
double baiy = chunck * 1.0;
double baiz = chuncks * 1.0;
// 防止分母為0出現(xiàn)NoN
if (baiz > 0) {
double fen = (baiy / baiz) * 100;
//NumberFormat nf = NumberFormat.getPercentInstance();
//nf.setMinimumFractionDigits(2); //保留到小數(shù)點(diǎn)后幾位
// 百分比格式,后面不足2位的用0補(bǔ)齊
//baifenbi = nf.format(fen);
//注釋掉的也是一種方法
DecimalFormat df1 = new DecimalFormat("0");//0.00
baifenbi = df1.format(fen);
}
return baifenbi;
}
private String getFileNameFromUrl(String url) {
if (!TextUtils.isEmpty(url)) {
return url.substring(url.lastIndexOf("/") + 1);
}
return System.currentTimeMillis() + "";
}
private void close(Closeable closeable) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void setClient(OkHttpClient mClient) {
this.mClient = mClient;
}
public Builder getBuilder() {
return mBuilder;
}
public void setBuilder(Builder builder) {
this.mBuilder = builder;
}
public String getId() {
if (!TextUtils.isEmpty(id)) {
} else {
id = url;
}
return id;
}
public String getUrl() {
return url;
}
public String getFileName() {
return fileName;
}
public void setUploadStatus(int uploadStatus) {
this.uploadStatus = uploadStatus;
}
public int getUploadStatus() {
return uploadStatus;
}
public void setmListener(UploadTaskListener mListener) {
this.mListener = mListener;
}
public static class Builder {
private String id;// task id
private String url;// file url
private String fileName; // File name when saving
private int uploadStatus = UploadStatus.UPLOAD_STATUS_INIT;
private int chunck;//第幾塊
private UploadTaskListener listener;
/**
* 作為上傳task開始、刪除、停止的key值,如果為空則默認(rèn)是url
*
* @param id
* @return
*/
public Builder setId(String id) {
this.id = id;
return this;
}
/**
* 上傳url(not null)
*
* @param url
* @return
*/
public Builder setUrl(String url) {
this.url = url;
return this;
}
/**
* 設(shè)置上傳狀態(tài)
*
* @param uploadStatus
* @return
*/
public Builder setUploadStatus(int uploadStatus) {
this.uploadStatus = uploadStatus;
return this;
}
/**
* 第幾塊
*
* @param chunck
* @return
*/
public Builder setChunck(int chunck) {
this.chunck = chunck;
return this;
}
/**
* 設(shè)置文件名
*
* @param fileName
* @return
*/
public Builder setFileName(String fileName) {
this.fileName = fileName;
return this;
}
/**
* 設(shè)置上傳回調(diào)
*
* @param listener
* @return
*/
public Builder setListener(UploadTaskListener listener) {
this.listener = listener;
return this;
}
public UploadTask build() {
return new UploadTask(this);
}
}
private void addParams(MultipartBody.Builder builder, Map<String, String> params) {
if (params != null && !params.isEmpty()) {
for (String key : params.keySet()) {
builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + key + "\""),
RequestBody.create(null, params.get(key)));
}
}
}
}
UploadManager上傳管理器
package com.mainaer.wjoklib.okhttp.upload;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
/**
* 上傳管理器
*
* @author wangjian
* @date 2016/5/13 .
*/
public class UploadManager {
private static Context mContext;
private static SQLiteDatabase db;
private OkHttpClient mClient;
private int mPoolSize = 20;
// 將執(zhí)行結(jié)果保存在future變量中
private Map<string, future=""> mFutureMap;
private ExecutorService mExecutor;
private Map<string, uploadtask=""> mCurrentTaskList;
static UploadManager manager;
/**
* 方法加鎖,防止多線程操作時(shí)出現(xiàn)多個(gè)實(shí)例
*/
private static synchronized void init() {
if (manager == null) {
manager = new UploadManager();
}
}
/**
* 獲得當(dāng)前對象實(shí)例
*
* @return 當(dāng)前實(shí)例對象
*/
public final static UploadManager getInstance() {
if (manager == null) {
init();
}
return manager;
}
/**
* 管理器初始化,建議在application中調(diào)用
*
* @param context
*/
public static void init(Context context, SQLiteDatabase db1) {
mContext = context;
db = db1;
getInstance();
}
public UploadManager() {
initOkhttpClient();
// 初始化線程池
mExecutor = Executors.newFixedThreadPool(mPoolSize);
mFutureMap = new HashMap<>();
mCurrentTaskList = new HashMap<>();
}
/**
* 初始化okhttp
*/
private void initOkhttpClient() {
OkHttpClient.Builder okBuilder = new OkHttpClient.Builder();
okBuilder.connectTimeout(1000, TimeUnit.SECONDS);
okBuilder.readTimeout(1000, TimeUnit.SECONDS);
okBuilder.writeTimeout(1000, TimeUnit.SECONDS);
mClient = okBuilder.build();
}
/**
* 添加上傳任務(wù)
*
* @param uploadTask
*/
public void addUploadTask(UploadTask uploadTask) {
if (uploadTask != null && !isUploading(uploadTask)) {
uploadTask.setClient(mClient);
uploadTask.setUploadStatus(UploadStatus.UPLOAD_STATUS_INIT);
// 保存上傳task列表
mCurrentTaskList.put(uploadTask.getId(), uploadTask);
Future future = mExecutor.submit(uploadTask);
mFutureMap.put(uploadTask.getId(), future);
}
}
private boolean isUploading(UploadTask task) {
if (task != null) {
if (task.getUploadStatus() == UploadStatus.UPLOAD_STATUS_UPLOADING) {
return true;
}
}
return false;
}
/**
* 暫停上傳任務(wù)
*
* @param id 任務(wù)id
*/
public void pause(String id) {
UploadTask task = getUploadTask(id);
if (task != null) {
task.setUploadStatus(UploadStatus.UPLOAD_STATUS_PAUSE);
}
}
/**
* 重新開始已經(jīng)暫停的上傳任務(wù)
*
* @param id 任務(wù)id
*/
public void resume(String id, UploadTaskListener listener) {
UploadTask task = getUploadTask(id);
if (task != null) {
addUploadTask(task);
}
}
/* *//**
* 取消上傳任務(wù)(同時(shí)會(huì)刪除已經(jīng)上傳的文件,和清空數(shù)據(jù)庫緩存)
*
* @param id 任務(wù)id
* @param listener
*//*
public void cancel(String id, UploadTaskListener listener) {
UploadTask task = getUploadTask(id);
if (task != null) {
mCurrentTaskList.remove(id);
mFutureMap.remove(id);
task.setmListener(listener);
task.cancel();
task.setDownloadStatus(UploadStatus.DOWNLOAD_STATUS_CANCEL);
}
}*/
/**
* 實(shí)時(shí)更新manager中的task信息
*
* @param task
*/
public void updateUploadTask(UploadTask task) {
if (task != null) {
UploadTask currTask = getUploadTask(task.getId());
if (currTask != null) {
mCurrentTaskList.put(task.getId(), task);
}
}
}
/**
* 獲得指定的task
*
* @param id task id
* @return
*/
public UploadTask getUploadTask(String id) {
UploadTask currTask = mCurrentTaskList.get(id);
if (currTask == null) {
currTask = parseEntity2Task(new UploadTask.Builder().build());
// 放入task list中
mCurrentTaskList.put(id, currTask);
}
return currTask;
}
private UploadTask parseEntity2Task(UploadTask currTask) {
UploadTask.Builder builder = new UploadTask.Builder()//
.setUploadStatus(currTask.getUploadStatus())
.setFileName(currTask.getFileName())//
.setUrl(currTask.getUrl())
.setId(currTask.getId());
currTask.setBuilder(builder);
return currTask;
}
}
FileUtils文件分塊類
package com.mainaer.wjoklib.okhttp.upload;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
public class FileUtils {
public static byte[] getBlock(long offset, File file, int blockSize) {
byte[] result = new byte[blockSize];
RandomAccessFile accessFile = null;
try {
accessFile = new RandomAccessFile(file, "r");
accessFile.seek(offset);
int readSize = accessFile.read(result);
if (readSize == -1) {
return null;
} else if (readSize == blockSize) {
return result;
} else {
byte[] tmpByte = new byte[readSize];
System.arraycopy(result, 0, tmpByte, 0, readSize);
return tmpByte;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (accessFile != null) {
try {
accessFile.close();
} catch (IOException e1) {
}
}
}
return null;
}
}
UploadTaskListener 接口類
package com.mainaer.wjoklib.okhttp.upload;
import com.mainaer.wjoklib.okhttp.download.DownloadStatus;
import java.io.File;
/**
* Created by hst on 16/9/21.
*/
public interface UploadTaskListener {
/**
* 上傳中
*
* @param percent
* @param uploadTask
*/
void onUploading(UploadTask uploadTask, String percent,int position)
/**
* 上傳成功
*
* @param file
* @param uploadTask
*/
void onUploadSuccess(UploadTask uploadTask, File file);
/**
* 上傳失敗
*
* @param uploadTask
* @param errorCode {@link DownloadStatus}
*/
void onError(UploadTask uploadTask, int errorCode,int position);
/**
* 上傳暫停
*
* @param uploadTask
*
*/
void onPause(UploadTask uploadTask);
}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android中Okhttp3實(shí)現(xiàn)上傳多張圖片同時(shí)傳遞參數(shù)
- Android OkHttp Post上傳文件并且攜帶參數(shù)實(shí)例詳解
- 使用Android的OkHttp包實(shí)現(xiàn)基于HTTP協(xié)議的文件上傳下載
- Android中實(shí)現(xiàn)OkHttp上傳文件到服務(wù)器并帶進(jìn)度
- Android使用OkHttp上傳圖片的實(shí)例代碼
- RxJava+Retrofit+OkHttp實(shí)現(xiàn)多文件下載之?dāng)帱c(diǎn)續(xù)傳
- android通過okhttpClient下載網(wǎng)頁內(nèi)容的實(shí)例代碼
- android中實(shí)現(xiàn)OkHttp下載文件并帶進(jìn)度條
- android使用OkHttp實(shí)現(xiàn)下載的進(jìn)度監(jiān)聽和斷點(diǎn)續(xù)傳
- Android基于OkHttp實(shí)現(xiàn)下載和上傳圖片
相關(guān)文章
Android根據(jù)包名停止其他應(yīng)用程序的方法
這篇文章主要介紹了Android根據(jù)包名停止其他應(yīng)用程序,需要的朋友可以參考下2020-03-03
從源碼分析Android的Glide庫的圖片加載流程及特點(diǎn)
這篇文章主要介紹了從源碼分析Android的Glide庫的圖片加載流程及特點(diǎn),Glide庫是Android下一款人氣很高的多媒體資源管理庫,特別是在處理gif加載方面受到眾多開發(fā)者青睞,需要的朋友可以參考下2016-04-04
利用Jetpack?Compose實(shí)現(xiàn)繪制五角星效果
這篇文章主要為大家介紹了Jetpack?Compose如何使用自定義操作符實(shí)現(xiàn)繪制五角星效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-04-04
詳解Android使用OKHttp3實(shí)現(xiàn)下載(斷點(diǎn)續(xù)傳、顯示進(jìn)度)
本篇文章主要介紹了詳解Android使用OKHttp3實(shí)現(xiàn)下載(斷點(diǎn)續(xù)傳、顯示進(jìn)度),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02
Android下拉刷新控件SwipeRefreshLayout源碼解析
這篇文章主要為大家詳細(xì)解析Android下拉刷新控件SwipeRefreshLayout源碼,感興趣的小伙伴們可以參考一下2016-07-07
保持Android Service在手機(jī)休眠后繼續(xù)運(yùn)行的方法
下面小編就為大家分享一篇保持Android Service在手機(jī)休眠后繼續(xù)運(yùn)行的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-03-03
android中設(shè)置TextView/Button 走馬燈(Marquee)效果示例
定義走馬燈(Marquee),主要在Project/res/layout/main.xml即可,下面與大家分享下具體的實(shí)現(xiàn),感興趣的朋友可以參考下哈2013-06-06

