Android控件<第二十四篇>:超詳細的Webview攻略(二)

由于文章太長荔仁,官方不讓發(fā)布更哄,所以文章就一分為二了

超詳細的Webview攻略(一)

WebViewClient相關(guān)方法

  1. shouldOverrideUrlLoading(WebView view, String url)和shouldOverrideUrlLoading(WebView view, WebResourceRequest request)

shouldOverrideUrlLoading執(zhí)行時機是重定向時(網(wǎng)頁自動重定向或手動點擊網(wǎng)頁內(nèi)部鏈接)

    mywebview.setWebViewClient(new WebViewClient(){

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            //過時
            Log.d("yunchong", "shouldOverrideUrlLoading1:"+url);
            if(!TextUtils.isEmpty(url)){
                if(url.startsWith("http://") || url.startsWith("https://")){
                    view.loadUrl(url);
                } else{
                    //跳轉(zhuǎn)到第三方
                    try {
                        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                        MainActivity.this.startActivity(intent);
                    }catch (Exception e){
                        //如果拋出異常,則說明本地沒有安裝第三方
                        e.printStackTrace();
                    }
                }
                return true;
            }
            return false;
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
            Log.d("yunchong", "shouldOverrideUrlLoading2:"+url);
            String url = view.getUrl();
            if(!TextUtils.isEmpty(url)){
                if(url.startsWith("http://") || url.startsWith("https://")){
                    view.loadUrl(url);
                } else{
                    //跳轉(zhuǎn)到第三方
                    try {
                        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                        MainActivity.this.startActivity(intent);
                    }catch (Exception e){
                        //如果拋出異常逻翁,則說明本地沒有安裝第三方
                        e.printStackTrace();
                    }
                }
                return true;
            }
            return false;
        }

主要注意以下幾點:

(1)前者在API24(Android 7.0)之后已經(jīng)過時,雖然已經(jīng)過時了,但是我們還是需要用到這個方法的掀序,原因是API24之前的手機只執(zhí)行前者,后者只有在API24之后才會執(zhí)行惭婿;
(2)以上代碼中不恭,后者的返回值有true和false兩種, 第三種返回值是默認返回值

   return super.shouldOverrideUrlLoading(view, request);

這個返回值最終還是會執(zhí)行前者方法审孽。

2.shouldInterceptRequest(WebView view, String url)和shouldInterceptRequest(final WebView view, WebResourceRequest request)

        @Nullable
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
            //過時
            Log.d("yunchong", url);
            return super.shouldInterceptRequest(view, url);
        }

捕獲的日志如下:

圖片.png

此方法廢棄于API21县袱,調(diào)用于非UI線程,雖然廢棄了佑力,但是我在Android4.4式散、Android6.0、Android8.0的手機經(jīng)過測試打颤,依然可以攔截到加載資源
攔截資源請求并返回響應(yīng)數(shù)據(jù)暴拄,返回null時WebView將繼續(xù)加載資源
注意:API21以下的AJAX請求會走onLoadResource漓滔,無法通過此方法攔截

在API21之后新增一個新的方法:

        @Nullable
        @Override
        public WebResourceResponse shouldInterceptRequest(final WebView view, WebResourceRequest request) {
            view.post(new Runnable() {
                @Override
                public void run() {
                    Log.d("yunchong", "shouldInterceptRequest2:"+view.getUrl());
                }
            });
            return super.shouldInterceptRequest(view, request);
        }

用于替代前者。

3.onLoadResource(WebView view, String url)

這個方法和shouldInterceptRequest一樣乖篷,同樣可以攔截到加載資源响驴,他們的區(qū)別是:shouldInterceptRequest已過時,onLoadResource沒有過時撕蔼。

3.onTooManyRedirects(WebView view, Message cancelMsg, Message continueMsg)

這個方法是為了解決一些重定向問題豁鲤,已經(jīng)徹底廢棄,現(xiàn)在處理重定向問題是用shouldOverrideUrlLoading方法了鲸沮。

4.onPageStarted和onPageFinished

網(wǎng)頁的加載開始和加載結(jié)束琳骡。

