android 单线程死循环断点续传下载管理类

本文介绍了一个Android单线程下载管理类,实现无限循环下载,支持断点续传,不会因错误自动停止,除非手动停止。需要网络和读写权限。提供了暂停、继续、停止功能,以及网络、空间错误处理,并通过回调更新进度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

package com.leelen.test;


import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;


import org.json.JSONException;
import org.json.JSONObject;


import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Environment;
import android.os.Handler;
import android.os.StatFs;
import android.text.TextUtils;


/**
 * 注:无限循环下载,不因报错而停止,除非手动停止。
 * 需要网络权限和读写权限 
 * <uses-permission android:name="android.permission.INTERNET" />
 * <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 * <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 */
public class DownloaderManager {


private static final String TAG = "DownloaderManager";

private static final String KEY_SP = "key_downloader";


private Context mContext = null;


private class DownloadFileInfo {
String mDownloadUrl = "";
String mSavePath = "";
int mFileLength = 0;
int mDownloadSize = 0;
}


private DownloadFileInfo mDFI = null;
private SharedPreferences mPreferences = null;


private boolean isPause = false;
private boolean isStop = false;
private boolean isWifiOnly = true;


private Handler mHandler = new Handler();


/**
* 因无限循环,所以以下大部分回调都可能多次调用。
*/
public interface OnDownloadListener {
void onEnter();


void onProgress(int fileLength, int downloadSize, float percent);

void onPause();

void onIOError();


void onNetworkError();

void onSpaceError();


void onExit(boolean isFinish);
}


private OnDownloadListener mListener = null;


/**
* 创建后即开始下载,不主动调用结束,就一直在下载
* 网络、空间出错时,会通过回调上报
* 注意:不因为出错而结束,如果想结束,要主动调用stop

* @param context
* @param listener
*            回调侦听,显示错误及进度等
* @param downloadUrl
*            下载地址
* @param savePath
*            文件存储全路径,包括路径和文件名(请保证路径存在,本程序不负责创建路径,只负责创建下载的文件)
*/
public DownloaderManager(Context context, OnDownloadListener listener, 
String downloadUrl, String savePath) {
mContext = context;
mListener = listener;
MyLogI("downladUrl = " + downloadUrl);
MyLogI("savePath = " + savePath);
if (TextUtils.isEmpty(downloadUrl)) {
MyLogE("empty url");
onListenerExit(false);
return;
}
if (TextUtils.isEmpty(savePath)) {
MyLogE("empty savePath");
onListenerExit(false);
return;
}
getInfo(downloadUrl, savePath);


new Thread(new Runnable() {


@Override
public void run() {
try { // 延迟开启下载
Thread.sleep(100);
} catch (InterruptedException e) {
}
doDownload();
}
}).start();
}


// 继续
public void resume() {
isPause = false;
}


// 暂停
public void pause() {
isPause = true;
}


// 判断是否暂停
public boolean isPause() {
return isPause;
}


// 停止
public void stop() {
isStop = true;
}


// 判断是否停止
public boolean isStop() {
return isStop;
}


// 设置数据网络可下载
public void setWifiOnly(boolean wifiOnly) {
isWifiOnly = wifiOnly;
}


// 是否只有wifi下可下载
public boolean isWifiOnly() {
return isWifiOnly;
}

// ------ 获取下载信息 0 ------
public String getDownloadUrl() {
if (mDFI != null) {
return mDFI.mDownloadUrl;
}
return "";
}

public String getSavePath() {
if (mDFI != null) {
return mDFI.mSavePath;
}
return "";
}

public int getFileLength() {
if (mDFI != null) {
return mDFI.mFileLength;
}
return 0;
}

public int getDownloadSize() {
if (mDFI != null) {
return mDFI.mDownloadSize;
}
return 0;
}
// ------ 获取下载信息 1 ------


// 执行下载
private void doDownload() {
MyLogW("doDownload() enter");
onListenerEnter();
int count = 0;
int COUNT_MAX = 20;
InputStream is = null;
RandomAccessFile raf = null;
boolean isError = false;
while (true) {
try {
isError = false;
if (isStop) { // 结束下载
MyLogI("doDownload() isStop = " + isStop);
break;
}
// 判断是否暂停以及网络是否可用
if (!isPause && isNetworkAvailable()) {
// 打开连接
URLConnection urlConn = new URL(mDFI.mDownloadUrl).openConnection();
if (mDFI.mFileLength <= 0) { // 获取文件大小
mDFI.mFileLength = urlConn.getContentLength();
}
MyLogI("doDownload() mDFI.mFileLength = " + mDFI.mFileLength);
MyLogI("doDownload() mDFI.mDownloadSize = " + mDFI.mDownloadSize);
if (mDFI.mFileLength > 0 && isSpaceEnough()) {
File file = new File(mDFI.mSavePath);
if (file.exists()) { // 本地文件存在
MyLogI("doDownload() file.length() = " + file.length());
if (mDFI.mFileLength == file.length()) { // 已完成下载
mDFI.mDownloadSize = mDFI.mFileLength;
isStop = true;
MyLogW("doDownload() download complete!");
break;
} else { // 下载未完成
if (mDFI.mDownloadSize != file.length()) {
MyLogW("doDownload() file size error : delete");
file.delete(); // 文件大小不对,说明已被改变,直接删除重新下载
mDFI.mDownloadSize = 0;
}
}
}
is = urlConn.getInputStream();
raf = new RandomAccessFile(mDFI.mSavePath, "rw");
if (mDFI.mDownloadSize > 0) {
long byteCount = is.skip(mDFI.mDownloadSize);
MyLogE("doDownload() skip byteCount = " + byteCount + ", mDFI.mDownloadSize = " + mDFI.mDownloadSize);
if (byteCount == mDFI.mDownloadSize) {
raf.seek(mDFI.mDownloadSize);
} else {
isError = true;
}
}
if (!isError) {
int len = 0;
byte[] bytes = new byte[10240];
while (true) {
if (isStop) { // 结束下载
MyLogI("doDownload() isStop = " + isStop);
break;
}
isError = false;
// 判断网络是否可用
if (!isPause && !isNetworkAvailable()) {
isError = true;
}
// 判断空间是否足够
if (!isPause && !isSpaceEnough()) {
isError = true;
}
// 判断是否暂停或出错
if (!isPause && !isError) {
len = is.read(bytes);
if (len != (-1)) { // 未完成
raf.write(bytes, 0, len);
mDFI.mDownloadSize += len; // 改变下载大小
count ++;
if (count >= COUNT_MAX) {
count = 0;
float percent = ((int) ((mDFI.mDownloadSize / (float) mDFI.mFileLength) * 1000)) / (float) 10;
MyLogI("doDownload() download mDFI.mFileLength = " + mDFI.mFileLength
+ ", mDFI.mDownloadSize = " + mDFI.mDownloadSize
+ ", percent = " + percent);
onListenerProgress(mDFI.mFileLength, mDFI.mDownloadSize, percent);
}
} else {
if (mDFI.mDownloadSize == mDFI.mFileLength) { // 完成
isStop = true;
MyLogI("doDownload() download mDFI.mFileLength = " + mDFI.mFileLength
+ ", mDFI.mDownloadSize = " + mDFI.mDownloadSize);
MyLogW("doDownload() download complete now!");
onListenerProgress(mDFI.mFileLength, mDFI.mDownloadSize, 100);
} else { // 下载失败
MyLogE("doDownload() download mDFI.mFileLength = " + mDFI.mFileLength
+ ", mDFI.mDownloadSize = " + mDFI.mDownloadSize);
MyLogE("doDownload() error mDownloadSize != mFileLength");
mDFI.mDownloadSize = 0; // 设置重新下载
}
break;
}
} else if (isPause) {
MyLogW("doDownload() is pause");
onListenerPause();
}

if (!isStop) {
try { // 进入休眠,不能老占用资源
if (isPause || isError) {
Thread.sleep(3000); // 暂停或出错时,休眠时间长一些
} else { // 未出错时,休眠时间短些
Thread.sleep(10);
}
} catch (InterruptedException e) {
MyLogW("doDownload() InterruptedException");
}
}
}
}
}
} else if (isPause) {
MyLogW("doDownload() is pause");
onListenerPause();
}
} catch (IOException e) {
e.printStackTrace();
MyLogW("doDownload() IOException");
onListenerIOError();
} finally {
if (raf != null) {
try {
raf.close();
raf = null;
} catch (IOException e) {
e.printStackTrace();
MyLogW("doDownload() IOException");
onListenerIOError();
}
}
if (is != null) {
try {
is.close();
is = null;
} catch (IOException e) {
e.printStackTrace();
MyLogW("doDownload() IOException");
onListenerIOError();
}
}
}


if (!isStop) {
try { // 进入休眠,不能老占用资源
Thread.sleep(3000);
} catch (InterruptedException e) {
MyLogW("doDownload() InterruptedException");
}
}
}
saveInfo();
boolean isFinish = false;
if (mDFI != null) {
MyLogW("mDFI.mFileLength = " + mDFI.mFileLength + 
", mDFI.mDownloadSize = " + mDFI.mDownloadSize);
if (mDFI.mFileLength > 0 && mDFI.mFileLength == mDFI.mDownloadSize) {
isFinish = true;
}
}
MyLogW("doDownload() exit isFinish = " + isFinish);
onListenerExit(isFinish);
}


// MD5
private String getMd5(String s) {
if (TextUtils.isEmpty(s)) {
return s;
}
try {
// Create MD5 Hash
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(s.getBytes());
byte messageDigest[] = digest.digest();


// Create Hex String
StringBuffer hexString = new StringBuffer();
for (int i = 0, v = 0; i < messageDigest.length; i++) {
v = 0xFF & messageDigest[i];
if (v < 16) {
hexString.append("0");
}
hexString.append(Integer.toHexString(v));
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
}
return s;
}


// 读取上次下载信息
private void getInfo(String downloadUrl, String savePath) {
// 初始化下载信息
if (mDFI == null) {
mDFI = new DownloadFileInfo();
}
mDFI.mDownloadUrl = downloadUrl;
mDFI.mSavePath = savePath;
mDFI.mFileLength = 0;
mDFI.mDownloadSize = 0;


// 获取保存信息
if (mPreferences == null) {
mPreferences = mContext.getSharedPreferences(KEY_SP, Context.MODE_PRIVATE);
}
JSONObject json = new JSONObject();
try {
json.put("url", downloadUrl);
json.put("path", savePath);
String jsonStr = mPreferences.getString(getMd5(json.toString()), "");
if (!TextUtils.isEmpty(jsonStr)) {
json = new JSONObject(jsonStr);
mDFI.mDownloadUrl = json.getString("url");
mDFI.mSavePath = json.getString("path");
mDFI.mFileLength = json.getInt("length");
mDFI.mDownloadSize = json.getInt("size");
}
} catch (JSONException e) {
}
MyLogI("mDFI.mDownloadUrl = " + mDFI.mDownloadUrl);
MyLogI("mDFI.mSavePath = " + mDFI.mSavePath);
MyLogI("mDFI.mFileLength = " + mDFI.mFileLength);
MyLogI("mDFI.mDownloadSize = " + mDFI.mDownloadSize);
}


private void saveInfo() {
// 初始化下载信息
if (mDFI == null) {
return;
}
// 保存信息
if (mPreferences == null) {
mPreferences = mContext.getSharedPreferences(KEY_SP, Context.MODE_PRIVATE);
}
JSONObject json = new JSONObject();
try {
json.put("url", mDFI.mDownloadUrl);
json.put("path", mDFI.mSavePath);
String jsonStr = json.toString();
json.put("length", mDFI.mFileLength);
json.put("size", mDFI.mDownloadSize);
Editor editor = mPreferences.edit();
if (mDFI.mFileLength == mDFI.mDownloadSize) {
editor.putString(getMd5(jsonStr), "");
} else {
editor.putString(getMd5(jsonStr), json.toString());
}
editor.commit();
} catch (JSONException e) {
}
}


// 获取网络制式
private static final String WIFI = "WIFI";
private static final String MOBILE = "MOBILE";


private String getNetworkType() {
String strNetworkType = "";
try { // 获取网络信息
NetworkInfo networkInfo = ((ConnectivityManager) mContext
.getSystemService(Context.CONNECTIVITY_SERVICE))
.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) {
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
strNetworkType = WIFI; // WIFI
} else if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
strNetworkType = MOBILE; // 数据网络
}
}
} catch (Throwable e) {
}
return strNetworkType;
}


