Okhttp實(shí)現(xiàn)對大文件和視頻斷點(diǎn)上傳

版權(quán)聲明:本文為衛(wèi)偉學(xué)習(xí)總結(jié)文章崭孤,轉(zhuǎn)載請注明出處!
前言:之前項目需要上傳大文件的功能祈餐,上傳大文件經(jīng)常遇到上傳一半由于網(wǎng)絡(luò)或者其他一些原因上傳失敗。然后又得重新上傳(很麻煩)戏阅,所以就想能不能做個斷點(diǎn)上傳得功能昼弟。于是網(wǎng)上搜索,發(fā)現(xiàn)市面上很少有斷點(diǎn)上傳得案例奕筐,有找到一些案例也是采用socket作為上傳方式(大文件上傳舱痘,不適合使用POST,GET形式)离赫。由于大文件夾不適合http上傳得方式芭逝,所以就能不能把大文件切割成n塊小文件,然后上傳這些小文件渊胸,所有小文件全部上傳成功后再在服務(wù)器上進(jìn)行拼接旬盯。這樣不就可以實(shí)現(xiàn)斷點(diǎn)上傳,又解決了http不適合上傳大文件得難題了嗎!胖翰!
一接剩、原理分析
Android客戶端:

  • 首先,android端調(diào)用服務(wù)器接口1萨咳,參數(shù)為filename(服務(wù)器標(biāo)識判斷是否上傳過)
  • 如果存在filename,說明之前上傳過懊缺,則續(xù)傳;如果沒有培他,則從零開始上傳鹃两。
  • 然后,android端調(diào)用服務(wù)器接口2舀凛,傳入?yún)?shù)name,chunck(傳到第幾塊),chuncks(總共多少塊)
  • 為了區(qū)分多文件上傳的識別俊扳,增加文件MD5和設(shè)備eid的參數(shù)。

服務(wù)器端:

  • 接口一:根據(jù)上傳文件名稱filename判斷是否之前上傳過猛遍,沒有則返回客戶端chunck=1,有則讀取記錄chunck并返回馋记。
  • 接口二:上傳文件,如果上傳塊數(shù)chunck=chuncks螃壤,遍歷所有塊文件拼接成一個完整文件抗果。

服務(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("臨時目錄:" + 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è)置臨時目錄
            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.jado.okhttp.video.upload;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;

import com.example.trafficinfo.CommandHelper;
import com.example.trafficinfo.devicesInfo;

import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import okhttp3.Call;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/**
 * 上傳線程
 * @author weiwei
 * @date 2019/12/23
 *
*/
public class UploadTask implements Runnable {
private static final String TAG ="UploadTask";
private static String FILE_MODE = "rwd";
private OkHttpClient mClient;
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);

public 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為計算單位
}

@Override
public void run() {
    try {
        int blockLength = 1024 * 1024;
        //long lastblockLength = 1024 * 1024;
        File file = new File(Environment.getExternalStorageDirectory()
                .getAbsolutePath() + File.separator + fileName);
        String md5 = getFileMD5(Environment.getExternalStorageDirectory()
                .getAbsolutePath() + File.separator + fileName);
        if (file.length() % blockLength == 0) { // 算出總塊數(shù)
            chuncks = (int) file.length() / blockLength;
        } else {
            chuncks = (int) file.length() / blockLength + 1;
        }
        //lastblockLength = file.length() / blockLength;
        
        Log.i(TAG,"chuncks =" +chuncks+ "fileName =" +fileName+ "uploadStatus =" +uploadStatus);
        Log.i(TAG,"chunck =" +chunck);
        Log.i(TAG,"md5 =" +md5);
        //Log.i(TAG,"lastblockLength =" +lastblockLength);
        String eid = null;
        try {
            eid = CommandHelper.getMMCId();
            Log.i(TAG,"eid =" +eid);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        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("filename", fileName);
            params.put("md5", md5);
            params.put("chunks", chuncks + "");
            params.put("chunk", chunck + "");
            params.put("size", blockLength + "");
            params.put("eid", eid);
            Log.i(TAG,"chunck =" +chunck+ "chuncks =" +chuncks);
            final byte[] mBlock = FileUtils.getBlock((chunck - 1)
                    * blockLength, file, blockLength);
            Log.i(TAG,"mBlock == " +mBlock.length);
            // 生成RequestBody
            MultipartBody.Builder builder = new MultipartBody.Builder();
            addParams(builder, params);
            String fileType = "file/*";
            RequestBody requestBody = RequestBody.create(
                    MediaType.parse(fileType), mBlock);
            builder.addFormDataPart("file", fileName, requestBody);
            Log.i(TAG,"url =" +url);
            
            //獲得Request實(shí)例
            Request request = new Request.Builder()
                    .url(url)
                    .post(builder.build())
                    .build();
            Log.i(TAG,"RequestBody execute~");
            
            Response response = null;
                    response = mClient.newCall(request).execute();
            Log.i(TAG,"isSuccessful =" +response.isSuccessful());
            if(response.isSuccessful()) {
                String ret = response.body().string();
                Log.d(TAG,"uploadVideo  UploadTask ret:" +ret);
                onCallBack();
                chunck++;
            } else {
                uploadStatus = UploadStatus.UPLOAD_STATUS_ERROR;
                onCallBack();
            }
        }
    } catch (IOException e) {
        Log.i(TAG,"run IOException");
        uploadStatus = UploadStatus.UPLOAD_STATUS_ERROR;
        onCallBack();
        Log.i(TAG,"e error: =" +e.toString());
        e.printStackTrace();
    }
}