5.onPageCommitVisible(WebView view, String url)

        // 這個回調(diào)添加于API23,僅用于主框架的導(dǎo)航
        // 通知應(yīng)用導(dǎo)航到之前頁面時讼溺,其遺留的WebView內(nèi)容將不再被繪制楣号。
        // 這個回調(diào)可以用來決定哪些WebView可見內(nèi)容能被安全地回收,以確保不顯示陳舊的內(nèi)容
        // 它最早被調(diào)用怒坯,以此保證WebView.onDraw不會繪制任何之前頁面的內(nèi)容炫狱,隨后繪制背景色或需要加載的新內(nèi)容。
        // 當(dāng)HTTP響應(yīng)body已經(jīng)開始加載并體現(xiàn)在DOM上將在隨后的繪制中可見時剔猿,這個方法會被調(diào)用视译。
        // 這個回調(diào)發(fā)生在文檔加載的早期,因此它的資源(css,和圖像)可能不可用归敬。
        // 如果需要更細粒度的視圖更新憎亚,查看 postVisualStateCallback(long, WebView.VisualStateCallback).
        // 請注意這上邊的所有條件也支持 postVisualStateCallback(long ,WebView.VisualStateCallback)
        @Override
        public void onPageCommitVisible(WebView view, String url) {
            Log.d("yunchong", "onPageCommitVisible:"+url);
            super.onPageCommitVisible(view, url);
        }

跟蹤日志發(fā)現(xiàn)任意的網(wǎng)頁跳轉(zhuǎn)都會執(zhí)行onPageCommitVisible方法,參數(shù)url就是當(dāng)前頁面的url弄慰。

6.onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse)

        // 此方法添加于API23
        // 在加載資源(iframe,image,js,css,ajax...)時收到了 HTTP 錯誤(狀態(tài)碼>=400)
        @Override
        public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
            super.onReceivedHttpError(view, request, errorResponse);
        }

7.onFormResubmission(WebView view, Message dontResend, Message resend)

        // 是否重新提交表單第美,默認不重發(fā)
        @Override
        public void onFormResubmission(WebView view, Message dontResend, Message resend) {
            super.onFormResubmission(view, dontResend, resend);
        }

8.doUpdateVisitedHistory(WebView view, String url, boolean isReload)

        // 通知應(yīng)用可以將當(dāng)前的url存儲在數(shù)據(jù)庫中,意味著當(dāng)前的訪問url已經(jīng)生效并被記錄在內(nèi)核當(dāng)中陆爽。
        // 此方法在網(wǎng)頁加載過程中只會被調(diào)用一次什往,網(wǎng)頁前進后退并不會回調(diào)這個函數(shù)。
        @Override
        public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
            super.doUpdateVisitedHistory(view, url, isReload);
        }

9.onReceivedClientCertRequest(WebView view, ClientCertRequest request)

        // 此方法添加于API21慌闭,在UI線程被調(diào)用
        // 處理SSL客戶端證書請求别威,必要的話可顯示一個UI來提供KEY。
        // 有三種響應(yīng)方式:proceed()/cancel()/ignore()驴剔,默認行為是取消請求
        // 如果調(diào)用proceed()或cancel()省古,Webview 將在內(nèi)存中保存響應(yīng)結(jié)果且對相同的"host:port"不會再次調(diào)用 onReceivedClientCertRequest
        // 多數(shù)情況下,可通過KeyChain.choosePrivateKeyAlias啟動一個Activity供用戶選擇合適的私鑰
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
            super.onReceivedClientCertRequest(view, request); //默認
            //request.cancel();//取消
            //request.ignore();//忽視
            //request.proceed(param1, param2);//接受
        }

10.onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm)

        // 處理HTTP認證請求,默認行為是取消請求
        @Override
        public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
            Log.d("yunchong", "onReceivedHttpAuthRequest");
            super.onReceivedHttpAuthRequest(view, handler, host, realm);//默認
            //handler.cancel();//取消
            //handler.proceed(username, password);//接受
        }

11.shouldOverrideKeyEvent(WebView view, KeyEvent event)

        // 給應(yīng)用一個機會處理按鍵事件
        // 如果返回true,WebView不處理該事件到忽,否則WebView會一直處理率挣,默認返回false
        @Override
        public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
            return super.shouldOverrideKeyEvent(view, event);
        }

12.onUnhandledKeyEvent(WebView view, KeyEvent event)

        // 處理未被WebView消費的按鍵事件
        // WebView總是消費按鍵事件琳拭,除非是系統(tǒng)按鍵或shouldOverrideKeyEvent返回true
        // 此方法在按鍵事件分派時被異步調(diào)用
        @Override
        public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
            super.onUnhandledKeyEvent(view, event);
        }

