Android WebView 支持文件下載的幾種方式

最近在開發(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ù)需要選擇迎献,歡迎留言交流~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瞎访,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子吁恍,更是在濱河造成了極大的恐慌扒秸,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冀瓦,死亡現(xiàn)場(chǎng)離奇詭異伴奥,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)翼闽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門拾徙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人肄程,你說(shuō)我怎么就攤上這事锣吼⊙』耄” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵玄叠,是天一觀的道長(zhǎng)古徒。 經(jīng)常有香客問(wèn)我,道長(zhǎng)读恃,這世上最難降的妖魔是什么隧膘? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮寺惫,結(jié)果婚禮上疹吃,老公的妹妹穿的比我還像新娘。我一直安慰自己西雀,他們只是感情好萨驶,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著艇肴,像睡著了一般腔呜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上再悼,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天核畴,我揣著相機(jī)與錄音,去河邊找鬼冲九。 笑死谤草,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的莺奸。 我是一名探鬼主播丑孩,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼憾筏!你這毒婦竟也來(lái)了嚎杨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤氧腰,失蹤者是張志新(化名)和其女友劉穎枫浙,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體古拴,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡箩帚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了黄痪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片紧帕。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出是嗜,到底是詐尸還是另有隱情愈案,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布鹅搪,位于F島的核電站站绪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏丽柿。R本人自食惡果不足惜恢准,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望甫题。 院中可真熱鬧馁筐,春花似錦、人聲如沸坠非。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)炎码。三九已至赦抖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辅肾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工轮锥, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留矫钓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓舍杜,卻偏偏與公主長(zhǎng)得像新娜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子既绩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,522評(píng)論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理概龄,服務(wù)發(fā)現(xiàn),斷路器饲握,智...
    卡卡羅2017閱讀 134,601評(píng)論 18 139
  • 相信很多人現(xiàn)在都在使用MarkDown碼字私杜,但是添加圖片是個(gè)頭疼的事,要是使用本地圖片救欧,別人就沒(méi)法看到圖片衰粹,所以很...
    淡墨半夏閱讀 2,753評(píng)論 0 0
  • 今天早上,我們先從實(shí)物開始笆怠,到中英文讀音及書寫了解了襪子铝耻。 吃完早飯,我和佑又參觀了襪廠蹬刷。 參觀結(jié)束后瓢捉,...
    李云剛閱讀 342評(píng)論 0 0
  • 東風(fēng)借频丘,火燒鐵索連環(huán);陷囚龍泡态,口隱文化之風(fēng) 古往囚龍秦武皇搂漠, 今朝怎評(píng)今闖王? 一笑繁塵七步里兽赁, 傾觴許攸滿愁腸状答。
    規(guī)凌閱讀 145評(píng)論 0 0