/**
 * 更加文件路徑生成唯一的MD5值
 * @param path
 * @return
 */
public static String getFileMD5(String path) {
    BigInteger bi = null;
    try {
        byte[] buffer = new byte[8192];
        int len = 0;
        MessageDigest md = MessageDigest.getInstance("MD5");
        File f = new File(path);
        FileInputStream fis = new FileInputStream(f);
        while ((len = fis.read(buffer)) != -1) {
            md.update(buffer, 0, len);
        }
        fis.close();
        byte[] b = md.digest();
        bi = new BigInteger(1, b);
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return bi.toString(16);
}

/**
 * 分發(fā)回調(diào)事件到UI層
 */
private void onCallBack() {
    mHandler.sendEmptyMessage(uploadStatus);
}

Handler mHandler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(android.os.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 percentage = "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
        percentage = df1.format(fen);
    }
    return percentage;
}

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.jado.okhttp.video.upload;

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;
import android.content.Context;

/**
  * 上傳管理器
  * @author weiwei
  * @date 2019/12/23
  *
 */
public class UploadManager {
private Context mContext;

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;

/**
 * 方法加鎖,防止多線程操作時出現(xiàn)多個實(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 void init(Context context) {
    mContext = context;
    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ù)(同時會刪除已經(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í)時更新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.jado.okhttp.video.upload;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
  * 文件分塊處理
  * @author weiwei
 *
 */
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.jado.okhttp.video.upload;

import java.io.File;

/**
 * create by weiwei on 19/12/23
 * @author weiwei
 *
*/
public interface UploadTaskListener {

/**
 * 上傳中
 * @param uploadTask
 * @param percent
 * @param position
 */
void onUploading(UploadTask uploadTask, String percent, int position);

/**
 * 上傳成功
 * @param uploadTask
 * @param file
 */
void onUploadSuccess(UploadTask uploadTask, File file);

/**
 * 上傳失敗
 * @param uploadTask
 * @param errorCode
 * @param position
 */
void onError(UploadTask uploadTask, int errorCode, int position);

/**
 * 上傳暫停
 * @param uploadTask
 */
void onPause(UploadTask uploadTask);
}

android源碼地址:
密碼:0edg

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末代箭,一起剝皮案震驚了整個濱河市墩划,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嗡综,老刑警劉巖乙帮,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異极景,居然都是意外死亡察净,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門盼樟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來氢卡,“玉大人,你說我怎么就攤上這事晨缴∫肭兀” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長筑悴。 經(jīng)常有香客問我们拙,道長,這世上最難降的妖魔是什么阁吝? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任砚婆,我火速辦了婚禮,結(jié)果婚禮上求摇,老公的妹妹穿的比我還像新娘射沟。我一直安慰自己,他們只是感情好与境,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布验夯。 她就那樣靜靜地躺著,像睡著了一般摔刁。 火紅的嫁衣襯著肌膚如雪挥转。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天共屈,我揣著相機(jī)與錄音绑谣,去河邊找鬼。 笑死拗引,一個胖子當(dāng)著我的面吹牛借宵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播矾削,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼壤玫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了哼凯?” 一聲冷哼從身側(cè)響起欲间,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎断部,沒想到半個月后猎贴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蝴光,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年她渴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虱疏。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡惹骂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出做瞪,到底是詐尸還是另有隱情对粪,我是刑警寧澤右冻,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站著拭,受9級特大地震影響纱扭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜儡遮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一乳蛾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鄙币,春花似錦肃叶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绩衷,卻和暖如春蹦魔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背咳燕。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工勿决, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人招盲。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓低缩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親曹货。 傳聞我的和親對象是個殘疾皇子表制,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 自上次參加完回音分享會后,我下定決心要洗心革面乖乖打基礎(chǔ)控乾,于是開啟了這個part,爭取兩個月不間斷更新娜遵,寫完Mat...
    霖醬閱讀 245評論 0 2
  • 今天中午吃完飯蜕衡,媽媽帶我去了我盼望已久的游樂場,我很喜歡游樂場里面大屏幕的游戲设拟,我和小朋友玩的時候還有一個...
    李佳晨寶寶閱讀 134評論 0 0
  • 大多數(shù)人在職場上都會感到工作壓力慨仿。有時候,當(dāng)我們在家里也要處理一些工作的時候纳胧,會把這種壓力帶給家人镰吆,讓家人為我們擔(dān)...
    知行當(dāng)下閱讀 1,369評論 0 1
  • 我開始恐婚, 恐婚的原因跑慕, 是因?yàn)槲遗聦Ψ浇o不了我想要的那種生活万皿, 我怕突然多一個人生活摧找, 會打亂我原有的生活, ...
    自縛_a374閱讀 167評論 0 1
  • 請點(diǎn)擊藍(lán)字收聽朗讀音頻 夕陽牢硅,寒冷蹬耘,死去 戀人的旋轉(zhuǎn)木馬 在原地打轉(zhuǎn) 七彩燈和月亮笑開了花 虛化閃爍的情話 真摯疊...
    云中飄舞閱讀 281評論 0 7