13.onScaleChanged(WebView view, float oldScale, float newScale)

        // 通知應(yīng)用頁面縮放系數(shù)變化
        @Override
        public void onScaleChanged(WebView view, float oldScale, float newScale) {
            super.onScaleChanged(view, oldScale, newScale);
        }

當(dāng)webview支持雙指縮放時训堆,onScaleChanged回調(diào)方法可以監(jiān)聽webview的縮放系數(shù)。

14.onReceivedLoginRequest(WebView view, String realm, @Nullable String account, String args)

        //通知應(yīng)用有個已授權(quán)賬號自動登陸了
        @Override
        public void onReceivedLoginRequest(WebView view, String realm, @Nullable String account, String args) {
            super.onReceivedLoginRequest(view, realm, account, args);
        }

15.onRenderProcessGone(WebView view, RenderProcessGoneDetail detail)

        //這個API處理一個WebView對象的渲染程序消失的情況白嘁,要么是因為系統(tǒng)殺死了渲染器以回收急需的內(nèi)存坑鱼,要么是因為渲染程序本身崩潰了。
        // 通過使用這個API絮缅,您可以讓您的應(yīng)用程序繼續(xù)執(zhí)行鲁沥,即使渲染過程已經(jīng)消失了。
        @RequiresApi(api = Build.VERSION_CODES.O)
        @Override
        public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) {
            //RenderProcessGoneDetail : 此類提供了有關(guān)渲染進程退出原因的更具體信息耕魄。應(yīng)用程序可以使用它來決定如何處理這種情況黍析。
            //RenderProcessGoneDetail提供了兩個方法
            //didCrash():指示是否觀察到呈現(xiàn)進程崩潰,或者是否被系統(tǒng)終止屎开。
            //rendererPriorityAtExit():返回在渲染器退出時設(shè)置的渲染器優(yōu)先級。
            if(!detail.didCrash()){
                //由于系統(tǒng)內(nèi)存不足马靠,渲染器被終止奄抽。
                //通過創(chuàng)建新的WebView實例,應(yīng)用程序可以正乘恢復(fù)
                if (mywebview != null) {
                    ViewGroup webViewContainer = (ViewGroup) findViewById(R.id.mywebview);
                    webViewContainer.removeView(mywebview);
                    mywebview.destroy();
                    mywebview = null;
                }
                return true;
            }
            return false;
        }

16.onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType, SafeBrowsingResponse callback)

        //通知主應(yīng)用程序加載URL已被安全瀏覽標記逞度。
        @Override
        public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType, SafeBrowsingResponse callback) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
                callback.backToSafety(true);//類似于用戶點擊了“返回安全”按鈕
                callback.proceed(true);//類似于用戶單擊了“訪問此不安全站點”按鈕一樣。
                callback.showInterstitial(true);//如果為true則顯示報告復(fù)選框
            }
            super.onSafeBrowsingHit(view, request, threatType, callback);
        }

以上的注釋是由谷歌官方文檔翻譯而來妙啃,可悲的是我嘗試了7.0档泽、8.0、8.1的手機仍然沒有執(zhí)行到該方法揖赴。沒有發(fā)現(xiàn)此方法的執(zhí)行時機馆匿。

圖片.png

17.onReceivedSslError(WebView view, SslErrorHandler handler, SslError error)

當(dāng)加載https網(wǎng)頁的情況下,默認不進行證書校驗燥滑,當(dāng)服務(wù)器要求webview必須進行證書校驗的時候渐北,默認情況下加載網(wǎng)頁是一片空白,這個時候我們需要在onReceivedSslError方法里做一些處理

        @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
            handler.proceed();
        }

也就是說铭拧,接受所有的https證書赃蛛。
然而這樣做是不安全的,我們需要對證書做ssl校驗搀菩,代碼如下:(我們事先將crt證書放入assets目錄下)

@Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
    String currentUrl = view.getUrl();
    if(TextUtils.isEmpty(currentUrl)){//個別手機獲取到的url是空的
        currentUrl = homeUrl;//這里的homeUrl是從其他地方獲取到的
    }
    SslManager.webviewSsl(currentUrl, new SslListener() {
        @Override
        public void onResponse() {
            handler.proceed();
        }

        @Override
        public void onFailure() {
            handler.cancel();
        }
    });
}


