详解Android使用OKHttp3实现下载(断点续传、显示进度)

OKHttp3是如今非常流行的Android网络请求框架,那么如何利用Android实现断点续传呢,今天写了个Demo尝试了一下,感觉还是有点意思

准备阶段

我们会用到OKHttp3来做网络请求,使用RxJava来实现线程的切换,并且开启Java8来启用Lambda表达式,毕竟RxJava实现线程切换非常方便,而且数据流的形式也非常舒服,同时Lambda和RxJava配合食用味道更佳

打开我们的app Module下的build.gradle,代码如下

apply plugin: 'com.android.application' 

android {
  compileSdkVersion 24
  buildToolsVersion "24.0.3" 

  defaultConfig {
    applicationId "com.lanou3g.downdemo"
    minSdkVersion 15
    targetSdkVersion 24
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    //为了开启Java8
    jackOptions{
      enabled true;
    }
  }
  buildTypes {
    release {
      minifyEnabled false
      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
  } 

  //开启Java1.8 能够使用lambda表达式
  compileOptions{
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
} 

dependencies {
  compile fileTree(dir: 'libs', include: ['*.jar'])
  androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
    exclude group: 'com.android.support', module: 'support-annotations'
  })
  compile 'com.android.support:appcompat-v7:24.1.1'
  testCompile 'junit:junit:4.12' 

  //OKHttp
  compile 'com.squareup.okhttp3:okhttp:3.6.0'
  //RxJava和RxAndroid 用来做线程切换的
  compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
  compile 'io.reactivex.rxjava2:rxjava:2.0.1'
}

OKHttp和RxJava,RxAndroid使用的都是最新的版本,并且配置开启了Java8

布局文件

接着开始书写布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/activity_main"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:paddingBottom="@dimen/activity_vertical_margin"
  android:paddingLeft="@dimen/activity_horizontal_margin"
  android:paddingRight="@dimen/activity_horizontal_margin"
  android:paddingTop="@dimen/activity_vertical_margin"
  android:orientation="vertical"
  tools:context="com.lanou3g.downdemo.MainActivity"> 

  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <ProgressBar
      android:id="@+id/main_progress1"
      android:layout_width="0dp"
      android:layout_weight="1"
      android:layout_height="match_parent"
      style="@style/Widget.AppCompat.ProgressBar.Horizontal" />
    <Button
      android:id="@+id/main_btn_down1"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="下载1"/>
    <Button
      android:id="@+id/main_btn_cancel1"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="取消1"/>
  </LinearLayout>
  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <ProgressBar
      android:id="@+id/main_progress2"
      android:layout_width="0dp"
      android:layout_weight="1"
      android:layout_height="match_parent"
      style="@style/Widget.AppCompat.ProgressBar.Horizontal" />
    <Button
      android:id="@+id/main_btn_down2"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="下载2"/>
    <Button
      android:id="@+id/main_btn_cancel2"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="取消2"/>
  </LinearLayout>
  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <ProgressBar
      android:id="@+id/main_progress3"
      android:layout_width="0dp"
      android:layout_weight="1"
      android:layout_height="match_parent"
      style="@style/Widget.AppCompat.ProgressBar.Horizontal" />
    <Button
      android:id="@+id/main_btn_down3"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="下载3"/>
    <Button
      android:id="@+id/main_btn_cancel3"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="取消3"/>
  </LinearLayout>
</LinearLayout>

大概是这个样子的

3个ProgressBar就是为了显示进度的,每个ProgressBar对应2个Button,一个是开始下载,一个是暂停(取消)下载,这里需要说明的是,对下载来说暂停和取消没有什么区别,除非当取消的时候,会顺带把临时文件都删除了,在本例里是不区分他俩的.

Application

我们这里需要用到一些文件路径,有一个全局Context会比较方便, 而Application也是Context的子类,使用它的是最方便的,所以我们写一个类来继承Application

package com.lanou3g.downdemo; 

import android.app.Application;
import android.content.Context; 

/**
 * Created by 陈丰尧 on 2017/2/2.
 */ 

public class MyApp extends Application {
  public static Context sContext;//全局的Context对象 

