最近做的项目中需要实现断点下载,即用户一次下载可以分多次进行,下载过程可以中断,在目前大多数的带离线缓存的软件都是需要实现这一功能。本文阐述了通过sqlite3简单实现了一个具有断点下载功能的demo。言归正传,开始正文。
设计
数据库表存储元数据
DBHelper.java
用于业务存储的Dao
Dao.java
抽象下载信息的Bean
LoadInfo.java
呈现下载信息View
MainActivity.java
存储下载信息Bean
DownloadInfo.java
封装好的下载类
Downloader.java
代码结构
具体实现
下载信息类:DownloadInfo.java
这里的代码很简单,就是一个用来保存下载信息的类,很简单,没啥好说的,具体看注释
package entity; public class DownloadInfo { private int threadId;//线程ID private int startPos;//下载起始位置 private int endPos;//下载结束位置 private int completeSize;//下载完成量 private String url;//资源URL public DownloadInfo(int tId,int sp, int ep,int cSize,String address){ threadId=tId; startPos=sp; endPos=ep; completeSize = cSize; url=address; } /** * @return the threadId */ public int getThreadId() { return threadId; } /** * @param threadId the threadId to set */ public void setThreadId(int threadId) { this.threadId = threadId; } /** * @return the startPos */ public int getStartPos() { return startPos; } /** * @param startPos the startPos to set */ public void setStartPos(int startPos) { this.startPos = startPos; } /** * @return the endPos */ public int getEndPos() { return endPos; } /** * @param endPos the endPos to set */ public void setEndPos(int endPos) { this.endPos = endPos; } /** * @return the completeSize */ public int getCompleteSize() { return completeSize; } /** * @param completeSize the completeSize to set */ public void setCompleteSize(int completeSize) { this.completeSize = completeSize; } /** * @return the url */ public String getUrl() { return url; } /** * @param url the url to set */ public void setUrl(String url) { this.url = url; } @Override public String toString() { // TODO Auto-generated method stub return "threadId:"+threadId+",startPos:"+startPos+",endPos:"+endPos+",completeSize:"+completeSize+",url:"+url; } }
数据库 DBHelper.java
package db; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class DBHelper extends SQLiteOpenHelper { String sql ="create table download_info (id integer PRIMARY KEY AUTOINCREMENT," + "thread_id integer," + "start_pos integer," + "end_pos integer," + "complete_size integer," + "url char)"; public DBHelper(Context context) { // TODO Auto-generated constructor stub super(context, "download.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub db.execSQL(sql); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub } }
数据库业务管理类 Dao.java
package db; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import entity.DownloadInfo; public class Dao { private DBHelper dbHelper; public Dao(Context context){ dbHelper = new DBHelper(context); } public boolean isNewTask(String url){ SQLiteDatabase db = dbHelper.getReadableDatabase(); String sql = "select count(*) from download_info where url=?"; Cursor cursor = db.rawQuery(sql, new String[]{url}); cursor.moveToFirst(); int count = cursor.getInt(0); cursor.close(); return count == 0; } public void saveInfo(Listinfos){ SQLiteDatabase db = dbHelper.getWritableDatabase(); String sql = "insert into download_info(thread_id,start_pos,end_pos,complete_size,url) values(?,?,?,?,?)"; Object[] bindArgs= null; for(DownloadInfo info:infos){ bindArgs=new Object[]{info.getThreadId(),info.getStartPos(),info.getEndPos(),info.getCompleteSize(),info.getUrl()}; db.execSQL(sql, bindArgs); } } public List getInfo(String url){ SQLiteDatabase db = dbHelper.getReadableDatabase(); List infos = new ArrayList (); String sql ="select thread_id,start_pos,end_pos,complete_size,url from download_info where url=?"; Cursor cursor=db.rawQuery(sql, new String[]{url}); while(cursor.moveToNext()){ DownloadInfo info = new DownloadInfo(cursor.getInt(0), cursor.getInt(1), cursor.getInt(2), cursor.getInt(3),cursor.getString(4)); infos.add(info); } cursor.close(); return infos; } public void deleteInfo(String url){ SQLiteDatabase db = dbHelper.getWritableDatabase(); db.delete("download_info", "url=?", new String[]{url}); db.close(); } public void updateInfo(int completeSize,int threadId,String url){ SQLiteDatabase db = dbHelper.getWritableDatabase(); String sql ="update download_info set complete_size=? where thread_id=? and url=?"; db.execSQL(sql, new Object[]{completeSize,threadId,url}); } public void closeDB(){ dbHelper.close(); } }
当前状态保存类 LoadInfo.java
package entity; public class LoadInfo { private int fileSize; private int completeSize; private String url; public LoadInfo(int fs,int cSize,String address){ fileSize=fs; completeSize = cSize; url=address; } /** * @return the fileSize */ public int getFileSize() { return fileSize; } /** * @param fileSize the fileSize to set */ public void setFileSize(int fileSize) { this.fileSize = fileSize; } /** * @return the completeSize */ public int getCompleteSize() { return completeSize; } /** * @param completeSize the completeSize to set */ public void setCompleteSize(int completeSize) { this.completeSize = completeSize; } /** * @return the url */ public String getUrl() { return url; } /** * @param url the url to set */ public void setUrl(String url) { this.url = url; } }
下载助手类:Downloader.java
package com.winton.downloadmanager; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.os.Handler; import android.os.Message; import db.Dao; import entity.DownloadInfo; import entity.LoadInfo; public class Downloader { private String url; private String localPath; private int threadCount; private Handler mHanler; private Dao dao; private int fileSize; private Listinfos; private final static int INIT = 1; private final static int DOWNLOADING =2; private final static int PAUSE =3; private int state = INIT; public Downloader(String address,String lPath,int thCount,Context context,Handler handler){ url =address; localPath = lPath; threadCount = thCount; mHanler = handler; dao = new Dao(context); } public boolean isDownloading(){ return state == DOWNLOADING; } public LoadInfo getDownLoadInfos(){ if(isFirstDownload(url)){ init(); int range = fileSize/threadCount; infos = new ArrayList (); for(int i=0;i<=threadCount-1;i++){ DownloadInfo info = new DownloadInfo(i, i*range, (i+1)*range-1, 0, url); infos.add(info); } dao.saveInfo(infos); return new LoadInfo(fileSize, 0, url); }else{ infos = dao.getInfo(url); int size = 0; int completeSize =0; for(DownloadInfo info:infos){ completeSize += info.getCompleteSize(); size += info.getEndPos()-info.getStartPos()+1; } return new LoadInfo(size, completeSize, url); } } public boolean isFirstDownload(String url){ return dao.isNewTask(url); } public void init(){ try { URL mUrl = new URL(this.url); HttpURLConnection connection = (HttpURLConnection)mUrl.openConnection(); connection.setConnectTimeout(5000); connection.setRequestMethod("GET"); fileSize = connection.getContentLength(); File file = new File(localPath); if(!file.exists()){ file.createNewFile(); } RandomAccessFile accessFile = new RandomAccessFile(file, "rwd"); accessFile.setLength(fileSize); accessFile.close(); connection.disconnect(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void download(){ if(infos != null){ if(state ==DOWNLOADING){ return; } state = DOWNLOADING; for(DownloadInfo info:infos){ new DownloadThread(info.getThreadId(), info.getStartPos(), info.getEndPos(), info.getCompleteSize(), info.getUrl()).start(); } } } class DownloadThread extends Thread{ private int threadId; private int startPos; private int endPos; private int completeSize; private String url; public DownloadThread(int tId,int sp,int ep,int cSize,String address) { // TODO Auto-generated constructor stub threadId=tId; startPos=sp; endPos = ep; completeSize = cSize; url = address; } @Override public void run() { // TODO Auto-generated method stub HttpURLConnection connection = null; RandomAccessFile randomAccessFile = null; InputStream is = null; try { URL mUrl = new URL(url); connection = (HttpURLConnection)mUrl.openConnection(); connection.setConnectTimeout(5000); connection.setRequestMethod("GET"); connection.setRequestProperty("Range", "bytes="+(startPos+completeSize)+"-"+endPos); randomAccessFile = new RandomAccessFile(localPath, "rwd"); randomAccessFile.seek(startPos+completeSize); is=connection.getInputStream(); byte[] buffer = new byte[4096]; int length =-1; while((length=is.read(buffer)) != -1){ randomAccessFile.write(buffer, 0, length); completeSize +=length; dao.updateInfo(threadId, completeSize, url); Message msg = Message.obtain(); msg.what=1; msg.obj=url; msg.arg1=length; mHanler.sendMessage(msg); if(state==PAUSE){ return; } } } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ try { is.close(); randomAccessFile.close(); connection.disconnect(); dao.closeDB(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public void delete(String url){ dao.deleteInfo(url); } public void reset(){ state=INIT; } public void pause(){ state=PAUSE; } }
View呈现类:MainActivity.java
package com.winton.downloadmanager; import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import entity.LoadInfo; public class MainActivity extends Activity implements OnClickListener{ private TextView name; private ProgressBar process; private Button start,stop; private Downloader downloader; //处理下载进度UI的 Handler handler = new Handler(){ public void handleMessage(android.os.Message msg) { if(msg.what==1){ name.setText((String)msg.obj); int lenght = msg.arg1; process.incrementProgressBy(lenght); } if(msg.what==2){ int max =msg.arg1; process.setMax(max); Toast.makeText(getApplicationContext(), max+"", 1).show(); } }; }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); name=(TextView)findViewById(R.id.tv_name); process=(ProgressBar)findViewById(R.id.pb_download); start = (Button)findViewById(R.id.bt_start); stop = (Button)findViewById(R.id.bt_stop); start.setOnClickListener(this); stop.setOnClickListener(this); downloader=new Downloader("http://img4.duitang.com/uploads/item/201206/11/20120611174542_5KRMj.jpeg", Environment.getExternalStorageDirectory().getPath()+"/test1.jpg", 1, getApplicationContext(), handler); } @Override public void onClick(View v) { // TODO Auto-generated method stub if(v==start){ new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub LoadInfo loadInfo = downloader.getDownLoadInfos(); Message msg =handler.obtainMessage(); msg.what=2; msg.arg1=loadInfo.getFileSize(); handler.sendMessage(msg); downloader.download(); } }).start(); return; } if(v==stop){ downloader.pause(); return; } } }
运行效果
总体比较简单,基本实现了 断点下载的功能,而且开启了多个线程去实现分块下载,对初学的同学有一定的帮助。
这些代码我也是从网上各种搜集而来,自己亲自动手敲了一遍,并做了一些小的改动,对整个断点下载的过程有了一个深刻的认识。因此平时要多敲代码,善于总结,必能有所收获。