Android开发多线程断点续传下载器
使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线、电量不足等情况下,这就需要使用到断点续传功能,下次启动时从记录位置继续下载,可避免重复部分的下载。这里采用数据库来记录下载的进度。
效果图


断点续传
1.断点续传需要在下载过程中记录每条线程的下载进度
2.每次下载开始之前先读取数据库,查询是否有未完成的记录,有就继续下载,没有则创建新记录插入数据库
3.在每次向文件中写入数据之后,在数据库中更新下载进度
4.下载完成之后删除数据库中下载记录
Handler传输数据
这个主要用来记录百分比,每下载一部分数据就通知主线程来记录时间
1.主线程中创建的View只能在主线程中修改,其他线程只能通过和主线程通信,在主线程中改变View数据
2.我们使用Handler可以处理这种需求
主线程中创建Handler,重写handleMessage()方法
新线程中使用Handler发送消息,主线程即可收到消息,并且执行handleMessage()方法
动态生成新View
可实现多任务下载
1.创建XML文件,将要生成的View配置好
2.获取系统服务LayoutInflater,用来生成新的View
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
3.使用inflate(int resource, ViewGroup root)方法生成新的View
4.调用当前页面中某个容器的addView,将新创建的View添加进来
示例
进度条样式 download.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight=""
>
<!--进度条样式默认为圆形进度条,水平进度条需要配置style属性,
?android:attr/progressBarStyleHorizontal -->
<ProgressBar
android:layout_width="fill_parent"
android:layout_height="20dp"
style="?android:attr/progressBarStyleHorizontal"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="0%"
/>
</LinearLayout>
<Button
android:layout_width="40dp"
android:layout_height="40dp"
android:onClick="pause"
android:text="||"
/>
</LinearLayout>
顶部样式 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:id="@+id/root"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="请输入下载路径"
/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="30dp"
>
<EditText
android:id="@+id/path"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:layout_weight=""
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载"
android:onClick="download"
/>
</LinearLayout>
</LinearLayout>
MainActivity.Java
public class MainActivity extends Activity {
    private LayoutInflater inflater;
    private LinearLayout rootLinearLayout;
    private EditText pathEditText;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        //动态生成新View,获取系统服务LayoutInflater,用来生成新的View
        inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
        rootLinearLayout = (LinearLayout) findViewById(R.id.root);
        pathEditText = (EditText) findViewById(R.id.path);
        // 窗体创建之后, 查询数据库是否有未完成任务, 如果有, 创建进度条等组件, 继续下载
        List<String> list = new InfoDao(this).queryUndone();
        for (String path : list)
            createDownload(path);
    }
    /**
     * 下载按钮
     * @param view
     */
    public void download(View view) {
        String path = "http://192.168.1.199:8080/14_Web/" + pathEditText.getText().toString();
        createDownload(path);
    }
    /**
     * 动态生成新View
     * 初始化表单数据
     * @param path
     */
    private void createDownload(String path) {
        //获取系统服务LayoutInflater,用来生成新的View
        LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
        LinearLayout linearLayout = (LinearLayout) inflater.inflate(R.layout.download, null);
        LinearLayout childLinearLayout = (LinearLayout) linearLayout.getChildAt();
        ProgressBar progressBar = (ProgressBar) childLinearLayout.getChildAt();
        TextView textView = (TextView) childLinearLayout.getChildAt();
        Button button = (Button) linearLayout.getChildAt();
        try {
            button.setOnClickListener(new MyListener(progressBar, textView, path));
            //调用当前页面中某个容器的addView,将新创建的View添加进来
            rootLinearLayout.addView(linearLayout);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private final class MyListener implements OnClickListener {
        private ProgressBar progressBar;
        private TextView textView;
        private int fileLen;
        private Downloader downloader;
        private String name;
        /**
         * 执行下载
         * @param progressBar //进度条
         * @param textView //百分比
         * @param path  //下载文件路径
         */
        public MyListener(ProgressBar progressBar, TextView textView, String path) {
            this.progressBar = progressBar;
            this.textView = textView;
            name = path.substring(path.lastIndexOf("/") + );
            downloader = new Downloader(getApplicationContext(), handler);
            try {
                downloader.download(path, );
            } catch (Exception e) {
                e.printStackTrace();
                Toast.makeText(getApplicationContext(), "下载过程中出现异常", ).show();
                throw new RuntimeException(e);
            }
        }
        //Handler传输数据
        private Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case :
                        //获取文件的大小
                        fileLen = msg.getData().getInt("fileLen");
                        //设置进度条最大刻度:setMax()
                        progressBar.setMax(fileLen);
                        break;
                    case :
                        //获取当前下载的总量
                        int done = msg.getData().getInt("done");
                        //当前进度的百分比
                        textView.setText(name + "\t" + done *  / fileLen + "%");
                        //进度条设置当前进度:setProgress()
                        progressBar.setProgress(done);
                        if (done == fileLen) {
                            Toast.makeText(getApplicationContext(), name + " 下载完成", ).show();
                            //下载完成后退出进度条
                            rootLinearLayout.removeView((View) progressBar.getParent().getParent());
                        }
                        break;
                }
            }
        };
        /**
         * 暂停和继续下载
         */
        public void onClick(View v) {
            Button pauseButton = (Button) v;
            if ("||".equals(pauseButton.getText())) {
                downloader.pause();
                pauseButton.setText("▶");
            } else {
                downloader.resume();
                pauseButton.setText("||");
            }
        }
    }
}
Downloader.java
public class Downloader {
    private int done;
    private InfoDao dao;
    private int fileLen;
    private Handler handler;
    private boolean isPause;
    public Downloader(Context context, Handler handler) {
        dao = new InfoDao(context);
        this.handler = handler;
    }
    /**
     * 多线程下载
     * @param path 下载路径
     * @param thCount 需要开启多少个线程
     * @throws Exception
     */
    public void download(String path, int thCount) throws Exception {
        URL url = new URL(path);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        //设置超时时间
        conn.setConnectTimeout();
        if (conn.getResponseCode() == ) {
            fileLen = conn.getContentLength();
            String name = path.substring(path.lastIndexOf("/") + );
            File file = new File(Environment.getExternalStorageDirectory(), name);
            RandomAccessFile raf = new RandomAccessFile(file, "rws");
            raf.setLength(fileLen);
            raf.close();
            //Handler发送消息,主线程接收消息,获取数据的长度
            Message msg = new Message();
            msg.what = ;
            msg.getData().putInt("fileLen", fileLen);
            handler.sendMessage(msg);
            //计算每个线程下载的字节数
            int partLen = (fileLen + thCount - ) / thCount;
            for (int i = ; i < thCount; i++)
                new DownloadThread(url, file, partLen, i).start();
        } else {
            throw new IllegalArgumentException("404 path: " + path);
        }
    }
    private final class DownloadThread extends Thread {
        private URL url;
        private File file;
        private int partLen;
        private int id;
        public DownloadThread(URL url, File file, int partLen, int id) {
            this.url = url;
            this.file = file;
            this.partLen = partLen;
            this.id = id;
        }
        /**
         * 写入操作
         */
        public void run() {
            // 判断上次是否有未完成任务
            Info info = dao.query(url.toString(), id);
            if (info != null) {
                // 如果有, 读取当前线程已下载量
                done += info.getDone();
            } else {
                // 如果没有, 则创建一个新记录存入
                info = new Info(url.toString(), id, );
                dao.insert(info);
            }
            int start = id * partLen + info.getDone(); // 开始位置 += 已下载量
            int end = (id + ) * partLen - ;
            try {
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setReadTimeout();
                //获取指定位置的数据,Range范围如果超出服务器上数据范围, 会以服务器数据末尾为准
                conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
                RandomAccessFile raf = new RandomAccessFile(file, "rws");
                raf.seek(start);
                //开始读写数据
                InputStream in = conn.getInputStream();
                byte[] buf = new byte[ * ];
                int len;
                while ((len = in.read(buf)) != -) {
                    if (isPause) {
                        //使用线程锁锁定该线程
                        synchronized (dao) {
                            try {
                                dao.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                    raf.write(buf, , len);
                    done += len;
                    info.setDone(info.getDone() + len);
                    // 记录每个线程已下载的数据量
                    dao.update(info);
                    //新线程中用Handler发送消息,主线程接收消息
                    Message msg = new Message();
                    msg.what = ;
                    msg.getData().putInt("done", done);
                    handler.sendMessage(msg);
                }
                in.close();
                raf.close();
                // 删除下载记录
                dao.deleteAll(info.getPath(), fileLen);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    //暂停下载
    public void pause() {
        isPause = true;
    }
    //继续下载
    public void resume() {
        isPause = false;
        //恢复所有线程
        synchronized (dao) {
            dao.notifyAll();
        }
    }
}
Dao:
DBOpenHelper:
public class DBOpenHelper extends SQLiteOpenHelper {
    public DBOpenHelper(Context context) {
        super(context, "download.db", null, );
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE info(path VARCHAR(1024), thid INTEGER, done INTEGER, PRIMARY KEY(path, thid))");
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}
InfoDao:
public class InfoDao {
    private DBOpenHelper helper;
    public InfoDao(Context context) {
        helper = new DBOpenHelper(context);
    }
    public void insert(Info info) {
        SQLiteDatabase db = helper.getWritableDatabase();
        db.execSQL("INSERT INTO info(path, thid, done) VALUES(?, ?, ?)", new Object[] { info.getPath(), info.getThid(), info.getDone() });
    }
    public void delete(String path, int thid) {
        SQLiteDatabase db = helper.getWritableDatabase();
        db.execSQL("DELETE FROM info WHERE path=? AND thid=?", new Object[] { path, thid });
    }
    public void update(Info info) {
        SQLiteDatabase db = helper.getWritableDatabase();
        db.execSQL("UPDATE info SET done=? WHERE path=? AND thid=?", new Object[] { info.getDone(), info.getPath(), info.getThid() });
    }
    public Info query(String path, int thid) {
        SQLiteDatabase db = helper.getWritableDatabase();
        Cursor c = db.rawQuery("SELECT path, thid, done FROM info WHERE path=? AND thid=?", new String[] { path, String.valueOf(thid) });
        Info info = null;
        if (c.moveToNext())
            info = new Info(c.getString(), c.getInt(), c.getInt());
        c.close();
        return info;
    }
    public void deleteAll(String path, int len) {
        SQLiteDatabase db = helper.getWritableDatabase();
        Cursor c = db.rawQuery("SELECT SUM(done) FROM info WHERE path=?", new String[] { path });
        if (c.moveToNext()) {
            int result = c.getInt();
            if (result == len)
                db.execSQL("DELETE FROM info WHERE path=? ", new Object[] { path });
        }
    }
    public List<String> queryUndone() {
        SQLiteDatabase db = helper.getWritableDatabase();
        Cursor c = db.rawQuery("SELECT DISTINCT path FROM info", null);
        List<String> pathList = new ArrayList<String>();
        while (c.moveToNext())
            pathList.add(c.getString());
        c.close();
        return pathList;
    }
}
Android开发多线程断点续传下载器的更多相关文章
- 实现android支持多线程断点续传下载器功能
		多线程断点下载流程图: 多线程断点续传下载原理介绍: 在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度手机端下载数据时难免会出现无信号断线.电量不足等情况,所以需要断点续传功能根据下 ... 
- Android网络多线程断点续传下载
		本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多 ... 
- android 多线程断点续传下载
		今天跟大家一起分享下Android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的,前面2次总结中已经为大家分享过有关技术的一些基 ... 
- Android实现网络多线程断点续传下载(转)
		本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多 ... 
- Android实现网络多线程断点续传下载
		本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多 ... 
- 我的Android进阶之旅------>Android基于HTTP协议的多线程断点下载器的实现
		一.首先写这篇文章之前,要了解实现该Android多线程断点下载器的几个知识点 1.多线程下载的原理,如下图所示 注意:由于Android移动设备和PC机的处理器还是不能相比,所以开辟的子线程建议不要 ... 
- 用 python 实现一个多线程网页下载器
		今天上来分享一下昨天实现的一个多线程网页下载器. 这是一个有着真实需求的实现,我的用途是拿它来通过 HTTP 方式向服务器提交游戏数据.把它放上来也是想大家帮忙挑刺,找找 bug,让它工作得更好. k ... 
- Python实现多线程HTTP下载器
		本文将介绍使用Python编写多线程HTTP下载器,并生成.exe可执行文件. 环境:windows/Linux + Python2.7.x 单线程 在介绍多线程之前首先介绍单线程.编写单线程的思路为 ... 
- Java多线程的下载器(1)
		实现了一个基于Java多线程的下载器,可提供的功能有: 1. 对文件使用多线程下载,并显示每时刻的下载速度. 2. 对多个下载进行管理,包括线程调度,内存管理等. 一:单个文件下载的管理 1. 单文件 ... 
随机推荐
- 黑马程序员_Java其他对象(System,Runtime,Date,Calendar,Marh-Random)
			System System类包含一些有用的类字段和方法(都是静态的).它不能被实例化. 在System类提供的设施中,有标准输入.标准输出和错误输出流:对外部定义的属性和环境变量的访问:加载文件和库的 ... 
- jsp中pageEncoding、charset=UTF -8
			jsp中pageEncoding.charset=UTF -8" 在JSP/Servlet 中主要有以下几个地方可以设置编码,pageEncoding="UTF-8". ... 
- [转]The culture name list in C#
			Culture Names [C#] This example shows how to get all culture names in the .NET Framework. Use static ... 
- java中文件操作的工具类
			代码: package com.lky.pojo; import java.io.BufferedReader; import java.io.BufferedWriter; import java. ... 
- IntelliJ IDEA常见问题及解决方法
			1.IDEA导入项目后,源码.java文件图标带有红色圆圈,圆圈中间是个J: 该现象的原因是由于项目未把该路径指定为源码路径.解决方法: 在project Structure中(快捷键ctrl+alt ... 
- cURL中的超时设置
			访问HTTP方式很多,可以使用curl, socket, file_get_contents() 等方法. 在访问http时,需要考虑超时的问题. CURL访问HTTP: CURL 是常用的访问HTT ... 
- Struts 2零配置
			从struts2.1开始,struts2不再推荐使用Codebehind作为零配置插件,而是改为使用Convention插件来支持零配置,和Codebehind相比,Convention插件更彻底,该 ... 
- 转载: C#: Left outer joins with LINQ
			I always considered Left Outer Join in LINQ to be complex until today when I had to use it in my app ... 
- nginx 配置正向 HTTP 代理服务器[转]
			如果不想写到 ngnix.conf 中,那么可以在相同的目录下建立另外一个文件夹存放单独的文件,比如新建一个 proxy 的子目录,然后再在里面新建文件 prox.conf ,然后添加如下内容: s ... 
- iOS蓝牙4.0协议简单介绍
			iOS开发蓝牙4.0的框架是CoreBluetooth,本文主要介绍CoreBluetooth的使用,关于本文中的代码片段大多来自github上的一个demo,地址是myz1104/Bluetooth ... 