  @Override
  public void onCreate() {
    super.onCreate();
    sContext = this;
  }
}

可以看到,我们就是要获得一个全局的Context对象的

我们在AndroidManifest中注册一下我们的Application,同时再把我们所需要的权限给上

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.lanou3g.downdemo"> 

  <!--网络权限-->
  <uses-permission android:name="android.permission.INTERNET"/> 

  <application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:name=".MyApp"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" /> 

        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application> 

</manifest>

我们只需要一个网络权限,在application标签下,添加name属性,来指向我们的Application

DownloadManager

接下来是核心代码了,就是我们的DownloadManager,先上代码

package com.lanou3g.downdemo; 

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicReference; 

import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response; 

/**
 * Created by 陈丰尧 on 2017/2/2.
 */ 

public class DownloadManager { 

  private static final AtomicReference<DownloadManager> INSTANCE = new AtomicReference<>();
  private HashMap<String, Call> downCalls;//用来存放各个下载的请求
  private OkHttpClient mClient;//OKHttpClient; 

  //获得一个单例类
  public static DownloadManager getInstance() {
    for (; ; ) {
      DownloadManager current = INSTANCE.get();
      if (current != null) {
        return current;
      }
      current = new DownloadManager();
      if (INSTANCE.compareAndSet(null, current)) {
        return current;
      }
    }
  } 

  private DownloadManager() {
    downCalls = new HashMap<>();
    mClient = new OkHttpClient.Builder().build();
  } 

  /**
   * 开始下载
   *
   * @param url       下载请求的网址
   * @param downLoadObserver 用来回调的接口
   */
  public void download(String url, DownLoadObserver downLoadObserver) {
    Observable.just(url)
        .filter(s -> !downCalls.containsKey(s))//call的map已经有了,就证明正在下载,则这次不下载
        .flatMap(s -> Observable.just(createDownInfo(s)))
        .map(this::getRealFileName)//检测本地文件夹,生成新的文件名
        .flatMap(downloadInfo -> Observable.create(new DownloadSubscribe(downloadInfo)))//下载
        .observeOn(AndroidSchedulers.mainThread())//在主线程回调
        .subscribeOn(Schedulers.io())//在子线程执行
        .subscribe(downLoadObserver);//添加观察者 

  } 

  public void cancel(String url) {
    Call call = downCalls.get(url);
    if (call != null) {
      call.cancel();//取消
    }
    downCalls.remove(url);
  } 

  /**
   * 创建DownInfo
   *
   * @param url 请求网址
   * @return DownInfo
   */
  private DownloadInfo createDownInfo(String url) {
    DownloadInfo downloadInfo = new DownloadInfo(url);
    long contentLength = getContentLength(url);//获得文件大小
    downloadInfo.setTotal(contentLength);
    String fileName = url.substring(url.lastIndexOf("/"));
    downloadInfo.setFileName(fileName);
    return downloadInfo;
  } 

  private DownloadInfo getRealFileName(DownloadInfo downloadInfo) {
    String fileName = downloadInfo.getFileName();
    long downloadLength = 0, contentLength = downloadInfo.getTotal();
    File file = new File(MyApp.sContext.getFilesDir(), fileName);
    if (file.exists()) {
      //找到了文件,代表已经下载过,则获取其长度
      downloadLength = file.length();
    }
    //之前下载过,需要重新来一个文件
    int i = 1;
    while (downloadLength >= contentLength) {
      int dotIndex = fileName.lastIndexOf(".");
      String fileNameOther;
      if (dotIndex == -1) {
        fileNameOther = fileName + "(" + i + ")";
      } else {
        fileNameOther = fileName.substring(0, dotIndex)
            + "(" + i + ")" + fileName.substring(dotIndex);
      }
      File newFile = new File(MyApp.sContext.getFilesDir(), fileNameOther);
      file = newFile;
      downloadLength = newFile.length();
      i++;
    }
    //设置改变过的文件名/大小
    downloadInfo.setProgress(downloadLength);
    downloadInfo.setFileName(file.getName());
    return downloadInfo;
  } 

