1. 程式人生 > >三種方式讓 Android WebView 支援檔案下載

三種方式讓 Android WebView 支援檔案下載

最近在開發的過程中遇到一個需求,那就是讓 WebView 支援檔案下載,比如說下載 apk。WebView 預設是不支援下載的,需要開發者自己實現。既然 PM 提出了需求,那咱就擼起袖子幹唄,於是乎在網上尋找了幾種方法,主要思路有這麼幾種:

  • 跳轉瀏覽器下載
  • 使用系統的下載服務
  • 自定義下載任務

有了思路就好辦了,下面介紹具體實現。 要想讓 WebView 支援下載,需要給 WebView 設定下載監聽器 setDownloadListener,DownloadListener 裡面只有一個方法 onDownloadStart,每當有檔案需要下載時,該方法就會被回撥,下載的 URL 通過方法引數傳遞,我們可以在這裡處理下載事件。

mWebView.setDownloadListener(new DownloadListener() {
    @Override
    public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
        // TODO: 2017-5-6 處理下載事件
    }
});

1. 跳轉瀏覽器下載

這種方式最為簡單粗暴,直接把下載任務拋給瀏覽器,剩下的就不用我們管了。缺點是無法感知下載完成,當然就沒有後續的處理,比如下載 apk 完成後開啟安裝介面。

private void downloadByBrowser(String url) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addCategory(Intent.CATEGORY_BROWSABLE);
        intent.setData(Uri.parse(url));
        startActivity(intent);
}

2. 使用系統的下載服務

DownloadManager 是系統提供的用於處理下載的服務,使用者只需提供下載 URI 和儲存路徑,並進行簡單的設定。DownloadManager 會在後臺進行下載,並且在下載失敗、網路切換以及系統重啟後嘗試重新下載。

private void downloadBySystem(String url, String contentDisposition, String mimeType) {
        // 指定下載地址
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
        // 允許媒體掃描,根據下載的檔案型別被加入相簿、音樂等媒體庫
        request.allowScanningByMediaScanner();
        // 設定通知的顯示型別,下載進行時和完成後顯示通知
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
        // 設定通知欄的標題,如果不設定,預設使用檔名
//        request.setTitle("This is title");
        // 設定通知欄的描述
//        request.setDescription("This is description");
        // 允許在計費流量下下載
        request.setAllowedOverMetered(false);
        // 允許該記錄在下載管理介面可見
        request.setVisibleInDownloadsUi(false);
        // 允許漫遊時下載
        request.setAllowedOverRoaming(true);
        // 允許下載的網路型別
        request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
        // 設定下載檔案儲存的路徑和檔名
        String fileName  = URLUtil.guessFileName(url, contentDisposition, mimeType);
        log.debug("fileName:{}", fileName);
        request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
//        另外可選一下方法,自定義下載路徑
//        request.setDestinationUri()
//        request.setDestinationInExternalFilesDir()
        final DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
        // 新增一個下載任務
        long downloadId = downloadManager.enqueue(request);
        log.debug("downloadId:{}", downloadId);
    }

這樣我們就添加了一項下載任務,然後就靜靜等待系統下載完成吧。還要注意一點,別忘了新增讀寫外接儲存許可權和網路許可權哦~ 那怎麼知道檔案下載成功呢?系統在下載完成後會發送一條廣播,裡面有任務 ID,告訴呼叫者任務完成,通過 DownloadManager 獲取到檔案資訊就可以進一步處理。

private class DownloadCompleteReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            log.verbose("onReceive. intent:{}", intent != null ? intent.toUri(0) : null);
            if (intent != null) {
                if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
                    long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
                    log.debug("downloadId:{}", downloadId);
                    DownloadManager downloadManager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
                    String type = downloadManager.getMimeTypeForDownloadedFile(downloadId);
                    log.debug("getMimeTypeForDownloadedFile:{}", type);
                    if (TextUtils.isEmpty(type)) {
                        type = "*/*";
                    }
                    Uri uri = downloadManager.getUriForDownloadedFile(downloadId);
                    log.debug("UriForDownloadedFile:{}", uri);
                    if (uri != null) {
                        Intent handlerIntent = new Intent(Intent.ACTION_VIEW);
                        handlerIntent.setDataAndType(uri, type);
                        context.startActivity(handlerIntent);
                    }
                }
            }
        }
    }

        // 使用
        DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
        registerReceiver(receiver, intentFilter);

Ok,到這裡,利用系統服務下載就算結束了,簡單總結一下。我們只關心開始和完成,至於下載過程中的暫停、重試等機制,系統已經幫我們做好了,是不是非常友好?

3. 自定義下載任務

有了下載連結就可以自己實現網路部分,我在這兒自定義了一個下載任務,使用 HttpURLConnection 和 AsyncTask 實現,程式碼還是比較簡單的。

private class DownloadTask extends AsyncTask<String, Void, Void> {
        // 傳遞兩個引數:URL 和 目標路徑
        private String url;
        private String destPath;

        @Override
        protected void onPreExecute() {
            log.info("開始下載");
        }

        @Override
        protected Void doInBackground(String... params) {
            log.debug("doInBackground. url:{}, dest:{}", params[0], params[1]);
            url = params[0];
            destPath = params[1];
            OutputStream out = null;
            HttpURLConnection urlConnection = null;
            try {
                URL url = new URL(params[0]);
                urlConnection = (HttpURLConnection) url.openConnection();
                urlConnection.setConnectTimeout(15000);
                urlConnection.setReadTimeout(15000);
                InputStream in = urlConnection.getInputStream();
                out = new FileOutputStream(params[1]);
                byte[] buffer = new byte[10 * 1024];
                int len;
                while ((len = in.read(buffer)) != -1) {
                    out.write(buffer, 0, len);
                }
                in.close();
            } catch (IOException e) {
                log.warn(e);
            } finally {
                if (urlConnection != null) {
                    urlConnection.disconnect();
                }
                if (out != null) {
                    try {
                        out.close();
                    } catch (IOException e) {
                        log.warn(e);
                    }
                }
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            log.info("完成下載");
            Intent handlerIntent = new Intent(Intent.ACTION_VIEW);
            String mimeType = getMIMEType(url);
            Uri uri = Uri.fromFile(new File(destPath));
            log.debug("mimiType:{}, uri:{}", mimeType, uri);
            handlerIntent.setDataAndType(uri, mimeType);
            startActivity(handlerIntent);
        }
    }

    private String getMIMEType(String url) {
        String type = null;
        String extension = MimeTypeMap.getFileExtensionFromUrl(url);
        log.debug("extension:{}", extension);
        if (extension != null) {
            type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
        }
        return type;
    }

        //  使用
        mWebView.setDownloadListener(new DownloadListener() {
            @Override
            public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
                String fileName = URLUtil.guessFileName(url, contentDisposition, mimeType);
                String destPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
                        .getAbsolutePath() + File.separator + fileName;
                new DownloadTask().execute(url, destPath);
            }
        });

優勢是我們可以感知下載進度,處理開始、取消、失敗、完成等事件,不足之處是對下載的控制不如系統服務,必須自己處理網路帶來的問題。

可以看出,這三種下載方式各有特點,大家可以根據需要選擇。