Android多线程断点续传下载示例详解

一、概述

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

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

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

二、服务端准备

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

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

三、Android实现

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

1、布局实现

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

具体布局内容如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:paddingBottom="@dimen/activity_vertical_margin"
 android:paddingLeft="@dimen/activity_horizontal_margin"
 android:paddingRight="@dimen/activity_horizontal_margin"
 android:paddingTop="@dimen/activity_vertical_margin"
 android:orientation="vertical"
 tools:context=".MainActivity" > 

 <TextView
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="下载路径" /> 

 <EditText
 android:id="@+id/ed_path"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="http://192.168.0.170:8080/web/youdao.exe"/>
 <Button
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="下载"
 android:onClick="download"/> 

 <ProgressBar
 android:id="@+id/pb"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 style="@android:style/Widget.ProgressBar.Horizontal"/> 

 <TextView
 android:id="@+id/tv_info"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:gravity="center"
 android:text="下载:0%"/> 

</LinearLayout> 

 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);
 } 

 /**
 * 创建数据库时调用
 */
 @Override
 public void onCreate(SQLiteDatabase db) {
 db.execSQL("create table download(_id integer primary key autoincrement," +
  "path text," +
  "threadid integer," +
  "downloadlength integer)"); 

 }
 /**
 * 更新数据库时调用
 */
 @Override
 public 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;
 @Override
 public boolean onCreate() {
 mOpenHelper = new DownloadDBHelper(getContext());
 return false;
 } 

 @Override
 public Cursor query(Uri uri, String[] projection, String selection,
 String[] selectionArgs, String sortOrder) {
 // TODO Auto-generated method stub
 Cursor 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;
 } 

 @Override
 public String getType(Uri uri) {
 // TODO Auto-generated method stub
 return null;
 } 

 @Override
 public Uri insert(Uri uri, ContentValues values) {
 // TODO Auto-generated method stub
 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
 int code = matcher.match(uri);
 switch (code) {
 case DOWANLOAD:
 db.insert("download", "_id", values);
 break; 

 default:
 break;
 }
 return null;
 } 

 @Override
 public 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;
 } 

 @Override
 public 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 {
 //主键id
 private int _id;
 //保存路径
 private String path;
 //线程的标识id
 private 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 {
 //下载的线程id
 private 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;
 } 

 @Override
 public 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);
 //不用再去判断状态码是否为200
 InputStream 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 exception
 e.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;
 }
 };
 };
 @Override
 protected 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);
 } 

 @Override
 public 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() {
 @Override
 public void run() {
 // TODO Auto-generated method stub
 try {
  manager.download(path, new ProgressBarListener() {
  @Override
  public void getMax(int length) {
  // TODO Auto-generated method stub
  Message message = new Message();
  message.what = SET_PROGRESS_MAX;
  message.obj = length;
  mHandler.sendMessage(message);
  } 

  @Override
  public void getDownload(int length) {
  // TODO Auto-generated method stub
  Message message = new Message();
  message.what = UPDATE_PROGRESS;
  message.obj = length;
  mHandler.sendMessage(message);
  }
  });
 } catch (Exception e) {
  // TODO: handle exception
  e.printStackTrace();
  Message message = new Message();
  message.what = ERROR_DOWNLOAD;
  mHandler.sendMessage(message);
 }
 }
 }).start();
 }
}

10、增加权限

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

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="com.example.multi"
 android:versionCode="1"
 android:versionName="1.0" > 

 <uses-sdk
 android:minSdkVersion="8"
 android:targetSdkVersion="18" />
 <uses-permission android:name="android.permission.INTERNET"/>
 <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 <application
 android:allowBackup="true"
 android:icon="@drawable/ic_launcher"
 android:label="@string/app_name"
 android:theme="@style/AppTheme" >
 <activity
 android:name="com.example.multi.MainActivity"
 android:label="@string/app_name" >
 <intent-filter>
 <action android:name="android.intent.action.MAIN" /> 

 <category android:name="android.intent.category.LAUNCHER" />
 </intent-filter>
 </activity>
 <provider android:name="com.example.provider.DownloadProvider" android:authorities="download"></provider>
 </application> 

</manifest>

四、运行效果

