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

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

超詳細(xì)的Webview攻略(一)

WebViewClient相關(guān)方法

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

shouldOverrideUrlLoading執(zhí)行時機(jī)是重定向時(網(wǎng)頁自動重定向或手動點(diǎn)擊網(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;
        }

主要注意以下幾點(diǎn):

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

   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ī)經(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,和圖像)可能不可用。
        // 如果需要更細(xì)粒度的視圖更新,查看 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)

        // 是否重新提交表單,默認(rèn)不重發(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)頁前進(jìn)后退并不會回調(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(),默認(rèn)行為是取消請求
        // 如果調(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); //默認(rèn)
            //request.cancel();//取消
            //request.ignore();//忽視
            //request.proceed(param1, param2);//接受
        }

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

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

11.shouldOverrideKeyEvent(WebView view, KeyEvent event)

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

12.onUnhandledKeyEvent(WebView view, KeyEvent event)

        // 處理未被WebView消費(fèi)的按鍵事件
        // WebView總是消費(fèi)按鍵事件,除非是系統(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)渲染進(jìn)程退出原因的更具體信息。應(yīng)用程序可以使用它來決定如何處理這種情況。
            //RenderProcessGoneDetail提供了兩個方法
            //didCrash():指示是否觀察到呈現(xiàn)進(jì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已被安全瀏覽標(biāo)記。
        @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);//類似于用戶點(diǎn)擊了“返回安全”按鈕
                callback.proceed(true);//類似于用戶單擊了“訪問此不安全站點(diǎn)”按鈕一樣。
                callback.showInterstitial(true);//如果為true則顯示報告復(fù)選框
            }
            super.onSafeBrowsingHit(view, request, threatType, callback);
        }

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

圖片.png

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

當(dāng)加載https網(wǎng)頁的情況下,默認(rèn)不進(jìn)行證書校驗,當(dāng)服務(wù)器要求webview必須進(jìn)行證書校驗的時候,默認(rèn)情況下加載網(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)){//個別手機(jī)獲取到的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)頁的進(jìn)度監(jiān)聽,常用作于添加瀏覽器的進(jìn)度條。

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

2.onReceivedTitle(WebView view, String title)

        //獲取網(wǎng)頁的標(biāo)題
        @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;//默認(rèn)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);
}

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

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

6.onCreateWindow和onCloseWindow

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


        //通知主機(jī)主機(jī)應(yīng)用WebView關(guān)閉了,并在需要的時候從view系統(tǒng)中移除它。
        //此時,WebCore已經(jīng)停止窗口中的所有加載進(jìn)度,并在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í)行時機(jī),其他博客的解釋是:請求獲得WebView的焦點(diǎn),這可能由于另一個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)用此方法以拒絕請求(默認(rèn))
                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);
        }

默認(rèn)是拒絕所有請求,開發(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標(biāo)簽的poster屬性來指定的。如果開發(fā)者沒有設(shè)置poster屬性, 則可以通過這個方法來設(shè)置默認(rèn)的預(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)之前,需要花一定的時間來進(jì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的所有版本了,唯獨(dú)Android4.4.*不支持,這里推薦H5和客戶端判斷Android的版本,采用JS的方式支持inputfile。

webview之JS交互

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

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

第一步:準(zhǔn)備好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");

第三步:點(diǎn)擊某按鈕,加載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)點(diǎn):不會刷新當(dāng)前頁面
弊端:Andorid4.4之后才能使用(不過現(xiàn)在4.4之前的手機(jī)幾乎已被淘汰)

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

方法一:采用addJavascriptInterface映射

步驟一:定義接口

public class AndoridToJsInterface {

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

}

AndoridToJsInterface 類專門負(fù)責(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ǎn)擊調(diào)用Android的hello方法</button>
</body>
</html>

優(yōu)點(diǎn):使用簡單
缺點(diǎn):注解使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ǎn)擊調(diào)用Android代碼</button>
</body>
</html>

其中"android://webview?name=zhangsan&age=22"是事先預(yù)定好的格式,然后在點(diǎn)擊網(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)點(diǎn):沒有方法一的安全漏洞
缺點(diǎn):使用復(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)點(diǎn):沒有方法一的安全漏洞
缺點(diǎn):使用復(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容