public class SslManager {

    /**
     * webview ssl校驗
     * @param url
     */
    public static void webviewSsl(String url, final SslListener sslListener) {

        if(!TextUtils.isEmpty(url)){
            getOkHttpBuilder(url).build().newCall( new Request.Builder().url(url).build()).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    sslListener.onFailure();
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    sslListener.onResponse();
                }
            });
        }
    }


    public static OkHttpClient.Builder getOkHttpBuilder(String url){
        OkHttpClient.Builder builder = null;
        String crt;
        if(!TextUtils.isEmpty(url)){
            crt = "crt/crtname.crt";
            try {
                builder = setCertificates(new OkHttpClient.Builder(), IMApp.getAppContext().getResources().getAssets().open(crt));
            } catch (IOException e) {
                builder = new OkHttpClient.Builder();
            }
        }
        return builder;
    }

    private static OkHttpClient.Builder setCertificates(OkHttpClient.Builder client, InputStream... certificates) {
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
            keyStore.load(null);
            int index = 0;
            for (InputStream certificate : certificates) {
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
                try {
                    if (certificate != null)
                        certificate.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            SSLContext sslContext = SSLContext.getInstance("TLS");
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
            SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
            X509TrustManager trustManager = Platform.get().trustManager(sslSocketFactory);
            client.sslSocketFactory(sslSocketFactory, trustManager);
            //hostName驗證
            client.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    String peerHost = session.getPeerHost();//服務(wù)器返回的域名
                    try {
                        X509Certificate[] peerCertificates = (X509Certificate[]) session.getPeerCertificates();
                        for (X509Certificate c : peerCertificates) {
                            X500Principal subjectX500Principal = c.getSubjectX500Principal();
                            String name = new X500p(subjectX500Principal).getName();
                            String[] split = name.split(",");
                            for (String s : split) {
                                if (s.startsWith("CN")) {
                                    if (s.contains(hostname) && s.contains(peerHost)) {
                                        return true;
                                    }
                                }
                            }
                        }
                    } catch (SSLPeerUnverifiedException e) {
                        e.printStackTrace();
                    }
                    return false;
                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        }
        return client;
    }
}

WebChromeClient相關(guān)方法