// 网络是否可用
private boolean isNetworkAvailable() {
boolean isAvailable = false;
String networkType = getNetworkType();
if (TextUtils.isEmpty(networkType)) { // 无网络
} else if (TextUtils.equals(networkType, WIFI)) { // WIFI
isAvailable = true;
} else if (TextUtils.equals(networkType, MOBILE)) { // 数据网络
if (!isWifiOnly) {
isAvailable = true;
} else { // 人为设置数据网络是否可用于下载
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + 
File.separator + "wifi_ex";
if (new File(path).exists()) {
isAvailable = true;
}
}
}
if (!isAvailable) {
MyLogW("isNetworkAvailable() isAvailable = " + isAvailable);
onListenerNetworkError();
}
return isAvailable;
}

// 获取空间大小
@SuppressWarnings("deprecation")
private long getVacantSpaceSize(String path) {
        StatFs fileStats = new StatFs(path); 
        fileStats.restat(path);
        long size = (long) fileStats.getAvailableBlocks() * (long) fileStats.getBlockSize();
        return size;
    }

// 空间富余量,即比要下载的文件大小多出多少空间,默认12MB
private long SPACE_MORE = 12 * 1024 * 1024;

// 设置空间富余量
public void setSpaceMore(long more) {
SPACE_MORE = more;
}

