Android之—多线程断点续传下载

本文介绍了一个基于Android平台的多线程断点续传文件下载系统。通过划分文件为多个部分并行下载,提高下载效率。利用数据库记录下载进度,支持断点续传,确保下载稳定性。

        

Android之——多线程断点续传下载示例

文件下载在App应用中也用到很多,一般版本更新时多要用的文件下载来进行处理,以前也有看过很多大神有过该方面的博客,今天我也自己来实践一下,写的一般,还请大家多提意见,共同进步。主要思路:

1.多线程下载:

首先通过下载总线程数来划分文件的下载区域:利用int range = fileSize / threadCount;得到每一段下载量;每一段的位置是i * range到(i + 1) * rang - 1,注意最后一段的位置是到filesize - 1;

通过Http协议的Range字段实现下载文件的分段;

通过Java类RandomAccessFile可以实现文件的随机访问,利用seek方法定位的文件的指定位置;

由HttpUrlConnection获取流来进行流的读写,实现文件的存储;

在下载过程中利用Handler来向外传递下载的信息。

2.断点续传:

对于每一个线程利用一个DownloadInfo类来保存下载的信息,每次在下载过程中向数据库更新信息(我也有想过只在下载暂停时进行更新,但那样的话我们的进程被杀掉时信息就无法保存下来)。在进行下载之前去访问数据库是否有记录存在,如果没有执行第一次下载的初始化,如果存在记录但下载文件不存在时,删掉数据库中的记录之后进行第一次下载的初始化,如果有记录且文件存在,则从数据库中取出信息。

实现的效果如图,自己封装的类提供了开始,暂停,删除,以及重新下载的方法。还没来得及将工程穿上CSDN,给大家一个百度云盘的下载地址:https://pan.baidu.com/s/1dD1Xo8T

\


package com.example.test;
 
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
 
/**
 * 利用数据库来记录下载信息
 * @author acer
 */
public class DownLoadHelper extendsSQLiteOpenHelper{
 
    privatestaticfinal String SQL_NAME ="download.db";
    privatestaticfinal int DOWNLOAD_VERSION=1;
     
    publicDownLoadHelper(Context context) {
        super(context, SQL_NAME,null, DOWNLOAD_VERSION);
        // TODO Auto-generated constructor stub
    }
     
     /**
     * 在download.db数据库下创建一个download_info表存储下载信息
     */
    @Override
    publicvoidonCreate(SQLiteDatabase db) {
        db.execSQL("create table download_info(_id integer PRIMARY KEY AUTOINCREMENT, thread_id integer, "
                +"start_pos integer, end_pos integer, compelete_size integer,url char)");
    }
    @Override
    publicvoidonUpgrade(SQLiteDatabase db,intoldVersion,int newVersion) {
 
    }
 
}

DownlaodSqlTool进行数据的插入更新删除等操作

package com.example.test;
 
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
 
/**
 *
 * 数据库操作工具类
 */
public class DownlaodSqlTool {
    privateDownLoadHelper dbHelper;
 
    publicDownlaodSqlTool(Context context) {
        dbHelper =newDownLoadHelper(context);
    }
 
    /**
     * 创建下载的具体信息
     */
    publicvoidinsertInfos(List<downloadinfo> infos) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        for(DownloadInfo info : infos) {
            String sql ="insert into download_info(thread_id,start_pos, end_pos,compelete_size,url) values (?,?,?,?,?)";
            Object[] bindArgs = { info.getThreadId(), info.getStartPos(),
                    info.getEndPos(), info.getCompeleteSize(), info.getUrl() };
            database.execSQL(sql, bindArgs);
        }
    }
 
    /**
     * 得到下载具体信息
     */
    publicList<downloadinfo> getInfos(String urlstr) {
        List<downloadinfo> list =newArrayList<downloadinfo>();
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        String sql ="select thread_id, start_pos, end_pos,compelete_size,url from download_info where url=?";
        Cursor cursor = database.rawQuery(sql,newString[] { urlstr });
        while(cursor.moveToNext()) {
            DownloadInfo info =newDownloadInfo(cursor.getInt(0),
                    cursor.getInt(1), cursor.getInt(2), cursor.getInt(3),
                    cursor.getString(4));
            list.add(info);
        }
        returnlist;
    }
 
    /**
     * 更新数据库中的下载信息
     */
    publicvoidupdataInfos(intthreadId,int compeleteSize, String urlstr) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        String sql ="update download_info set compelete_size=? where thread_id=? and url=?";
        Object[] bindArgs = { compeleteSize, threadId, urlstr };
        database.execSQL(sql, bindArgs);
    }
    /**
     * 关闭数据库
     */
    publicvoidcloseDb() {
        dbHelper.close();
    }
 
    /**
     * 下载完成后删除数据库中的数据
     */
    publicvoiddelete(String url) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        database.delete("download_info","url=?",new String[] { url });
    }
}</downloadinfo></downloadinfo></downloadinfo></downloadinfo>

