2016-06-05 00:18:23 teashui 阅读数 262
  • MT5编程从入门到精通:MQL5编程认知

    本课程是MT5编程从入门到精通的第一阶段课程,详细的介绍了没有编程经验的小白也能快速的进入MT5编程的世界,为没有编程经验的外汇交易爱好者,树立MT5程序化交易的信心。 本阶段课程共分为三个章节,第一章重点讲解了MT5的安装与调试,MT5数据文件介绍,以及学完本课程后能开发出怎么的程序化交易;第二章重点讲解了MT5拥有多货币、多周期回测功能,以及MT5强大的参数优化功能和MT5的EA生成功能;第三章重点讲解了MT5的脚本开发框架,指标开发框架和EA开发框架,以及MT5的断点调试功能。

    2944 人正在学习 去看看 李文帅

多线程下载之断点续传android版本
第一个页面布局

<RelativeLayout 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"
    tools:context=".MainActivity" >

    <EditText
        android:id="@+id/en_path"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="http://192.168.80.212:8080/feiqiu.exe"
        android:hint="请输入下载路径" />

    <EditText
        android:id="@+id/en_threadCount"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="55dp"
        android:hint="请输入下载线程数量" >
    </EditText>

    <Button
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_margin="90dp"
        android:onClick="click"
        android:text="下载" />
<!-- 写一个线性布局,动态的添加布局 -->
    <LinearLayout
        android:id="@+id/ll_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="200dp"
        android:orientation="vertical" >
    </LinearLayout>

</RelativeLayout>

第二个页面布局

<?xml version="1.0" encoding="utf-8"?>
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/progressBar1"
    style="?android:attr/progressBarStyleHorizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

</ProgressBar>

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;


public class MainActivity extends Activity {