// 空间是否足够
private boolean isSpaceEnough() {
boolean isEnough = true;

// 获取sd卡剩余空间大小
long vacantSpace = getVacantSpaceSize(Environment.getExternalStorageDirectory()
.getAbsolutePath());
// 文件大小
int fileLength = 0;
// 完成下载大小
int downloadSize = 0;
// 初始化文件和下载大小
if (mDFI != null) {
fileLength = mDFI.mFileLength;
downloadSize = mDFI.mDownloadSize;
}
// 剩余空间比需要下载的文件大小多出富余大小以上,被认为是空间足够
if (fileLength + SPACE_MORE - downloadSize >= vacantSpace) {
isEnough = false;
}

// 人为设置空间是否足够
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + 
File.separator + "low_space";
if (new File(path).exists()) {
isEnough = false;
}

if (!isEnough) {
MyLogW("isSpaceEnough() false");
onListenerSpaceError();
}

return isEnough;
}


// ------ 侦听回调处理 0 ------
private void onListenerEnter() {
if (mListener != null) {
mHandler.post(new Runnable() {


@Override
public void run() {
if (mListener != null) {
mListener.onEnter();
}
}
});
}
}


private void onListenerProgress(final int fileLength, final int downloadSize, 
final float percent) {
if (mListener != null) {
mHandler.post(new Runnable() {


@Override
public void run() {
if (mListener != null) {
mListener.onProgress(fileLength, downloadSize, percent);
}
}
});
}
}