如上:实现了Android中的断点下载功能。
提醒:大家可以到这个链接来获取完整的Android断点下载示例源码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • PC版与Android手机版带断点续传的多线程下载

    一.多线程下载 多线程下载就是抢占服务器资源 原理:服务器CPU 分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源. 1.设置开启线程数,发送http请求到下载地址,获取下载文件的总长度           然后创建一个长度一致的临时文件,避免下载到一半存储空间不够了,并计算每个线程下载多少数据              2.计算每个线程下载数据的开始和结束位置           再次发送请求,用 Range 头请求开始位置和结束位

  • Android多线程+单线程+断点续传+进度条显示下载功能

    效果图 白话分析: 多线程:肯定是多个线程咯 断点:线程停止下载的位置 续传:线程从停止下载的位置上继续下载,直到完成任务为止. 核心分析: 断点: 当前线程已经下载的数据长度 续传: 向服务器请求上次线程停止下载位置的数据 con.setRequestProperty("Range", "bytes=" + start + "-" + end); 分配线程: int currentPartSize = fileSize / mThreadNum

  • Android实现网络多线程断点续传下载功能

    我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多线程断点需要什么功能? 1.多线程下载 2.支持断点 使用多线程的好处:使用多线程下载会提升文件下载的速度 原理 多线程下载的原理就是将要下载的文件分成若干份,其中每份都使用一个单独的线程进行下载,这样对于文件的下载速度自然就提高了许多. 既然要分成若干部分分工下载,自然要知道各个线程自己要下载的起始位置,与要下载的大小.所以我们要解决线程的分配与各个线程定位到下载

  • android实现多线程断点续传功能

    本文实例为大家分享了android实现多线程断点续传的具体代码,供大家参考,具体内容如下 布局: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools&qu

  • Android多线程断点续传下载功能实现代码

    原理 其实断点续传的原理很简单,从字面上理解,所谓断点续传就是从停止的地方重新下载. 断点:线程停止的位置. 续传:从停止的位置重新下载. 用代码解析就是: 断点:当前线程已经下载完成的数据长度. 续传:向服务器请求上次线程停止位置之后的数据. 原理知道了,功能实现起来也简单.每当线程停止时就把已下载的数据长度写入记录文件,当重新下载时,从记录文件读取已经下载了的长度.而这个长度就是所需要的断点. 续传的实现也简单,可以通过设置网络请求参数,请求服务器从指定的位置开始读取数据. 而要实现这两个功

  • Android FTP 多线程断点续传下载\上传的实例

    最近在给我的开源下载框架Aria增加FTP断点续传下载和上传功能,在此过程中,爬了FTP的不少坑,终于将功能实现了,在此把一些核心功能点记录下载. FTP下载原理 FTP单线程断点续传 FTP和传统的HTTP协议有所不同,由于FTP没有所谓的头文件,因此我们不能像HTTP那样通过设置header向服务器指定下载区间. 但是FTP协议提供了一个更好用的命令REST用于从指定位置恢复任务,同时FTP协议也提供了一个命令SIZE用于获取下载的文件大小,有了这两个命令,FTP断点续传也就没有什么问题.

  • android实现多线程下载文件(支持暂停、取消、断点续传)

    多线程下载文件(支持暂停.取消.断点续传) 多线程同时下载文件即:在同一时间内通过多个线程对同一个请求地址发起多个请求,将需要下载的数据分割成多个部分,同时下载,每个线程只负责下载其中的一部分,最后将每一个线程下载的部分组装起来即可. 涉及的知识及问题 请求的数据如何分段 分段完成后如何下载和下载完成后如何组装到一起 暂停下载和继续下载的实现(wait().notifyAll().synchronized的使用) 取消下载和断点续传的实现 一.请求的数据如何分段 首先通过HttpURLConne

  • Android编程开发实现多线程断点续传下载器实例

    本文实例讲述了Android编程开发实现多线程断点续传下载器.分享给大家供大家参考,具体如下: 使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线.电量不足等情况下,这就需要使用到断点续传功能,下次启动时从记录位置继续下载,可避免重复部分的下载.这里采用数据库来记录下载的进度. 效果图:   断点续传 1.断点续传需要在下载过程中记录每条线程的下载进度 2.每次下载开始之前先读取数据库

  • Android实现网络多线程断点续传下载实例

    我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多线程断点需要什么功能? 1.多线程下载, 2.支持断点. 使用多线程的好处:使用多线程下载会提升文件下载的速度.那么多线程下载文件的过程是: (1)首先获得下载文件的长度,然后设置本地文件的长度. HttpURLConnection.getContentLength();//获取下载文件的长度 RandomAccessFile file = new RandomAc

  • Android多线程断点续传下载示例详解

    一.概述 在上一篇博文<Android多线程下载示例>中,我们讲解了如何实现Android的多线程下载功能,通过将整个文件分成多个数据块,开启多个线程,让每个线程分别下载一个相应的数据块来实现多线程下载的功能.多线程下载中,可以将下载这个耗时的操作放在子线程中执行,即不阻塞主线程,又符合Android开发的设计规范. 但是当下载的过程当中突然出现手机卡死,或者网络中断,手机电量不足关机的现象,这时,当手机可以正常使用后,如果重新下载文件,似乎不太符合大多数用户的心理期望,那如何实现当手机可以正

  • iOS开发探索多线程GCD队列示例详解

    目录 引言 进程与线程 1.进程的定义 2.线程的定义 3. 进程和线程的关系 4. 多线程 5. 时间片 6. 线程池 GCD 1.任务 2.队列 3.死锁 总结 引言 在iOS开发过程中,绕不开网络请求.下载图片之类的耗时操作,这些操作放在主线程中处理会造成卡顿现象,所以我们都是放在子线程进行处理,处理完成后再返回到主线程进行展示. 多线程贯穿了我们整个的开发过程,iOS的多线程操作有NSThread.GCD.NSOperation,其中我们最常用的就是GCD. 进程与线程 在了解GCD之前

  • JavaScript进阶之前端文件上传和下载示例详解

    目录 文件下载 1.通过a标签点击直接下载 2.open或location.href 3.Blob和Base64 文件上传 文件上传思路 File文件 上传单个文件-客户端 上传文件-服务端 多文件上传-客户端 大文件上传-客户端 大文件上传-服务端 文件下载 1.通过a标签点击直接下载 <a href="https:xxx.xlsx" rel="external nofollow" download="test">下载文件</

  • kotlin android extensions 插件实现示例详解

    目录 前言 原理浅析 总体结构 源码分析 插件入口 配置编译器插件传参 编译器插件接收参数 注册各种Extension IrGenerationExtension ExpressionCodegenExtension StorageComponentContainerContributor ClassBuilderInterceptorExtension PackageFragmentProviderExtension 总结 前言 kotlin-android-extensions 插件是 Ko

  • iOS开发探索多线程GCD任务示例详解

    目录 引言 同步任务 死锁 异步任务 总结 引言 在上一篇文章中,我们探寻了队列是怎么创建的,串行队列和并发队列之间的区别,接下来我们在探寻一下GCD的另一个核心 - 任务 同步任务 void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block); 我们先通过lldb查看其堆栈信息,分别查看其正常运行和死锁状态的信息 我们再通过源码查询其实现 #define _dispatch_Block_

  • Spi机制在Android开发的应用示例详解

    目录 Spi机制介绍 举个例子 ServiceLoader.load 在Android中的应用 总结 Spi机制介绍 SPI 全称是 Service Provider Interface,是一种将服务接口与服务实现分离以达到解耦.可以提升程序可扩展性的机制.嘿嘿,看到这个概念很多人肯定是一头雾水了,没事,我们直接就可以简单理解为是一种反射机制,即我们不需要知道具体的实现方,只要定义好接口,我们就能够在运行时找到一个实现接口的类,我们具体看一下官方定义. 举个例子 加入我是一个库设计者,我希望把一

  • vue前端实现打印下载示例详解

    目录 html2canvas介绍 jspdf介绍 printjs介绍 html2canvas介绍 分享一下几个后台管理系统比较常用的插件:下载.打印 html2canvas是在浏览器上对网页进行截图操作,实际上是操作DOM,这个插件也有好长时间了,比较稳定,目前使用还没有遇到什么bug jspdf介绍 如果下载出来是pdf文件,可以加上jspdf插件,会先通过html2canvas把页面转化成base64图片,再通过jspdf导出 安装 npm i html2canvas jspdf 或 yar

  • android中webview定位问题示例详解

    前言 现在很多App里都内置了Web网页(Hyprid App),比如说很多电商平台,淘宝.京东.聚划算等等 京东首页 那么这种该如何实现呢?其实这是Android里一个叫WebView的组件实现的. 最近在做安卓的网页开发.有一个页面需要用到定位,但是一直定位获取失败.很难过.网上教程也很多,但是无一例外全部失败.最后老夫花了3天时间,呕心沥血,终于研制出了解决方案. 三步走战略: 一.获取权限 android 6.0 以后,需要动态的获取位置或者存储权限,按照各自的爱好放置位置.我是应用开启

  • Android多线程下载示例详解

    一.概述 说到Android中的文件下载,Android API中明确要求将耗时的操作放到一个子线程中执行,文件的下载无疑是需要耗费时间的,所以要将文件的下载放到子线程中执行.下面,我们一起来实现一个Android中利用多线程下载文件的小例子. 二.服务端准备 在这个小例子中我以下载有道词典为例,在网上下载有道词典的安装包,在eclipse中新建项目web,将下载的有道词典安装包放置在WebContent目录下,并将项目发布到Tomcat中,具体如下图所示 三.Android实现 1.布局 界面

随机推荐