    private EditText en_path;
    private EditText en_threadCount;
    private List<ProgressBar> pbs;
    private int runningThread ;
    private int threadCount;
    private LinearLayout ll_layout;
    private String downThreadCount;
    private String downloadPath;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //获取我们关心的控件
        en_path = (EditText) findViewById(R.id.en_path);
        en_threadCount = (EditText) findViewById(R.id.en_threadCount);
        ll_layout = (LinearLayout) findViewById(R.id.ll_layout);
        pbs = new ArrayList<ProgressBar>();
    }


  public void click(View v) {
      downloadPath = en_path.getText().toString().trim();
      downThreadCount = en_threadCount.getText().toString().trim();
      //将从控件获取到的下载数量转换为int型
      threadCount = Integer.parseInt(downThreadCount);    
      //清除集合和进度条
     ll_layout.removeAllViews();  //清除集合中的进度条
     pbs.clear();
     //有几个线程就添加几个进度条
     for (int i = 0; i < threadCount; i++) {
        ProgressBar pb = (ProgressBar) View.inflate(getApplication(), R.layout.item, null);
        pbs.add(pb);             //将进度条添加到集合
        ll_layout.addView(pb);   //将进度条添加到布局
    }
     //开启子线程
     new Thread() {
        public void run() {
            try {
                URL url = new URL(downloadPath);
                HttpURLConnection con = (HttpURLConnection) url
                        .openConnection();
                con.setRequestMethod("GET");
                con.setConnectTimeout(5000);
                int code = con.getResponseCode();
                if (code == 200) {
                    //从服务器获取文件的长度
                    int length = con.getContentLength();  
                    //将线程数赋值
                    runningThread = threadCount;
                    //将文件写到内存
                    RandomAccessFile rafAccessFile  = new RandomAccessFile(getFileName(downloadPath), "rw");
                    rafAccessFile.setLength(length);  //设置该文件的大小

                    int blockSize = length / threadCount;    //得到每个线程下载的总大小

                    for (int i = 0; i < threadCount; i++) {
                        int startIndex = i * blockSize;      //计算每个线程出开始的位置
                        int endIndex = (i + 1) * blockSize;  //计算每个线程的结束位置
                    if(i == threadCount - 1){                //计算最后一个线程的位置
                        endIndex = length - 1;
                    }
                    new DownLoadThread(i,startIndex,endIndex).start();  //开启线程下载
                    }

                }
            } catch (Exception e) {

                e.printStackTrace();
            }
        }
    }.start();

}
  //下载文件
  class DownLoadThread extends Thread{
      private int threadid;   //线程id
      private int startIndex; //线程的开始位置
      private int endIndex;   //线程的结束位置
      private int pbMax;      //进度条的最大值
      private int pbLastPosition;  //进度条的当前位置
      public DownLoadThread(int threadid,int startIndex,int endIndex){
          this.threadid = threadid;
          this.startIndex = startIndex;
          this.endIndex = endIndex;
      }

      @Override
    public void run() {
        try {
            pbMax = endIndex - startIndex;     //计算出进度条的最大值
            URL url = new URL((downloadPath));
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            con.setRequestMethod("GET");
            con.setConnectTimeout(5000);
            //如果线程下载过程中中断过,则走下面的 if语句,没有则跳过该语句继续执行
            File file = new File(getFileName(downloadPath) + threadid + ".txt");  //存储每个线程的txt文件
            if(file.exists() && file.length() > 0){
                //用高效的字符流关联该文件
                BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
                String lastPosition = br.readLine();
                pbLastPosition = Integer.parseInt(lastPosition);
                startIndex = Integer.parseInt(lastPosition);   //将读取出来的数据作为当前线程的开始位置
                 br.close();
            }
            //设置Range头,指定下载的开始位置和结束位置
            con.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);

            int code = con.getResponseCode();
            if (code == 206) {       //请求资源部分求成功
                InputStream in = con.getInputStream();
                RandomAccessFile raf = new RandomAccessFile(getFileName(downloadPath), "rw");
                raf.seek(startIndex); //设置文件的开始位置

                int len;
                int total = 0;
                byte[] arr = new byte[1024*1024];
                while((len = in.read(arr)) != -1){   
                    total += len;  //获取到正在下载的长度并累加
                    int currentThreadPosition = startIndex + total;   //将当前下载的长度和开始位置相加得到线程当前的位置

                    //创建一个当前线程的txt文件,用于存储中断时的位置
                    RandomAccessFile accessFile = new RandomAccessFile(getFileName(downloadPath) + threadid + ".txt", "rwd");
                    //将此位置写入到文件中
                    accessFile.write(String.valueOf(currentThreadPosition).getBytes());
                    accessFile.close();
                    //下载数据(将数据写到文件中,此文件为.exe(以飞秋为例)形式)
                    raf.write(arr, 0, len);
                    //设置进度条的大小
                    pbs.get(threadid).setMax(pbMax);   //进度条的最大值
                    //设置进度条的当前位置
                    pbs.get(threadid).setProgress(pbLastPosition + total - startIndex);
                }
                //关流
                in.close();
                raf.close();
                //为什么不用threadCount--,而是给它赋值为runningThread在操作呢?
                //个人理解:如果直接操作threadCount,那么当threadCount出现错误时全局都会受到影响
                //但是如果操作runningThread出现错误时,并不会对全局造成影响,并且代码容易维护
                synchronized ((DownLoadThread.class) ) {//同步:是为了在当前线程能够完全执行,其他线程不中断当前线程
                    runningThread --;
                    if(runningThread == 0){ //如果正在运行的线程等于0,则将生成的临时文件删除
                        for (int i = 0; i < threadCount; i++) {
                            File txtfile = new File(getFileName(downloadPath) + i + ".txt");
                            txtfile.delete();

                        }
                    }
                }

            }
        } catch (Exception e) {

            e.printStackTrace();
        }

    }
  }

  public static String getFileName(String path){
      int start = path.lastIndexOf("/") + 1;
      String sdCard = Environment.getExternalStorageDirectory().getPath();
      return sdCard + "/" + path.substring(start);
  }

}
2017-02-11 21:02:45 li15225271052 阅读数 462
  • MT5编程从入门到精通:MQL5编程认知

    本课程是MT5编程从入门到精通的第一阶段课程,详细的介绍了没有编程经验的小白也能快速的进入MT5编程的世界,为没有编程经验的外汇交易爱好者,树立MT5程序化交易的信心。 本阶段课程共分为三个章节,第一章重点讲解了MT5的安装与调试,MT5数据文件介绍,以及学完本课程后能开发出怎么的程序化交易;第二章重点讲解了MT5拥有多货币、多周期回测功能,以及MT5强大的参数优化功能和MT5的EA生成功能;第三章重点讲解了MT5的脚本开发框架,指标开发框架和EA开发框架,以及MT5的断点调试功能。

    2944 人正在学习 去看看 李文帅

android实用方法- - - -断点续传详解

最近遇到一点小需求,音乐下载,需要暂停下载,由于之前没有写过也是找了一些资源才搞懂的O(∩_∩)O哈哈~

首先要明确一点这个是需要后台支持的,也就是后台必须提供我们可以在某个字节开始下载的接口

下面我给大家说下基本思路,首先我们需要暂停下载,也就是我们需要在下载线程中中断下载,这个需要广播。然后就是我们的断点位置需要保存,所以我们需要实体类,并且我们需要存放在sqlite中方便增删改查。然后暂停之后我们从sqlite中取出上次的断点,向服务器请求实现断点下载。并且,在当前的文件的字节后开始下载实现断点续传。

这里写图片描述

如上图所示基本上整个程序的逻辑

简答说下几个重要的点吧
首先你要有个保存当前下载位置的实体类

代码块

代码块语法遵循标准markdown代码,例如:

package com.example.nick.downloaddemo.entity;

/**
 * Created by Nick on 2017/2/7.
 * 线程信息
 */

public class ThreadInfo {
    private  int  id;
    private String url;
    private int start;//线程从哪里开始下载
    private int end;//下载到哪里结束
    private int finished;//完成了多少

    public ThreadInfo() {
    }