1.onProgressChanged(WebView view, int newProgress)
這是webview加載網(wǎng)頁的進度監(jiān)聽呕臂,常用作于添加瀏覽器的進度條。

    //監(jiān)聽加載網(wǎng)頁的進度情況
    mywebview.setWebChromeClient(new WebChromeClient(){
        @Override
        public void onProgressChanged(WebView view, int newProgress) {
            super.onProgressChanged(view, newProgress);
        }

2.onReceivedTitle(WebView view, String title)

        //獲取網(wǎng)頁的標題
        @Override
        public void onReceivedTitle(WebView view, String title) {
            super.onReceivedTitle(view, title);
        }

3.onReceivedIcon(WebView view, Bitmap icon)

        //字面上的意思是:當(dāng)前網(wǎng)頁接收到icon的時候執(zhí)行此方法
        @Override
        public void onReceivedIcon(WebView view, Bitmap icon) {
            super.onReceivedIcon(view, icon);
        }

4.onReceivedTouchIconUrl(WebView view, String url, boolean precomposed)

        //字面上的意思是:觸摸當(dāng)前網(wǎng)頁接收到icon的時候執(zhí)行此方法
        @Override
        public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {
            super.onReceivedTouchIconUrl(view, url, precomposed);
        }

5.onShowCustomView和onHideCustomView
這兩個方法往往是一起使用肪跋,常用于解決webview不能全屏播放視頻的問題歧蒋。(有必要的話對當(dāng)前activity開啟硬件加速)
代碼如下:

private View customView;//默認view
private FrameLayout fullscreenContainer;//全屏view
/** 視頻全屏參數(shù) */
private FrameLayout.LayoutParams COVER_SCREEN_PARAMS = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
private WebChromeClient.CustomViewCallback customViewCallback;


        //顯示自定義View:常用于視頻全屏展示
        @Override
        public void onShowCustomView(View view, CustomViewCallback callback) {
            if (customView != null) {
                callback.onCustomViewHidden();
                return;
            }

            //MainActivity.this.getWindow().getDecorView();

            FrameLayout decor = (FrameLayout) getWindow().getDecorView();
            fullscreenContainer = new FullscreenHolder(MainActivity.this);
            fullscreenContainer.addView(view, COVER_SCREEN_PARAMS);
            decor.addView(fullscreenContainer, COVER_SCREEN_PARAMS);
            customView = view;
            setStatusBarVisibility(false);
            customViewCallback = callback;
        }

        //顯示自定義View:常用于視頻全屏展示
        @Override
        public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) {
            onShowCustomView(view, callback);
        }

        //隱藏自定義View:
        @Override
        public void onHideCustomView() {

            hideCustomView();
            
        }


//視頻從全屏還原到原來
private void hideCustomView(){
    if (customView == null) {
        return;
    }

    setStatusBarVisibility(true);
    FrameLayout decor = (FrameLayout) getWindow().getDecorView();
    decor.removeView(fullscreenContainer);
    fullscreenContainer = null;
    customView = null;
    customViewCallback.onCustomViewHidden();

    mywebview.setVisibility(View.VISIBLE);
}

這里還需要注意的是,點擊返回鍵應(yīng)該退出全屏

@Override
public void onBackPressed() {
    if(customView != null){
        hideCustomView();
    }else if(mywebview !=  null && mywebview.canGoBack()){
        mywebview.goBack();
    }else{
        finish();
    }
}

6.onCreateWindow和onCloseWindow

        //請求主機應(yīng)用創(chuàng)建一個新窗口。
        //如果主機應(yīng)用選擇響應(yīng)這個請求疏尿,則該方法返回true瘟芝,并創(chuàng)建一個新的WebView,
        //將其插入到視圖系統(tǒng)中褥琐,并將其提供的resultMsg作為參數(shù)提供給新的WebView锌俱。 
        //如果主機應(yīng)用選擇不響應(yīng)這個請求時,則該方法返回false。 
        //默認情況下敌呈,該方法不做任何處理并返回false贸宏。
        @Override
        public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
            return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
        }


        //通知主機主機應(yīng)用WebView關(guān)閉了,并在需要的時候從view系統(tǒng)中移除它磕洪。
        //此時,WebCore已經(jīng)停止窗口中的所有加載進度,并在javascript中移除了所有cross-scripting的功能吭练。
        @Override
        public void onCloseWindow(WebView window) {
            super.onCloseWindow(window);
        }

7.onRequestFocus(WebView view)

        @Override
        public void onRequestFocus(WebView view) {
            super.onRequestFocus(view);
        }

不太清楚它的執(zhí)行時機,其他博客的解釋是:請求獲得WebView的焦點,這可能由于另一個WebView打開一個連接,需要被展示.

8.三種提示框Alert析显、Confirm鲫咽、Prompt

        @Override
        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
            return super.onJsAlert(view, url, message, result);
        }

        @Override
        public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
            return super.onJsConfirm(view, url, message, result);
        }

        @Override
        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
            return super.onJsPrompt(view, url, message, defaultValue, result);
        }

常用的提示框是Alert和Confirm。
網(wǎng)上也是有相關(guān)博客 js中三種彈出框谷异。

9.onJsBeforeUnload(WebView view, String url, String message, JsResult result)

        //JS相關(guān)操作分尸,會在頁面關(guān)閉或刷新調(diào)用,觸發(fā)事件時歹嘹,彈出對話框是否關(guān)閉箩绍,確定則關(guān)閉頁面,取消則保持該頁面尺上。
        @Override
        public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
            return super.onJsBeforeUnload(view, url, message, result);
        }

10.onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater)

        //使webview支持建立html5本地緩存數(shù)據(jù)庫
        @Override
        public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater) {
            quotaUpdater.updateQuota(estimatedDatabaseSize * 2);
        }

11.onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater)

        //已被廢棄材蛛,不用管
        @Override
        public void onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater) {
            super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater);
        }

