多线程下载 断点续传
来源:互联网 发布: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); }}
阅读全文
0 0
- 多线程断点续传后台下载
- 多线程断点续传后台下载
- android 多线程断点续传下载
- 多线程断点续传后台下载
- 多线程并行下载,断点续传
- Android多线程.断点续传下载
- android 多线程断点续传下载
- Android 多线程下载断点续传
- Java多线程断点续传下载
- 多线程断点续传后台下载
- Android多线程断点续传下载
- 多线程断点续传下载Demo
- 多线程下载 断点续传
- 多线程断点续传下载
- 多线程断点续传下载。
- Android多线程断点续传下载
- JAVA多线程断点续传下载
- android多线程断点续传下载
- Oracle数据库迁移
- JDBC连接MySQL数据库
- #算法# 递归法找多元素
- Guava LoadingCache使用记录
- 为什么经理、总监多是空降, 而不是从内部提拔?
- 多线程下载 断点续传
- C++启动其它exe程序的代码
- java并发编程实战:对象的共享笔记
- 求一个整数的平方根
- PDF里面复制出来的文章,在word里去掉回车符
- tcpdump
- mysql分布式数据库中间件对比
- 安全性测试-跨站脚本攻击测试方法和修改方法
- svn--2.安装