转载请注明出处:http://blog.csdn.net/l1028386804/article/details/46897641

一、概述

在上一篇博文《Android之——多线程下载示例》中,我们讲解了如何实现Android的多线程下载功能,通过将整个文件分成多个数据块,开启多个线程,让每个线程分别下载一个相应的数据块来实现多线程下载的功能。多线程下载中,可以将下载这个耗时的操作放在子线程中执行,即不阻塞主线程,又符合Android开发的设计规范。

但是当下载的过程当中突然出现手机卡死,或者网络中断,手机电量不足关机的现象,这时,当手机可以正常使用后,如果重新下载文件,似乎不太符合大多数用户的心理期望,那如何实现当手机可以正常联网时,基于上次断网时下载的数据来下载呢?这就是所谓的断点下载了。这篇文章主要是讲解如何实现断点下载的功能。

本文讲解的Android断点下载是基于上一篇文章《Android之——多线程下载示例》,本示例是在上一示例的基础上通过在下载的过程中,将下载的信息保存到Andoid系统自带的数据库SQLite中,当手机出现异常情况而断开网络时,由于数据库中记录了上次下载的数据信息,当手机再次联网时,读取数据库中的信息,从上次断开下载的地方继续下载数据。好,不多说了,进入正文。

二、服务端准备

服务端的实现很简单,这里为了使下载的文件大些,我在网络上下载了有道词典来作为要下载的测试资源。将它放置在项目的WebContent目录下,并将项目发布在Tomcat服务器中,具体如下图所示:

就这样,服务端算是弄好了,怎么样?很简单吧?相信大家都会的!

三、Android实现

Android实现部分是本文的重点,这里我们从布局开始由浅入深慢慢讲解,这里我们通过Activity来显示程序的界面,以SQLite数据库来保存下载的信息,通过ContentProvider来操作保存的记录信息,通过Handler和Message机制将子线程中的数据传递到主线程来更新UI显示。同时通过自定义监听器来实现对UI显示更新的监听操作。

1、布局实现

布局基本上和上一博文中的布局一样,没有什么大的变动,界面上自上而下放置一个TextView,用来提示文本框中输入的信息,一个文本框用来输入网络中下载文件的路径,一个Button按钮,点击下载文件,一个ProgressBar显示下载进度,一个TextView显示下载的百分比。

具体布局内容如下:

                

2、自定义ProgressBarListener监听器接口

新建自定义ProgressBarListener监听器接口,这个接口中定义两个方法,void getMax(int length)用来获取下载文件的长度,void getDownload(int length);用来获取每次下载的长度,这个方法中主要是在多线程中调用,子线程中获取到的数据传递到这两个接口方法中,然后在这两个接口方法中通过Handler将相应的长度信息传递到主线程,更新界面显示信息。

具体代码实现如下:

package com.example.inter;/** * 自定义进度条监听器 * @author liuyazhuang * */public interface ProgressBarListener {/** * 获取文件的长度 * @param length */void getMax(int length);/** * 获取每次下载的长度 * @param length */void getDownload(int length);}

3.定义数据库的相关信息类DownloadDBHelper

在这个实例中,我们将数据库的名称定义为download.db,我们需要保存主键id,文件下载后要保存的路径,每个线程的标识id,每个线程下载的文件数据块大小,所以,在创建的数据表中共有_id, path,threadid,downloadlength,详情见下图

DownloadDBHelper实现的具体代码如下:

package com.example.db;import android.content.Context;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteDatabase.CursorFactory;import android.database.sqlite.SQLiteOpenHelper;/** * 数据库相关类 * @author liuyazhuang * */public class DownloadDBHelper extends SQLiteOpenHelper {/** * 数据库名称 */private static final String NAME = "download.db";/** * 原有的构造方法 * @param context * @param name * @param factory * @param version */public DownloadDBHelper(Context context, String name,CursorFactory factory, int version) {super(context, name, factory, version);}/** * 重载构造方法 * @param context */public DownloadDBHelper(Context context){super(context, NAME, null, 1);}/**  * 创建数据库时调用 */@Overridepublic void onCreate(SQLiteDatabase db) { db.execSQL("create table download(_id integer primary key autoincrement," +        "path text," +        "threadid integer," +        "downloadlength integer)");}/**  * 更新数据库时调用 */@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}}