12.webview定位權(quán)限提示框

        @Override
        public void onGeolocationPermissionsShowPrompt(final String origin, final GeolocationPermissions.Callback callback) {
            final boolean remember = false;//是否記住
            AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
            builder.setTitle("位置信息");
            builder.setMessage(origin + "允許獲取您的地理位置信息嗎?")
                    .setCancelable(true)
                    .setPositiveButton("允許", new DialogInterface.OnClickListener() {
                @Override public void onClick(DialogInterface dialog, int id) {
                    callback.invoke(origin, true, remember);
                } })
                    .setNegativeButton("不允許", new DialogInterface.OnClickListener() {
                        @Override public void onClick(DialogInterface dialog, int id) {
                            callback.invoke(origin, false, remember);
                        }
                    });
            AlertDialog alert = builder.create();
            alert.show();
        }

        @Override
        public void onGeolocationPermissionsHidePrompt() {
            super.onGeolocationPermissionsHidePrompt();
        }

13.來自網(wǎng)頁的權(quán)限請求

        @Override
        public void onPermissionRequest(PermissionRequest request) {

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                request.deny();//調(diào)用此方法以拒絕請求(默認)
                request.getOrigin();//調(diào)用此方法以獲取試圖訪問受限資源的網(wǎng)頁的來源怎抛。
                request.getResources();//調(diào)用此方法獲取網(wǎng)頁試圖訪問的資源卑吭。
                request.grant(request.getResources());//調(diào)用此方法授予Origin訪問給定資源的權(quán)限。
            }
        }

        @Override
        public void onPermissionRequestCanceled(PermissionRequest request) {
            super.onPermissionRequestCanceled(request);
        }

默認是拒絕所有請求马绝,開發(fā)者可以根據(jù)需求授予指定網(wǎng)頁的權(quán)限請求陨簇。

14.onJsTimeout()

        //已過時,不用管
        @Override
        public boolean onJsTimeout() {
            return super.onJsTimeout();
        }

15.onConsoleMessage

        //三個參數(shù)的方法已經(jīng)廢棄迹淌,被一個參數(shù)的方法替代
        @Override
        public void onConsoleMessage(String message, int lineNumber, String sourceID) {
            super.onConsoleMessage(message, lineNumber, sourceID);
        }

        @Override
        public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
            return super.onConsoleMessage(consoleMessage);
        }

可以捕獲到網(wǎng)頁打印在控制臺的日志河绽,有利于日志的跟蹤。

16.getDefaultVideoPoster()

        //Html中唉窃,視頻(video)控件在沒有播放的時候?qū)⒔o用戶展示一張“海報”圖片(預(yù)覽圖)耙饰。
        // 其預(yù)覽圖是由Html中video標簽的poster屬性來指定的。如果開發(fā)者沒有設(shè)置poster屬性, 則可以通過這個方法來設(shè)置默認的預(yù)覽圖纹份。
        @Override
        public Bitmap getDefaultVideoPoster() {
            Bitmap bitmap = super.getDefaultVideoPoster();
            if(bitmap == null){
                return BitmapFactory.decodeResource(getApplicationContext().getResources(), R.mipmap.ic_launcher);
            }
            return super.getDefaultVideoPoster();
        }

17.getVideoLoadingProgressView()

        //播放視頻時苟跪,在第一幀呈現(xiàn)之前廷痘,需要花一定的時間來進行數(shù)據(jù)緩沖。
        //ChromeClient可以使用這個函數(shù)來提供一個在數(shù)據(jù)緩沖時顯示的視圖件已。
        // 例如,ChromeClient可以在緩沖時顯示一個轉(zhuǎn)輪動畫笋额。
        @Override
        public View getVideoLoadingProgressView() {
            return super.getVideoLoadingProgressView();
        }

18.getVisitedHistory(ValueCallback<String[]> callback)

        //獲得所有訪問歷史項目的列表,用于鏈接著色篷扩。
        @Override
        public void getVisitedHistory(ValueCallback<String[]> callback) {
            super.getVisitedHistory(callback);
        }

19.讓webview支持inputfile控件

public ValueCallback<Uri> mUploadMessage;
public ValueCallback<Uri[]> mUploadMessageForAndroid5;
public final static int FILECHOOSER_RESULTCODE = 1;
public final static int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 2;


        //android3.0以前
        public void openFileChooser(ValueCallback<Uri> uploadMsg) {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
        }

        //android3.0到android4.0
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
        }

        //android4.0到android4.3
        public void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
            mUploadMessage = filePathCallback;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
        }

        //android5.0以上
        @Override
        public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
            if (mUploadMessageForAndroid5 != null) {
                mUploadMessageForAndroid5.onReceiveValue(null);
                mUploadMessageForAndroid5 = null;
            }

            mUploadMessageForAndroid5 = filePathCallback;
            Intent intent = null;
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
                intent = fileChooserParams.createIntent();
            }
            try {
                startActivityForResult(intent, FILECHOOSER_RESULTCODE_FOR_ANDROID_5);
            } catch (ActivityNotFoundException e) {
                mUploadMessageForAndroid5 = null;
                return false;
            }
            return true;
        }
    });