DownloadHttpTool进行网络下载的类

package com.example.test;
 
import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
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 android.util.Log;
 
public class DownloadHttpTool {
    /**
     *
     * 利用Http协议进行多线程下载具体实践类
     */
 
    privatestaticfinal String TAG = DownloadHttpTool.class.getSimpleName();
 
    privateintthreadCount;//线程数量
    privateString urlstr;//URL地址
    privateContext mContext;
    privateHandler mHandler;
    privateList<downloadinfo> downloadInfos;//保存下载信息的类
 
    privateString localPath;//目录
    privateString fileName;//文件名
    privateintfileSize;
    privateDownlaodSqlTool sqlTool;//文件信息保存的数据库操作类
 
    privateenumDownload_State {
        Downloading, Pause, Ready;//利用枚举表示下载的三种状态
    }
 
    privateDownload_State state = Download_State.Ready;//当前下载状态
 
    privateintglobalCompelete = 0;//所有线程下载的总数
 
    publicDownloadHttpTool(intthreadCount, String urlString,
            String localPath, String fileName, Context context, Handler handler) {
        super();
        this.threadCount = threadCount;
        this.urlstr = urlString;
        this.localPath = localPath;
        this.mContext = context;
        this.mHandler = handler;
        this.fileName = fileName;
        sqlTool =newDownlaodSqlTool(mContext);
    }
 
    //在开始下载之前需要调用ready方法进行配置
    publicvoidready() {
        Log.w(TAG,"ready");
        globalCompelete =0;
        downloadInfos = sqlTool.getInfos(urlstr);
        if(downloadInfos.size() ==0) {
            initFirst();
        }else{
            File file =newFile(localPath +"/"+ fileName);
            if(!file.exists()) {
                sqlTool.delete(urlstr);
                initFirst();
            }else{
                fileSize = downloadInfos.get(downloadInfos.size() -1)
                        .getEndPos();
                for(DownloadInfo info : downloadInfos) {
                    globalCompelete += info.getCompeleteSize();
                }
                Log.w(TAG,"globalCompelete:::"+ globalCompelete);
            }
        }
    }
 
    publicvoidstart() {
        Log.w(TAG,"start");
        if(downloadInfos !=null) {
            if(state == Download_State.Downloading) {
                return;
            }
            state = Download_State.Downloading;
            for(DownloadInfo info : downloadInfos) {
                Log.v(TAG,"startThread");
                newDownloadThread(info.getThreadId(), info.getStartPos(),
                        info.getEndPos(), info.getCompeleteSize(),
                        info.getUrl()).start();
            }
        }
    }
 
    publicvoidpause() {
        state = Download_State.Pause;
        sqlTool.closeDb();
    }
     
    publicvoiddelete(){
        compelete();
        File file =newFile(localPath +"/"+ fileName);
        file.delete();
    }
     
    publicvoidcompelete() {
        sqlTool.delete(urlstr);
        sqlTool.closeDb();
    }
 
    publicintgetFileSize() {
        returnfileSize;
    }
 
    publicintgetCompeleteSize() {
        returnglobalCompelete;
    }
 