    public ThreadInfo(int id, String url, int start, int end, int finished) {
        this.id = id;
        this.url = url;
        this.start = start;
        this.end = end;
        this.finished = finished;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public int getStart() {
        return start;
    }

    public void setStart(int start) {
        this.start = start;
    }

    public int getEnd() {
        return end;
    }

    public void setEnd(int end) {
        this.end = end;
    }

    public int getFinished() {
        return finished;
    }

    public void setFinished(int finished) {
        this.finished = finished;
    }

    @Override
    public String toString() {
        return "ThreadInfo{" +
                "id=" + id +
                ", url='" + url + '\'' +
                ", start=" + start +
                ", end=" + end +
                ", finished=" + finished +
                '}';
    }
}

下面就需要sqlite来存储这些信息这个我就不贴了

然后你需要暂停下载 判断条件是传过来的 保存方法时工具类写好的

  // 在下载暂停时,保存下载进度  终止下载
                        if (isPause)
                        {
                            mDao.updateThread(mThreadInfo.getUrl(), mThreadInfo.getId(), mFinised);
                            return;
                        }

最重也要的就是下载代码这里给出,就是不存在就创建,存在就取出

 public void downLoad()
    {
        // 读取数据库的线程信息
        List<ThreadInfo> threads = mDao.getThreads(mFileInfo.getUrl());
        ThreadInfo threadInfo = null;

        if (0 == threads.size())
        {
            // 初始化线程信息对象
            threadInfo = new ThreadInfo(0, mFileInfo.getUrl(),
                    0, mFileInfo.getLength(), 0);
        }
        else
        {
            threadInfo = threads.get(0);
        }

        // 创建子线程进行下载
        new DownloadThread(threadInfo).start();
    }

/**
* 下载线程 这里是断点续传的关键之一
* @author Yann
* @date 2015-8-8 上午11:18:55
*/
private class DownloadThread extends Thread
{
private ThreadInfo mThreadInfo = null;

    /**
     *@param mInfo
     */
    public DownloadThread(ThreadInfo mInfo)
    {
        this.mThreadInfo = mInfo;
    }

    /**
     * @see java.lang.Thread#run()
     */
    @Override
    public void run()
    {
        // 向数据库插入线程信息
        if (!mDao.isExists(mThreadInfo.getUrl(), mThreadInfo.getId()))
        {
            mDao.insertThread(mThreadInfo);
        }

        HttpURLConnection connection = null;
        RandomAccessFile raf = null;
        InputStream inputStream = null;

        try
        {
            URL url = new URL(mThreadInfo.getUrl());
            connection = (HttpURLConnection) url.openConnection();
            connection.setConnectTimeout(5000);
            connection.setRequestMethod("GET");
            // 设置下载位置
            int start = mThreadInfo.getStart() + mThreadInfo.getFinished();
            Log.e("下载的位置",start+"");
            //设置下开始位置到结束的位置   setRequestProperty主要是设置HttpURLConnection请求头里面的属性  这个主要是看后台怎么写
            connection.setRequestProperty("Range",
                    "bytes=" + start + "-" + mThreadInfo.getEnd());
            // 设置文件写入位置
            File file = new File(DownloadService.DOWNLOAD_PATH,
                    mFileInfo.getFilename());
            raf = new RandomAccessFile(file, "rwd");
            //设置开始下载的位置   这是设置跳过多少个字节开始写入文件夹 例如raf.seek(10);就是跳过10个字节从11个字节开始
            raf.seek(start);
            Intent intent = new Intent();
            intent.setAction(DownloadService.ACTION_UPDATE);
            mFinised += mThreadInfo.getFinished();
            // 开始下载
            if (connection.getResponseCode() == 206)
            {
                // 读取数据
                inputStream = connection.getInputStream();
                byte buf[] = new byte[1024<<2];
                int len = -1;
                long time = System.currentTimeMillis();
                while ((len = inputStream.read(buf)) != -1)
                {
                    // 写入文件
                    raf.write(buf, 0, len);
                    // 把下载进度发送广播给Activity
                    mFinised += len;
                    if (System.currentTimeMillis() - time > 500)
                    {
                        Log.i("file——Start","发送广播");
                        time = System.currentTimeMillis();
                        intent.putExtra("finished", mFinised * 100 / mThreadInfo.getEnd());
                        mContext.sendBroadcast(intent);
                    }
                    // 在下载暂停时,保存下载进度  终止下载
                    if (isPause)
                    {
                        mDao.updateThread(mThreadInfo.getUrl(), mThreadInfo.getId(), mFinised);
                        return;
                    }
                }
                //最后一次可能小于500导致最后一次广播发不出去,进而不能显示完成100%
                Log.i("file——Start","发送广播");

                intent.putExtra("finished", mFinised * 100 / mThreadInfo.getEnd());
                mContext.sendBroadcast(intent);

                // 删除线程信息
                mDao.deleteThread(mThreadInfo.getUrl(), mThreadInfo.getId());
                Log.i("DownloadTask", "下载完毕");
                MainActivity.mMainActivity.handler.sendEmptyMessage(0);
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            try
            {
                if (connection != null)
                {
                    connection.disconnect();
                }
                if (raf != null)
                {
                    raf.close();
                }
                if (inputStream != null)
                {
                    inputStream.close();
                }
            }
            catch (Exception e2)
            {
                e2.printStackTrace();
            }
        }
    }
}

以上就是下载的代码,上注释很清楚

// 设置下载位置
int start = mThreadInfo.getStart() + mThreadInfo.getFinished();
Log.e(“下载的位置”,start+”“);
//设置下开始位置到结束的位置 setRequestProperty主要是设置HttpURLConnection请求头里面的属性 这个主要是看后台怎么写
connection.setRequestProperty(“Range”,
“bytes=” + start + “-” + mThreadInfo.getEnd());

// 设置文件写入位置
File file = new File(DownloadService.DOWNLOAD_PATH,
mFileInfo.getFilename());
raf = new RandomAccessFile(file, “rwd”);
//设置开始下载的位置 这是设置跳过多少个字节开始写入文件夹 例如raf.seek(10);就是跳过10个字节从11个字节开始
raf.seek(start);

这就实现了文件的断点拼接

这里会给出源码希望能帮助到你们( ⊙ o ⊙ )!

源码

2019-05-27 00:12:23 qq_28468727 阅读数 135
  • MT5编程从入门到精通:MQL5编程认知

