当前位置: 代码迷 >> Android >> Android之断点续传上载(转)
  详细解决方案

Android之断点续传上载(转)

热度:25   发布时间:2016-05-01 19:20:29.0
Android之断点续传下载(转)
?

原文链接

??? 在我们做开发的时候经常遇到的就是下载了,现在下载的方法有很多很多,那么怎么做到断点续传下载呢!很多人都头疼这个问题,如果我们没有很好的逻辑真不是很容易解决啊。我参考了一下前辈们的资料了整理了一个项目,能实现多个文件的同时下载。
????断点续传下载,顾名思义,那就是我们在一次下载未结束时,退出下载,第二次下载时会接着第一次下载的进度继续下载。那么怎么记录第一次下载的数据呢,这里肯定就要用到数据库了。下面就是我创建数据库的一个SQLiteOpenHelper类。用来首次运行时创建数据库。
DBHelper.java

package com.icss.DBHelper;import android.content.Context;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteOpenHelper;/** * 建立一个数据库帮助类 */public class DBHelper extends SQLiteOpenHelper {        // download.db-->数据库名        public DBHelper(Context context) {                super(context, "download.db", null, 1);        }        /**         * 在download.db数据库下创建一个download_info表存储下载信息         */        @Override        public void onCreate(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        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {        }}

?下面看主界面的布局,在这里,我只设计了一个ListView来显示下载的音乐的名称,和一个开始下载按钮和一个暂停按钮。
布局文件如下:
main.xml:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"        android:orientation="vertical" android:layout_width="fill_parent"        android:layout_height="fill_parent" android:weightSum="1">        <ListView android:id="@android:id/list" android:layout_height="wrap_content"                android:layout_width="match_parent" android:layout_weight="0.70"></ListView></LinearLayout>

?list_item.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"        android:layout_height="fill_parent" android:id="@+id/linearLayout1"        android:layout_width="fill_parent" android:orientation="vertical">        <LinearLayout android:layout_width="fill_parent"                android:id="@+id/linearLayout2" android:layout_height="wrap_content"                android:orientation="horizontal" android:layout_marginBottom="5dip">                <TextView android:layout_width="fill_parent"                        android:layout_height="wrap_content" android:layout_weight="1"                        android:id="@+id/tv_resouce_name" />                <Button android:layout_width="fill_parent"                        android:layout_height="wrap_content" android:layout_weight="1"                        android:text="下载" android:id="@+id/btn_start" android:onClick="startDownload" />                <Button android:layout_width="fill_parent"                        android:layout_height="wrap_content" android:layout_weight="1"                        android:text="暂停" android:id="@+id/btn_pause" android:onClick="pauseDownload" />        </LinearLayout></LinearLayout>

???? 我们要定义一个记录在下载时各个时期的数据的类,这里我创建了一个DownloadInfo类来记录。代码如下:
DownloadInfo:

package com.icss.entity;public class DownloadInfo {        private int threadId;// 下载器id        private int startPos;// 开始点        private int endPos;// 结束点        private int compeleteSize;// 完成度        private String url;// 下载器网络标识                                public DownloadInfo(int threadId, int startPos, int endPos,                        int compeleteSize, String url) {                super();                this.threadId = threadId;                this.startPos = startPos;                this.endPos = endPos;                this.compeleteSize = compeleteSize;                this.url = url;        }        public int getThreadId() {                return threadId;        }        public void setThreadId(int threadId) {                this.threadId = threadId;        }        public int getStartPos() {                return startPos;        }        public void setStartPos(int startPos) {                this.startPos = startPos;        }        public int getEndPos() {                return endPos;        }        public void setEndPos(int endPos) {                this.endPos = endPos;        }        public int getCompeleteSize() {                return compeleteSize;        }        public void setCompeleteSize(int compeleteSize) {                this.compeleteSize = compeleteSize;        }        public String getUrl() {                return url;        }        public void setUrl(String url) {                this.url = url;        }                @Override             public String toString() {                 return "DownloadInfo [threadId=" + threadId                         + ", startPos=" + startPos + ", endPos=" + endPos                         + ", compeleteSize=" + compeleteSize +"]";     }}

???? 在下载时,我们有进度条来显示进度,怎么确定进度条的进度,大小和起始位置呢?这里我定义了一个LoadInfo类来记录下载器详细信息。代码如下:

LoadInfo:

package com.icss.entity;public class LoadInfo {        public int fileSize;// 文件大小        private int complete;// 完成度        private String urlstring;// 下载器标识        public LoadInfo() {                super();                // TODO Auto-generated constructor stub        }        public LoadInfo(int fileSize, int complete, String urlstring) {                super();                this.fileSize = fileSize;                this.complete = complete;                this.urlstring = urlstring;        }        public int getFileSize() {                return fileSize;        }        public void setFileSize(int fileSize) {                this.fileSize = fileSize;        }        public int getComplete() {                return complete;        }        public void setComplete(int complete) {                this.complete = complete;        }        public String getUrlstring() {                return urlstring;        }        public void setUrlstring(String urlstring) {                this.urlstring = urlstring;        }        @Override            public String toString() {         return "LoadInfo [fileSize=" + fileSize + ", complete=" + complete                         + ", urlstring=" + urlstring + "]";     }}

??? 下面是最最重要的一步,那就是定义一个下载器来进行下载了,这里我就不多说,具体解释在代码中都有注解,供大家研究参考。
Downloader:

package com.icss.service;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 com.icss.dao.Dao;import com.icss.entity.DownloadInfo;import com.icss.entity.LoadInfo;import android.content.Context;import android.os.Handler;import android.os.Message;import android.util.Log;public class Downloader {        private String urlstr;// 下载的地址        private String localfile;// 保存路径        private int threadcount;// 线程数        private Handler mHandler;// 消息处理器        private Dao dao;// 工具类        private int fileSize;// 所要下载的文件的大小        private List<DownloadInfo> infos;// 存放下载信息类的集合        private static final int INIT = 1;// 定义三种下载的状态:初始化状态,正在下载状态,暂停状态        private static final int DOWNLOADING = 2;        private static final int PAUSE = 3;        private int state = INIT;        public Downloader(String urlstr, String localfile, int threadcount,                        Context context, Handler mHandler) {                this.urlstr = urlstr;                this.localfile = localfile;                this.threadcount = threadcount;                this.mHandler = mHandler;                dao = new Dao(context);        }        /**         * 判断是否正在下载         */        public boolean isdownloading() {                return state == DOWNLOADING;        }        /**         * 得到downloader里的信息 首先进行判断是否是第一次下载,如果是第一次就要进行初始化,并将下载器的信息保存到数据库中         * 如果不是第一次下载,那就要从数据库中读出之前下载的信息(起始位置,结束为止,文件大小等),并将下载信息返回给下载器         */        public LoadInfo getDownloaderInfors() {                if (isFirst(urlstr)) {                        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, urlstr);                                infos.add(info);                        }                        DownloadInfo info = new DownloadInfo(threadcount - 1,                                        (threadcount - 1) * range, fileSize - 1, 0, urlstr);                        infos.add(info);                        // 保存infos中的数据到数据库                        dao.saveInfos(infos);                        // 创建一个LoadInfo对象记载下载器的具体信息                        LoadInfo loadInfo = new LoadInfo(fileSize, 0, urlstr);                        return loadInfo;                } else {                        // 得到数据库中已有的urlstr的下载器的具体信息                        infos = dao.getInfos(urlstr);                        Log.v("TAG", "not isFirst size=" + infos.size());                        int size = 0;                        int compeleteSize = 0;                        for (DownloadInfo info : infos) {                                compeleteSize += info.getCompeleteSize();                                size += info.getEndPos() - info.getStartPos() + 1;                        }                        return new LoadInfo(size, compeleteSize, urlstr);                }        }        /**      */        private void init() {                try {                        URL url = new URL(urlstr);                        HttpURLConnection connection = (HttpURLConnection) url                                        .openConnection();                        connection.setConnectTimeout(5000);                        connection.setRequestMethod("GET");                        fileSize = connection.getContentLength();                        File file = new File(localfile);                        if (!file.exists()) {                                file.createNewFile();                        }                        // 本地访问文件                        RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");                        accessFile.setLength(fileSize);                        accessFile.close();                        connection.disconnect();                } catch (Exception e) {                        e.printStackTrace();                }        }        /**         * 判断是否是第一次 下载         */        private boolean isFirst(String urlstr) {                return dao.isHasInfors(urlstr);        }        /**         * 114 * 利用线程开始下载数据 115         */        public void download() {                if (infos != null) {                        if (state == DOWNLOADING)                                return;                        state = DOWNLOADING;                        for (DownloadInfo info : infos) {                                new MyThread(info.getThreadId(), info.getStartPos(),                                                info.getEndPos(), info.getCompeleteSize(),                                                info.getUrl()).start();                        }                }        }        public class MyThread extends Thread {                private int threadId;                private int startPos;                private int endPos;                private int compeleteSize;                private String urlstr;                public MyThread(int threadId, int startPos, int endPos,                                int compeleteSize, String urlstr) {                        this.threadId = threadId;                        this.startPos = startPos;                        this.endPos = endPos;                        this.compeleteSize = compeleteSize;                        this.urlstr = urlstr;                }                @Override                public void run() {                        HttpURLConnection connection = null;                        RandomAccessFile randomAccessFile = null;                        InputStream is = null;                        try {                                URL url = new URL(urlstr);                                connection = (HttpURLConnection) url.openConnection();                                connection.setConnectTimeout(5000);                                connection.setRequestMethod("GET");                                // 设置范围,格式为Range:bytes x-y;                                connection.setRequestProperty("Range", "bytes="                                                + (startPos + compeleteSize) + "-" + endPos);                                randomAccessFile = new RandomAccessFile(localfile, "rwd");                                randomAccessFile.seek(startPos + compeleteSize);                                // 将要下载的文件写到保存在保存路径下的文件中                                is = connection.getInputStream();                                byte[] buffer = new byte[4096];                                int length = -1;                                while ((length = is.read(buffer)) != -1) {                                        randomAccessFile.write(buffer, 0, length);                                        compeleteSize += length;                                        // 更新数据库中的下载信息                                        dao.updataInfos(threadId, compeleteSize, urlstr);                                        // 用消息将下载信息传给进度条,对进度条进行更新                                        Message message = Message.obtain();                                        message.what = 1;                                        message.obj = urlstr;                                        message.arg1 = length;                                        mHandler.sendMessage(message);                                        if (state == PAUSE) {                                                return;                                        }                                }                        } catch (Exception e) {                                e.printStackTrace();                        } finally {                                try {                                        is.close();                                        randomAccessFile.close();                                        connection.disconnect();                                        dao.closeDb();                                } catch (Exception e) {                                        e.printStackTrace();                                }                        }                }        }        // 删除数据库中urlstr对应的下载器信息        public void delete(String urlstr) {                dao.delete(urlstr);        }        // 设置暂停        public void pause() {                state = PAUSE;        }        // 重置下载状态        public void reset() {                state = INIT;        }}

???? 在这边下载器类的定义中,我们用到了许多关于进行数据库操作的方法,这里我定义了一个数据库工具类,来提供这些方法,代码如下:
Dao:

package com.icss.dao;import java.util.ArrayList;import java.util.List;import javax.crypto.spec.DESKeySpec;import com.icss.DBHelper.DBHelper;import com.icss.entity.DownloadInfo; import android.content.Context; import android.database.Cursor;import android.database.sqlite.SQLiteDatabase; /**  *  * 一个业务类  */ public class Dao {     private DBHelper dbHelper;     public Dao(Context context) {         dbHelper = new DBHelper(context);              }     /**      * 查看数据库中是否有数据      */     public boolean isHasInfors(String urlstr) {         SQLiteDatabase database = dbHelper.getReadableDatabase();        String sql = "select count(*)  from download_info where url=?";         Cursor cursor = database.rawQuery(sql, new String[] { urlstr });         cursor.moveToFirst();         int count = cursor.getInt(0);        cursor.close();         return count == 0;     }     /**      * 保存 下载的具体信息      */     public void saveInfos(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);         }     }     /**      * 得到下载具体信息      */     public List<DownloadInfo> getInfos(String urlstr) {         List<DownloadInfo> list = new ArrayList<DownloadInfo>();         SQLiteDatabase database = dbHelper.getReadableDatabase();         String sql = "select thread_id, start_pos, end_pos,compelete_size,url from download_info where url=?";         Cursor cursor = database.rawQuery(sql, new String[] { urlstr });         while (cursor.moveToNext()) {             DownloadInfo info = new DownloadInfo(cursor.getInt(0),                     cursor.getInt(1), cursor.getInt(2), cursor.getInt(3),                    cursor.getString(4));             list.add(info);         }         cursor.close();         return list;     }     /**     * 更新数据库中的下载信息      */     public void updataInfos(int threadId, int compeleteSize, String urlstr) {         SQLiteDatabase database = dbHelper.getReadableDatabase();         String sql = "update download_info set compelete_size=? where thread_id=? and url=?";         Object[] bindArgs = { compeleteSize, threadId, urlstr };        database.execSQL(sql, bindArgs);     }     /**      * 关闭数据库      */     public void closeDb() {         dbHelper.close();    }     /**     * 下载完成后删除数据库中的数据      */     public void delete(String url) {        SQLiteDatabase database = dbHelper.getReadableDatabase();         database.delete("download_info", "url=?", new String[] { url });         database.close();     } }

?下面的是程序的主程序,其他的不说了,直接上代码:

package com.icss;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import android.app.ListActivity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.view.View;import android.widget.LinearLayout;import android.widget.LinearLayout.LayoutParams;import android.widget.ProgressBar;import android.widget.SimpleAdapter;import android.widget.TextView;import android.widget.Toast;import com.icss.entity.LoadInfo;import com.icss.service.Downloader;public class MainActivity extends ListActivity {        // 固定下载的资源路径,这里可以设置网络上的地址        private static final String URL = "http://10.0.0.80:8080/xiangce/";        // 固定存放下载的音乐的路径:SD卡目录下        private static final String SD_PATH = "/mnt/sdcard/";        // 存放各个下载器        private Map<String, Downloader> downloaders = new HashMap<String, Downloader>();        // 存放与下载器对应的进度条        private Map<String, ProgressBar> ProgressBars = new HashMap<String, ProgressBar>();        /**         * 31 * 利用消息处理机制适时更新进度条 32         */        private Handler mHandler = new Handler() {                public void handleMessage(Message msg) {                        if (msg.what == 1) {                                String url = (String) msg.obj;                                int length = msg.arg1;                                ProgressBar bar = ProgressBars.get(url);                                if (bar != null) {                                        // 设置进度条按读取的length长度更新                                        bar.incrementProgressBy(length);                                        if (bar.getProgress() == bar.getMax()) {                                                Toast.makeText(MainActivity.this, "下载完成!", 0).show();                                                // 下载完成后清除进度条并将map中的数据清空                                                LinearLayout layout = (LinearLayout) bar.getParent();                                                layout.removeView(bar);                                                ProgressBars.remove(url);                                                downloaders.get(url).delete(url);                                                downloaders.get(url).reset();                                                downloaders.remove(url);                                        }                                }                        }                }        };        @Override        public void onCreate(Bundle savedInstanceState) {                super.onCreate(savedInstanceState);                setContentView(R.layout.main);                showListView();        }        // 显示listView,这里可以随便添加音乐        private void showListView() {                List<Map<String, String>> data = new ArrayList<Map<String, String>>();                Map<String, String> map = new HashMap<String, String>();                map.put("name", "mm.mp3");                data.add(map);                map = new HashMap<String, String>();                map.put("name", "pp.mp3");                data.add(map);                map = new HashMap<String, String>();                map.put("name", "tt.mp3");                data.add(map);                map = new HashMap<String, String>();                map.put("name", "You.mp3");                data.add(map);                SimpleAdapter adapter = new SimpleAdapter(this, data,                                R.layout.list_item, new String[] { "name" },                                new int[] { R.id.tv_resouce_name });                setListAdapter(adapter);        }        /**         * 83 * 响应开始下载按钮的点击事件 84         */        public void startDownload(View v) {                // 得到textView的内容                LinearLayout layout = (LinearLayout) v.getParent();                String musicName = ((TextView) layout                                .findViewById(R.id.tv_resouce_name)).getText().toString();                String urlstr = URL + musicName;                String localfile = SD_PATH + musicName;                // 设置下载线程数为4,这里是我为了方便随便固定的                int threadcount = 4;                // 初始化一个downloader下载器                Downloader downloader = downloaders.get(urlstr);                if (downloader == null) {                        downloader = new Downloader(urlstr, localfile, threadcount, this,                                        mHandler);                        downloaders.put(urlstr, downloader);                }                if (downloader.isdownloading())                        return;                // 得到下载信息类的个数组成集合                LoadInfo loadInfo = downloader.getDownloaderInfors();                // 显示进度条                showProgress(loadInfo, urlstr, v);                // 调用方法开始下载                downloader.download();        }        /**         * 显示进度条         */        private void showProgress(LoadInfo loadInfo, String url, View v) {                ProgressBar bar = ProgressBars.get(url);                if (bar == null) {                        bar = new ProgressBar(this, null,                                        android.R.attr.progressBarStyleHorizontal);                        bar.setMax(loadInfo.getFileSize());                        bar.setProgress(loadInfo.getComplete());                        System.out.println(loadInfo.getFileSize()+"--"+loadInfo.getComplete());                        ProgressBars.put(url, bar);                        LinearLayout.LayoutParams params = new LayoutParams(                                        LayoutParams.FILL_PARENT, 5);                        ((LinearLayout) ((LinearLayout) v.getParent()).getParent())                                        .addView(bar, params);                }        }        /**         * 响应暂停下载按钮的点击事件         */        public void pauseDownload(View v) {                LinearLayout layout = (LinearLayout) v.getParent();                String musicName = ((TextView) layout                                .findViewById(R.id.tv_resouce_name)).getText().toString();                String urlstr = URL + musicName;                downloaders.get(urlstr).pause();        }}

???? 最后我们需要在android Manifest.xml中添加这两个权限一个是访问internet的权限,另一个是写外存的权限。

<uses-permission android:name="android.permission.INTERNET"></uses-permission>    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>

???? 万事俱备只欠东风啦!哈哈,我需要在我的web工程里放几个音乐文件。

???下载后得到的文件可在sdcard下面找到!

  相关解决方案