Android斷點下載實現

#引言
最近做的項目中需要實現斷點下載,即用戶一次下載可以分多次進行,下載過程可以中斷,在目前大多數的帶離線緩存的軟件都是需要實現這一功能。本文闡述了通過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(List<DownloadInfo> infos){
		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<DownloadInfo> getInfo(String url){
		SQLiteDatabase db = dbHelper.getReadableDatabase();
		List<DownloadInfo> infos = new ArrayList<DownloadInfo>();
		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 List<DownloadInfo> infos;
	
	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<DownloadInfo>();
			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;
		}
	}	
}

#運行效果
這裏寫圖片描述
總體比較簡單,基本實現了 斷點下載的功能,而且開啓了多個線程去實現分塊下載,對初學的同學有一定的幫助。

#總結

這些代碼我也是從網上各種蒐集而來,自己親自動手敲了一遍,並做了一些小的改動,對整個斷點下載的過程有了一個深刻的認識。因此平時要多敲代碼,善於總結,必能有所收穫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章