    //第一次下载初始化
    privatevoidinitFirst() {
        Log.w(TAG,"initFirst");
        try{
            URL url =newURL(urlstr);
            HttpURLConnection connection = (HttpURLConnection) url
                    .openConnection();
            connection.setConnectTimeout(5000);
            connection.setRequestMethod("GET");
            fileSize = connection.getContentLength();
            Log.w(TAG,"fileSize::"+ fileSize);
            File fileParent =newFile(localPath);
            if(!fileParent.exists()) {
                fileParent.mkdir();
            }
            File file =newFile(fileParent, fileName);
            if(!file.exists()) {
                file.createNewFile();
            }
            // 本地访问文件
            RandomAccessFile accessFile =newRandomAccessFile(file,"rwd");
            accessFile.setLength(fileSize);
            accessFile.close();
            connection.disconnect();
        }catch(Exception e) {
            e.printStackTrace();
        }
        intrange = fileSize / threadCount;
        downloadInfos =newArrayList<downloadinfo>();
        for(inti = 0; i < threadCount -1; i++) {
            DownloadInfo info =newDownloadInfo(i, i * range, (i +1) * range
                    -1,0, urlstr);
            downloadInfos.add(info);
        }
        DownloadInfo info =newDownloadInfo(threadCount -1, (threadCount -1)
                * range, fileSize -1,0, urlstr);
        downloadInfos.add(info);
        sqlTool.insertInfos(downloadInfos);
    }
 