4、创建DownloadProvider类

DownloadProvider类继承自ContentProvider,提供操作数据库的方法,在这个类中,通过UriMatcher类匹配要操作的数据库,通过DownloadDBHelper对象来得到一个具体数据库实例,来对相应的数据库进行增、删、改、查操作。

具体实现如下代码所示:

package com.example.provider;import com.example.db.DownloadDBHelper;import android.content.ContentProvider;import android.content.ContentValues;import android.content.UriMatcher;import android.database.Cursor;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteOpenHelper;import android.net.Uri;/** * 自定义ContentProvider实例 * @author liuyazhuang * */public class DownloadProvider extends ContentProvider {//实例化UriMatcher对象private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);//配置访问规则private static final String AUTHORITY = "download";//自定义常量private static final int DOWANLOAD = 10;static{//添加匹配的规则matcher.addURI(AUTHORITY, "download", DOWANLOAD);}private SQLiteOpenHelper mOpenHelper;@Overridepublic boolean onCreate() {mOpenHelper = new DownloadDBHelper(getContext());return false;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {// TODO Auto-generated method stubCursor ret = null;SQLiteDatabase db = mOpenHelper.getReadableDatabase();int code = matcher.match(uri);switch (code) {case DOWANLOAD:ret = db.query("download", projection, selection, selectionArgs, null, null, sortOrder);break;default:break;}return ret;}@Overridepublic String getType(Uri uri) {// TODO Auto-generated method stubreturn null;}@Overridepublic Uri insert(Uri uri, ContentValues values) {// TODO Auto-generated method stubSQLiteDatabase db = mOpenHelper.getWritableDatabase();int code = matcher.match(uri);switch (code) {case DOWANLOAD:db.insert("download", "_id", values);break;default:break;}return null;}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {SQLiteDatabase db = mOpenHelper.getWritableDatabase();int code = matcher.match(uri);switch (code) {case DOWANLOAD:db.delete("download", selection, selectionArgs);break;default:break;}return 0;}@Overridepublic int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {SQLiteDatabase db = mOpenHelper.getWritableDatabase();int code = matcher.match(uri);switch (code) {case DOWANLOAD:db.update("download", values, selection, selectionArgs);break;default:break;}return 0;}}

5、创建DownloadInfo实体类

为了使程序更加面向对象化,这里我们建立DownloadInfo实体类来对数据库中的数据进行封装,DownloadInfo实体类中的数据字段与数据库中的字段相对应

具体实现代码如下:

package com.example.domain;/** * 支持断点续传时, * 要保存到数据库的信息 * @author liuyazhuang * */public class DownloadInfo {//主键idprivate int _id;//保存路径private String path;//线程的标识idprivate String threadId;//下载文件的大小private int downloadSize;public DownloadInfo() {super();}public DownloadInfo(int _id, String path, String threadId, int downloadSize) {super();this._id = _id;this.path = path;this.threadId = threadId;this.downloadSize = downloadSize;}public int get_id() {return _id;}public void set_id(int _id) {this._id = _id;}public String getPath() {return path;}public void setPath(String path) {this.path = path;}public String getThreadId() {return threadId;}public void setThreadId(String threadId) {this.threadId = threadId;}public int getDownloadSize() {return downloadSize;}public void setDownloadSize(int downloadSize) {this.downloadSize = downloadSize;}}

6、定义外界调用的操作数据库的方法类DownloadDao

DownloadDao类中封装了一系列操作数据库的方法,这个类不是直接操作数据库对象,而是通过ContentResolver这个对象来调用DownloadProvider中的方法来实现操作数据库的功能,这里用到了ContentResolver与ContentProvider这两个Android中非常重要的类。ContentProvider即内容提供者,主要是向外提供数据,简单理解就是一个应用程序可以通过ContentProvider向外提供操作本应用程序的接口,其他应用程序可以调用ContentProvider提供的接口来操作本应用程序的数据。ContentResolver内容接接收者,它可以接收ContentProvider的向外提供的数据。

具体代码实现如下:

package com.example.dao;import android.content.ContentResolver;import android.content.ContentValues;import android.content.Context;import android.database.Cursor;import android.net.Uri;import com.example.domain.DownloadInfo;/** * 保存下载文件信息的dao类 * @author liuyazhuang * */public class DownloadDao {/** * ContentResolver对象 */private ContentResolver cr;public DownloadDao(Context context){this.cr = context.getContentResolver();}/** * 保存下载信息记录 * @param info */public void save(DownloadInfo info){Uri uri = Uri.parse("content://download/download");ContentValues values = new ContentValues();values.put("path", info.getPath());values.put("threadid", info.getThreadId());cr.insert(uri, values);}/** * 更新下载信息记录 * @param info */public void update(DownloadInfo info){Uri uri = Uri.parse("content://download/download");ContentValues values = new ContentValues();values.put("downloadlength", info.getDownloadSize());values.put("threadid", info.getThreadId());cr.update(uri, values, " path = ? and threadid = ? ", new String[]{info.getPath(), info.getThreadId()});}/** * 删除下载信息记录 * @param info */public void delete(DownloadInfo info){Uri uri = Uri.parse("content://download/download");cr.delete(uri, " path = ? and threadid = ? ", new String[]{info.getPath(), info.getThreadId()});}/** * 删除下载信息记录 * @param info */public void delete(String path){Uri uri = Uri.parse("content://download/download");cr.delete(uri, " path = ? ", new String[]{path});}/** * 判断是否有下载记录 * @param path * @return */public boolean isExist(String path){boolean result = false;Uri uri = Uri.parse("content://download/download");Cursor cursor = cr.query(uri, null, " path = ? ", new String[]{path}, null);if(cursor.moveToNext()){result = true;}cursor.close();return result;}/** * 计算所有的下载长度 * @param path * @return */public int queryCount(String path){int count = 0;Uri uri = Uri.parse("content://download/download");Cursor cursor = cr.query(uri, new String[]{"downloadlength"}, " path = ? ", new String[]{path}, null);while(cursor.moveToNext()){int len = cursor.getInt(0);count += len;}cursor.close();return count;}/** * 计算每个线程的下载长度 * @param path * @return */public int query(DownloadInfo info){int count = 0;Uri uri = Uri.parse("content://download/download");Cursor cursor = cr.query(uri, new String[]{"downloadlength"}, " path = ? and threadid = ?", new String[]{info.getPath(), info.getThreadId()}, null);while(cursor.moveToNext()){int len = cursor.getInt(0);count += len;}cursor.close();return count;}}

7、自定义线程类DownThread

这里通过继承Thread的方式来实现自定义线程操作,在这个类中主要是实现文件的下载操作,在这个类中,定义了一系列与下载有关的实例变量来控制下载的数据,通过自定义监听器ProgressBarListener中的void getDownload(int length)方法来跟新界面显示的进度信息,同时通过调用DownloadDao的方法来记录和更新数据的下载信息。

具体实现代码如下:

package com.example.download;import java.io.File;import java.io.InputStream;import java.io.RandomAccessFile;import java.net.HttpURLConnection;import java.net.URL;import android.content.Context;import com.example.dao.DownloadDao;import com.example.domain.DownloadInfo;import com.example.inter.ProgressBarListener;/** * 自定义线程类 * @author liuyazhuang * */public class DownloadThread extends Thread {//下载的线程idprivate int threadId;//下载的文件路径private String path;//保存的文件private File file;//下载的进度条更新的监听器private ProgressBarListener listener;//每条线程下载的数据量private int block;//下载的开始位置private int startPosition;//下载的结束位置private int endPosition;private DownloadDao downloadDao;public DownloadThread(int threadId, String path, File file, ProgressBarListener listener, int block, Context context) {this.threadId = threadId;this.path = path;this.file = file;this.listener = listener;this.block = block;this.downloadDao = new DownloadDao(context);this.startPosition = threadId * block;this.endPosition = (threadId + 1) * block - 1;}@Overridepublic void run() {super.run();try {//判断该线程是否有下载记录DownloadInfo info = new DownloadInfo();info.setPath(path);info.setThreadId(String.valueOf(threadId));int length =  downloadDao.query(info);startPosition += length;//创建RandomAccessFile对象RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");//跳转到开始位置accessFile.seek(startPosition);URL url = new URL(path);//打开http链接HttpURLConnection conn  = (HttpURLConnection) url.openConnection();//设置超时时间conn.setConnectTimeout(5000);//指定请求方式为GET方式conn.setRequestMethod("GET");//指定下载的位置conn.setRequestProperty("Range", "bytes="+startPosition + "-" + endPosition);//不用再去判断状态码是否为200InputStream in = conn.getInputStream();byte[] buffer = new byte[1024];int len = 0;//该线程下载的总数据量int count = length;while((len = in.read(buffer)) != -1){accessFile.write(buffer, 0, len);//更新下载进度listener.getDownload(len);count += len;info.setDownloadSize(count);//更新下载的信息downloadDao.update(info);}accessFile.close();in.close();} catch (Exception e) {// TODO: handle exceptione.printStackTrace();}}}

8、新建下载的管理类DownloadManager

这个类主要是对下载过程的管理,包括下载设置下载后文件要保存的位置,计算多线程中每个线程的数据下载量等等,同时相比《Android之——多线程下载示例》一文中,它多了多下载数据的记录与更新操作。

具体实现代码如下:

package com.example.download;import java.io.File;import java.io.RandomAccessFile;import java.net.HttpURLConnection;import java.net.URL;import android.content.Context;import android.os.Environment;import com.example.dao.DownloadDao;import com.example.domain.DownloadInfo;import com.example.inter.ProgressBarListener;/** * 文件下载管理器 * @author liuyazhuang * */public class DownloadManager {//下载线程的数量private static final int TREAD_SIZE = 3;private File file;private DownloadDao downloadDao;private Context context;public DownloadManager(Context context) {this.context = context;this.downloadDao = new DownloadDao(context);}/** * 下载文件的方法 * @param path:下载文件的路径 * @param listener:自定义的下载文件监听接口 * @throws Exception */public void download(String path, ProgressBarListener listener) throws Exception{URL url = new URL(path);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setConnectTimeout(5000);conn.setRequestMethod("GET");if(conn.getResponseCode() == 200){int filesize = conn.getContentLength();//设置进度条的最大长度listener.getMax(filesize);//判断下载记录是否存在boolean ret = downloadDao.isExist(path);if(ret){//得到下载的总长度,设置进度条的刻度int count = downloadDao.queryCount(path);listener.getDownload(count);}else{//保存下载记录for(int i = 0; i < filesize; i++){DownloadInfo info = new DownloadInfo();info.setPath(path);info.setThreadId(String.valueOf(i));//保存下载的记录信息downloadDao.save(info);}}//创建一个和服务器大小一样的文件file = new File(Environment.getExternalStorageDirectory(), this.getFileName(path));RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");accessFile.setLength(filesize);//要关闭RandomAccessFile对象accessFile.close();//计算出每条线程下载的数据量int block = filesize % TREAD_SIZE == 0 ? (filesize / TREAD_SIZE) : (filesize / TREAD_SIZE +1 ); //开启线程下载for(int i = 0; i < TREAD_SIZE; i++){new DownloadThread(i, path, file, listener, block, context).start();}}}/** * 截取路径中的文件名称 * @param path:要截取文件名称的路径 * @return:截取到的文件名称 */private String getFileName(String path){return path.substring(path.lastIndexOf("/") + 1);}}

9、完善MainActivity

在这个类中首先,找到页面中的各个控件,实现Button按钮的onClick事件,在onClick事件中开启一个线程进行下载操作,同时子线程中获取到的数据,通过handler与Message机制传递到主线程,更新界面显示,利用DownloadDao类中的方法来记录和更新下载数据。

具体实现代码如下:

package com.example.multi;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.view.Menu;import android.view.View;import android.widget.EditText;import android.widget.ProgressBar;import android.widget.TextView;import android.widget.Toast;import com.example.dao.DownloadDao;import com.example.download.DownloadManager;import com.example.inter.ProgressBarListener;/** * MainActivity整个应用程序的入口 * @author liuyazhuang * */public class MainActivity extends Activity {protected static final int ERROR_DOWNLOAD = 0;protected static final int SET_PROGRESS_MAX = 1;protected static final int UPDATE_PROGRESS = 2;private EditText ed_path;private ProgressBar pb;private TextView tv_info;private DownloadManager manager;private DownloadDao downloadDao;//handler操作private Handler mHandler = new Handler(){public void handleMessage(android.os.Message msg) {switch (msg.what) {case ERROR_DOWNLOAD://提示用户下载失败Toast.makeText(MainActivity.this, "下载失败", Toast.LENGTH_SHORT).show();break;case SET_PROGRESS_MAX://得到最大值int max = (Integer) msg.obj;//设置进度条的最大值pb.setMax(max);break;case UPDATE_PROGRESS://获取当前下载的长度int currentprogress = pb.getProgress();//获取新下载的长度int len = (Integer) msg.obj;//计算当前总下载长度int crrrentTotalProgress = currentprogress + len;pb.setProgress(crrrentTotalProgress);//获取总大小int maxProgress = pb.getMax();//计算百分比float value = (float)currentprogress / (float)maxProgress;int percent = (int) (value * 100);//显示下载的百分比tv_info.setText("下载:"+percent+"%");if(maxProgress == crrrentTotalProgress){//删除下载记录downloadDao.delete(ed_path.getText().toString());}break;default:break;}};};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);this.ed_path = (EditText) super.findViewById(R.id.ed_path);this.pb = (ProgressBar) super.findViewById(R.id.pb);this.tv_info = (TextView) super.findViewById(R.id.tv_info);this.manager = new DownloadManager(this);this.downloadDao = new DownloadDao(this);}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}public void download(View v){final String path = ed_path.getText().toString();//下载new Thread(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubtry {manager.download(path, new ProgressBarListener() {@Overridepublic void getMax(int length) {// TODO Auto-generated method stubMessage message = new Message();message.what = SET_PROGRESS_MAX;message.obj = length;mHandler.sendMessage(message);}@Overridepublic void getDownload(int length) {// TODO Auto-generated method stubMessage message = new Message();message.what = UPDATE_PROGRESS;message.obj = length;mHandler.sendMessage(message);}});} catch (Exception e) {// TODO: handle exceptione.printStackTrace();Message message = new Message();message.what = ERROR_DOWNLOAD;mHandler.sendMessage(message);}}}).start();}}

10、增加权限

最后,别忘了给应用授权,这里要用到Android联网授权和向SD卡中写入文件的权限。

具体实现如下:

<?xml version="1.0" encoding="utf-8"?>                                                                                            

四、运行效果

此时,关闭模拟器,再次打开点击下载后

如上:实现了Android中的断点下载功能。

提醒:大家可以到http://download.csdn.net/detail/l1028386804/8903201链接来获取完整的Android断点下载示例源码

更多相关文章

  1. Android进程与线程基本知识
  2. Android开发学习总结(一)——搭建最新版本的Android开发环境
  3. Android之进程与线程
  4. Android进程与线程基本知识
  5. 解决在3.6.2安装android的ADT文件bug
  6. Android之Adapter用法总结
  7. Android(安卓)SDK Platforms 提取下载
  8. Android(安卓)MediaStore数据之增删查改剖析
  9. android访问远程数据库

随机推荐

  1. 浅析GridView制作九宫格列表
  2. android 程序 发布加密
  3. android sdk+eclipse+adt 配置与开发
  4. EditText的详细属性说明
  5. 转-Android数据存储(总结篇)
  6. 为什么Android不是GPL许可证?
  7. checkbox 与 listview 等混用焦点问题
  8. Eclipse Jar包源码关联
  9. android 学习基础篇---开发环境的搭建 Ec
  10. Android(安卓)绘制动画(波浪动画/轨迹动