WebView文件下载

在 Android 开发中,WebView 是一个强大的组件,用于在应用中加载网页或 HTML 内容。然而,WebView 默认并不直接支持文件下载(如 PDF、图片、APK 等),需要开发者通过拦截下载请求并结合 Android 的文件下载机制来实现。以下是 WebView 文件下载的详细中文讲解,涵盖基本实现、进度监听、权限管理、注意事项以及代码示例,特别针对 Android 4.4 及以上版本(基于 Chromium 引擎)。


一、WebView 文件下载概述

  • 目标:拦截 WebView 中的文件下载请求(如点击网页中的下载链接),并使用 Android 的下载机制(如 DownloadManager 或自定义下载)将文件保存到设备存储。
  • 常见场景
  • 用户在 WebView 中点击下载链接(如 https://example.com/file.pdf)。
  • 下载图片、视频、文档等文件。
  • 显示下载进度或通知用户下载完成。
  • 实现方式
  • 使用 WebViewClientshouldOverrideUrlLoadingonDownloadStart 拦截下载链接。
  • 结合 DownloadManager(系统下载服务)或自定义下载(如 OkHttp、HttpURLConnection)处理文件。
  • 关键点
  • 处理权限(网络和存储)。
  • 适配 Android 10+ 的 Scoped Storage。
  • 支持下载进度监听和错误处理。
  • 确保 WebView 的安全性和性能。

二、准备工作

  1. 权限声明
    AndroidManifest.xml 中添加以下权限:
   <uses-permission android:name="android.permission.INTERNET" />
   <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
   <!-- Android 9 及以下需要 -->
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
   <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  1. 动态权限(Android 6.0+):
  • 对于存储权限,需动态请求:
    java if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100); } }
  1. 布局文件
    res/layout/activity_main.xml 中添加 WebView:
   <WebView
       android:id="@+id/webView"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />
  1. 依赖(可选):
  • 如果使用 OkHttp 进行自定义下载:
    gradle implementation 'com.squareup.okhttp3:okhttp:4.12.0'
  1. 文件保存路径
  • 内部存储context.getFilesDir()
  • 外部存储Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
  • Android 10+:使用 MediaStoreScoped Storage 保存文件。

三、WebView 文件下载实现

以下是使用 WebView 实现文件下载的详细步骤,重点介绍两种主要方式:DownloadManager(推荐用于简单下载)和 自定义下载(支持进度监听和断点续传)。

1. 使用 DownloadManager 实现文件下载

DownloadManager 是 Android 提供的系统级下载服务,适合简单文件下载,支持通知栏显示进度和完成状态。

  • 代码示例
  import android.app.DownloadManager;
  import android.content.Context;
  import android.net.Uri;
  import android.os.Bundle;
  import android.os.Environment;
  import android.webkit.DownloadListener;
  import android.webkit.WebView;
  import android.webkit.WebViewClient;
  import androidx.appcompat.app.AppCompatActivity;

  public class MainActivity extends AppCompatActivity {
      private WebView webView;

      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);

          webView = findViewById(R.id.webView);
          webView.getSettings().setJavaScriptEnabled(true); // 启用 JavaScript(谨慎)
          webView.setWebViewClient(new WebViewClient());

          // 设置下载监听器
          webView.setDownloadListener((url, userAgent, contentDisposition, mimetype, contentLength) -> {
              // 创建下载请求
              DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
              request.setTitle("Downloading File");
              request.setDescription("File from WebView");
              request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, getFileNameFromUrl(url));
              request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
              request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE);

              // 获取 DownloadManager 并开始下载
              DownloadManager downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
              downloadManager.enqueue(request);

              Toast.makeText(this, "Download started", Toast.LENGTH_SHORT).show();
          });

          // 加载网页
          webView.loadUrl("https://example.com");
      }

      // 从 URL 提取文件名
      private String getFileNameFromUrl(String url) {
          return url.substring(url.lastIndexOf('/') + 1);
      }
  }
  • 说明
  • setDownloadListener:拦截 WebView 中的下载请求,获取 URL、MIME 类型等信息。
  • DownloadManager.Request:配置下载任务,设置保存路径和通知。
  • 优点:简单,自动显示通知栏进度,无需手动管理下载。
  • 局限性:不支持自定义进度监听或断点续传。
  • 监听下载完成(可选):
  import android.content.BroadcastReceiver;
  import android.content.Intent;
  import android.content.IntentFilter;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      // ... 其他代码
      // 注册广播监听下载完成
      registerReceiver(new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
              Toast.makeText(context, "Download completed", Toast.LENGTH_SHORT).show();
          }
      }, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
  }