    本课程是MT5编程从入门到精通的第一阶段课程,详细的介绍了没有编程经验的小白也能快速的进入MT5编程的世界,为没有编程经验的外汇交易爱好者,树立MT5程序化交易的信心。 本阶段课程共分为三个章节,第一章重点讲解了MT5的安装与调试,MT5数据文件介绍,以及学完本课程后能开发出怎么的程序化交易;第二章重点讲解了MT5拥有多货币、多周期回测功能,以及MT5强大的参数优化功能和MT5的EA生成功能;第三章重点讲解了MT5的脚本开发框架,指标开发框架和EA开发框架,以及MT5的断点调试功能。

    2944 人正在学习 去看看 李文帅


我们在刷一下面试题的时候,有时候会看到一些大厂会问关于断点续传的原理,那么今天在这里从 HTTP 断点续传知识和 Android 中如何实现断点续传的思路来做一个关于 Android 断点续传原理的总结。

Http 断点续传知识点

什么是断点续传

指的是在上传/下载时,将任务(一个文件或压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传/下载,如果碰到网络故障,可以从已经上传/下载的部分开始继续上传/下载未完成的部分,而没有必要从头开始上传/下载。可以节省时间,提高速度。

Http 怎么支持断点续传的?

Http 1.1 协议中默认支持获取文件的部分内容,这其中主要是通过头部的两个参数:Range 和 Content Range 来实现的。客户端发请求时对应的是 Range ,服务器端响应时对应的是 Content-Range。

Range

客户端想要获取文件的部分内容,那么它就需要请求头部中的 Range 参数中指定获取内容的起始字节的位置和终止字节的位置,它的格式一般为:

Range:(unit=first byte pos)-[last byte pos]

例如:

Range: bytes=0-499      表示第 0-499 字节范围的内容 
Range: bytes=500-999    表示第 500-999 字节范围的内容 
Range: bytes=-500       表示最后 500 字节的内容 
Range: bytes=500-       表示从第 500 字节开始到文件结束部分的内容 
Range: bytes=0-0,-1     表示第一个和最后一个字节 
Range: bytes=500-600,601-999 同时指定几个范围

Content Range

在收到客户端中携带 Range 的请求后,服务器会在响应的头部中添加 Content Range 参数,返回可接受的文件字节范围及其文件的总大小。它的格式如下:

Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity legth]

例如:

Content-Range: bytes 0-499/22400    // 0-499 是指当前发送的数据的范围,而 22400 则是文件的总大小。

使用断点续传和不使用断点续传的响应内容区别

  • 不使用断点续传
HTTP/1.1 200 Ok
  • 使用断点续传
HTTP/1.1 206 Partial Content

处理请求资源发生改变的问题

在现实的场景中,服务器中的文件是会有发生变化的情况的,那么我们发起续传的请求肯定是失败的,那么为了处理这种服务器文件资源发生改变的问题,在 RFC2616 中定义了 Last-ModifiedEtag 来判断续传文件资源是否发生改变。

Last-Modified & If-Modified-Since(文件最后修改时间)

  • Last-Modified:记录 Http 页面最后修改时间的 Http 头部参数,Last-Modified 是由服务端发送给客户端的
  • If-Modified-Since:记录 Http 页面最后修改时间的 Http 头部参数,If-Modified-Since 是有客户端发送给客户端的
  • 验证过程
    • step 1:客户端缓存从服务端获取的页面
    • step 1:客户端访问相同页面时,客户端将服务器发送过来的 Last-Modified 通过 If-Modified-Since 发送给服务器
    • step 2:服务器通过客户端发送过来的 If-Modified-Since 进行判断客户端当前的缓存的页面是否为最新的
      • 如果不是最新的,那么就发送最新的页面给客户端
      • 如果是最新的,那么就发送 304 告诉客户端它本地缓存的页面是最新的

        Etag & if-Range(文件唯一标志)

  • Etag:作为文件的唯一标志,这个标志可以是文件的 hash 值或者是一个版本
  • if-Range:用于判断实体是否发生改变,如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。一般格式:
If-Range: Etag | HTTP-Date

If-Range 可以使用 Etag 或者 Last-Modified 返回的值。当没有 ETage 却有 Last-modified 时,可以把 Last-modified 作为 If-Range 字段的值

