欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

android中okhttp实现断点上传示例

程序员文章站 2023-12-20 15:59:16
前言 之前项目需要上传大文件的功能,上传大文件经常遇到上传一半由于网络或者其他一些原因上传失败。然后又得重新上传(很麻烦),所以就想能不能做个断点上传的功能。于是网上...

前言

之前项目需要上传大文件的功能,上传大文件经常遇到上传一半由于网络或者其他一些原因上传失败。然后又得重新上传(很麻烦),所以就想能不能做个断点上传的功能。于是网上搜索,发现市面上很少有断点上传的案例,有找到一个案例也是采用socket作为上传方式(大文件上传,不适合使用post,get形式)。由于大文件夹不适合http上传的方式,所以就想能不能把大文件切割成n块小文件,然后上传这些小文件,所有小文件全部上传成功后再在服务器上进行拼接。这样不就可以实现断点上传,又解决了http不适合上传大文件的难题了吗!!!

原理分析

android客户端

首先,android端调用服务器接口1,参数为filename(服务器标识判断是否上传过)

如果存在filename,说明之前上传过,则续传;如果没有,则从零开始上传。

然后,android端调用服务器接口2,传入参数name,chunck(传到第几块),chuncks(总共多少块)

android中okhttp实现断点上传示例 

android中okhttp实现断点上传示例

服务器端

接口一:根据上传文件名称filename 判断是否之前上传过,没有则返回客户端chunck=1,有则读取记录chunck并返回。

接口二:上传文件,如果上传块数chunck=chuncks,遍历所有块文件拼接成一个完整文件。

 服务端源代码

服务器接口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();
    }
  }
}

服务器接口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;// 分割块数
    integer schunks = null;// 总分割数
    string name = null;// 文件名
    bufferedoutputstream outputstream = null;
    if (servletfileupload.ismultipartcontent(request)) {
      try {
        diskfileitemfactory factory = new diskfileitemfactory();
        factory.setsizethreshold(1024);
        factory.setrepository(new file(repositorypath));// 设置临时目录
        servletfileupload upload = new servletfileupload(factory);
        upload.setheaderencoding("utf-8");
        upload.setsizemax(5 * 1024 * 1024 * 1024);// 设置附近大小
        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为计算单位
  }

  @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();
    }
  }


/*  *//**
   * 删除数据库文件和已经上传的文件
   *//*
  public void cancel() {
    if (mlistener != null)
      mlistener.oncancel(uploadtask.this);
  }*/

  /**
   * 分发回调事件到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出现non
    if (baiz > 0) {
      double fen = (baiy / baiz) * 100;
      //numberformat nf = numberformat.getpercentinstance();
      //nf.setminimumfractiondigits(2); //保留到小数点后几位
      // 百分比格式,后面不足2位的用0补齐
      //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值,如果为空则默认是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;
    }

    /**
     * 设置上传状态
     *
     * @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;
    }


    /**
     * 设置文件名
     *
     * @param filename
     * @return
     */
    public builder setfilename(string filename) {
      this.filename = filename;
      return this;
    }

    /**
     * 设置上传回调
     *
     * @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;

  // 将执行结果保存在future变量中

  private map<string, future=""> mfuturemap;

  private executorservice mexecutor;

  private map<string, uploadtask=""> mcurrenttasklist;

 

  static uploadmanager manager;

 

  /**

   * 方法加锁,防止多线程操作时出现多个实例

   */

  private static synchronized void init() {

    if (manager == null) {

      manager = new uploadmanager();

    }

  }

 

  /**

   * 获得当前对象实例

   *

   * @return 当前实例对象

   */

  public final static uploadmanager getinstance() {

    if (manager == null) {

      init();

    }

    return manager;

  }

 

  /**

   * 管理器初始化,建议在application中调用

   *

   * @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();

  }

 

  /**

   * 添加上传任务

   *

   * @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;

  }

 

  /**

   * 暂停上传任务

   *

   * @param id 任务id

   */

  public void pause(string id) {

    uploadtask task = getuploadtask(id);

    if (task != null) {

      task.setuploadstatus(uploadstatus.upload_status_pause);

    }

  }

 

  /**

   * 重新开始已经暂停的上传任务

   *

   * @param id 任务id

   */

  public void resume(string id, uploadtasklistener listener) {

    uploadtask task = getuploadtask(id);

    if (task != null) {

      adduploadtask(task);

    }

  }

 

/*  *//**

   * 取消上传任务(同时会删除已经上传的文件,和清空数据库缓存)

   *

   * @param id    任务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);

    }

  }*/

 

  /**

   * 实时更新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);

 } 

源码下载:okhttpuploader_jb51.rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。