多线程下载 断点续传

来源:互联网 发布:sublime windows 编辑:程序博客网 时间:2024/05/18 02:47
    //依赖          compile 'com.squareup.okhttp3:okhttp:3.6.0'          compile 'com.squareup.okio:okio:1.11.0'          compile 'fm.jiecao:jiecaovideoplayer:5.5'          //权限          <!-- 联网权限 -->          <uses-permission android:name="android.permission.INTERNET" />          <!-- 读取手机状态权限 -->          <uses-permission android:name="android.permission.READ_PHONE_STATE" />          <!-- 往sdcard中写入数据的权限 -->          <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />          <!-- 读取sdcard中数据的权限 -->          <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />          <!-- 在sdcard中创建/删除文件的权限 -->          <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />                  //main.xml      <?xml version="1.0" encoding="utf-8"?>      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"          xmlns:tools="http://schemas.android.com/tools"          android:id="@+id/activity_main"          android:layout_width="match_parent"          android:layout_height="match_parent"          android:orientation="vertical">                            <LinearLayout              android:layout_width="match_parent"              android:layout_height="wrap_content"              android:layout_marginTop="15dp"              android:gravity="center_vertical"              android:orientation="horizontal">                          <TextView                  android:id="@+id/tv_progress2"                  android:layout_width="wrap_content"                  android:layout_height="wrap_content"                  android:layout_marginLeft="15dp"                  android:text="0%" />                      </LinearLayout>                      <ProgressBar              android:id="@+id/pb_progress2"              style="?android:attr/progressBarStyleHorizontal"              android:layout_width="match_parent"              android:layout_height="40dp"              android:layout_margin="20dp" />                <LinearLayout              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:layout_gravity="center_horizontal"              android:orientation="horizontal">                    <Button                  android:id="@+id/btn_download2"                  android:layout_width="wrap_content"                  android:layout_height="wrap_content"                  android:onClick="downloadOrPause"                  android:text="下载" />                    <Button                  android:id="@+id/btn_cancel2"                  android:layout_width="wrap_content"                  android:layout_height="wrap_content"                  android:layout_marginLeft="10dp"                  android:onClick="cancel"                  android:text="取消" />                </LinearLayout>           <fm.jiecao.jcvideoplayer_lib.JCVideoPlayerStandard        android:id="@+id/video_view"        android:layout_width="match_parent"        android:layout_height="250dp"/>          </LinearLayout>                        //DownloadListner      public interface DownloadListner {          void onFinished();                void onProgress(float progress);                void onPause();                void onCancel();      }                  //DownloadManager            import android.os.Environment;      import android.text.TextUtils;            import java.io.File;      import java.util.HashMap;      import java.util.Map;            /**      * 下载管理器,断点续传      */      public class DownloadManager {                private String DEFAULT_FILE_DIR;//默认下载目录          private Map<String, DownloadTask> mDownloadTasks;//文件下载任务索引,String为url,用来唯一区别并操作下载的文件          private static DownloadManager mInstance;          private static final String TAG = "DownloadManager";          /**          * 下载文件          */          public void download(String... urls) {              //单任务开启下载或多任务开启下载              for (int i = 0, length = urls.length; i < length; i++) {                  String url = urls[i];                  if (mDownloadTasks.containsKey(url)) {                      mDownloadTasks.get(url).start();                  }              }          }                      // 获取下载文件的名称          public String getFileName(String url) {              return url.substring(url.lastIndexOf("/") + 1);          }                /**          * 暂停          */          public void pause(String... urls) {              //单任务暂停或多任务暂停下载              for (int i = 0, length = urls.length; i < length; i++) {                  String url = urls[i];                  if (mDownloadTasks.containsKey(url)) {                      mDownloadTasks.get(url).pause();                  }              }          }                /**          * 取消下载          */          public void cancel(String... urls) {              //单任务取消或多任务取消下载              for (int i = 0, length = urls.length; i < length; i++) {                  String url = urls[i];                  if (mDownloadTasks.containsKey(url)) {                      mDownloadTasks.get(url).cancel();                  }              }          }                /**          * 添加下载任务          */          public void add(String url, DownloadListner l) {              add(url, null, null, l);          }                /**          * 添加下载任务          */          public void add(String url, String filePath, DownloadListner l) {              add(url, filePath, null, l);          }                /**          * 添加下载任务          */          public void add(String url, String filePath, String fileName, DownloadListner l) {              if (TextUtils.isEmpty(filePath)) {//没有指定下载目录,使用默认目录                  filePath = getDefaultDirectory();              }              if (TextUtils.isEmpty(fileName)) {                  fileName = getFileName(url);              }              mDownloadTasks.put(url, new DownloadTask(new FilePoint(url, filePath, fileName), l));          }                /**          * 默认下载目录          * @return          */          private String getDefaultDirectory() {              if (TextUtils.isEmpty(DEFAULT_FILE_DIR)) {                  DEFAULT_FILE_DIR = Environment.getExternalStorageDirectory().getAbsolutePath()                          + File.separator + "icheny" + File.separator;              }              return DEFAULT_FILE_DIR;          }                public static DownloadManager getInstance() {//管理器初始化              if (mInstance == null) {                  synchronized (DownloadManager.class) {                      if (mInstance == null) {                          mInstance = new DownloadManager();                      }                  }              }              return mInstance;          }                public DownloadManager() {              mDownloadTasks = new HashMap<>();          }                /**          * 取消下载          */          public boolean isDownloading(String... urls) {              //这里传一个url就是判断一个下载任务              //多个url数组适合下载管理器判断是否作操作全部下载或全部取消下载              boolean result = false;              for (int i = 0, length = urls.length; i < length; i++) {                  String url = urls[i];                  if (mDownloadTasks.containsKey(url)) {                      result = mDownloadTasks.get(url).isDownloading();                  }              }              return result;          }      }                  //DownloadTask      import android.os.Handler;      import android.os.Message;      import android.util.Log;            import java.io.Closeable;      import java.io.File;      import java.io.IOException;      import java.io.InputStream;      import java.io.RandomAccessFile;            import okhttp3.Call;      import okhttp3.Response;                        public class DownloadTask extends Handler {                private final int THREAD_COUNT = 4;//线程数          private FilePoint mPoint;          private long mFileLength;                      private boolean isDownloading = false;          private int childCanleCount;//子线程取消数量          private int childPauseCount;//子线程暂停数量          private int childFinshCount;          private HttpUtil mHttpUtil;          private long[] mProgress;          private File[] mCacheFiles;          private File mTmpFile;//临时占位文件          private boolean pause;//是否暂停          private boolean cancel;//是否取消下载                private final int MSG_PROGRESS = 1;//进度          private final int MSG_FINISH = 2;//完成下载          private final int MSG_PAUSE = 3;//暂停          private final int MSG_CANCEL = 4;//暂停          private DownloadListner mListner;//下载回调监听                /**          * 任务管理器初始化数据          * @param point          * @param l          */          DownloadTask(FilePoint point, DownloadListner l) {              this.mPoint = point;              this.mListner = l;              this.mProgress = new long[THREAD_COUNT];              this.mCacheFiles = new File[THREAD_COUNT];              this.mHttpUtil = HttpUtil.getInstance();          }                /**          * 任务回调消息          * @param msg          */          @Override          public void handleMessage(Message msg) {              super.handleMessage(msg);              if (null == mListner) {                  return;              }              switch (msg.what) {                  case MSG_PROGRESS://进度                      long progress = 0;                      for (int i = 0, length = mProgress.length; i < length; i++) {                          progress += mProgress[i];                      }                      mListner.onProgress(progress * 1.0f / mFileLength);                      break;                  case MSG_PAUSE://暂停                      childPauseCount++;                      if (childPauseCount % THREAD_COUNT != 0) return;                      resetStutus();                      mListner.onPause();                      break;                  case MSG_FINISH://完成                      childFinshCount++;                      if (childFinshCount % THREAD_COUNT != 0) return;                      mTmpFile.renameTo(new File(mPoint.getFilePath(), mPoint.getFileName()));//下载完毕后,重命名目标文件名                      resetStutus();                      mListner.onFinished();                      break;                  case MSG_CANCEL://取消                      childCanleCount++;                      if (childCanleCount % THREAD_COUNT != 0) return;                      resetStutus();                      mProgress = new long[THREAD_COUNT];                      mListner.onCancel();                      break;              }          }                private static final String TAG = "DownloadTask";                public synchronized void start() {              try {                  Log.e(TAG, "start: " + isDownloading + "\t" + mPoint.getUrl());                  if (isDownloading) return;                  isDownloading = true;                  mHttpUtil.getContentLength(mPoint.getUrl(), new okhttp3.Callback() {                      @Override                      public void onResponse(Call call, Response response) throws IOException {                          if (response.code() != 200) {                              close(response.body());                              resetStutus();                              return;                          }                          // 获取资源大小                          mFileLength = response.body().contentLength();                          close(response.body());                          // 在本地创建一个与资源同样大小的文件来占位                          mTmpFile = new File(mPoint.getFilePath(), mPoint.getFileName() + ".tmp");                          if (!mTmpFile.getParentFile().exists()) mTmpFile.getParentFile().mkdirs();                          RandomAccessFile tmpAccessFile = new RandomAccessFile(mTmpFile, "rw");                          tmpAccessFile.setLength(mFileLength);                          /*将下载任务分配给每个线程*/                          long blockSize = mFileLength / THREAD_COUNT;// 计算每个线程理论上下载的数量.                                /*为每个线程配置并分配任务*/                          for (int threadId = 0; threadId < THREAD_COUNT; threadId++) {                              long startIndex = threadId * blockSize; // 线程开始下载的位置                              long endIndex = (threadId + 1) * blockSize - 1; // 线程结束下载的位置                              if (threadId == (THREAD_COUNT - 1)) { // 如果是最后一个线程,将剩下的文件全部交给这个线程完成                                  endIndex = mFileLength - 1;                              }                              download(startIndex, endIndex, threadId);// 开启线程下载                          }                      }                            @Override                      public void onFailure(Call call, IOException e) {                      }                  });              } catch (IOException e) {                  e.printStackTrace();                  resetStutus();              }          }                public void download(final long startIndex, final long endIndex, final int threadId) throws IOException {              long newStartIndex = startIndex;              // 分段请求网络连接,分段将文件保存到本地.              // 加载下载位置缓存文件              final File cacheFile = new File(mPoint.getFilePath(), "thread" + threadId + "_" + mPoint.getFileName() + ".cache");              mCacheFiles[threadId] = cacheFile;              final RandomAccessFile cacheAccessFile = new RandomAccessFile(cacheFile, "rwd");              if (cacheFile.exists()) {// 如果文件存在                  String startIndexStr = cacheAccessFile.readLine();                  try {                      newStartIndex = Integer.parseInt(startIndexStr);//重新设置下载起点                  } catch (NumberFormatException e) {                      e.printStackTrace();                  }              }              final long finalStartIndex = newStartIndex;              mHttpUtil.downloadFileByRange(mPoint.getUrl(), finalStartIndex, endIndex, new okhttp3.Callback() {                  @Override                  public void onResponse(Call call, Response response) throws IOException {                      if (response.code() != 206) {// 206:请求部分资源成功码                          resetStutus();                          return;                      }                      InputStream is = response.body().byteStream();// 获取流                      RandomAccessFile tmpAccessFile = new RandomAccessFile(mTmpFile, "rw");// 获取前面已创建的文件.                      tmpAccessFile.seek(finalStartIndex);// 文件写入的开始位置.                        /*  将网络流中的文件写入本地*/                      byte[] buffer = new byte[1024 << 2];                      int length = -1;                      int total = 0;// 记录本次下载文件的大小                      long progress = 0;                      while ((length = is.read(buffer)) > 0) {                          if (cancel) {                              //关闭资源                              close(cacheAccessFile, is, response.body());                              cleanFile(cacheFile);                              sendEmptyMessage(MSG_CANCEL);                              return;                          }                          if (pause) {                              //关闭资源                              close(cacheAccessFile, is, response.body());                              //发送暂停消息                              sendEmptyMessage(MSG_PAUSE);                              return;                          }                          tmpAccessFile.write(buffer, 0, length);                          total += length;                          progress = finalStartIndex + total;                                //将当前现在到的位置保存到文件中                          cacheAccessFile.seek(0);                          cacheAccessFile.write((progress + "").getBytes("UTF-8"));                          //发送进度消息                          mProgress[threadId] = progress - startIndex;                          sendEmptyMessage(MSG_PROGRESS);                      }                      //关闭资源                      close(cacheAccessFile, is, response.body());                      // 删除临时文件                      cleanFile(cacheFile);                      //发送完成消息                      sendEmptyMessage(MSG_FINISH);                  }                        @Override                  public void onFailure(Call call, IOException e) {                      isDownloading = false;                  }              });          }                /**          * 关闭资源          *          * @param closeables          */          private void close(Closeable... closeables) {              int length = closeables.length;              try {                  for (int i = 0; i < length; i++) {                      Closeable closeable = closeables[i];                      if (null != closeable)                          closeables[i].close();                  }              } catch (IOException e) {                  e.printStackTrace();              } finally {                  for (int i = 0; i < length; i++) {                      closeables[i] = null;                  }              }          }                /**          * 删除临时文件          */          private void cleanFile(File... files) {              for (int i = 0, length = files.length; i < length; i++) {                  if (null != files[i])                      files[i].delete();              }          }                /**          * 暂停          */          public void pause() {              pause = true;          }                /**          * 取消          */          public void cancel() {              cancel = true;              cleanFile(mTmpFile);              if (!isDownloading) {                  if (null != mListner) {                      cleanFile(mCacheFiles);                      resetStutus();                      mListner.onCancel();                  }              }          }                /**          * 重置下载状态          */          private void resetStutus() {              pause = false;              cancel = false;              isDownloading = false;          }                public boolean isDownloading() {              return isDownloading;          }      }                  //FilePoint      public class FilePoint {          private String fileName;//文件名          private String url;//下载地址          private String filePath;//下载目录                public FilePoint(String url) {              this.url = url;          }                public FilePoint(String filePath, String url) {              this.filePath = filePath;              this.url = url;          }                public FilePoint(String url, String filePath, String fileName) {              this.url = url;              this.filePath = filePath;              this.fileName = fileName;          }                public String getFileName() {              return fileName;          }                public void setFileName(String fileName) {              this.fileName = fileName;          }                public String getUrl() {              return url;          }                public void setUrl(String url) {              this.url = url;          }                public String getFilePath() {              return filePath;          }                public void setFilePath(String filePath) {              this.filePath = filePath;          }            }                  //HttpUtil      public class HttpUtil {          private OkHttpClient mOkHttpClient;          private static HttpUtil mInstance;          private final static long CONNECT_TIMEOUT = 60;//超时时间,秒          private final static long READ_TIMEOUT = 60;//读取时间,秒          private final static long WRITE_TIMEOUT = 60;//写入时间,秒                /**          * @param url        下载链接          * @param startIndex 下载起始位置          * @param endIndex   结束为止          * @param callback   回调          * @throws IOException          */          public void downloadFileByRange(String url, long startIndex, long endIndex, Callback callback) throws IOException {              // 创建一个Request              // 设置分段下载的头信息。 Range:做分段数据请求,断点续传指示下载的区间。格式: Range bytes=0-1024或者bytes:0-1024              Request request = new Request.Builder().header("RANGE", "bytes=" + startIndex + "-" + endIndex)                      .url(url)                      .build();              doAsync(request, callback);          }                public void getContentLength(String url, Callback callback) throws IOException {              // 创建一个Request              Request request = new Request.Builder()                      .url(url)                      .build();              doAsync(request, callback);          }                /**          * 异步请求          */          private void doAsync(Request request, Callback callback) throws IOException {              //创建请求会话              Call call = mOkHttpClient.newCall(request);              //同步执行会话请求              call.enqueue(callback);          }                /**          * 同步请求          */          private Response doSync(Request request) throws IOException {                    //创建请求会话              Call call = mOkHttpClient.newCall(request);              //同步执行会话请求              return call.execute();          }                      /**          * @return HttpUtil实例对象          */          public static HttpUtil getInstance() {              if (null == mInstance) {                  synchronized (HttpUtil.class) {                      if (null == mInstance) {                          mInstance = new HttpUtil();                      }                  }              }              return mInstance;          }                /**          * 构造方法,配置OkHttpClient          */          public HttpUtil() {              //创建okHttpClient对象              OkHttpClient.Builder builder = new OkHttpClient.Builder()                      .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)                      .writeTimeout(READ_TIMEOUT, TimeUnit.SECONDS)                      .readTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS);              mOkHttpClient = builder.build();          }      }            //mainactivity            import android.Manifest;import android.content.pm.PackageManager;import android.os.Build;import android.os.Bundle;import android.support.v4.app.ActivityCompat;import android.support.v4.content.ContextCompat;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.Button;import android.widget.ProgressBar;import android.widget.TextView;import android.widget.Toast;import android.widget.VideoView;import fm.jiecao.jcvideoplayer_lib.JCVideoPlayer;import fm.jiecao.jcvideoplayer_lib.JCVideoPlayerStandard;public class MainActivity extends AppCompatActivity {    private static final int PERMISSION_REQUEST_CODE = 001;    Button btn_download2;    TextView tv_progress2;    ProgressBar  pb_progress2;    DownloadManager mDownloadManager;    String Url = "http://pic.ibaotu.com/00/34/48/06n888piCANy.mp4";    private VideoView view;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initViews();        initDownloads();    }    @Override    public void onBackPressed() {        if (JCVideoPlayer.backPress()) {            return;        }        super.onBackPressed();    }    @Override    protected void onPause() {        super.onPause();        JCVideoPlayer.releaseAllVideos();    }    private void initDownloads() {        mDownloadManager = DownloadManager.getInstance();        mDownloadManager.add(Url, new DownloadListner() {            @Override            public void onFinished() {                Toast.makeText(MainActivity.this, "下载完成!", Toast.LENGTH_SHORT).show();                JCVideoPlayerStandard jc= (JCVideoPlayerStandard) findViewById(R.id.video_view);                jc.setUp("http://pic.ibaotu.com/00/34/48/06n888piCANy.mp4",                        JCVideoPlayerStandard.SCREEN_LAYOUT_NORMAL);            }            @Override            public void onProgress(float progress) {                pb_progress2.setProgress((int) (progress * 100));                tv_progress2.setText(String.format("%.2f", progress * 100) + "%");            }            @Override            public void onPause() {                Toast.makeText(MainActivity.this, "暂停了!", Toast.LENGTH_SHORT).show();            }            @Override            public void onCancel() {                tv_progress2.setText("0%");                pb_progress2.setProgress(0);                btn_download2.setText("下载");                Toast.makeText(MainActivity.this, "下载已取消!", Toast.LENGTH_SHORT).show();            }        });    }    /**     * 初始化View控件     */    private void initViews() {        tv_progress2 = (TextView) findViewById(R.id.tv_progress2);        pb_progress2 = (ProgressBar) findViewById(R.id.pb_progress2);        btn_download2 = (Button) findViewById(R.id.btn_download2);    }    /**     * 下载或暂停下载     */    public void downloadOrPause(View view) {        if (!mDownloadManager.isDownloading(Url)) {            mDownloadManager.download(Url);            btn_download2.setText("暂停");        } else {            btn_download2.setText("下载");            mDownloadManager.pause(Url);        }    }    /**     * 取消下载     */    public void cancel(View view) {        switch (view.getId()) {            case R.id.btn_cancel2:                mDownloadManager.cancel(Url);                break;        }    }    public void cancelAll(View view) {        btn_download2.setText("下载");    }    @Override    protected void onStart() {        super.onStart();        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {            return;        }        String permission = Manifest.permission.WRITE_EXTERNAL_STORAGE;        if (!checkPermission(permission)) {//针对android6.0动态检测申请权限            if (shouldShowRationale(permission)) {                showMessage("需要权限跑demo哦...");            }            ActivityCompat.requestPermissions(this, new String[]{permission}, PERMISSION_REQUEST_CODE);        }    }    /**     * 显示提示消息     */    private void showMessage(String msg) {        Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();    }    /**     * 检测用户权限     */    protected boolean checkPermission(String permission) {        return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED;    }    /**     * 是否需要显示请求权限的理由     */    protected boolean shouldShowRationale(String permission) {        return ActivityCompat.shouldShowRequestPermissionRationale(this, permission);    }}


原创粉丝点击