2. 自定义下载(使用 OkHttp)

对于需要进度监听或断点续传的场景,可以通过 OkHttp 实现自定义下载。

  • 代码示例
  import android.content.Context;
  import android.os.Bundle;
  import android.os.Environment;
  import android.webkit.DownloadListener;
  import android.webkit.WebView;
  import android.webkit.WebViewClient;
  import androidx.appcompat.app.AppCompatActivity;
  import okhttp3.OkHttpClient;
  import okhttp3.Request;
  import okio.BufferedSink;
  import okio.Okio;
  import java.io.File;

  public class MainActivity extends AppCompatActivity {
      private WebView webView;

      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);

          webView = findViewById(R.id.webView);
          webView.getSettings().setJavaScriptEnabled(true);
          webView.setWebViewClient(new WebViewClient());

          // 设置下载监听器
          webView.setDownloadListener((url, userAgent, contentDisposition, mimetype, contentLength) -> {
              new Thread(() -> {
                  String result = downloadFileWithOkHttp(url, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/" + getFileNameFromUrl(url));
                  runOnUiThread(() -> Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show());
              }).start();
          });

          // 加载网页
          webView.loadUrl("https://example.com");
      }

      private String getFileNameFromUrl(String url) {
          return url.substring(url.lastIndexOf('/') + 1);
      }

      private String downloadFileWithOkHttp(String fileUrl, String savePath) {
          OkHttpClient client = new OkHttpClient();
          Request request = new Request.Builder()
                  .url(fileUrl)
                  .get()
                  .addHeader("Accept", "*/*")
                  .build();

          try (Response response = client.newCall(request).execute()) {
              if (response.isSuccessful() && response.body() != null) {
                  File file = new File(savePath);
                  if (!file.getParentFile().exists()) {
                      file.getParentFile().mkdirs();
                  }
                  BufferedSink sink = Okio.buffer(Okio.sink(file));
                  sink.writeAll(response.body().source());
                  sink.close();
                  return "Download completed: " + savePath;
              } else {
                  return "Error: HTTP " + response.code();
              }
          } catch (Exception e) {
              e.printStackTrace();
              return "Exception: " + e.getMessage();
          }
      }
  }
  • 说明
  • setDownloadListener:捕获下载请求,启动异步线程使用 OkHttp 下载。
  • OkHttp:高效处理文件流,适合自定义下载。
  • 优点:支持进度监听(需扩展,见下文)和断点续传。

3. 添加下载进度监听