private void onListenerPause() {
if (mListener != null) {
mHandler.post(new Runnable() {

@Override
public void run() {
if (mListener != null) {
mListener.onPause();
}
}
});
}
}

private void onListenerIOError() {
if (mListener != null) {
mHandler.post(new Runnable() {


@Override
public void run() {
if (mListener != null) {
mListener.onIOError();
}
}
});
}
}


private void onListenerNetworkError() {
if (mListener != null) {
mHandler.post(new Runnable() {


@Override
public void run() {
if (mListener != null) {
mListener.onNetworkError();
}
}
});
}
}

private void onListenerSpaceError() {
if (mListener != null) {
mHandler.post(new Runnable() {

@Override
public void run() {
if (mListener != null) {
mListener.onSpaceError();
}
}
});
}
}


private void onListenerExit(final boolean isFinish) {
if (mListener != null) {
mHandler.post(new Runnable() {


@Override
public void run() {
if (mListener != null) {
mListener.onExit(isFinish);
}
}
});
}
}
// ------ 侦听回调处理 1 ------


// ------ 打印相关 0 ------
private void MyLogE(String info) {
android.util.Log.e(TAG, info);
}


private void MyLogW(String info) {
android.util.Log.w(TAG, info);
}


private boolean isInfoLog = false;


public void setIsInfoLog(boolean isLog) {
isInfoLog = isLog;
}


private void MyLogI(String info) {
if (isInfoLog) {
android.util.Log.i(TAG, info);
}
}
// ------ 打印相关 1 ------
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值