  private class DownloadSubscribe implements ObservableOnSubscribe<DownloadInfo> {
    private DownloadInfo downloadInfo; 

    public DownloadSubscribe(DownloadInfo downloadInfo) {
      this.downloadInfo = downloadInfo;
    } 

    @Override
    public void subscribe(ObservableEmitter<DownloadInfo> e) throws Exception {
      String url = downloadInfo.getUrl();
      long downloadLength = downloadInfo.getProgress();//已经下载好的长度
      long contentLength = downloadInfo.getTotal();//文件的总长度
      //初始进度信息
      e.onNext(downloadInfo); 

      Request request = new Request.Builder()
          //确定下载的范围,添加此头,则服务器就可以跳过已经下载好的部分
          .addHeader("RANGE", "bytes=" + downloadLength + "-" + contentLength)
          .url(url)
          .build();
      Call call = mClient.newCall(request);
      downCalls.put(url, call);//把这个添加到call里,方便取消
      Response response = call.execute(); 

      File file = new File(MyApp.sContext.getFilesDir(), downloadInfo.getFileName());
      InputStream is = null;
      FileOutputStream fileOutputStream = null;
      try {
        is = response.body().byteStream();
        fileOutputStream = new FileOutputStream(file, true);
        byte[] buffer = new byte[2048];//缓冲数组2kB
        int len;
        while ((len = is.read(buffer)) != -1) {
          fileOutputStream.write(buffer, 0, len);
          downloadLength += len;
          downloadInfo.setProgress(downloadLength);
          e.onNext(downloadInfo);
        }
        fileOutputStream.flush();
        downCalls.remove(url);
      } finally {
        //关闭IO流
        IOUtil.closeAll(is, fileOutputStream); 

      }
      e.onComplete();//完成
    }
  } 