通过自定义 OkHttp 的 Interceptor 实现下载进度监听。

  • 代码示例
  import android.os.Bundle;
  import android.os.Environment;
  import android.webkit.DownloadListener;
  import android.webkit.WebView;
  import android.webkit.WebViewClient;
  import android.widget.Toast;
  import androidx.appcompat.app.AppCompatActivity;
  import okhttp3.Interceptor;
  import okhttp3.OkHttpClient;
  import okhttp3.Request;
  import okhttp3.Response;
  import okio.BufferedSink;
  import okio.Okio;
  import java.io.File;

  public class MainActivity extends AppCompatActivity {
      private WebView webView;

      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);

          webView = findViewById(R.id.webView);
          webView.getSettings().setJavaScriptEnabled(true);
          webView.setWebViewClient(new WebViewClient());

          // 设置下载监听器
          webView.setDownloadListener((url, userAgent, contentDisposition, mimetype, contentLength) -> {
              new Thread(() -> {
                  String result = downloadFileWithProgress(url, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/" + getFileNameFromUrl(url), progress -> {
                      runOnUiThread(() -> Toast.makeText(MainActivity.this, "Progress: " + progress + "%", Toast.LENGTH_SHORT).show());
                  });
                  runOnUiThread(() -> Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show());
              }).start();
          });

          // 加载网页
          webView.loadUrl("https://example.com");
      }

      private String getFileNameFromUrl(String url) {
          return url.substring(url.lastIndexOf('/') + 1);
      }

      public interface ProgressListener {
          void onProgress(int progress);
      }

      private String downloadFileWithProgress(String fileUrl, String savePath, ProgressListener listener) {
          OkHttpClient client = new OkHttpClient.Builder()
                  .addNetworkInterceptor(chain -> {
                      Response originalResponse = chain.proceed(chain.request());
                      return originalResponse.newBuilder()
                              .body(new ProgressResponseBody(originalResponse.body(), listener))
                              .build();
                  })
                  .build();

          Request request = new Request.Builder()
                  .url(fileUrl)
                  .get()
                  .build();

          try (Response response = client.newCall(request).execute()) {
              if (response.isSuccessful() && response.body() != null) {
                  File file = new File(savePath);
                  if (!file.getParentFile().exists()) {
                      file.getParentFile().mkdirs();
                  }
                  BufferedSink sink = Okio.buffer(Okio.sink(file));
                  sink.writeAll(response.body().source());
                  sink.close();
                  return "Download completed: " + savePath;
              } else {
                  return "Error: HTTP " + response.code();
              }
          } catch (Exception e) {
              e.printStackTrace();
              return "Exception: " + e.getMessage();
          }
      }

      private static class ProgressResponseBody extends ResponseBody {
          private final ResponseBody responseBody;
          private final ProgressListener listener;
          private BufferedSource bufferedSource;

          public ProgressResponseBody(ResponseBody responseBody, ProgressListener listener) {
              this.responseBody = responseBody;
              this.listener = listener;
          }

          @Override
          public MediaType contentType() {
              return responseBody.contentType();
          }

          @Override
          public long contentLength() {
              return responseBody.contentLength();
          }

          @Override
          public BufferedSource source() {
              if (bufferedSource == null) {
                  bufferedSource = Okio.buffer(new ProgressSource(responseBody.source(), contentLength(), listener));
              }
              return bufferedSource;
          }

          private static class ProgressSource extends ForwardingSource {
              private final ProgressListener listener;
              private final long totalBytes;
              private long bytesRead = 0;

              public ProgressSource(Source source, long totalBytes, ProgressListener listener) {
                  super(source);
                  this.totalBytes = totalBytes;
                  this.listener = listener;
              }

              @Override
              public long read(Buffer sink, long byteCount) throws IOException {
                  long bytesReadThisTime = super.read(sink, byteCount);
                  if (bytesReadThisTime != -1) {
                      bytesRead += bytesReadThisTime;
                      if (totalBytes > 0) {
                          int progress = (int) (bytesRead * 100 / totalBytes);
                          listener.onProgress(progress);
                      }
                  }
                  return bytesReadThisTime;
              }
          }
      }
  }
  • 说明
  • 使用 OkHttp 的 Interceptor 包装 ResponseBody,监听下载进度。
  • 通过 ProgressListener 回调更新 UI(如显示进度条)。

4. 支持断点续传

通过 Range 请求头实现断点续传,需记录已下载的文件大小。

  • 代码示例
  private String downloadFileWithResume(String fileUrl, String savePath, ProgressListener listener) {
      File file = new File(savePath);
      long downloadedSize = file.exists() ? file.length() : 0;

      OkHttpClient client = new OkHttpClient.Builder()
              .addNetworkInterceptor(chain -> {
                  Response originalResponse = chain.proceed(chain.request());
                  return originalResponse.newBuilder()
                          .body(new ProgressResponseBody(originalResponse.body(), listener))
                          .build();
              })
              .build();

      Request request = new Request.Builder()
              .url(fileUrl)
              .get()
              .addHeader("Range", "bytes=" + downloadedSize + "-")
              .build();

      try (Response response = client.newCall(request).execute()) {
          if ((response.isSuccessful() || response.code() == 206) && response.body() != null) {
              if (!file.getParentFile().exists()) {
                  file.getParentFile().mkdirs();
              }
              BufferedSink sink = Okio.buffer(Okio.appendingSink(file));
              sink.writeAll(response.body().source());
              sink.close();
              return "Download completed: " + savePath;
          } else {
              return "Error: HTTP " + response.code();
          }
      } catch (Exception e) {
          e.printStackTrace();
          return "Exception: " + e.getMessage();
      }
  }
  • 说明
  • 使用 Range 头指定下载起始位置。
  • 使用 Okio.appendingSink 支持追加写入。
  • 服务器需支持 206 Partial Content

