最近在开发的过程中遇到一个需求,那就是让 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)