主页面
package com.example.zy_duandianxvchuan;
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 com.example.zy_duandianxvchuan.DownloadListner;
import com.example.zy_duandianxvchuan.DownloadManager;
import com.example.zy_duandianxvchuan.R;
/**
* Demo演示,临时写的Demo,难免有些bug
*
* @author Cheny
*/
public class MainActivity extends AppCompatActivity {
private static final int PERMISSION_REQUEST_CODE = 001;
TextView tv_file_name1, tv_progress1, tv_file_name2, tv_progress2,tv_file_name3,tv_progress3;
Button btn_download1, btn_download2,btn_download3, btn_download_all;
ProgressBar pb_progress1, pb_progress2,pb_progress3;
DownloadManager mDownloadManager;
String wechatUrl = "http://dldir1.qq.com/weixin/android/weixin657android1040.apk";
String qqUrl = "https://qd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apk";
String zfbUrl = "http://www.517dv.com/upload/travel.apk";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
initDownloads();
}
private void initDownloads() {
mDownloadManager = DownloadManager.getInstance();
mDownloadManager.add(wechatUrl, new DownloadListner() {
@Override
public void onFinished() {
Toast.makeText(MainActivity.this, "下载完成!", Toast.LENGTH_SHORT).show();
}
@Override
public void onProgress(float progress) {
pb_progress1.setProgress((int) (progress * 100));
tv_progress1.setText(String.format("%.2f", progress * 100) + "%");
}
@Override
public void onPause() {
Toast.makeText(MainActivity.this, "暂停了!", Toast.LENGTH_SHORT).show();
}
@Override
public void onCancel() {
tv_progress1.setText("0%");
pb_progress1.setProgress(0);
btn_download1.setText("下载");
Toast.makeText(MainActivity.this, "下载已取消!", Toast.LENGTH_SHORT).show();
}
});
mDownloadManager.add(qqUrl, new DownloadListner() {
@Override
public void onFinished() {
Toast.makeText(MainActivity.this, "下载完成!", Toast.LENGTH_SHORT).show();
}
@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();
}
});
mDownloadManager.add(zfbUrl, new DownloadListner() {
@Override
public void onFinished() {
Toast.makeText(MainActivity.this, "下载完成!", Toast.LENGTH_SHORT).show();
}
@Override
public void onProgress(float progress) {
pb_progress3.setProgress((int) (progress * 100));
tv_progress3.setText(String.format("%.2f", progress * 100) + "%");
}
@Override
public void onPause() {
Toast.makeText(MainActivity.this, "暂停了!", Toast.LENGTH_SHORT).show();
}
@Override
public void onCancel() {
tv_progress3.setText("0%");
pb_progress3.setProgress(0);
btn_download3.setText("下载");
Toast.makeText(MainActivity.this, "下载已取消!", Toast.LENGTH_SHORT).show();
}
});
}
/**
* 初始化View控件
*/
private void initViews() {
tv_file_name1 = (TextView) findViewById(R.id.tv_file_name1);
tv_progress1 = (TextView) findViewById(R.id.tv_progress1);
pb_progress1 = (ProgressBar) findViewById(R.id.pb_progress1);
btn_download1 = (Button) findViewById(R.id.btn_download1);
tv_file_name1.setText("微信");
tv_file_name2 = (TextView) findViewById(R.id.tv_file_name2);
tv_progress2 = (TextView) findViewById(R.id.tv_progress2);
pb_progress2 = (ProgressBar) findViewById(R.id.pb_progress2);
btn_download2 = (Button) findViewById(R.id.btn_download2);
tv_file_name2.setText("qq");
tv_file_name3 = (TextView) findViewById(R.id.tv_file_name3);
tv_progress3 = (TextView) findViewById(R.id.tv_progress3);
pb_progress3 = (ProgressBar) findViewById(R.id.pb_progress3);
btn_download3 = (Button) findViewById(R.id.btn_download3);
tv_file_name3.setText("trvael.apk");
btn_download_all = (Button) findViewById(R.id.btn_download_all);
}
/**
* 下载或暂停下载
*
* @param view
*/
public void downloadOrPause(View view) {
switch (view.getId()) {
case R.id.btn_download1:
if (!mDownloadManager.isDownloading(wechatUrl)) {
mDownloadManager.download(wechatUrl);
btn_download1.setText("暂停");
} else {
btn_download1.setText("下载");
mDownloadManager.pause(wechatUrl);
}
break;
case R.id.btn_download2:
if (!mDownloadManager.isDownloading(qqUrl)) {
mDownloadManager.download(qqUrl);
btn_download2.setText("暂停");
} else {
btn_download2.setText("下载");
mDownloadManager.pause(qqUrl);
}
break;
case R.id.btn_download3:
if (!mDownloadManager.isDownloading(zfbUrl)) {
mDownloadManager.download(zfbUrl);
btn_download3.setText("暂停");
} else {
btn_download3.setText("下载");
mDownloadManager.pause(zfbUrl);
}
break;
}
}
public void downloadOrPauseAll(View view) {
if (!mDownloadManager.isDownloading(wechatUrl, qqUrl,zfbUrl)) {
btn_download1.setText("暂停");
btn_download2.setText("暂停");
btn_download3.setText("暂停");
btn_download_all.setText("全部暂停");
mDownloadManager.download(wechatUrl, qqUrl,zfbUrl);//最好传入个String[]数组进去
} else {
mDownloadManager.pause(wechatUrl, qqUrl,zfbUrl);
btn_download1.setText("下载");
btn_download2.setText("下载");
btn_download3.setText("下载");
btn_download_all.setText("全部下载");
}
}
/**
* 取消下载
*
* @param view
*/
public void cancel(View view) {
switch (view.getId()) {
case R.id.btn_cancel1:
mDownloadManager.cancel(wechatUrl);
break;
case R.id.btn_cancel2:
mDownloadManager.cancel(qqUrl);
break;
case R.id.btn_cancel3:
mDownloadManager.cancel(zfbUrl);
break;
}
}
public void cancelAll(View view) {
mDownloadManager.cancel(wechatUrl, qqUrl, zfbUrl);
btn_download1.setText("下载");
btn_download2.setText("下载");
btn_download3.setText("下载");
btn_download_all.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);
}
}
/**
* 显示提示消息
*
* @param msg
*/
private void showMessage(String msg) {
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
}
/**
* 检测用户权限
*
* @param permission
* @return
*/
protected boolean checkPermission(String permission) {
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED;
}
/**
* 是否需要显示请求权限的理由
*
* @param permission
* @return
*/
protected boolean shouldShowRationale(String permission) {
return ActivityCompat.shouldShowRequestPermissionRationale(this, permission);
}
}
ok封装
package com.example.zy_duandianxvchuan;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* Http网络工具,基于OkHttp
* Created by Cheny on 2017/04/29.
*/
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();
}
}
FilePoint
package com.example.zy_duandianxvchuan;
/**
* Created by Cheny on 2017/4/29.
*/
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;
}
}
操作类
DownloadTask
package com.example.zy_duandianxvchuan;
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;
/**
* Created by Cheny on 2017/4/29.
*/
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;
}
}
DownloadManager多线程下载
package com.example.zy_duandianxvchuan;
import android.os.Environment;
import android.text.TextUtils;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
/**
* 下载管理器,断点续传
*
* @author Cheny
*/
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;
}
}
DownloadListner
package com.example.zy_duandianxvchuan;
/**
* 下载监听
*
* @author Cheny
*/
public interface DownloadListner {
void onFinished();
void onProgress(float progress);
void onPause();
void onCancel();
}