最近在開發(fā)的過(guò)程中遇到一個(gè)需求叭喜,那就是讓 WebView 支持文件下載,比如說(shuō)下載 apk靖榕。WebView 默認(rèn)是不支持下載的标锄,需要開發(fā)者自己實(shí)現(xiàn)。既然 PM 提出了需求茁计,那咱就擼起袖子干唄料皇,于是乎在網(wǎng)上尋找了幾種方法,主要思路有這么幾種:
- 跳轉(zhuǎn)瀏覽器下載
- 使用系統(tǒng)的下載服務(wù)
- 自定義下載任務(wù)
有了思路就好辦了星压,下面介紹具體實(shí)現(xiàn)践剂。
要想讓 WebView 支持下載,需要給 WebView 設(shè)置下載監(jiān)聽器 setDownloadListener
娜膘,DownloadListener 里面只有一個(gè)方法 onDownloadStart逊脯,每當(dāng)有文件需要下載時(shí),該方法就會(huì)被回調(diào)竣贪,下載的 URL 通過(guò)方法參數(shù)傳遞军洼,我們可以在這里處理下載事件。
mWebView.setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
// TODO: 2017-5-6 處理下載事件
}
});
1. 跳轉(zhuǎn)瀏覽器下載
這種方式最為簡(jiǎn)單粗暴演怎,直接把下載任務(wù)拋給瀏覽器匕争,剩下的就不用我們管了。缺點(diǎn)是無(wú)法感知下載完成颤枪,當(dāng)然就沒(méi)有后續(xù)的處理汗捡,比如下載 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. 使用系統(tǒng)的下載服務(wù)
DownloadManager 是系統(tǒng)提供的用于處理下載的服務(wù)畏纲,使用者只需提供下載 URI 和存儲(chǔ)路徑扇住,并進(jìn)行簡(jiǎn)單的設(shè)置。DownloadManager 會(huì)在后臺(tái)進(jìn)行下載盗胀,并且在下載失敗艘蹋、網(wǎng)絡(luò)切換以及系統(tǒng)重啟后嘗試重新下載。
private void downloadBySystem(String url, String contentDisposition, String mimeType) {
// 指定下載地址
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
// 允許媒體掃描票灰,根據(jù)下載的文件類型被加入相冊(cè)女阀、音樂(lè)等媒體庫(kù)
request.allowScanningByMediaScanner();
// 設(shè)置通知的顯示類型,下載進(jìn)行時(shí)和完成后顯示通知
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
// 設(shè)置通知欄的標(biāo)題屑迂,如果不設(shè)置浸策,默認(rèn)使用文件名
// request.setTitle("This is title");
// 設(shè)置通知欄的描述
// request.setDescription("This is description");
// 允許在計(jì)費(fèi)流量下下載
request.setAllowedOverMetered(false);
// 允許該記錄在下載管理界面可見
request.setVisibleInDownloadsUi(false);
// 允許漫游時(shí)下載
request.setAllowedOverRoaming(true);
// 允許下載的網(wǎng)路類型
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
// 設(shè)置下載文件保存的路徑和文件名
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);
// 添加一個(gè)下載任務(wù)
long downloadId = downloadManager.enqueue(request);
log.debug("downloadId:{}", downloadId);
}
這樣我們就添加了一項(xiàng)下載任務(wù)惹盼,然后就靜靜等待系統(tǒng)下載完成吧庸汗。還要注意一點(diǎn),別忘了添加讀寫外置存儲(chǔ)權(quán)限和網(wǎng)絡(luò)權(quán)限哦~
那怎么知道文件下載成功呢手报?系統(tǒng)在下載完成后會(huì)發(fā)送一條廣播蚯舱,里面有任務(wù) ID改化,告訴調(diào)用者任務(wù)完成,通過(guò) DownloadManager 獲取到文件信息就可以進(jìn)一步處理枉昏。
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陈肛,到這里,利用系統(tǒng)服務(wù)下載就算結(jié)束了兄裂,簡(jiǎn)單總結(jié)一下句旱。我們只關(guān)心開始和完成,至于下載過(guò)程中的暫停晰奖、重試等機(jī)制前翎,系統(tǒng)已經(jīng)幫我們做好了,是不是非常友好畅涂?
3. 自定義下載任務(wù)
有了下載鏈接就可以自己實(shí)現(xiàn)網(wǎng)絡(luò)部分,我在這兒自定義了一個(gè)下載任務(wù)道川,使用 HttpURLConnection 和 AsyncTask 實(shí)現(xiàn)午衰,代碼還是比較簡(jiǎn)單的。
private class DownloadTask extends AsyncTask<String, Void, Void> {
// 傳遞兩個(gè)參數(shù):URL 和 目標(biāo)路徑
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);
}
});
優(yōu)勢(shì)是我們可以感知下載進(jìn)度冒萄,處理開始臊岸、取消、失敗尊流、完成等事件帅戒,不足之處是對(duì)下載的控制不如系統(tǒng)服務(wù),必須自己處理網(wǎng)絡(luò)帶來(lái)的問(wèn)題崖技。
可以看出逻住,這三種下載方式各有特點(diǎn),大家可以根據(jù)需要選擇迎献,歡迎留言交流~~