@Override
protected void onActivityResult(int requestCode, int resultCode,Intent intent) {

    if (mUploadMessage != null) {
        mUploadMessage.onReceiveValue(null);
        mUploadMessage = null;
    }
    if (mUploadMessageForAndroid5 != null) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mUploadMessageForAndroid5.onReceiveValue(null);
            mUploadMessageForAndroid5 = null;
        }
    }



    if (requestCode == FILECHOOSER_RESULTCODE) {
        if (null == mUploadMessage){
            return;
        }

        Uri result = intent == null || resultCode != RESULT_OK ? null:intent.getData();
        mUploadMessage.onReceiveValue(result);
        mUploadMessage = null;
    } else if (requestCode == FILECHOOSER_RESULTCODE_FOR_ANDROID_5){
        if (null == mUploadMessageForAndroid5){
            return;
        }
        Uri result = (intent == null || resultCode != RESULT_OK) ? null: intent.getData();
            if (result != null) {
                mUploadMessageForAndroid5.onReceiveValue(new Uri[]{result});
            } else {
                mUploadMessageForAndroid5.onReceiveValue(new Uri[]{});
        }
        mUploadMessageForAndroid5 = null;
    }
}

Android有個很大的坑兄猩,添加以上代碼幾乎可以支持Android的所有版本了,唯獨Android4.4.*不支持鉴未,這里推薦H5和客戶端判斷Android的版本枢冤,采用JS的方式支持inputfile。

webview之JS交互

  • Android調(diào)用JS代碼

(1)方法一:采用loadUrl方式

第一步:準備好html靜態(tài)頁面铜秆,寫好JS方法

<!DOCTYPE html>
<html>
<head> <meta charset="utf-8">
    <title>Carson_Ho</title> // JS代碼
    <script>
   function callJS(){
      alert("Android調(diào)用了JS的callJS方法");
   }
</script>
</head>
</html>

第二步:加載該頁面

mywebview.loadUrl("file:///android_asset/htmldemo.html");

第三步:點擊某按鈕淹真,加載JS代碼

@Override
public void onClick(View v) {
    switch (v.getId()){
        case R.id.bt_js:
            mywebview.loadUrl("javascript:callJS()");
            break;
    }
}

效果展示:


圖片.png

弊端:會刷新當(dāng)前頁面,執(zhí)行效率慢连茧。

(2)方法二:采用evaluateJavascript方式

其步驟和展示效果和方法一一樣

@Override
public void onClick(View v) {
    switch (v.getId()){
        case R.id.bt_js:
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                mywebview.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
                    @Override
                    public void onReceiveValue(String value) {

                    }
                });
            }else {
                mywebview.loadUrl("javascript:callJS()");
            }
            break;
    }
}

優(yōu)點:不會刷新當(dāng)前頁面
弊端:Andorid4.4之后才能使用(不過現(xiàn)在4.4之前的手機幾乎已被淘汰)

  • JS調(diào)用Android代碼

方法一:采用addJavascriptInterface映射

步驟一:定義接口

public class AndoridToJsInterface {

    @JavascriptInterface
    public void hello(){
        System.out.println("hello");
    }

}

AndoridToJsInterface 類專門負責(zé)聲明接口核蘸,接口方法必須添加“@JavascriptInterface”修飾,該注解將接口暴露給JS啸驯。其作用是為JS代碼提供調(diào)用的接口客扎。JS代碼想要調(diào)用Android代碼聲明的接口,就必須給JS創(chuàng)建一個映射

    //AndroidtoJS類對象映射到j(luò)s的test對象
    mywebview.addJavascriptInterface(new AndoridToJsInterface(), "android");

第一個參數(shù)是AndoridToJsInterface 對象坯汤,第二個參數(shù)就是AndoridToJsInterface對象的別名,也就是給JS使用的映射搀愧。