  • 验证过程
    • step 1:客户端发起续传请求,头部包含 Range 和 if-Range 参数
    • step 2:服务器中收到客户端的请求之后,将客户端和服务器的 Etag 进行比对
      • 相等:请求文件资源没有发生变化,应答报文为 206
      • 不相等:请求文件资源发生变化,应答报文为 200

检查服务器是否支持断点续传

05941F7C-66E3-4019-ACB1-4F02EBEE2006.png
我们使用 curl 进行检测,可以看出以下的几个关键信息

  • HTTP/1.1 206 Partial Content
  • Content-Range: bytes 10-222/7877
  • Etag: “1ec5-502264e2ae4c0”
  • Last-Modified: Wed, 03 Sep 2014 10:00:27 GMT

OkHttp 断点下载

断点下载思路

  • step 1:判断检查本地是否有下载文件,若存在,则获取已下载的文件大小 downloadLength,若不存在,那么本地已下载文件的长度为 0
  • step 2:获取将要下载的文件总大小(HTTP 响应头部的 content-Length)
  • step 3:比对已下载文件大小和将要下载的文件总大小(contentLength),判断要下载的长度
  • step 4:再即将发起下载请求的 HTTP 头部中添加即将下载的文件大小范围(Range: bytes = downloadLength - contentLength)

Okhttp 简单短断点下载代码示例

DownloadTask.java


/**
 * String 在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
 * Integer 后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
 * Integer 当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
 */
public class DownloadTask extends AsyncTask<String, Integer, Integer> {

    public static final int TYPE_SUCCESS = 0;

    public static final int TYPE_FAILED = 1;

    public static final int TYPE_PAUSED = 2;

    public static final int TYPE_CANCELED = 3;

    private DownloadListener listener;

    private boolean isCanceled = false;

    private boolean isPaused = false;

    private int lastProgress;

    public DownloadTask(DownloadListener listener) {
        this.listener = listener;
    }