四、注意事项

  1. 权限管理
  • Android 6.0+:动态请求存储权限。
  • Android 10+:使用 MediaStore 保存文件:
    java ContentValues values = new ContentValues(); values.put(MediaStore.Downloads.DISPLAY_NAME, "file.pdf"); values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS); Uri uri = getContentResolver().insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values); OutputStream out = getContentResolver().openOutputStream(uri);
  • 配置 AndroidManifest.xml
    xml <application android:requestLegacyExternalStorage="true">
  1. 安全问题
  • HTTPS:Android 9+ 默认禁用 HTTP,优先使用 HTTPS 下载链接。
  • 验证 URL:检查下载 URL 是否来自可信域名:
    java if (!url.startsWith("https://")) { Toast.makeText(this, "Only HTTPS downloads allowed", Toast.LENGTH_SHORT).show(); return; }
  • 文件类型:验证 MIME 类型,避免下载恶意文件:
    java if (!mimetype.startsWith("application/") && !mimetype.startsWith("image/")) { Toast.makeText(this, "Unsupported file type", Toast.LENGTH_SHORT).show(); return; }
  1. 错误处理
  • 处理 HTTP 错误和网络异常:
    java if (response.code() >= 400) { runOnUiThread(() -> Toast.makeText(this, "Download failed: HTTP " + response.code(), Toast.LENGTH_SHORT).show()); }
  • 检查网络状态:
    java ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = cm.getActiveNetworkInfo(); if (networkInfo == null || !networkInfo.isConnected()) { Toast.makeText(this, "No network", Toast.LENGTH_SHORT).show(); }
  1. 性能优化
  • 使用缓冲区(4KB 或 8KB)读取流,减少 IO 开销。
  • 启用 WebView 缓存:
    java webView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT); webView.getSettings().setAppCacheEnabled(true);
  • 异步下载,避免阻塞主线程。
  1. WebView 生命周期
  • 暂停/恢复 WebView: @Override protected void onPause() { super.onPause(); webView.onPause(); webView.pauseTimers(); } @Override protected void onResume() { super.onResume(); webView.onResume(); webView.resumeTimers(); }
  • 销毁 WebView:
    java @Override protected void onDestroy() { if (webView != null) { webView.stopLoading(); webView.clearCache(true); webView.loadUrl("about:blank"); webView.removeAllViews(); webView.destroy(); webView = null; } super.onDestroy(); }
  1. Android 4.4+ 特性
  • Chromium 引擎:支持现代 Web 标准,但内存占用较高。
  • WebView 更新:通过 Google Play 更新,可能导致版本差异,需测试兼容性:
    java String webViewVersion = WebView.getCurrentWebViewPackage().versionName; Log.d("WebView", "Version: " + webViewVersion);

五、学习建议与实践

  1. 学习路径
  • 掌握 setDownloadListener 拦截下载请求。
  • 使用 DownloadManager 实现简单下载。
  • 学习 OkHttp 实现自定义下载,支持进度监听和断点续传。
  • 适配 Android 10+ 的 Scoped Storage。
  • 调试 WebView 下载行为。
  1. 实践项目
  • 简单项目:在 WebView 中加载网页,点击下载链接使用 DownloadManager 保存文件。
  • 进阶项目:使用 OkHttp 实现下载,支持进度条显示。
  • 高级项目:实现断点续传,保存下载状态。
  1. 调试工具
  • Chrome DevTools:通过 chrome://inspect 调试 WebView(需开发者模式)。
  • Charles/Fiddler:抓包分析下载请求。
  • Logcat:查看下载日志和错误。
  • Android Studio Network Profiler:监控网络请求。
  1. 推荐资源
  • Android 官方文档:https://developer.android.com/reference/android/webkit/WebView
  • DownloadManager 文档:https://developer.android.com/reference/android/app/DownloadManager
  • OkHttp 文档:https://square.github.io/okhttp/
  • 测试 URL:https://speedtest.tele2.net/1MB.zip

六、总结

  • 实现方式
  • DownloadManager:简单,自动通知,适合基本下载。
  • OkHttp 自定义下载:支持进度监听和断点续传,适合复杂场景。
  • 核心步骤
  • 使用 setDownloadListener 拦截下载请求。
  • 配置保存路径,处理权限。
  • 处理下载进度和错误。
  • 注意事项
  • 权限管理:动态请求存储权限,适配 Scoped Storage。
  • 安全性:优先 HTTPS,验证文件类型。
  • 性能:异步下载,启用缓存。
  • 生命周期:正确暂停和销毁 WebView。
  • 推荐:简单场景使用 DownloadManager,复杂需求使用 OkHttp。

七、完整代码示例

以下是一个完整的 WebView 文件下载示例,使用 DownloadManager 和 OkHttp,支持进度监听:


import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.webkit.DownloadListener;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okio.BufferedSink;
import okio.Okio;
import java.io.File;

public class MainActivity extends AppCompatActivity {
private WebView webView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    webView = findViewById(R.id.webView);
    webView.getSettings().setJavaScriptEnabled(true);
    webView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
    webView.getSettings().setAppCacheEnabled(true);
    webView.getSettings().setAppCachePath(getCacheDir().getAbsolutePath());
    webView.setWebViewClient(new WebViewClient());

