本项目完成的功能类似与迅雷等下载工具所实现的功能——实现多线程断点下载。
主要设计的技术有:
1、android中主线程与非主线程通信机制。
2、多线程的编程和管理。
3、android网络编程
4、自己设计实现设计模式-监听器模式
5、Activity、Service、数据库编程
6、android文件系统
7、缓存
博文链接:
Android-多线程断点下载详解及源码下载(一)
Android-多线程断点下载详解及源码下载(三)
Android-多线程断点下载详解及源码下载(四)
本篇接着上篇开始详细讲述客户端代码的具体实现。
- 数据库设计DownDatabase
首先讲述的是数据库的设计部分,为什么首先讲述数据库呢?因为数据库的设计和客户端代码的其他部分在逻辑上是独立的,并且可以单独测试,同时数据库也应该在实现断点下载的功能实现之前设计出来(因为断点下载依赖于数据库)。所以首先讲述数据库的设计,涉及的技术主要是android的SQLiteOpenHelper,继承该类重写该类的方法就可以实现数据库的自动创建和更新,这个部分就不详细说明了,如果详述这部分功能又是一大段,如果大家不熟,希望大家一定搞熟,很重要!
具体到该项目中,数据库的设计说明如下:
因为是实现多线程下载,每个线程已经下载的文件的长度需要保存在数据库中,等到下次再开始下载的时候再次从数据库中获取每个线程已经下载的长度,从已经下载的位置开始下载。
那么需要保存在数据库中的数据就有已下载的文件的长度,线程ID,下载路径。
线程ID区分那个线程,避免混淆。
下载路径是唯一的,每次文件的下载,路径必然唯一。
每个线程已经下载的文件的长度这个数据必然保存,并与线程ID相对应。
基于以上分析,下面就给出数据库类DownDatabase的实现代码:
public class DownDatabase extends SQLiteOpenHelper {
private static final String NAME = "fileloader.db";
private static final int VERSION = 1;
public DownDatabase(Context context) {
super(context, NAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table if not exists fileloadertable (" +
"_id integer primary key AUTOINCREMENT," +
"threadId integer not null," +
"downpath text not null," +
"downedlen long not null)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop talbe if exists fileloadertable");
onCreate(db);
}
}
这个类主要的工作量在于数据库的设计,需要注意的是_id,这个值是固定的,这是区别与其他数据库设计不同的地方,SQLite数据库要求主键ID名字是_id,其他的地方就没有什么特殊的了。
- 数据库管理类DownDatabaseService
数据库设计已经完成,针对数据库的操作,则放在DownDatabaseService中,主要是避免类太庞大,同时也分离的数据库的操作,增删改查与数据库定义分离,后期即使数据库更改,改动也不是很大。
既然是数据库管理类,所以主要实现数据库的管理,增删改查四个操作,代码如下:
public class DownDatabaseService {
private DownDatabase downDatabase;
public DownDatabaseService(Context context){
downDatabase = new DownDatabase(context);
}
/** * 获取每条线程对应的已经下载的长度 * @param loaderPath * @return HashMap<Integer, Long> */
public HashMap<Integer, Long> getDownLoadedLen(String loaderPath){
SQLiteDatabase sqLiteDatabase = downDatabase.getReadableDatabase();
Cursor cursor = sqLiteDatabase.rawQuery("select * from fileloadertable where downpath=?",
new String[]{loaderPath});
HashMap<Integer, Long> hashMap = new HashMap<>();
System.out.println("DownDatabaseService.getDownLoadedLen()" +
"cursor的数量="+cursor.getCount());
if (cursor != null) {
int threadIdIndex = cursor.getColumnIndexOrThrow("threadId");
int downedLenIndex = cursor.getColumnIndexOrThrow("downedlen");
while (cursor.moveToNext()) {
hashMap.put(cursor.getInt(threadIdIndex), cursor.getLong(downedLenIndex));
System.out.println("已经下载的数据:"+cursor.getInt(threadIdIndex)+"=="+cursor.getInt(downedLenIndex));
}
cursor.close();
}
sqLiteDatabase.close();
return hashMap;
}
/** * 插入数据 * 将每条线程已下载数据插入数据库中 * @param loaderPath * @param map */
public void setData(String loaderPath,Map<Integer, Long> map){
SQLiteDatabase sqLiteDatabase = downDatabase.getWritableDatabase();
sqLiteDatabase.beginTransaction();
Set<Entry<Integer,Long>> set = map.entrySet();
try{
for (Entry<Integer, Long> entry : set) {
// sqLiteDatabase.execSQL(
// "insert into fileloadertable (threadId,downedlen,downpath) values(?,?,?)",
// new String[]{entry.getKey()+"",entry.getValue()+"",loaderPath});
ContentValues contentValues = new ContentValues();
contentValues.put("threadId", entry.getKey());
contentValues.put("downedlen", entry.getValue());
contentValues.put("downpath", loaderPath);
sqLiteDatabase.insert("fileloadertable", "_id", contentValues);
}
sqLiteDatabase.setTransactionSuccessful();
}catch(Exception e){
}finally{
sqLiteDatabase.endTransaction();
}
sqLiteDatabase.close();
}
/** * 更新每条线程已经下载的文件的长度 * @param loaderPath * @param map */
public void update(String loaderPath,Map<Integer, Long> map){
SQLiteDatabase sqLiteDatabase = downDatabase.getWritableDatabase();
sqLiteDatabase.beginTransaction();
try {
Set<Entry<Integer, Long>> set = map.entrySet();
for (Entry<Integer, Long> entry : set) {
sqLiteDatabase.execSQL("update fileloadertable set downedlen=? where " +
"threadId=? and downpath=?",
new String[]{
""+entry.getValue(),""+entry.getKey(),loaderPath});
System.out.println("DownDatabaseService.update()" +
"设置每条线程下载长度="+entry.getKey()+":"+entry.getValue());
}
sqLiteDatabase.setTransactionSuccessful();
} catch (Exception e) {
}finally{
sqLiteDatabase.endTransaction();
}
sqLiteDatabase.close();
}
/** * 删除下载路径下的所有线程记录 * @param loaderPath */
public void delete(String loaderPath){
SQLiteDatabase sqLiteDatabase = downDatabase.getWritableDatabase();
sqLiteDatabase.execSQL("delete from fileloadertable where downpath=?",
new String[]{loaderPath});
sqLiteDatabase.close();
}
}
没有什么特别要说明的地方,可能有人奇怪为什么会有删除的操作,是这样的,当针对一个下载路径,该下载路径中的文件已经下载完成,则这个时候就有必要删除该下载路径所对应的每个线程的记录,下载完毕就没有用了,所以下载完成之后就删除了。
在上面的类中,我是用了两种方式进行操作,一种是使用execSQL方法,自己手动书写SQL语句,调用该方法完成数据库操作;还有一种方法是使用ContentValues类定义KEY-VALUE对,然后调用database中相应的方法进行增删改的操作,大家看看,自己想怎么实现都可以。
至此数据库的设计和操作部分的功能设计完成,如果大家实际开发 过程中不知道数据库设计对不对,这个时候可以实现单元测试,其实就是定一个Main函数的类,然后调用数据库管理类进行相应的操作,测试数据到底对不对,就可以了。
- 回调接口 IDownProgressing
回调接口IDownProgressing很简单,只有一个方法,目的就是不断获取已经下载的文件的长度,进而更新进度条,既然获取的是文件的长度,那就一个方法就可以了,参数就是已下载的文件的长度,接口定义代码如下:
public interface IDownProgressing {
public void setDownLoaderNum(int size);
}
本文主要讲述了三点,数据库的设计、数据库的操作、回调接口的定义。下一篇继续讲述下载器的详细实现过程。
博文链接:
Android-多线程断点下载详解及源码下载(一)
Android-多线程断点下载详解及源码下载(三)
Android-多线程断点下载详解及源码下载(四)