    /**
     * 这个方法中的所有代码都会在子线程中运行,我们应该在这里处理所有的耗时任务。
     *
     * @param params
     * @return
     */
    @Override
    protected Integer doInBackground(String... params) {
        InputStream is = null;
        RandomAccessFile savedFile = null;
        File file = null;
        long downloadLength = 0;   //记录已经下载的文件长度
        //文件下载地址
        String downloadUrl = params[0];
        //下载文件的名称
        String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
        //下载文件存放的目录
        String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
        //创建一个文件
        file = new File(directory + fileName);
        if (file.exists()) {
            //如果文件存在的话,得到文件的大小
            downloadLength = file.length();
        }
        //得到下载内容的大小
        long contentLength = getContentLength(downloadUrl);
        if (contentLength == 0) {
            return TYPE_FAILED;
        } else if (contentLength == downloadLength) {
            //已下载字节和文件总字节相等,说明已经下载完成了
            return TYPE_SUCCESS;
        }
        OkHttpClient client = new OkHttpClient();
        /**
         * HTTP请求是有一个Header的,里面有个Range属性是定义下载区域的,它接收的值是一个区间范围,
         * 比如:Range:bytes=0-10000。这样我们就可以按照一定的规则,将一个大文件拆分为若干很小的部分,
         * 然后分批次的下载,每个小块下载完成之后,再合并到文件中;这样即使下载中断了,重新下载时,
         * 也可以通过文件的字节长度来判断下载的起始点,然后重启断点续传的过程,直到最后完成下载过程。
         */
        Request request = new Request.Builder()
                .addHeader("RANGE", "bytes=" + downloadLength + "-" + contentLength)  //断点续传要用到的,指示下载的区间
                .url(downloadUrl)
                .build();
        try {
            Response response = client.newCall(request).execute();
            if (response != null) {
                is = response.body().byteStream();
                savedFile = new RandomAccessFile(file, "rw");
                savedFile.seek(downloadLength);//跳过已经下载的字节
                byte[] b = new byte[1024];
                int total = 0;
                int len;
                while ((len = is.read(b)) != -1) {
                    if (isCanceled) {
                        return TYPE_CANCELED;
                    } else if (isPaused) {
                        return TYPE_PAUSED;
                    } else {
                        total += len;
                        savedFile.write(b, 0, len);
                        //计算已经下载的百分比
                        int progress = (int) ((total + downloadLength) * 100 / contentLength);
                        //注意:在doInBackground()中是不可以进行UI操作的,如果需要更新UI,比如说反馈当前任务的执行进度,
                        //可以调用publishProgress()方法完成。
                        publishProgress(progress);
                    }

                }
                response.body().close();
                return TYPE_SUCCESS;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
                if (savedFile != null) {
                    savedFile.close();
                }
                if (isCanceled && file != null) {
                    file.delete();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return TYPE_FAILED;
    }

    /**
     * 当在后台任务中调用了publishProgress(Progress...)方法之后,onProgressUpdate()方法
     * 就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以
     * 对界面进行相应的更新。
     *
     * @param values
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        int progress = values[0];
        if (progress > lastProgress) {
            listener.onProgress(progress);
            lastProgress = progress;
        }
    }

    /**
     * 当后台任务执行完毕并通过Return语句进行返回时,这个方法就很快被调用。返回的数据会作为参数
     * 传递到此方法中,可以利用返回的数据来进行一些UI操作。
     *
     * @param status
     */
    @Override
    protected void onPostExecute(Integer status) {
        switch (status) {
            case TYPE_SUCCESS:
                listener.onSuccess();
                break;
            case TYPE_FAILED:
                listener.onFailed();
                break;
            case TYPE_PAUSED:
                listener.onPaused();
                break;
            case TYPE_CANCELED:
                listener.onCanceled();
                break;
            default:
                break;
        }
    }

    public void pauseDownload() {
        isPaused = true;
    }

    public void cancelDownload() {
        isCanceled = true;
    }

    /**
     * 得到下载内容的完整大小
     *
     * @param downloadUrl
     * @return
     */
    private long getContentLength(String downloadUrl) {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(downloadUrl).build();
        try {
            Response response = client.newCall(request).execute();
            if (response != null && response.isSuccessful()) {
                long contentLength = response.body().contentLength();
                response.body().close();
                return contentLength;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return 0;
    }

}

DownloadListener.java


public class DownloadListener {

    /**
     * 通知当前的下载进度
     * @param progress
     */
    void onProgress(int progress);

    /**
     * 通知下载成功
     */
    void onSuccess();

    /**
     * 通知下载失败
     */
    void onFailed();

    /**
     * 通知下载暂停
     */
    void onPaused();

    /**
     * 通知下载取消事件
     */
    void onCanceled();

}
2017-05-07 22:06:39 baidu_35701759 阅读数 1459
  • MT5编程从入门到精通:MQL5编程认知

    本课程是MT5编程从入门到精通的第一阶段课程,详细的介绍了没有编程经验的小白也能快速的进入MT5编程的世界,为没有编程经验的外汇交易爱好者,树立MT5程序化交易的信心。 本阶段课程共分为三个章节,第一章重点讲解了MT5的安装与调试,MT5数据文件介绍,以及学完本课程后能开发出怎么的程序化交易;第二章重点讲解了MT5拥有多货币、多周期回测功能,以及MT5强大的参数优化功能和MT5的EA生成功能;第三章重点讲解了MT5的脚本开发框架,指标开发框架和EA开发框架,以及MT5的断点调试功能。

    2944 人正在学习 去看看 李文帅

1.导入依赖包

//retrofit, 基于Okhttp,考虑到项目中经常会用到retrofit,就导入这个了。
compile 'com.squareup.retrofit2:retrofit:2.1.0'
//ButterKnife
compile 'com.jakewharton:butterknife:7.0.1'
//rxjava 本例中线程切换要用到,代替handler
compile 'io.reactivex:rxjava:1.1.6'
compile 'io.reactivex:rxandroid:1.2.1'

2.继承ResponseBody,生成带进度监听的ProgressResponseBody

package com.example.hxl.breakpointresumeokhttp;

import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;
/**
 * Created by hxl on 2017/2/24 at haiChou.
 * 进度响应体
 */
public class ProgressResponseBody extends ResponseBody{
    //设置对外访问的进度监听
    public interface ProgressListener {
        void onPreExecute(long contentLength);
        void update(long totalBytes, boolean done);
    }
    private final ResponseBody responseBody;
    private final ProgressListener progressListener;
    private BufferedSource bufferedSource;
    public ProgressResponseBody(ResponseBody responseBody,
                                ProgressListener progressListener) {
        this.responseBody = responseBody;
        this.progressListener = progressListener;
        if(progressListener!=null){
            progressListener.onPreExecute(contentLength());
        }
    }
    @Override
    public MediaType contentType() {
        return responseBody.contentType();
    }
    @Override
    public long contentLength() {
        return responseBody.contentLength();
    }
    @Override
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(source(responseBody.source()));
        }
        return bufferedSource;
    }
    private Source source(Source source) {
        return new ForwardingSource(source) {
            long totalBytes = 0L;
            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long bytesRead = super.read(sink, byteCount);
                // read() returns the number of bytes read, or -1 if this source is exhausted.
                totalBytes += bytesRead != -1 ? bytesRead : 0;
                if (null != progressListener) {
                    progressListener.update(totalBytes, bytesRead == -1);
                }
                return bytesRead;
            }
        };
    }
}

3.创建ProgressDownloader

package com.example.hxl.breakpointresumeokhttp;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
/**
 * Created by hxl on 2017/2/24 at haiChou.
 * 创建ProgressDownloader带进度监听功能的辅助类
 */
public class ProgressDownloader {
    public static final String TAG = "ProgressDownloader";
    private ProgressResponseBody.ProgressListener progressListener;
    private String url;
    private OkHttpClient client;
    private File destination;
    private Call call;
    public ProgressDownloader(String url, File destination, ProgressResponseBody.ProgressListener progressListener) {
        this.url = url;
        this.destination = destination;
        this.progressListener = progressListener;
        //在下载、暂停后的继续下载中可复用同一个client对象
        client = getProgressClient();
    }
    //每次下载需要新建新的Call对象
    private Call newCall(long startPoints) {
        Request request = new Request.Builder()
                .url(url)
                .header("RANGE", "bytes=" + startPoints + "-")//断点续传要用到的,指示下载的区间
                .build();
        return client.newCall(request);
    }
    public OkHttpClient getProgressClient() {
        // 拦截器,用上ProgressResponseBody
        Interceptor interceptor = new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Response originalResponse = chain.proceed(chain.request());
                return originalResponse.newBuilder()
                        .body(new ProgressResponseBody(originalResponse.body(), progressListener))
                        .build();
            }
        };
        return new OkHttpClient.Builder()
                .addNetworkInterceptor(interceptor)
                .build();
    }
    // startsPoint指定开始下载的点
    public void download(final long startsPoint) {
        call = newCall(startsPoint);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                save(response, startsPoint);
            }
        });
    }
    public void pause() {
        if(call!=null){
            call.cancel();
        }
    }
    private void save(Response response, long startsPoint) {
        ResponseBody body = response.body();
        InputStream in = body.byteStream();
        FileChannel channelOut = null;
        // 随机访问文件,可以指定断点续传的起始位置
        RandomAccessFile randomAccessFile = null;
        try {
            randomAccessFile = new RandomAccessFile(destination, "rwd");
            //Chanel NIO中的用法,由于RandomAccessFile没有使用缓存策略,直接使用会使得下载速度变慢,亲测缓存下载3.3秒的文件,用普通的RandomAccessFile需要20多秒。
            channelOut = randomAccessFile.getChannel();
            // 内存映射,直接使用RandomAccessFile,是用其seek方法指定下载的起始位置,使用缓存下载,在这里指定下载位置。
            MappedByteBuffer mappedBuffer = channelOut.map(FileChannel.MapMode.READ_WRITE, startsPoint, body.contentLength());
            byte[] buffer = new byte[1024];
            int len;
            while ((len = in.read(buffer)) != -1) {
                mappedBuffer.put(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                in.close();
                if (channelOut != null) {
                    channelOut.close();
                }
                if (randomAccessFile != null) {
                    randomAccessFile.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
4.清单文件中添加网络权限和文件访问权限

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
5.测试
package com.example.hxl.breakpointresumeokhttp;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import java.io.File;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;
/**
 * 1.添加依赖
 * 2.生成带进度监听的ProgressResponseBody
 * 3.创建ProgressDownloader
 * 4.清单文件中添加网络权限和文件访问权限
 */
public class MainActivity extends AppCompatActivity implements ProgressResponseBody.ProgressListener{
    public static final String TAG = "MainActivity";
    public static final String PACKAGE_URL = "http://gdown.baidu.com/data/wisegame/df65a597122796a4/weixin_821.apk";
    @Bind(R.id.progressBar)
    ProgressBar progressBar;
    private long breakPoints;
    private ProgressDownloader downloader;
    private File file;
    private long totalBytes;
    private long contentLength;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
    }
    @OnClick({R.id.downloadButton, R.id.cancel_button, R.id.continue_button})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.downloadButton:
                // 新下载前清空断点信息
                breakPoints = 0L;
                file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "sample.apk");
                downloader = new ProgressDownloader(PACKAGE_URL, file, this);
                downloader.download(0L);
                break;
            case R.id.cancel_button:
                downloader.pause();
                Toast.makeText(this, "下载暂停", Toast.LENGTH_SHORT).show();
                // 存储此时的totalBytes,即断点位置。
                breakPoints = totalBytes;
                break;
            case R.id.continue_button:
                downloader.download(breakPoints);
                break;
        }
    }
    @Override
    public void onPreExecute(long contentLength) {
        // 文件总长只需记录一次,要注意断点续传后的contentLength只是剩余部分的长度
        if (this.contentLength == 0L) {
            this.contentLength = contentLength;
            progressBar.setMax((int) (contentLength / 1024));
        }
    }
    @Override
    public void update(long totalBytes, boolean done) {
        // 注意加上断点的长度
        this.totalBytes = totalBytes + breakPoints;
        progressBar.setProgress((int) (totalBytes + breakPoints) / 1024);
        if (done) {
            // 切换到主线程
            Observable
                    .empty()
                    .observeOn(AndroidSchedulers.mainThread())
                    .doOnCompleted(new Action0() {
                        @Override
                        public void call() {
                            Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
                        }
                    })
                    .subscribe();
        }
    }
}
6.xml布局文件
<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:orientation="vertical"
    tools:context="com.example.hxl.breakpointresumeokhttp.MainActivity">

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:max="100"
        style="?android:attr/progressBarStyleHorizontal" />
    <Button
        android:id="@+id/downloadButton"
        android:layout_marginTop="10dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="下载"/>
    <Button
        android:id="@+id/cancel_button"
        android:layout_marginTop="10dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="暂停"/>
    <Button
        android:id="@+id/continue_button"
        android:layout_marginTop="10dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="继续"/>
</LinearLayout>
7.效果图


2016-11-11 16:09:24 a1533588867 阅读数 1901
  • MT5编程从入门到精通:MQL5编程认知

    本课程是MT5编程从入门到精通的第一阶段课程,详细的介绍了没有编程经验的小白也能快速的进入MT5编程的世界,为没有编程经验的外汇交易爱好者,树立MT5程序化交易的信心。 本阶段课程共分为三个章节,第一章重点讲解了MT5的安装与调试,MT5数据文件介绍,以及学完本课程后能开发出怎么的程序化交易;第二章重点讲解了MT5拥有多货币、多周期回测功能,以及MT5强大的参数优化功能和MT5的EA生成功能;第三章重点讲解了MT5的脚本开发框架,指标开发框架和EA开发框架,以及MT5的断点调试功能。

    2944 人正在学习 去看看 李文帅

上一篇中我们主要介绍了数据的封装和界面的简单实现,如果你还没阅读过,建议先阅读上一篇Android多文件断点续传(一)——数据封装以及界面实现。接着我们还需要先将数据库准备好,这里用了Android自带的SQLite,对SQLite的使用一般都可以按照固定模版来实现的,具体接着往下看。

一.创建DBHelper

/**
 * Created by kun on 2016/11/10.
 */
public class DBHelper extends SQLiteOpenHelper{

    private static final String DB_NAME = "download.db";
    private static  DBHelper dbHelper = null;

    public static DBHelper getInstance(Context context){
        if(dbHelper==null) dbHelper = new DBHelper(context);
        return dbHelper;
    }

    private DBHelper(Context context) {
        super(context, DB_NAME, null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "create table thread_info (_id integer primary key autoincrement," +
                "thread_id integer,url text,start integer,end integer,finished integer)";
        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

由于我们要实现的功能是多文件并且分段下载的功能,因此会涉及到多线程,为了避免在数据库读取的过程中出现问题,在这里我们就使用单例模式创建DBHelper 。

上面代码中我们创建了一个名为download.db的数据库,并在其中创建了一张表thread_info,里面的字段有_id ,thread_id ,url ,start ,end , finished。用于保存下载线程中的下载信息。

如果对数据库更新有要求的,可以继续实现onUpgrade方法。

二. 定义ThreadDao

/**
 * Created by kun on 2016/11/10.
 */
public interface ThreadDao {
    /**
     * 插入下载线程信息
     * @param threadBean
     */
    void insertThread(ThreadBean threadBean);

    /**
     * 更新下载线程信息
     * @param url
     * @param thread_id
     * @param finished
     */
    void updateThread(String url ,int thread_id,int finished);

    /**
     * 删除下载线程
     * @param url
     */
    void deleteThread(String url);

    /**
     * 获取下载线程
     * @param url
     * @return
     */
    List<ThreadBean> getThreads(String url);

    /**
     * 判断下载线程是否存在
     * @param url
     * @param thread_id
     * @return
     */
    boolean isExists(String url ,int thread_id);
}

在这里我们定义了一个接口,里面主要有5个方法,具体看注释就可以了。

三.实现ThreadDao

/**
 * Created by kun on 2016/11/10.
 */
public class ThreadDaoImpl implements ThreadDao {

    private DBHelper dbHelper;

    public ThreadDaoImpl(Context context){
        dbHelper = DBHelper.getInstance(context);
    }

    @Override
    public synchronized void insertThread(ThreadBean threadBean) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        db.execSQL("insert into thread_info ( thread_id, url, start ,end, finished) values (?,?,?,?,?)"
        ,new Object[]{threadBean.getId(),threadBean.getUrl(),threadBean.getStart(),threadBean.getEnd(),threadBean.getFinished()});
        db.close();
    }

    @Override
    public synchronized void updateThread(String url, int thread_id, int finished) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        db.execSQL("update thread_info set finished = ? where url = ? and thread_id = ?"
                ,new Object[]{finished,url,thread_id});
        db.close();
    }

    @Override
    public void deleteThread(String url) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        db.execSQL("delete from thread_info where url = ?",new Object[]{url});
        db.close();
    }

    @Override
    public List<ThreadBean> getThreads(String url) {
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select * from thread_info where url = ?",new String[]{url});
        List<ThreadBean> threadBeanList = new ArrayList<>();
        while (cursor.moveToNext()){
            ThreadBean bean = new ThreadBean();
            bean.setId(cursor.getInt(cursor.getColumnIndex("thread_id")));
            bean.setUrl(cursor.getString(cursor.getColumnIndex("url")));
            bean.setStart(cursor.getInt(cursor.getColumnIndex("start")));
            bean.setEnd(cursor.getInt(cursor.getColumnIndex("end")));
            bean.setFinished(cursor.getInt(cursor.getColumnIndex("finished")));
            threadBeanList.add(bean);
        }
        cursor.close();
        db.close();
        return threadBeanList;
    }

    @Override
    public boolean isExists(String url, int thread_id) {
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select * from thread_info where url = ? and thread_id = ?", new String[]{url,thread_id+""});
        boolean exists = cursor.moveToNext();
        cursor.close();
        db.close();
        return exists;
    }
}

我们创建了ThreadDaoImpl来实现ThreadDao接口,通过DBHelper获取到可写或可读的数据库来实现增删查改,这里需要注意的是在写的操作方法上都加上了同步锁,目的还是一样为了避免在多线程情况下数据库读取的过程中出现问题,另外还需要注意的就是SQL的语句不要写错了。

处理完成数据库后,接着就是实现断点续传了,欢迎阅读下一篇。

Android多文件断点续传(三)——实现文件断点续传

没有更多推荐了,返回首页