原理:
1、将一个任务等分成几个小部分
2、开多个线程每个负责一个,记录每个线程其开始位置和结束位置
3、分别写入同一个文件,也是按起开始位置和结束位置写入。同时在下载时创建临时文件记录这次下载到哪儿
4、下载前看是否有上次记录,有则接着从后面下载
public class MainActivity extends AppCompatActivity {
@BindView(R.id.btnDownload)
Button mBtnDownload;
@BindView(R.id.pbDownload)
ProgressBar mPbDownload;
@BindView(R.id.tvDownload)
TextView mTvDownload;
private static final int DOWNLOAD_SUCCESS = 0;
private static final int DOWNLOAD_FAILURE = 1;
private static final int DOWNLOADING = 2;
private static final String DOWNLOAD_URL = "xxxxx";
private static final String DOWNLOAD_PATH = Environment.getExternalStorageDirectory().getPath();
@BindView(R.id.btnStop)
Button mBtnStop;
private int threadCount = 3; //线程数量
private PermissionListener mListener;
private int downloadProgress = 0;
private long finishedThread = 0;
private volatile boolean mStopDownload = false;
private Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case DOWNLOAD_SUCCESS:
mPbDownload.setProgress(mPbDownload.getMax());
mTvDownload.setText("下载完成");
break;
case DOWNLOADING:
Log.e("MainActivity", (long) mPbDownload.getProgress() * 100 / mPbDownload.getMax() + "%");
mTvDownload.setText((long) mPbDownload.getProgress() * 100 / mPbDownload.getMax() + "%");
break;
case DOWNLOAD_FAILURE:
mTvDownload.setText("下载失败");
break;
default:
break;
}
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mBtnDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mStopDownload = false;
requestPermission(new String[]{}, new PermissionListener() {
@Override
public void onGranted() {
new Thread(new Runnable() {
@Override
public void run() {
try {
URL url = new URL(DOWNLOAD_URL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET"); //设置请求模式
connection.setConnectTimeout(5000); //设置连接超时
connection.setReadTimeout(5000); //设置读取超时
//完整下载 code:200 部分下载 code:206
if (connection.getResponseCode() == 200) {
int length = connection.getContentLength(); //获得下载文件大小
//创建临时文件
File file = new File(DOWNLOAD_PATH, getName(DOWNLOAD_URL));
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//临时文件占位,防止下载到一半空间不足
raf.setLength(length);
raf.close();
//设置最大进度条
mPbDownload.setMax(length);
//计算每个线程下载区间
int size = length / threadCount;
for (int i = 0; i < threadCount; i++) {
int startIndex = i * size; //开始下载位置
int endIndex = (i + 1) * size - 1; //结束下载位置
if (i == threadCount - 1) {
endIndex = length - 1;
}
Log.e("MainActivity", "线程:" + i + ",下载区间:" + startIndex + "~" + endIndex);
new MyDownload(i, startIndex, endIndex).start();
}
}
} catch (Exception e) {
e.printStackTrace();
mHandler.sendEmptyMessage(DOWNLOAD_FAILURE);
}
}
}).start();
}
@Override
public void onDenied(List<String> deniedPermission) {
Toast.makeText(MainActivity.this, "need Permission", Toast.LENGTH_SHORT).show();
}
});
}
});
mBtnStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mStopDownload = true;
}
});
}
public static String getName(String path) {
int index = path.lastIndexOf("/");
return path.substring(index + 1);
}
/**
* 权限请求
*
* @param permissions
* @param listener
*/
public void requestPermission(String[] permissions, PermissionListener listener) {
mListener = listener;
List<String> permissionList = new ArrayList<>(); //权限集合
for (String permission : permissions) {
if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
permissionList.add(permission);
}
}
if (permissionList.isEmpty()) {
listener.onGranted();
} else {
ActivityCompat.requestPermissions(this, permissionList.toArray(new String[permissionList.size()]), 1);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0) {
List<String> deniedPermission = new ArrayList<>();
for (int i = 0; i < grantResults.length; i++) {
int grantResult = grantResults[i]; //授权结果
String permission = permissions[i]; //授权名称
if (grantResult != PackageManager.PERMISSION_GRANTED) {
deniedPermission.add(permission);
}
if (deniedPermission.isEmpty()) {
mListener.onGranted();
} else {
mListener.onDenied(deniedPermission);
}
}
}
break;
default:
break;
}
}
/**
* 下载线程
*/
class MyDownload extends Thread {
private int threadId;
private int startIndex;
private int endIndex;
public MyDownload(int threadId, int startIndex, int endIndex) {
super();
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public void run() {
super.run();
try {
File fileProgress = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");
int lastProgress = 0;
if (fileProgress.exists()) {
//读取进度临时文件中的内容
FileInputStream fis = new FileInputStream(fileProgress);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
//得到上一次下载进度
lastProgress = Integer.parseInt(br.readLine());
//改变下载的开始位置,上一次下过的,这次就不请求了
startIndex += lastProgress;
fis.close();
//把上一次下载进度加到进度条进度中
downloadProgress += lastProgress;
//发送消息,让文本进度条改变
mHandler.sendEmptyMessage(DOWNLOADING);
}
URL url = new URL(DOWNLOAD_URL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET"); //设置请求模式
connection.setConnectTimeout(5000); //设置连接超时
connection.setReadTimeout(5000); //设置读取超时
//设置请求数据的区间 Range:区间
connection.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
//完整下载 code:200 部分下载 code:206
if (connection.getResponseCode() == 206) {
InputStream is = connection.getInputStream();
byte[] buffer = new byte[1024];
int len = 0;
//当前线程下载的总进度
int total = lastProgress;
File file = new File(DOWNLOAD_PATH, MainActivity.getName(DOWNLOAD_URL));
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//设置写入的开始位置
raf.seek(startIndex);
while ((len = is.read(buffer)) != -1) {
if (mStopDownload) {
break;
}
raf.write(buffer, 0, len);
total += len;
// Log.e("MyDownload", "线程:" + threadId + ",下载了:" + total);
//创建临时文件保存下载进度
RandomAccessFile rafProgress = new RandomAccessFile(fileProgress, "rwd");
rafProgress.write((total + "").getBytes());
rafProgress.close();
//每次下载len个长度的字节,马上把len加到下载进度中,让进度条能反应这len个长度的下载进度
downloadProgress += len;
mPbDownload.setProgress(downloadProgress);
mHandler.sendEmptyMessage(DOWNLOADING);
}
raf.close();
//3条线程全部下载完毕,才去删除进度临时文件
finishedThread++;
synchronized (DOWNLOAD_URL) {
if (finishedThread == threadCount) {
//断点下载可能会造成数据记录不准确,依靠线程结束来刷新界面
mHandler.sendEmptyMessage(DOWNLOAD_SUCCESS);
for (int i = 0; i < threadCount; i++) {
File f = new File(Environment.getExternalStorageDirectory(), i + ".txt");
f.delete();
}
finishedThread = 0;
}
}
// //判断是否用户手动暂停
// if (!mStopDownload) {
// //3条线程全部下载完毕,才去删除进度临时文件
// finishedThread++;
//
// synchronized (DOWNLOAD_URL) {
// if (finishedThread == threadCount) {
// //断点下载可能会造成数据记录不准确,依靠线程结束来刷新界面
// mHandler.sendEmptyMessage(DOWNLOAD_SUCCESS);
// for (int i = 0; i < threadCount; i++) {
// File f = new File(Environment.getExternalStorageDirectory(), i + ".txt");
// f.delete();
// }
// finishedThread = 0;
// }
// }
// }
}
} catch (Exception e) {
e.printStackTrace();
mHandler.sendEmptyMessage(DOWNLOAD_FAILURE);
}
}
}
}
另外推荐使用三方多线程下载框架:xUtils3
https://github.com/wyouflf/xUtils
导入以下代码
package com.leixiansheng.test.download;
import com.lidroid.xutils.db.annotation.Transient;
import com.lidroid.xutils.http.HttpHandler;
import java.io.File;
/**
* Author: wyouflf
* Date: 13-11-10
* Time: 下午8:11
*/
public class DownloadInfo {
public DownloadInfo() {
}
private long id;
@Transient
private HttpHandler<File> handler;
private HttpHandler.State state;
private String downloadUrl;
private String fileName;
private String fileSavePath;
private long progress;
private long fileLength;
private boolean autoResume;
private boolean autoRename;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public HttpHandler<File> getHandler() {
return handler;
}
public void setHandler(HttpHandler<File> handler) {
this.handler = handler;
}
public HttpHandler.State getState() {
return state;
}
public void setState(HttpHandler.State state) {
this.state = state;
}
public String getDownloadUrl() {
return downloadUrl;
}
public void setDownloadUrl(String downloadUrl) {
this.downloadUrl = downloadUrl;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFileSavePath() {
return fileSavePath;
}
public void setFileSavePath(String fileSavePath) {
this.fileSavePath = fileSavePath;
}
public long getProgress() {
return progress;
}
public void setProgress(long progress) {
this.progress = progress;
}
public long getFileLength() {
return fileLength;
}
public void setFileLength(long fileLength) {
this.fileLength = fileLength;
}
public boolean isAutoResume() {
return autoResume;
}
public void setAutoResume(boolean autoResume) {
this.autoResume = autoResume;
}
public boolean isAutoRename() {
return autoRename;
}
public void setAutoRename(boolean autoRename) {
this.autoRename = autoRename;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DownloadInfo)) return false;
DownloadInfo that = (DownloadInfo) o;
if (id != that.id) return false;
return true;
}
@Override
public int hashCode() {
return (int) (id ^ (id >>> 32));
}
}
package com.leixiansheng.test.download;
import android.content.Context;
import android.database.Cursor;
import com.lidroid.xutils.DbUtils;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.db.converter.ColumnConverter;
import com.lidroid.xutils.db.converter.ColumnConverterFactory;
import com.lidroid.xutils.db.sqlite.ColumnDbType;
import com.lidroid.xutils.db.sqlite.Selector;
import com.lidroid.xutils.exception.DbException;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.HttpHandler;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.util.LogUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Author: wyouflf
* Date: 13-11-10
* Time: 下午8:10
*/
public class DownloadManager {
private List<DownloadInfo> downloadInfoList;
private int maxDownloadThread = 3;
private Context mContext;
private DbUtils db;
/*package*/ DownloadManager(Context appContext) {
ColumnConverterFactory.registerColumnConverter(HttpHandler.State.class, new HttpHandlerStateConverter());
mContext = appContext;
db = DbUtils.create(mContext);
try {
downloadInfoList = db.findAll(Selector.from(DownloadInfo.class));
} catch (DbException e) {
LogUtils.e(e.getMessage(), e);
}
if (downloadInfoList == null) {
downloadInfoList = new ArrayList<DownloadInfo>();
}
}
public int getDownloadInfoListCount() {
return downloadInfoList.size();
}
public DownloadInfo getDownloadInfo(int index) {
return downloadInfoList.get(index);
}
public void addNewDownload(String url, String fileName, String target,
boolean autoResume, boolean autoRename,
final RequestCallBack<File> callback) throws DbException {
final DownloadInfo downloadInfo = new DownloadInfo();
downloadInfo.setDownloadUrl(url);
downloadInfo.setAutoRename(autoRename);
downloadInfo.setAutoResume(autoResume);
downloadInfo.setFileName(fileName);
downloadInfo.setFileSavePath(target);
HttpUtils http = new HttpUtils();
http.configRequestThreadPoolSize(maxDownloadThread);
HttpHandler<File> handler = http.download(url, target, autoResume, autoRename, new ManagerCallBack(downloadInfo, callback));
downloadInfo.setHandler(handler);
downloadInfo.setState(handler.getState());
downloadInfoList.add(downloadInfo);
db.saveBindingId(downloadInfo);
}
public void resumeDownload(int index, final RequestCallBack<File> callback) throws DbException {
final DownloadInfo downloadInfo = downloadInfoList.get(index);
resumeDownload(downloadInfo, callback);
}
public void resumeDownload(DownloadInfo downloadInfo, final RequestCallBack<File> callback) throws DbException {
HttpUtils http = new HttpUtils();
http.configRequestThreadPoolSize(maxDownloadThread);
HttpHandler<File> handler = http.download(
downloadInfo.getDownloadUrl(),
downloadInfo.getFileSavePath(),
downloadInfo.isAutoResume(),
downloadInfo.isAutoRename(),
new ManagerCallBack(downloadInfo, callback));
downloadInfo.setHandler(handler);
downloadInfo.setState(handler.getState());
db.saveOrUpdate(downloadInfo);
}
public void removeDownload(int index) throws DbException {
DownloadInfo downloadInfo = downloadInfoList.get(index);
removeDownload(downloadInfo);
}
public void removeDownload(DownloadInfo downloadInfo) throws DbException {
HttpHandler<File> handler = downloadInfo.getHandler();
if (handler != null && !handler.isCancelled()) {
handler.cancel();
}
downloadInfoList.remove(downloadInfo);
db.delete(downloadInfo);
}
public void stopDownload(int index) throws DbException {
DownloadInfo downloadInfo = downloadInfoList.get(index);
stopDownload(downloadInfo);
}
public void stopDownload(DownloadInfo downloadInfo) throws DbException {
HttpHandler<File> handler = downloadInfo.getHandler();
if (handler != null && !handler.isCancelled()) {
handler.cancel();
} else {
downloadInfo.setState(HttpHandler.State.CANCELLED);
}
db.saveOrUpdate(downloadInfo);
}
public void stopAllDownload() throws DbException {
for (DownloadInfo downloadInfo : downloadInfoList) {
HttpHandler<File> handler = downloadInfo.getHandler();
if (handler != null && !handler.isCancelled()) {
handler.cancel();
} else {
downloadInfo.setState(HttpHandler.State.CANCELLED);
}
}
db.saveOrUpdateAll(downloadInfoList);
}
public void backupDownloadInfoList() throws DbException {
for (DownloadInfo downloadInfo : downloadInfoList) {
HttpHandler<File> handler = downloadInfo.getHandler();
if (handler != null) {
downloadInfo.setState(handler.getState());
}
}
db.saveOrUpdateAll(downloadInfoList);
}
public int getMaxDownloadThread() {
return maxDownloadThread;
}
public void setMaxDownloadThread(int maxDownloadThread) {
this.maxDownloadThread = maxDownloadThread;
}
public class ManagerCallBack extends RequestCallBack<File> {
private DownloadInfo downloadInfo;
private RequestCallBack<File> baseCallBack;
public RequestCallBack<File> getBaseCallBack() {
return baseCallBack;
}
public void setBaseCallBack(RequestCallBack<File> baseCallBack) {
this.baseCallBack = baseCallBack;
}
private ManagerCallBack(DownloadInfo downloadInfo, RequestCallBack<File> baseCallBack) {
this.baseCallBack = baseCallBack;
this.downloadInfo = downloadInfo;
}
@Override
public Object getUserTag() {
if (baseCallBack == null) return null;
return baseCallBack.getUserTag();
}
@Override
public void setUserTag(Object userTag) {
if (baseCallBack == null) return;
baseCallBack.setUserTag(userTag);
}
@Override
public void onStart() {
HttpHandler<File> handler = downloadInfo.getHandler();
if (handler != null) {
downloadInfo.setState(handler.getState());
}
try {
db.saveOrUpdate(downloadInfo);
} catch (DbException e) {
LogUtils.e(e.getMessage(), e);
}
if (baseCallBack != null) {
baseCallBack.onStart();
}
}
@Override
public void onCancelled() {
HttpHandler<File> handler = downloadInfo.getHandler();
if (handler != null) {
downloadInfo.setState(handler.getState());
}
try {
db.saveOrUpdate(downloadInfo);
} catch (DbException e) {
LogUtils.e(e.getMessage(), e);
}
if (baseCallBack != null) {
baseCallBack.onCancelled();
}
}
@Override
public void onLoading(long total, long current, boolean isUploading) {
HttpHandler<File> handler = downloadInfo.getHandler();
if (handler != null) {
downloadInfo.setState(handler.getState());
}
downloadInfo.setFileLength(total);
downloadInfo.setProgress(current);
try {
db.saveOrUpdate(downloadInfo);
} catch (DbException e) {
LogUtils.e(e.getMessage(), e);
}
if (baseCallBack != null) {
baseCallBack.onLoading(total, current, isUploading);
}
}
@Override
public void onSuccess(ResponseInfo<File> responseInfo) {
HttpHandler<File> handler = downloadInfo.getHandler();
if (handler != null) {
downloadInfo.setState(handler.getState());
}
try {
db.saveOrUpdate(downloadInfo);
} catch (DbException e) {
LogUtils.e(e.getMessage(), e);
}
if (baseCallBack != null) {
baseCallBack.onSuccess(responseInfo);
}
}
@Override
public void onFailure(HttpException error, String msg) {
HttpHandler<File> handler = downloadInfo.getHandler();
if (handler != null) {
downloadInfo.setState(handler.getState());
}
try {
db.saveOrUpdate(downloadInfo);
} catch (DbException e) {
LogUtils.e(e.getMessage(), e);
}
if (baseCallBack != null) {
baseCallBack.onFailure(error, msg);
}
}
}
private class HttpHandlerStateConverter implements ColumnConverter<HttpHandler.State> {
@Override
public HttpHandler.State getFieldValue(Cursor cursor, int index) {
return HttpHandler.State.valueOf(cursor.getInt(index));
}
@Override
public HttpHandler.State getFieldValue(String fieldStringValue) {
if (fieldStringValue == null) return null;
return HttpHandler.State.valueOf(fieldStringValue);
}
@Override
public Object fieldValue2ColumnValue(HttpHandler.State fieldValue) {
return fieldValue.value();
}
@Override
public ColumnDbType getColumnDbType() {
return ColumnDbType.INTEGER;
}
}
}
package com.leixiansheng.test.download;
import android.app.ActivityManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import com.lidroid.xutils.exception.DbException;
import com.lidroid.xutils.util.LogUtils;
import java.util.List;
/**
* Author: wyouflf
* Date: 13-11-10
* Time: 上午1:04
*/
public class DownloadService extends Service {
private static DownloadManager DOWNLOAD_MANAGER;
public static DownloadManager getDownloadManager(Context appContext) {
if (!DownloadService.isServiceRunning(appContext)) {
Intent downloadSvr = new Intent(appContext, DownloadService.class);
appContext.startService(downloadSvr);
}
if (DownloadService.DOWNLOAD_MANAGER == null) {
DownloadService.DOWNLOAD_MANAGER = new DownloadManager(appContext);
}
return DOWNLOAD_MANAGER;
}
public DownloadService() {
super();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
}
@Override
public void onDestroy() {
if (DOWNLOAD_MANAGER != null) {
try {
DOWNLOAD_MANAGER.stopAllDownload();
DOWNLOAD_MANAGER.backupDownloadInfoList();
} catch (DbException e) {
LogUtils.e(e.getMessage(), e);
}
}
super.onDestroy();
}
public static boolean isServiceRunning(Context context) {
boolean isRunning = false;
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> serviceList
= activityManager.getRunningServices(Integer.MAX_VALUE);
if (serviceList == null || serviceList.size() == 0) {
return false;
}
for (int i = 0; i < serviceList.size(); i++) {
if (serviceList.get(i).service.getClassName().equals(DownloadService.class.getName())) {
isRunning = true;
break;
}
}
return isRunning;
}
}
在需要的地方按照以下代码调用即可:
private static final String DOWNLOAD_URL = "http://mys-src.oss-cn-shanghai.aliyuncs.com/source/mys.apk";
private DownloadManager downloadManager;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
downloadManager = DownloadService.getDownloadManager(getApplicationContext());
mBtnDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String target = "/sdcard/test/" + "test.apk";
try {
downloadManager.addNewDownload(DOWNLOAD_URL,
"mys", //
target, //保存路径
true, // 如果目标文件存在,接着未完成的部分继续下载。服务器不支持RANGE时将从新下载。
false, // 如果从请求返回信息中获取到文件名,下载完成后自动重命名。
new RequestCallBack<File>() {
@Override
public void onSuccess(ResponseInfo<File> responseInfo) {
mTvDownload.setText("下载完成");
Log.e("SecondActivity", responseInfo.result.getPath());
}
@Override
public void onFailure(HttpException e, String s) {
mTvDownload.setText("下载失败:" + s);
}
@Override
public void onLoading(long total, long current, boolean isUploading) {
super.onLoading(total, current, isUploading);
mTvDownload.setText((current * 100) / total + "%");
}
@Override
public void onStart() {
mTvDownload.setText("等待下载···");
}
});
} catch (DbException e) {
LogUtils.e(e.getMessage(), e);
}
}
});
mBtnStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
int index = downloadManager.getDownloadInfoListCount();
downloadManager.stopDownload(index-1);
} catch (DbException e) {
e.printStackTrace();
}
}
});
}