Android原生实现多线程断点下载
Posted 单灿灿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android原生实现多线程断点下载相关的知识,希望对你有一定的参考价值。
各位父老乡亲,我单汉三又回来了,今天为大家带来一个用原生的安卓写的下载Demo。
通过本文你可以学习到:
- SQLite的基本使用,数据库的增删改查。
- Handler的消息处理与更新UI(你也可以看我的这篇文章)。
- Service(主要用于下载)的进阶与使用。
- 原生的json文件解析(多层嵌套)。
- RandomAccessFile的基本使用,可以将文件分段。
- 基于HttpURLConnection的大文件下载。
- 上面内容结合,实现多线程,断点下载。
Demo是在TV上运行的,图片显示的问题不要纠结了。
文件下载的Demo已完成,没时间上传与讲解,今天为您展示并讲解一下,纯原生的东西来下载文件,希望可以帮你理解更多安卓比较基础的问题。
我们的思路:建立一个数据库,两个表,一个用来保存网络数据,一个保存本地下载的进度等等。在点击下载按钮的时候启动DownloadService,在Service中开启线程进行下载文件,文件采用RandomAccessFile,可进行多线程分段下载,如不是第一次下载,比对之后进行断点下载。
先看一下Demo的目录结构:
所有的步骤在代码里有非常详细的讲解,一定要看代码(下面是抽取的几个重要的类讲解)!
数据库的建立与DAO
DownLoadDBHelper 创建数据库 数据库更新升级,检测到版本的变化,发现版本号不一样,就会自动调用onUpgrade函数。
/**
* Created by Administrator on 2017/3/6 0006.
*/
public class DownLoadDBHelper extends SQLiteOpenHelper
/**
* DownLoadDBHelper用于创建数据库,如果不会使用原生的建库的话
*
* 跟随小司机我的脚步来一起练一练
* 建两个表:
* download_info表存储下载信息
* localdownload_info表存储本地下载信息
* 之后对比两个表进行继续下载等等
*/
public static String DATABASE_NAME = "downloadFILES.db";
public static String TABLE_DOWNLOAD_INFO = "download_info";
public static String TABLE_LOCALDOWNLOAD_INFO = "localdownload_info";
private static int version = 1;
public DownLoadDBHelper(Context context)
super(context, DATABASE_NAME, null, version);
@Override
public void onCreate(SQLiteDatabase db)
/*在此进行创建数据库和表格,来一起动手写一遍,就是两个sqlite语句*/
db.execSQL("create table " + TABLE_DOWNLOAD_INFO + "(" + "id integer PRIMARY KEY AUTOINCREMENT," +
"thread_id integer," + "start_position integer," + "end_position integer," + " completed_size integer," + "url varchar(100))");
db.execSQL("create table " + TABLE_LOCALDOWNLOAD_INFO + "(" + "id integer PRIMARY KEY AUTOINCREMENT," + "name varchar(50)," +
"url varchar(100)," + "completedSize integer," + "fileSize integer," + "status integer)");
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
/*数据库更新升级,检测到版本的变化,发现版本号不一样,就会自动调用onUpgrade函数
* 新版本号和老版本号都会作为onUpgrade函数的参数传进来,便于开发者知道数据库应该从哪个版本升级到哪个版本。
* */
String sql = "drop table if exists " + TABLE_DOWNLOAD_INFO + "";
String sqlOne = "drop table if exists " + TABLE_LOCALDOWNLOAD_INFO + "";
db.execSQL(sql);
db.execSQL(sqlOne);
onCreate(db);//删除数据库,重新创建。这里只是简单的,并没有添加或者减少数据库中的其他字段
DAO对数据库进行增删改查
DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此。对数据库的操作,我们基本要用到的就是新增,更新,删除,查询等方法。
/**
* Created by ShanCanCan on 2017/3/6 0006.
*/
public class Dao
/*
* DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此。
* DAO层所定义的接口里的方法都大同小异,这是由我们在DAO层对数据库访问的操作来决定的,
* 对数据库的操作,我们基本要用到的就是新增,更新,删除,查询等方法。
* 因而DAO层里面基本上都应该要涵盖这些方法对应的操作。
* */
private static Dao dao;
private static DownLoadDBHelper dbHelper;
public static final byte[] Lock = new byte[0]; //新建两个字节作为对象锁
public static final byte[] file_Lock = new byte[0];
public Dao() //空构造方法,
public static synchronized Dao getInstance(Context context) //本demo用单例模式中的懒汉模式+线程不安全 线程安全的代价是效率变低
if (dao == null)
dao = new Dao();
dbHelper = new DownLoadDBHelper(context);
return dao;
/* public static synchronized Dao getInstance(Context context) //本demo用单例模式中的懒汉模式+线程安全 线程安全的代价是效率变低,99%情况下不需要同步
if (dao == null) //你可以在这两个方法中随便选择一个
dao = new Dao();
dbHelper = new DownLoadDBHelper(context);
return dao;
*/
/*************************************** 下方Dao层中对数据库的增、删、改、查 *********************************************************/
/**
* 检查本地下载记录,是否下载过
*
* @param url
* @return
*/
public boolean isExist(String url)
SQLiteDatabase database = dbHelper.getReadableDatabase(); //获取本app所创建的数据库
String sql = "select count(*) from " + TABLE_LOCALDOWNLOAD_INFO + " where url=?"; //查询语句,查询总共有多少条的语句
Cursor cursor = database.rawQuery(sql, new String[]url);
/**
*
* @Cursor
* Cursor 是每行的集合。
* 使用 moveToFirst() 定位第一行。
* 你必须知道每一列的名称。
* 你必须知道每一列的数据类型。
* Cursor 是一个随机的数据源。
* 所有的数据都是通过下标取得。
* Cursor按照我的理解就是一个箭头,指到哪一行就是那一行的集合
* 比较重要的方法有:close(),moveToFirst(),moveToNext(),moveToLast(),moveToPrevious(),getColumnCount()等。
*
* @rawQuery
* rawQuery是直接使用SQL语句进行查询的,也就是第一个参数字符串,
* 在字符串内的“?”会被后面的String[]数组逐一对换掉
* cursor用完之后要关闭,cursor用完之后要关闭,cursor用完之后要关闭。重要的事情说三遍!!!
*
* */
cursor.moveToFirst();
int count = cursor.getInt(0);
cursor.close();
return count > 0;
/**
* 是否为首次下载
*
* @param url
* @return
*/
public boolean isFirstDownload(String url)
SQLiteDatabase database = dbHelper.getReadableDatabase();
String sql = "select count(*) from " + TABLE_DOWNLOAD_INFO + " where url=?";
Cursor cursor = database.rawQuery(sql, new String[]url);
cursor.moveToFirst();
int count = cursor.getInt(0);
cursor.close();
return count == 0;
/**
* 保存下载的具体信息 保存所下载的list集合中的数据
*
* @param infos
* @param context
*/
public void saveInfos(List<DownLoadInfo> infos, Context context)
/**
* 事务(Transaction)是并发控制的单位,是用户定义的一个操作序列。
* 这些操作要么都做,要么都不做,是一个不可分割的工作单位。
* 通过事务,SQL Server能将逻辑相关的一组操作绑定在一起,
* 以便保持数据的完整性。
*
* 事务具有四个特征:原子性( Atomicity )、一致性( Consistency )、
* 隔离性( Isolation )和持续性( Durability )。这四个特性简称为 ACID 特性。
*
* */
synchronized (Lock)
SQLiteDatabase database = dbHelper.getWritableDatabase();
database.beginTransaction();//开启事务
try //如果有异常,在这里捕获
for (DownLoadInfo info : infos) //for循环将数据存入数据库
String sql = "insert into " + TABLE_DOWNLOAD_INFO + "(thread_id,start_position, end_position, completed_size, url) values (?,?,?,?,?)";
Object[] bindArgs = info.getThreadId(), info.getStartPosition(), info.getEndPosition(), info.getCompletedSize(), info.getUrl();
database.execSQL(sql, bindArgs);
database.setTransactionSuccessful();//结束事务
catch (SQLException e)
e.printStackTrace();
finally
database.endTransaction();//关闭事务
/**
* 得到下载具体信息
*
* @param urlstr
* @return List<DownloadInfo> 一个下载器信息集合器,里面存放了每条线程的下载信息
*/
public List<DownLoadInfo> getInfos(String urlstr)
List<DownLoadInfo> list = new ArrayList<DownLoadInfo>();
SQLiteDatabase database = dbHelper.getReadableDatabase();
String sql = "select thread_id, start_position, end_position, completed_size, url from " + TABLE_DOWNLOAD_INFO + " where url=?";
Cursor cursor = database.rawQuery(sql, new String[]urlstr);
while (cursor.moveToNext()) //通过cursor取到下载器信息,循环遍历,得到下载器集合
DownLoadInfo info = new DownLoadInfo(cursor.getInt(0), cursor.getInt(1), cursor.getInt(2), cursor.getInt(3), cursor.getString(4));
list.add(info);
cursor.close();
return list;
/**
* 本地下载列表添加记录,添加本地数据库信息,完成度等等
*
* @param fileStatus
**/
public void insertFileStatus(FileStatus fileStatus)
synchronized (file_Lock) //异步加开启事务,保证数据的完整性
SQLiteDatabase database = dbHelper.getWritableDatabase();
database.beginTransaction();
try
String sql = "insert into " + TABLE_LOCALDOWNLOAD_INFO + " (name,url,completedSize,fileSize,status) values(?,?,?,?,?)";
Object[] bindArgs = fileStatus.getName(), fileStatus.getUrl(), fileStatus.getCompletedSize(), fileStatus.getFileSize(), fileStatus.getStatus();
database.execSQL(sql, bindArgs);
database.setTransactionSuccessful();
catch (SQLException e)
e.printStackTrace();
finally
database.endTransaction();
/**
* @param context
* @param compeletedSize
* @param threadId
* @param urlstr 这里是更新数据库,建议在保存一个表格的时候就对另一个表格数据库进行更新
*/
public void updataInfos(int threadId, int compeletedSize, String urlstr, Context context)
synchronized (Lock)
String sql = "update " + TABLE_DOWNLOAD_INFO + "set completed_size = ? where thread_id =? and url=?";
String localSql = "update " + TABLE_LOCALDOWNLOAD_INFO + "set completedSize = (select sum(completed_size) from " +
TABLE_DOWNLOAD_INFO + "where url=? group by url ) where url=?";
Object[] bindArgs = compeletedSize, threadId, urlstr;
Object[] localArgs = urlstr, urlstr;
SQLiteDatabase database = dbHelper.getWritableDatabase();
database.beginTransaction();
try
database.execSQL(sql, bindArgs);
database.execSQL(localSql, localArgs);
database.setTransactionSuccessful();
catch (SQLException e)
e.printStackTrace();
finally
database.endTransaction();
/**
* @param url 更新文件的状态,0为正在下载,1为已经下载完成,2为下载出错
**/
public void updateFileStatus(String url)
synchronized (file_Lock)
String sql = "update " + TABLE_LOCALDOWNLOAD_INFO + " set status = ? where url = ?";
Object[] bindArgs = 1, url;
SQLiteDatabase database = dbHelper.getWritableDatabase();
database.beginTransaction();
try
database.execSQL(sql, bindArgs);
database.setTransactionSuccessful();
catch (SQLException e)
e.printStackTrace();
finally
database.endTransaction();
/**
* @return List<FileStatus>
* 取出本地下载列表数据,如在重新进入应用时,要重新把进度之类的设置好
**/
public List<FileStatus> getFileStatus()
List<FileStatus> list = new ArrayList<FileStatus>();
SQLiteDatabase database = dbHelper.getReadableDatabase();
//String sql = "slect * from " + TABLE_LOCALDOWNLOAD_INFO + ""; //不能用,需要哪些条件就在语句中写出哪些条件
String sql = "select name, url, status, completedSize, fileSize from " + TABLE_LOCALDOWNLOAD_INFO + "";
Cursor cursor = database.rawQuery(sql, null);
while (cursor.moveToNext())
FileStatus fileState = new FileStatus(cursor.getString(0), cursor.getString(1), cursor.getInt(2), cursor.getInt(3), cursor.getInt(4));
list.add(fileState);
cursor.close();
return list;
/**
* @param url
* @param completeSize
* @param status 更新文件的下载状态
**/
public void updateFileDownStatus(int completeSize, int status, String url)
synchronized (file_Lock)
String sql = "update " + TABLE_LOCALDOWNLOAD_INFO + " set completedSize = ?,status = ? where url = ?";
SQLiteDatabase database = dbHelper.getWritableDatabase();
database.beginTransaction();
try
Object[] bindArgs = completeSize, status, url;
database.execSQL(sql, bindArgs);
database.delete(TABLE_DOWNLOAD_INFO, "url = ?", new String[]url);
database.setTransactionSuccessful();
catch (SQLException e)
e.printStackTrace();
finally
database.endTransaction();
/**
* @param url 获取文件名称
**/
public String getFileName(String url)
String result = "";
String sql = "select name from " + TABLE_LOCALDOWNLOAD_INFO + " where url = ?";
SQLiteDatabase database = dbHelper.getReadableDatabase();
Cursor cursor = database.rawQuery(sql, new String[]url);
if (cursor.moveToNext())
result = cursor.getString(0);
cursor.close();
return result;
/**
* 删除文件之后,要删除下载的数据,一个是用户可以重新下载
* 另一个是表再次添加一条数据的时候不出现错误
*
* @param url
*/
public void deleteFile(String url)
SQLiteDatabase database = dbHelper.getWritableDatabase();
database.beginTransaction();
try
database.delete(TABLE_DOWNLOAD_INFO, " url = ?", new String[]url);
database.delete(TABLE_LOCALDOWNLOAD_INFO, " url = ?", new String[]url);
database.setTransactionSuccessful();
catch (Exception e)
e.printStackTrace();
finally
database.endTransaction();
/**
* 关闭数据库
*
* @close
*/
public void closeDB()
dbHelper.close();
DownloadService 主要代码
开启线程进行下载 文件保存地址 保存每个文件下载的下载器 每个下载文件完成的长度
@SuppressLint("HandlerLeak")
public class DownloadService extends Service
public IBinder binder = new MyBinder();
public class MyBinder extends Binder
public DownloadService getService()
return DownloadService.this;
@Override
public IBinder onBind(Intent intent)
return binder;
public static int number = 0;
// 文件保存地址
public final String savePath = "/mnt/sdcard/Download/";
// 存放下载列表的引用
public static List<FileStatus> list = new ArrayList<FileStatus>();
public static Map<String, String> localDownList = new HashMap<String, String>();
// 保存每个文件下载的下载器
public static Map<String, Downloader> downloaders = new HashMap<String, Downloader>();
// 每个下载文件完成的长度
private Map<String, Integer> completeSizes = new HashMap<String, Integer>();
// 每个下载文件的总长度
private Map<String, Integer> fileSizes = new HashMap<String, Integer>();
private Downloader downloader;
private int threadCount = 5;
private Dao dao;
private DownLoadCallback loadCallback;
private FileStatus mFileStatus = null;
private Handler handler = new Handler()
@Override
public void handleMessage(Message msg)
super.handleMessage(msg);
if (msg.what == 1)
String url = (String) msg.obj;
int length = msg.arg1;
int completeSize = completeSizes.get(url);
int fileSize = fileSizes.get(url);
completeSize += length;
completeSizes.put(url, completeSize);
synchronized (list)
for (int i = 0; i < list.size(); i++)
FileStatus fileStatus = list.get(i);
if (fileStatus.getUrl().equals(url))
if (completeSize == fileStatus.getFileSize())
list.set(i, new FileStatus(fileStatus.getName(), fileStatus.getUrl(), 1, completeSize, fileStatus.getFileSize()));
dao.updateFileDownStatus(completeSize, 1, url);
else
list.set(i, new FileStatus(fileStatus.getName(), fileStatus.getUrl(), 0, completeSize, fileStatus.getFileSize()));
mFileStatus = list.get(i);
this.postDelayed(new Runnable()
@Override
public void run()
if (loadCallback != null && mFileStatus != null)
loadCallback.refreshUI(mFileStatus);
, 1000);
;
@Override
public void onCreate()
super.onCreate();
dao = Dao.getInstance(this);
list = dao.getFileStatus();
for (FileStatus fileStatus : list)
localDownList.put(fileStatus.getUrl(), fileStatus.getUrl());
Timer timer = new Timer();
timer.schedule(new TimerTask()
@Override
public void run()
number++;
, 0, 1000);
public void download(final Button button, final String url, final String name, final Handler mHandler)
if (dao.isExist(url))
Toast.makeText(this, "别点了,已经在下载了", Toast.LENGTH_SHORT).show();
return;
final String fileName = name + url.substring(url.lastIndexOf("."));
new Thread(new Runnable()
@Override
public void run()
downloader = downloaders.get(url);
if (downloader == null)
downloader = new Downloader(url, savePath, fileName, threadCount, DownloadService.this, handler);
downloaders.put(url, downloader);
if (downloader.isDownloading())
return;
LoadInfo loadInfo = downloader.getDownloaderInfors();
if(loadInfo != null)
FileStatus fileStatus = new FileStatus(fileName, url, 0, loadInfo.getComplete(), loadInfo.getFileSize());
dao.insertFileStatus(fileStatus);
completeSizes.put(url, loadInfo.getComplete());
fileSizes.put(url, fileStatus.getFileSize());
list.add(fileStatus);
localDownList.put(url, url);
downloader.download();
Message msg = new Message();
msg.what = 1;
msg.obj = button;
mHandler.sendMessage(msg);
else
Message msg = new Message();
msg.what = 2;
msg.obj = button;
mHandler.sendMessage(msg);
).start();
//暂停下载
public void Pause(Downloader downloader)
downloader.pause();
//继续下载
public void reDownload(final Button button, final String url, final String name, final Handler mHandler)
new Thread(new Runnable()
@Override
public void run()
String fileName = dao.getFileName(url);
downloader = downloaders.get(url);
if (downloader == null)
downloader = new Downloader(url, savePath, fileName, threadCount, DownloadService.this, handler);
downloaders.put(url, downloader);
if (downloader.isDownloading())
return;
LoadInfo loadInfo = downloader.getDownloaderInfors();
if(loadInfo != null && !fileName.equals(""))
if(!completeSizes.containsKey(url))
completeSizes.put(url, loadInfo.getComplete());
if(!fileSizes.containsKey(url))
fileSizes.put(url, loadInfo.getFileSize());
downloader.download();
Message msg = new Message();
msg.what = 1;
msg.obj = button;
mHandler.sendMessage(msg);
else
Message msg = new Message();
msg.what = 2;
msg.obj = button;
mHandler.sendMessage(msg);
).start();
public void delete(final String url)
Downloader down = downloaders.get(url);
if(down != null)
down.pause();
handler.postDelayed(new Runnable()
@Override
public void run()
dao.deleteFile(url);
for (int i = 0; i < list.size(); i++)
FileStatus fileStatus = list.get(i);
if (fileStatus.getUrl().equals(url))
list.remove(i);
localDownList.remove(url);
downloaders.remove(url);
completeSizes.remove(url);
fileSizes.remove(url);
if(loadCallback != null)
loadCallback.deleteFile(url);
, 1000);
public interface DownLoadCallback
public void refreshUI(FileStatus fileStatus);
public void deleteFile(String url);
public void setLoadCallback(DownLoadCallback loadCallback)
this.loadCallback = loadCallback;
下载工具类DownLoadUtil
此类的主要功能 1、检查是否下载 2、下载文件,文件的下载采用httpurlconnection 定义三种下载的状态:初始化状态,正在下载状态,暂停状态。 进行下载的比对。
/**
* Created by ShanCanCan on 2017/3/7 0007.
*/
public class DownLoadUtil
/**
* 此类的主要功能
* 1、检查是否下载
* 2、下载文件,文件的下载采用httpurlconnection
*/
private String downPath;// 下载路径
private String savePath;// 保存路径
private String fileName;// 文件名称
private int threadCount;// 线程数
private Handler mHandler;
private Dao dao;
private Context context;
private int fileSize;// 文件大小
private int range;
private List<DownLoadInfo> infos;// 存放下载信息类的集合
private int state = INIT;
private static final int INIT = 1;// 定义三种下载的状态:初始化状态,正在下载状态,暂停状态
private static final int DOWNLOADING = 2;
private static final int PAUSE = 3;
/**
* 构造方法,获取dao的对象
*
* @param downPath
* @param savePath
* @param fileName
* @param threadCount
* @param context
* @param mHandler
*/
public DownLoadUtil(String downPath, String savePath, String fileName, int threadCount, Handler mHandler, Context context)
this.downPath = downPath;
this.savePath = savePath;
this.fileName = fileName;
this.threadCount = threadCount;
this.mHandler = mHandler;
this.context = context;
dao = Dao.getInstance(context);
/**
* 判断是否PAUSE
**/
public boolean isPause()
return state == PAUSE;
/**
* 判断是否DOWNLOADING
*/
public boolean isDownloading()
return state == DOWNLOADING;
/**
* @param url 判断是否是第一次下载,利用dao查询数据库中是否有下载这个地址的记录
*/
private boolean isFirst(String url)
return dao.isFirstDownload(url);
/**
* 获取要下载的东西
*/
public LoadItemInfo getDownloadInfos()
if (isFirst(downPath))
if (initFirst()) //如果是第一次下载的话,要进行初始化,1.获得下载文件的长度 2.创建文件,设置文件的大小
range = this.fileSize / this.threadCount;
infos = new ArrayList<DownLoadInfo>();
//这里就是启动多线程下载,看出来了吗?配合RandomAccessFile。每一个DownLoadInfo就是RandomAccessFile文件的一部分
for (int i = 0; i < this.threadCount - 1; i++)
DownLoadInfo info = new DownLoadInfo(i, i * range, (i + 1) * range - 1, 0, downPath);
infos.add(info);
DownLoadInfo info = new DownLoadInfo(this.threadCount - 1, (this.threadCount - 1) * range, this.fileSize, 0, downPath);
infos.add(info);
dao.saveInfos(infos, this.context);
//(String urlDownload, int completePercent, int fileSize)
LoadItemInfo loadInfo = new LoadItemInfo(this.downPath, 0, this.fileSize);
return loadInfo;
else
return null;
else
//不是第一次下载,我们应该怎么做呢?从数据库里面取回来
infos = dao.getInfos(this.downPath);
if (infos != null && infos.size() > 0)
int size = 0;
int completeSize = 0;
for (DownLoadInfo info : infos)
completeSize += info.getCompletedSize();
size += info.getEndPosition() - info.getStartPosition() + this.threadCount - 1;
LoadItemInfo loadInfo = new LoadItemInfo(this.downPath, completeSize, size);
return loadInfo;
else
return null;
// 设置暂停
public void pause()
state = PAUSE;
// 重置下载状态,将下载状态设置为init初始化状态
public void reset()
state = INIT;
/**
* 基本上,RandomAccessFile的工作方式是,把DataInputStream和DataOutputStream结合起来,再加上它自己的一些方法,
* 比如定位用的getFilePointer( ),在文件里移动用的seek( ),以及判断文件大小的length( )、skipBytes()跳过多少字节数。
* 此外,它的构造函数还要一个表示以只读方式("r"),还是以读写方式("rw")打开文件的参数 (和C的fopen( )一模一样)。它不支持只写文件。
*/
private boolean initFirst()
boolean result = true;
HttpURLConnection conn = null;
RandomAccessFile randomFile = null;
URL url = null;
try
url = new URL(downPath);
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5 * 1000);
conn.setRequestMethod("GET");
// 如果http返回的代码是200或者206则为连接成功
if (conn.getResponseCode() == 200 || conn.getResponseCode() == 206) //状态码(206),表示服务器已经执行完部分对资源的GET请求
fileSize = conn.getContentLength();// 得到文件的大小
if (fileSize <= 0)
//("网络故障,无法获取文件大小");
return false;
File dir = new File(savePath);
// 如果文件目录不存在,则创建
if (!dir.exists())
if (dir.mkdirs())
//("mkdirs success.");
File file = new File(this.savePath, this.fileName);
randomFile = new RandomAccessFile(file, "rwd");
randomFile.setLength(fileSize);// 设置保存文件的大小
randomFile.close();
conn.disconnect();
catch (Exception e)
e.printStackTrace();
result = false;
finally
if (randomFile != null)
try
randomFile.close();
catch (IOException e)
e.printStackTrace();
if (conn != null)
conn.disconnect();
return result;
/**
* 下面的这个方法就是开启多线程进行下载了数据了
*/
public void downLoad()
if (infos != null)
if (state == DOWNLOADING)
return;
state = DOWNLOADING;// 把状态设置为正在下载
for (DownLoadInfo info : infos) //为什么说我们是多线程呢?因为我们分别用新线程去下载刚才分割好的一个RandomAccessFile文件
new DownLoadThread(info.getThreadId(), info.getStartPosition(), info.getEndPosition(), info.getCompletedSize(), info.getUrl(), this.context).start();
/**
* 现在要创建线程用来下载了,这里采用内部类
*/
public class DownLoadThread extends Thread
private int threadId;
private int startPostion;
private int endPostion;
private int compeletedSize;
private String url;
private Context context;
public static final int PROGRESS = 1;
public DownLoadThread(int threadId, int startPostion, int endPostion, int compeletedSize, String url, Context context) //构造方法,传入特定的参数
this.threadId = threadId;
this.startPostion = startPostion;
this.endPostion = endPostion;
this.compeletedSize = compeletedSize;
this.url = url;
this.context = context;
//开始下载
@Override
public void run()
HttpURLConnection conn = null;
RandomAccessFile randomAccessFile = null;
InputStream inStream = null;
File file = new File(savePath, fileName);
URL url = null;
try
url = new URL(this.url);
conn = (HttpURLConnection) url.openConnection();
constructConnection(conn);
if (conn.getResponseCode() == 200 || conn.getResponseCode() == 206)
randomAccessFile = new RandomAccessFile(file, "rwd");
randomAccessFile.seek(this.startPostion + this.compeletedSize);//RandomAccessFile移动指针,到需要下载的块
inStream = conn.getInputStream();
byte buffer[] = new byte[4096];//这个4096为么子呢?我也不知道,就是看阿里的人下载apk的时候都用4096,我也用
int length = 0;
while ((length = inStream.read(buffer, 0, buffer.length)) != -1)
randomAccessFile.write(buffer, 0, length);
compeletedSize += length;
// 更新数据库中的下载信息
dao.updataInfos(threadId, compeletedSize, this.url, this.context);
// 用消息将下载信息传给进度条,对进度条进行更新
Message message = Message.obtain();
message.what = PROGRESS;
message.obj = this.url;
message.arg1 = length;
mHandler.sendMessage(message);// 给DownloadService发送消息
if (state == PAUSE)
//("-----pause-----");
return;
// ("------------线程:" + this.threadId + "下载完成");
catch (IOException e)
e.printStackTrace();
//("-----下载异常-----"); 这里下载异常我就不处理了,你可以发一条重新下载的消息
finally //用完只后流要关闭,不然容易造成资源抢占,内存泄漏
try
if (inStream != null)
inStream.close();
if (randomAccessFile != null)
randomAccessFile.close();
以上是关于Android原生实现多线程断点下载的主要内容,如果未能解决你的问题,请参考以下文章