最近在開發的過程中遇到一個需求,那就是讓 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)