  /**
   * 获取下载长度
   *
   * @param downloadUrl
   * @return
   */
  private long getContentLength(String downloadUrl) {
    Request request = new Request.Builder()
        .url(downloadUrl)
        .build();
    try {
      Response response = mClient.newCall(request).execute();
      if (response != null && response.isSuccessful()) {
        long contentLength = response.body().contentLength();
        response.close();
        return contentLength == 0 ? DownloadInfo.TOTAL_ERROR : contentLength;
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
    return DownloadInfo.TOTAL_ERROR;
  } 

}

代码稍微有点长,关键部位我都加了注释了,我们挑关键地方看看

首先我们这个类是单例类,我们下载只需要一个OKHttpClient就足够了,所以我们让构造方法私有,而单例类的获取实例方法就是这个getInstance();当然大家用别的方式实现单例也可以的,然后我们在构造方法里初始化我们的HttpClient,并且初始化一个HashMap,用来放所有的网络请求的,这样当我们取消下载的时候,就可以找到url对应的网络请求然后把它取消掉就可以了

接下来就是核心的download方法了,首先是参数,第一个参数url不用多说,就是请求的网址,第二个参数是一个Observer对象,因为我们使用的是RxJava,并且没有特别多复杂的方法,所以就没单独写接口,而是谢了一个Observer对象来作为回调,接下来是DownLoadObserver的代码

package com.lanou3g.downdemo; 

import io.reactivex.Observer;
import io.reactivex.disposables.Disposable; 

/**
 * Created by 陈丰尧 on 2017/2/2.
 */ 

public abstract class DownLoadObserver implements Observer<DownloadInfo> {
  protected Disposable d;//可以用于取消注册的监听者
  protected DownloadInfo downloadInfo;
  @Override
  public void onSubscribe(Disposable d) {
    this.d = d;
  } 

  @Override
  public void onNext(DownloadInfo downloadInfo) {
    this.downloadInfo = downloadInfo;
  } 

  @Override
  public void onError(Throwable e) {
    e.printStackTrace();
  } 

}

在RxJava2中 这个Observer有点变化,当注册观察者的时候,会调用onSubscribe方法,而该方法参数就是用来取消注册的,这样的改动可以更灵活的有监听者来取消监听了,我们的进度信息会一直的传送的onNext方法里,这里将下载所需要的内容封了一个类叫DownloadInfo

package com.lanou3g.downdemo; 

/**
 * Created by 陈丰尧 on 2017/2/2.
 * 下载信息
 */ 

public class DownloadInfo {
  public static final long TOTAL_ERROR = -1;//获取进度失败
  private String url;
  private long total;
  private long progress;
  private String fileName; 

  public DownloadInfo(String url) {
    this.url = url;
  } 

  public String getUrl() {
    return url;
  } 

  public String getFileName() {
    return fileName;
  } 

  public void setFileName(String fileName) {
    this.fileName = fileName;
  } 

  public long getTotal() {
    return total;
  } 

  public void setTotal(long total) {
    this.total = total;
  } 

  public long getProgress() {
    return progress;
  } 

  public void setProgress(long progress) {
    this.progress = progress;
  }
}

这个类就是一些基本信息,total就是需要下载的文件的总大小,而progress就是当前下载的进度了,这样就可以计算出下载的进度信息了

接着看DownloadManager的download方法,首先通过url生成一个Observable对象,然后通过filter操作符过滤一下,如果当前正在下载这个url对应的内容,那么就不下载它,

接下来调用createDownInfo重新生成Observable对象,这里应该用map也是可以的,createDownInfo这个方法里会调用getContentLength来获取服务器上的文件大小,可以看一下这个方法的代码,

/**
  * 获取下载长度
  *
  * @param downloadUrl
  * @return
  */
  private long getContentLength(String downloadUrl) {
    Request request = new Request.Builder()
        .url(downloadUrl)
        .build();
    try {
      Response response = mClient.newCall(request).execute();
      if (response != null && response.isSuccessful()) {
        long contentLength = response.body().contentLength();
        response.close();
        return contentLength == 0 ? DownloadInfo.TOTAL_ERROR : contentLength;
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
    return DownloadInfo.TOTAL_ERROR;
  }

可以看到,其实就是在通过OK进行了一次网络请求,并且从返回的头信息里拿到文件的大小信息,一般这个信息都是可以拿到的,除非下载网址不是直接指向资源文件的,而是自己手写的Servlet,那就得跟后台人员沟通好了.注意,这次网络请求并没有真正的去下载文件,而是请求个大小就结束了,具体原因会在后面真正请求数据的时候解释

接着download方法

获取完文件大小后,就可以去硬盘里找文件了,这里调用了getRealFileName方法

private DownloadInfo getRealFileName(DownloadInfo downloadInfo) {
    String fileName = downloadInfo.getFileName();
    long downloadLength = 0, contentLength = downloadInfo.getTotal();
    File file = new File(MyApp.sContext.getFilesDir(), fileName);
    if (file.exists()) {
      //找到了文件,代表已经下载过,则获取其长度
      downloadLength = file.length();
    }
    //之前下载过,需要重新来一个文件
    int i = 1;
    while (downloadLength >= contentLength) {
      int dotIndex = fileName.lastIndexOf(".");
      String fileNameOther;
      if (dotIndex == -1) {
        fileNameOther = fileName + "(" + i + ")";
      } else {
        fileNameOther = fileName.substring(0, dotIndex)
            + "(" + i + ")" + fileName.substring(dotIndex);
      }
      File newFile = new File(MyApp.sContext.getFilesDir(), fileNameOther);
      file = newFile;
      downloadLength = newFile.length();
      i++;
    }
    //设置改变过的文件名/大小
    downloadInfo.setProgress(downloadLength);
    downloadInfo.setFileName(file.getName());
    return downloadInfo;
  }

这个方法就是看本地是否有已经下载过的文件,如果有,再判断一次本地文件的大小和服务器上数据的大小,如果是一样的,证明之前下载全了,就再成一个带(1)这样的文件,而如果本地文件大小比服务器上的小的话,那么证明之前下载了一半断掉了,那么就把进度信息保存上,并把文件名也存上,看完了再回到download方法

之后就开始真正的网络请求了,这里写了一个内部类来实现ObservableOnSubscribe接口,这个接口也是RxJava2的,东西和之前一样,好像只改了名字,看一下代码

private class DownloadSubscribe implements ObservableOnSubscribe<DownloadInfo> {
    private DownloadInfo downloadInfo; 

    public DownloadSubscribe(DownloadInfo downloadInfo) {
      this.downloadInfo = downloadInfo;
    } 

    @Override
    public void subscribe(ObservableEmitter<DownloadInfo> e) throws Exception {
      String url = downloadInfo.getUrl();
      long downloadLength = downloadInfo.getProgress();//已经下载好的长度
      long contentLength = downloadInfo.getTotal();//文件的总长度
      //初始进度信息
      e.onNext(downloadInfo); 

      Request request = new Request.Builder()
          //确定下载的范围,添加此头,则服务器就可以跳过已经下载好的部分
          .addHeader("RANGE", "bytes=" + downloadLength + "-" + contentLength)
          .url(url)
          .build();
      Call call = mClient.newCall(request);
      downCalls.put(url, call);//把这个添加到call里,方便取消
      Response response = call.execute(); 

      File file = new File(MyApp.sContext.getFilesDir(), downloadInfo.getFileName());
      InputStream is = null;
      FileOutputStream fileOutputStream = null;
      try {
        is = response.body().byteStream();
        fileOutputStream = new FileOutputStream(file, true);
        byte[] buffer = new byte[2048];//缓冲数组2kB
        int len;
        while ((len = is.read(buffer)) != -1) {
          fileOutputStream.write(buffer, 0, len);
          downloadLength += len;
          downloadInfo.setProgress(downloadLength);
          e.onNext(downloadInfo);
        }
        fileOutputStream.flush();
        downCalls.remove(url);
      } finally {
        //关闭IO流
        IOUtil.closeAll(is, fileOutputStream); 

      }
      e.onComplete();//完成
    }
  }

主要看subscribe方法

首先拿到url,当前进度信息和文件的总大小,然后开始网络请求,注意这次网络请求的时候需要添加一条头信息

.addHeader("RANGE", "bytes=" + downloadLength + "-" + contentLength) 

这条头信息的意思是下载的范围是多少,downloadLength是从哪开始下载,contentLength是下载到哪,当要断点续传的话必须添加这个头,让输入流跳过多少字节的形式是不行的,所以我们要想能成功的添加这条信息那么就必须对这个url请求2次,一次拿到总长度,来方便判断本地是否有下载一半的数据,第二次才开始真正的读流进行网络请求,我还想了一种思路,当文件没有下载完成的时候添加一个自定义的后缀,当下载完成再把这个后缀取消了,应该就不需要请求两次了.

接下来就是正常的网络请求,向本地写文件了,而写文件到本地这,网上大多用的是RandomAccessFile这个类,但是如果不涉及到多个部分拼接的话是没必要的,直接使用输出流就好了,在输出流的构造方法上添加一个true的参数,代表是在原文件的后面添加数据即可,而在循环里,不断的调用onNext方法发送进度信息,当写完了之后别忘了关流,同时把call对象从hashMap中移除了.这里写了一个IOUtil来关流

package com.lanou3g.downdemo; 

import java.io.Closeable;
import java.io.IOException; 

/**
 * Created by 陈丰尧 on 2017/2/2.
 */ 

public class IOUtil {
  public static void closeAll(Closeable... closeables){
    if(closeables == null){
      return;
    }
    for (Closeable closeable : closeables) {
      if(closeable!=null){
        try {
          closeable.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }
}

其实就是挨一个判断是否为空,并关闭罢了

这样download方法就完成了,剩下的就是切换线程,注册观察者了

MainActivity

最后是aty的代码

package com.lanou3g.downdemo; 

import android.net.Uri;
import android.support.annotation.IdRes;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast; 

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
  private Button downloadBtn1, downloadBtn2, downloadBtn3;
  private Button cancelBtn1, cancelBtn2, cancelBtn3;
  private ProgressBar progress1, progress2, progress3;
  private String url1 = "http://192.168.31.169:8080/out/dream.flac";
  private String url2 = "http://192.168.31.169:8080/out/music.mp3";
  private String url3 = "http://192.168.31.169:8080/out/code.zip";
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main); 

    downloadBtn1 = bindView(R.id.main_btn_down1);
    downloadBtn2 = bindView(R.id.main_btn_down2);
    downloadBtn3 = bindView(R.id.main_btn_down3); 

    cancelBtn1 = bindView(R.id.main_btn_cancel1);
    cancelBtn2 = bindView(R.id.main_btn_cancel2);
    cancelBtn3 = bindView(R.id.main_btn_cancel3); 

    progress1 = bindView(R.id.main_progress1);
    progress2 = bindView(R.id.main_progress2);
    progress3 = bindView(R.id.main_progress3); 

    downloadBtn1.setOnClickListener(this);
    downloadBtn2.setOnClickListener(this);
    downloadBtn3.setOnClickListener(this); 

    cancelBtn1.setOnClickListener(this);
    cancelBtn2.setOnClickListener(this);
    cancelBtn3.setOnClickListener(this);
  } 

  @Override
  public void onClick(View v) {
    switch (v.getId()) {
      case R.id.main_btn_down1:
        DownloadManager.getInstance().download(url1, new DownLoadObserver() {
          @Override
          public void onNext(DownloadInfo value) {
            super.onNext(value);
            progress1.setMax((int) value.getTotal());
            progress1.setProgress((int) value.getProgress());
          } 

          @Override
          public void onComplete() {
            if(downloadInfo != null){
              Toast.makeText(MainActivity.this,
                  downloadInfo.getFileName() + "-DownloadComplete",
                  Toast.LENGTH_SHORT).show();
            }
          }
        });
        break;
      case R.id.main_btn_down2:
        DownloadManager.getInstance().download(url2, new DownLoadObserver() {
          @Override
          public void onNext(DownloadInfo value) {
            super.onNext(value);
            progress2.setMax((int) value.getTotal());
            progress2.setProgress((int) value.getProgress());
          } 

          @Override
          public void onComplete() {
            if(downloadInfo != null){
              Toast.makeText(MainActivity.this,
                  downloadInfo.getFileName() + Uri.encode("下载完成"),
                  Toast.LENGTH_SHORT).show();
            }
          }
        });
        break;
      case R.id.main_btn_down3:
        DownloadManager.getInstance().download(url3, new DownLoadObserver() {
          @Override
          public void onNext(DownloadInfo value) {
            super.onNext(value);
            progress3.setMax((int) value.getTotal());
            progress3.setProgress((int) value.getProgress());
          } 

          @Override
          public void onComplete() {
            if(downloadInfo != null){
              Toast.makeText(MainActivity.this,
                  downloadInfo.getFileName() + "下载完成",
                  Toast.LENGTH_SHORT).show();
            }
          }
        });
        break;
      case R.id.main_btn_cancel1:
        DownloadManager.getInstance().cancel(url1);
        break;
      case R.id.main_btn_cancel2:
        DownloadManager.getInstance().cancel(url2);
        break;
      case R.id.main_btn_cancel3:
        DownloadManager.getInstance().cancel(url3);
        break;
    }
  } 

  private <T extends View> T bindView(@IdRes int id){
    View viewById = findViewById(id);
    return (T) viewById;
  }
}

Activity里没什么了,就是注册监听,开始下载,取消下载这些了,下面我们来看看效果吧

运行效果

可以看到 多个下载,断点续传什么的都已经成功了,最后我的文件网址是我自己的局域网,大家写的时候别忘了换了..

代码地址:demo

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

(0)

相关推荐

  • 详解Android中OkHttp3的例子和在子线程更新UI线程的方法

    okHttp用于android的http请求.据说很厉害,我们来一起尝尝鲜.但是使用okHttp也会有一些小坑,后面会讲到如何掉进坑里并爬出来. 首先需要了解一点,这里说的UI线程和主线程是一回事儿.就是唯一可以更新UI的线程.这个只是点会在给okHttp填坑的时候用到.而且,这个内容本身在日常的开发中也经常用到,值得好好学一学. okHttp发起同步请求 第一个列子是一个同步请求的例子. private void performSyncHttpRequest() { OkHttpClient

  • Android中Okhttp3实现上传多张图片同时传递参数

    之前上传图片都是直接将图片转化为io流传给服务器,没有用框架传图片. 最近做项目,打算换个方法上传图片. Android发展到现在,Okhttp显得越来越重要,所以,这次我选择用Okhttp上传图片. Okhttp目前已经更新到Okhttp3版本了,用法跟之前相比,也有一些差别.在网上找了很多资料, 并和java后台同事反复调试,终于成功上传多张图片,同时传递一些键值对参数. 以下是我对该过程的封装: private static final MediaType MEDIA_TYPE_PNG =

  • Android OKHTTP的单例和再封装的实例

    Android OKHTTP的单例和再封装的实例 /** * Created by zm on 16-2-1 * okhttp的再封装,对于2.x版本,3.x版本将原有对okhttpclient配置 * 改成了builder模式配 * 置,对于超时.代理.dns,okhttp已经做好了配置, * 若不需要特殊配置,可以跳过 */ public class OkHttpUtil { private static OkHttpClient singleton; //非常有必要,要不此类还是可以被ne

  • Android使用OkHttp上传图片的实例代码

    简介 上传图片是一个APP的常见功能,可以是通过OOS上传到阿里云,也可以直接上传到Server后台,OOS有提供相应的SDK,此处忽略.下面通过OkHttp来实现图片的上传 代码 直接上代码UploadFileHelper.kt object UploadFileHelper { //--------ContentType private val MEDIA_OBJECT_STREAM = MediaType.parse("multipart/form-data") //------

  • android Retrofit2+okHttp3使用总结

    使用前准备 Build.gradle文件配置 dependencies配置 compile 'com.squareup.retrofit2:retrofit:2.0.0' compile 'com.squareup.retrofit2:converter-gson:2.0.0' compile 'com.squareup.okhttp3:logging-interceptor:3.2.0' 网络框架搭建 服务创建类封装(HTTP): public class ServiceGenerator {

  • Android OkHttp Post上传文件并且携带参数实例详解

    Android OkHttp Post上传文件并且携带参数 这里整理一下 OkHttp 的 post 在上传文件的同时,也要携带请求参数的方法. 使用 OkHttp 版本如下: compile 'com.squareup.okhttp3:okhttp:3.4.1' 代码如下: protected void post_file(final String url, final Map<String, Object> map, File file) { OkHttpClient client = n

  • Android中实现OkHttp上传文件到服务器并带进度

    在上一讲中 OkHttp下载文件并带进度条 中,我们知道怎样去下载文件了.那上传文件呢 一.编写服务器端 在上一讲服务器下新建UploadFileServlet,代码如下:然后重启服务器! @WebServlet("/UploadFileServlet") @MultipartConfig public class UploadFileServlet extends HttpServlet { private static final long serialVersionUID = 1

  • android中实现OkHttp下载文件并带进度条

    OkHttp是比较火的网络框架,它支持同步与异步请求,支持缓存,可以拦截,更方便下载大文件与上传文件的操作.下面我们用OkHttp来下载文件并带进度条! 相关资料: 官网地址:http://square.github.io/okhttp/ github源码地址:https://github.com/square/okhttp 一.服务器端简单搭建 可以参考搭建本地Tomcat服务器及相关配置这篇文章. 新建项目OkHttpServer,在WebContent目录下新建downloadfile目录

  • 详解Android使用OKHttp3实现下载(断点续传、显示进度)

    OKHttp3是如今非常流行的Android网络请求框架,那么如何利用Android实现断点续传呢,今天写了个Demo尝试了一下,感觉还是有点意思 准备阶段 我们会用到OKHttp3来做网络请求,使用RxJava来实现线程的切换,并且开启Java8来启用Lambda表达式,毕竟RxJava实现线程切换非常方便,而且数据流的形式也非常舒服,同时Lambda和RxJava配合食用味道更佳 打开我们的app Module下的build.gradle,代码如下 apply plugin: 'com.an

  • 实例详解Android文件存储数据方式

    总体的来讲,数据存储方式有三种:一个是文件,一个是数据库,另一个则是网络.下面通过本文给大家介绍Android文件存储数据方式. 1.文件存储数据使用了Java中的IO操作来进行文件的保存和读取,只不过Android在Context类中封装好了输入流和输出流的获取方法. 创建的存储文件保存在/data/data/<package name>/files文件夹下. 2.操作. 保存文件内容:通过Context.openFileOutput获取输出流,参数分别为文件名和存储模式. 读取文件内容:通

  • 详解Android中图片的三级缓存及实例

    详解Android中图片的三级缓存及实例 为什么要使用三级缓存 如今的 Android App 经常会需要网络交互,通过网络获取图片是再正常不过的事了 假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量.在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响 特别是,当我们想要重复浏览一些图片时,如果每一次浏览都需要通过网络获取,流量的浪费可想而知 所以提出三级缓存策略,通过网络.本地.内存三级缓存图片,来减少不必要的网络交互,避免浪费流量

  • 详解Android 中AsyncTask 的使用

    详解Android 中AsyncTask 的使用 1.首先我们来看看AsyncTask 的介绍:   Handler 和 AsyncTask 都是android 中用来实现异步任务处理的方式:其中: Handler 实例向 UI 线程发送消息,完成界面更新, 优点:对整个过程控制的比较精细:         缺点:代码相对臃肿,多个任务同时执行时,不易对线程进行精确的控制: AsyncTask :比Handler 更轻量级一些,适用于简单的异步处理: 优点:简单 | 快捷 | 过程可控:    

  • 详解Android Studio正式签名进行调试的实现步骤

    详解Android Studio正式签名进行调试的实现步骤 在Android Studio中,可以使用Gradle进行打包时自动签名.其实Android Studio默认会给调试应用加上Debug签名,但有时候调一些第三方SDK时,需要正式签名才能调起来,所以接下来分享一下使用Gradle自动签名的方法. 一.创建签名文件 打开AS,选择Build->Generate Signed APK,选择要打包的项目,点击Next,再点击Create new...创建签名文件 填写签名文件响应信息,如下所

  • 详解Android studio如何导入jar包方法

    下面我就总结一下Android studio大家在导入jar包时遇到的一些问题和解决方法: 1,首先先说一下怎么在AS 中找到sdk,jdk,ndk的安装路径,可能一部分人一开始找不到,下面贴出方法: Android studio 中更改sdk的路径,如下图,在右边红色方框中更改sdk的路径 还有一种更好的方式可以把sdk,jdk,ndk的路径全部找到,首先File---Other Settings---Default Project Structure...,打开如下图界面,从红方框处即可直接

  • 详解Android Studio中Git的配置及协同开发

    一. Android Stutio配置git setting–>Version Control–>Git–>Path to Git executable中选择git.exe的位置,这个Stutio一般会默认配置好: 配置完路径后点击后面的Test按钮,出现下面提示框则表示配置成功: 二. 将项目分享到github 1. 设置github账号密码 打开Setting–>Version Control–>GitHub,填写完账号密码后,点击Test测试,如果连接成功会弹出如下提示

  • 详解Android studio 3+版本apk安装失败问题

    studio2.3升级到3.1之后将apk发给别人下载到手机上安装,华为提示安装包无效或与操作系统不兼容,魅族提示apk仅为测试版,要求下载正式版安装. 在网上找了一下,发现是studio3.0之后的instant run功能引起的,直接点击绿色箭头按钮烧出来的apk都是不完整的,也就是魅族指的测试版,并且这个apk的路径在app\build\intermediates\instant-run-apk\debug下,而原来的app\build\outputs\apk\debug路径下已经没有ap

  • 详解Android Studio实现用户登陆界面demo(xml实现)

    使用Android Studio 编写的第一个demo,使用布局文件-xml实现用户登录界面 注:所建工程均为Android 6.0 所以只要是Android 6.0(包括6.0)以上的真机,模拟机都可以使用 Step1:Android Studio 开发环境的搭建: 1.安装JDK (1.8): 2.安装Android studio (3.3.1) 包含 gradle.sdk manage .avd manage : 3.使用sdk manage 下载安装 sdk: 4.使用avd manag

随机推荐