現(xiàn)在開始編寫JS代碼

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Carson</title>
    <script>
         function callAndroid(){
            android.hello();
         }
      </script>
</head>
<body>
<button type="button" id="button1" onclick="callAndroid()">點擊調(diào)用Android的hello方法</button>
</body>
</html>

優(yōu)點:使用簡單
缺點:注解使Android的接口暴露出來惰聂,是一個高危漏洞

方法二:采用shouldOverrideUrlLoading攔截URl

編寫好JS代碼

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Carson_Ho</title>
    <script>
         function callAndroid(){
            //事先預(yù)定的URI
            document.location = "android://webview?name=zhangsan&age=22";
         }
      </script>
</head>
<body>
<button type="button" id="button1" onclick="callAndroid()">點擊調(diào)用Android代碼</button>
</body>
</html>

其中"android://webview?name=zhangsan&age=22"是事先預(yù)定好的格式,然后在點擊網(wǎng)頁上的按鈕觸發(fā)JS代碼咱筛,并在Android端捕獲

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Uri uri = Uri.parse(url);
            if(uri.getScheme().equals("android") && uri.getAuthority().equals("webview")){
                Set<String> paraNames = uri.getQueryParameterNames();
                Iterator it = paraNames.iterator();
                while (it.hasNext()){
                    String name = (String) it.next();
                    Log.d("aaa", "name:"+name);
                }
            }
            return super.shouldOverrideUrlLoading(view,url);
        }

優(yōu)點:沒有方法一的安全漏洞
缺點:使用復(fù)雜搓幌,并且將所有的JS處理都放在shouldOverrideUrlLoading中略顯臃腫

方法三:采用onJsAlert、onJsConfirm迅箩、onJsPrompt捕獲URL

和方法二原理類似溉愁,這里就不在描述

        @Override
        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
            return super.onJsAlert(view, url, message, result);
        }

        @Override
        public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
            return super.onJsConfirm(view, url, message, result);
        }

        @Override
        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
            return super.onJsPrompt(view, url, message, defaultValue, result);
        }

優(yōu)點:沒有方法一的安全漏洞
缺點:使用復(fù)雜,并且將所有的JS處理都放在onJsAlert饲趋、onJsConfirm拐揭、onJsPrompt中略顯臃腫。

JS交互總結(jié):

  • Android調(diào)用JS代碼推薦使用evaluateJavascript奕塑,他的好處顯而易見堂污,既不會重新刷新頁面,也可以獲取返回值
  • 三種JS調(diào)用Android代碼的方法都不推薦使用龄砰,addJavascriptInterface映射的方式是一個高危漏洞盟猖,其他兩種使用太過復(fù)雜讨衣,這里推薦使用JsBridge實現(xiàn)JS交互。

最后分享一個github上開源的JsBridge案例:

https://github.com/lzyzsd/JsBridge

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末式镐,一起剝皮案震驚了整個濱河市反镇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌娘汞,老刑警劉巖歹茶,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異价说,居然都是意外死亡辆亏,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門鳖目,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扮叨,“玉大人,你說我怎么就攤上這事领迈〕勾牛” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵狸捅,是天一觀的道長衷蜓。 經(jīng)常有香客問我,道長尘喝,這世上最難降的妖魔是什么磁浇? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮朽褪,結(jié)果婚禮上置吓,老公的妹妹穿的比我還像新娘。我一直安慰自己缔赠,他們只是感情好衍锚,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嗤堰,像睡著了一般戴质。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上踢匣,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天告匠,我揣著相機與錄音,去河邊找鬼离唬。 笑死凫海,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的男娄。 我是一名探鬼主播行贪,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼漾稀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了建瘫?” 一聲冷哼從身側(cè)響起崭捍,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎啰脚,沒想到半個月后殷蛇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡橄浓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年粒梦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荸实。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡匀们,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出准给,到底是詐尸還是另有隱情泄朴,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布露氮,位于F島的核電站祖灰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏畔规。R本人自食惡果不足惜局扶,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望叁扫。 院中可真熱鬧三妈,春花似錦、人聲如沸陌兑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兔综。三九已至,卻和暖如春狞玛,著一層夾襖步出監(jiān)牢的瞬間软驰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工心肪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锭亏,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓硬鞍,卻偏偏與公主長得像慧瘤,于是被迫代替她去往敵國和親戴已。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

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