    // 设置下载监听器
    webView.setDownloadListener((url, userAgent, contentDisposition, mimetype, contentLength) -> {
        if (!url.startsWith("https://")) {
            Toast.makeText(this, "Only HTTPS downloads allowed", Toast.LENGTH_SHORT).show();
            return;
        }

        // 使用 DownloadManager 下载
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
        request.setTitle(getFileNameFromUrl(url));
        request.setDescription("Downloading from WebView");
        request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, getFileNameFromUrl(url));
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
        request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE);

        DownloadManager downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
        downloadManager.enqueue(request);
        Toast.makeText(this, "Download started", Toast.LENGTH_SHORT).show();

        // 或者使用 OkHttp 自定义下载(支持进度)
        /*
        new Thread(() -> {
            String result = downloadFileWithProgress(url, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/" + getFileNameFromUrl(url), progress -> {
                runOnUiThread(() -> Toast.makeText(this, "Progress: " + progress + "%", Toast.LENGTH_SHORT).show());
            });
            runOnUiThread(() -> Toast.makeText(this, result, Toast.LENGTH_SHORT).show());
        }).start();
        */
    });

    // 注册下载完成广播
    registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "Download completed", Toast.LENGTH_SHORT).show();
        }
    }, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));

    // 加载网页
    webView.loadUrl("https://example.com");
}

private String getFileNameFromUrl(String url) {
    return url.substring(url.lastIndexOf('/') + 1);
}

public interface ProgressListener {
    void onProgress(int progress);
}

private String downloadFileWithProgress(String fileUrl, String savePath, ProgressListener listener) {
    OkHttpClient client = new OkHttpClient.Builder()
            .addNetworkInterceptor(chain -> {
                Response originalResponse = chain.proceed(chain.request());
                return originalResponse.newBuilder()
                        .body(new ProgressResponseBody(originalResponse.body(), listener))
                        .build();
            })
            .build();

    Request request = new Request.Builder()
            .url(fileUrl)
            .get()
            .build();

    try (Response response = client.newCall(request).execute()) {
        if (response.isSuccessful() && response.body() != null) {
            File file = new File(savePath);
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            BufferedSink sink = Okio.buffer(Okio.sink(file));
            sink.writeAll(response.body().source());
            sink.close();
            return "Download completed: " + savePath;
        } else {
            return "Error: HTTP " + response.code();
        }
    } catch (Exception e) {
        e.printStackTrace();
        return "Exception: " + e.getMessage();
    }
}

private static class ProgressResponseBody extends ResponseBody {
    private final ResponseBody responseBody;
    private final ProgressListener listener;
    private BufferedSource bufferedSource;

    public ProgressResponseBody(ResponseBody responseBody, ProgressListener listener) {
        this.responseBody = responseBody;
        this.listener = listener;
    }

    @Override
    public MediaType contentType() {
        return responseBody.contentType();
    }

    @Override
    public long contentLength() {
        return responseBody.contentLength();
    }

    @Override
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(new ProgressSource(responseBody.source(), contentLength(), listener));
        }
        return bufferedSource;
    }

    private static class ProgressSource extends ForwardingSource {
        private final ProgressListener listener;
        private final long totalBytes;
        private long bytesRead = 0;

        public ProgressSource(Source source, long totalBytes, ProgressListener listener) {
            super(source);
            this.totalBytes = totalBytes;
            this.listener = listener;
        }

        @Override
        public long read(Buffer sink, long byteCount) throws IOException {
            long bytesReadThisTime = super.read(sink, byteCount);
            if (bytesReadThisTime != -1) {
                bytesRead += bytesReadThisTime;
                if (totalBytes > 0) {
                    int progress = (int) (bytesRead * 100 / totalBytes);
                    listener.onProgress(progress);
                }
            }
            return bytesReadThisTime;
        }
    }
}

@Override
protected void onPause() {
    super.onPause();
    webView.onPause();
    webView.pauseTimers();
}

@Override
protected void onResume() {
    super.onResume();
    webView.onResume();
    webView.resumeTimers();
}

@Override
protected void onDestroy() {
    if (webView != null) {
        webView.stopLoading();
        webView.clearCache(true);
        webView.loadUrl("about:blank");
        webView.removeAllViews();
        webView.destroy();
        webView = null;
    }
    super.onDestroy();
}

}

如果需要更详细的代码(如断点续传、MediaStore 集成)或特定场景的讲解,请告诉我!

文章已创建 2371

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部