    //自定义下载线程
    privateclassDownloadThread extendsThread {
 
        privateintthreadId;
        privateintstartPos;
        privateintendPos;
        privateintcompeleteSize;
        privateString urlstr;
        privateinttotalThreadSize;
 
        publicDownloadThread(intthreadId,int startPos, int endPos,
                intcompeleteSize, String urlstr) {
            this.threadId = threadId;
            this.startPos = startPos;
            this.endPos = endPos;
            totalThreadSize = endPos - startPos +1;
            this.urlstr = urlstr;
            this.compeleteSize = compeleteSize;
        }
 
        @Override
        publicvoidrun() {
            HttpURLConnection connection =null;
            RandomAccessFile randomAccessFile =null;
            InputStream is =null;
            try{
                randomAccessFile =newRandomAccessFile(localPath +"/"
                        + fileName,"rwd");
                randomAccessFile.seek(startPos + compeleteSize);
                URL url =newURL(urlstr);
                connection = (HttpURLConnection) url.openConnection();
                connection.setConnectTimeout(5000);
                connection.setRequestMethod("GET");
                connection.setRequestProperty("Range","bytes="
                        + (startPos + compeleteSize) +"-"+ endPos);
                is = connection.getInputStream();
                byte[] buffer =newbyte[1024];
                intlength = -1;
                while((length = is.read(buffer)) != -1) {
                    randomAccessFile.write(buffer,0, length);
                    compeleteSize += length;
                    Message message = Message.obtain();
                    message.what = threadId;
                    message.obj = urlstr;
                    message.arg1 = length;
                    mHandler.sendMessage(message);
                    sqlTool.updataInfos(threadId, compeleteSize, urlstr);
                    Log.w(TAG,"Threadid::"+ threadId +"    compelete::"
                            + compeleteSize +"    total::"+ totalThreadSize);
                    if(compeleteSize >= totalThreadSize) {
                        break;
                    }
                    if(state != Download_State.Downloading) {
                        break;
                    }
                }
 
            }catch(Exception e) {
                e.printStackTrace();
            }finally{
                try{
                    if(is !=null) {
                        is.close();
                    }
                    randomAccessFile.close();
                    connection.disconnect();
                }catch(Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}</downloadinfo></downloadinfo>

DownloadUtils提供下载向外接口方法类:

package com.example.test;
 
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
 
/**
 * 将下载方法封装在此类
 * 提供下载,暂停,删除,以及重置的方法
 */
public class DownloadUtil {
 
    privateDownloadHttpTool mDownloadHttpTool;
    privateOnDownloadListener onDownloadListener;
 
    privateintfileSize;
    privateintdownloadedSize = 0;
 
    @SuppressLint("HandlerLeak")
    privateHandler mHandler =newHandler() {
 
        @Override
        publicvoidhandleMessage(Message msg) {
            // TODO Auto-generated method stub
            super.handleMessage(msg);
            intlength = msg.arg1;
            synchronized(this) {//加锁保证已下载的正确性
                downloadedSize += length;
            }
            if(onDownloadListener !=null) {
                onDownloadListener.downloadProgress(downloadedSize);
            }
            if(downloadedSize >= fileSize) {
                mDownloadHttpTool.compelete();
                if(onDownloadListener !=null) {
                    onDownloadListener.downloadEnd();
                }
            }
        }
 
    };
 
    publicDownloadUtil(intthreadCount, String filePath, String filename,
            String urlString, Context context) {
 
        mDownloadHttpTool =newDownloadHttpTool(threadCount, urlString,
                filePath, filename, context, mHandler);
    }
 
    //下载之前首先异步线程调用ready方法获得文件大小信息,之后调用开始方法
    publicvoidstart() {
        newAsyncTask<void,void,=""void="">() {
 
            @Override
            protectedVoid doInBackground(Void... arg0) {
                // TODO Auto-generated method stub
                mDownloadHttpTool.ready();
                returnnull;
            }
 
            @Override
            protectedvoidonPostExecute(Void result) {
                // TODO Auto-generated method stub
                super.onPostExecute(result);
                fileSize = mDownloadHttpTool.getFileSize();
                downloadedSize = mDownloadHttpTool.getCompeleteSize();
                Log.w("Tag","downloadedSize::"+ downloadedSize);
                if(onDownloadListener !=null) {
                    onDownloadListener.downloadStart(fileSize);
                }
                mDownloadHttpTool.start();
            }
        }.execute();
    }
 
    publicvoidpause() {
        mDownloadHttpTool.pause();
    }
     
    publicvoiddelete(){
        mDownloadHttpTool.delete();
    }
 
    publicvoidreset(){
        mDownloadHttpTool.delete();
        start();
    }
     
    publicvoidsetOnDownloadListener(OnDownloadListener onDownloadListener) {
        this.onDownloadListener = onDownloadListener;
    }
 
    //下载回调接口
    publicinterfaceOnDownloadListener {
        publicvoiddownloadStart(intfileSize);
 
        publicvoiddownloadProgress(intdownloadedSize);//记录当前所有线程下总和
 
        publicvoiddownloadEnd();
    }
}</void,>

在MainActivity

package com.example.test;
 
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
 
import com.example.test.DownloadUtil.OnDownloadListener;
 
public class MainActivity extendsFragmentActivity {
 
    privatestaticfinal String TAG = MainActivity.class.getSimpleName();
 
    privateProgressBar mProgressBar;
    privateButton start;
    privateButton pause;
    privateButton delete;
    privateButton reset;
    privateTextView total;
 
    privateintmax;
 
    privateDownloadUtil mDownloadUtil;
 
    @Override
    protectedvoidonCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mProgressBar = (ProgressBar) findViewById(R.id.progressBar1);
        start = (Button) findViewById(R.id.button_start);
        pause = (Button) findViewById(R.id.button_pause);
        delete = (Button) findViewById(R.id.button_delete);
        reset = (Button) findViewById(R.id.button_reset);
        total = (TextView) findViewById(R.id.textView_total);
        String urlString ="https://bbra.cn/Uploadfiles/imgs/20110303/fengjin/013.jpg";
        String localPath = Environment.getExternalStorageDirectory()
                .getAbsolutePath() +"/local";
        mDownloadUtil =newDownloadUtil(2, localPath,"abc.jpg", urlString,
                this);
        mDownloadUtil.setOnDownloadListener(newOnDownloadListener() {
 
            @Override
            publicvoiddownloadStart(intfileSize) {
                // TODO Auto-generated method stub
                Log.w(TAG,"fileSize::"+ fileSize);
                max = fileSize;
                mProgressBar.setMax(fileSize);
            }
 
            @Override
            publicvoiddownloadProgress(intdownloadedSize) {
                // TODO Auto-generated method stub
                Log.w(TAG,"Compelete::"+ downloadedSize);
                mProgressBar.setProgress(downloadedSize);
                total.setText((int) downloadedSize *100/ max + "%");
            }
 
            @Override
            publicvoiddownloadEnd() {
                // TODO Auto-generated method stub
                Log.w(TAG,"ENd");
            }
        });
        start.setOnClickListener(newOnClickListener() {
 
            @Override
            publicvoidonClick(View arg0) {
                // TODO Auto-generated method stub
                mDownloadUtil.start();
            }
        });
        pause.setOnClickListener(newOnClickListener() {
 
            @Override
            publicvoidonClick(View arg0) {
                // TODO Auto-generated method stub
                mDownloadUtil.pause();
            }
        });
        delete.setOnClickListener(newOnClickListener() {
 
            @Override
            publicvoidonClick(View arg0) {
                // TODO Auto-generated method stub
                mDownloadUtil.delete();
            }
        });
        reset.setOnClickListener(newOnClickListener() {
 
            @Override
            publicvoidonClick(View arg0) {
                // TODO Auto-generated method stub
                mDownloadUtil.reset();
            }
        });
    }
 
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值