超詳細的Webview攻略(二)

由於文章太長,官方不讓發佈,所以文章就一分爲二了

超詳細的Webview攻略(一)

WebViewClient相關方法

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

shouldOverrideUrlLoading執行時機是重定向時(網頁自動重定向或手動點擊網頁內部鏈接)

    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{
                    //跳轉到第三方
                    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{
                    //跳轉到第三方
                    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)之後已經過時,雖然已經過時了,但是我們還是需要用到這個方法的,原因是API24之前的手機只執行前者,後者只有在API24之後纔會執行;
(2)以上代碼中,後者的返回值有true和false兩種, 第三種返回值是默認返回值

   return super.shouldOverrideUrlLoading(view, request);

這個返回值最終還是會執行前者方法。

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);
        }

捕獲的日誌如下:

此方法廢棄於API21,調用於非UI線程,雖然廢棄了,但是我在Android4.4、Android6.0、Android8.0的手機經過測試,依然可以攔截到加載資源
攔截資源請求並返回響應數據,返回null時WebView將繼續加載資源
注意: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一樣,同樣可以攔截到加載資源,他們的區別是:shouldInterceptRequest已過時,onLoadResource沒有過時。

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

這個方法是爲了解決一些重定向問題,已經徹底廢棄,現在處理重定向問題是用shouldOverrideUrlLoading方法了。

4.onPageStarted和onPageFinished

網頁的加載開始和加載結束。

5.onPageCommitVisible(WebView view, String url)

        // 這個回調添加於API23,僅用於主框架的導航
        // 通知應用導航到之前頁面時,其遺留的WebView內容將不再被繪製。
        // 這個回調可以用來決定哪些WebView可見內容能被安全地回收,以確保不顯示陳舊的內容
        // 它最早被調用,以此保證WebView.onDraw不會繪製任何之前頁面的內容,隨後繪製背景色或需要加載的新內容。
        // 當HTTP響應body已經開始加載並體現在DOM上將在隨後的繪製中可見時,這個方法會被調用。
        // 這個回調發生在文檔加載的早期,因此它的資源(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);
        }

跟蹤日誌發現任意的網頁跳轉都會執行onPageCommitVisible方法,參數url就是當前頁面的url。

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

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

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

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

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

        // 通知應用可以將當前的url存儲在數據庫中,意味着當前的訪問url已經生效並被記錄在內核當中。
        // 此方法在網頁加載過程中只會被調用一次,網頁前進後退並不會回調這個函數。
        @Override
        public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
            super.doUpdateVisitedHistory(view, url, isReload);
        }

9.onReceivedClientCertRequest(WebView view, ClientCertRequest request)

        // 此方法添加於API21,在UI線程被調用
        // 處理SSL客戶端證書請求,必要的話可顯示一個UI來提供KEY。
        // 有三種響應方式:proceed()/cancel()/ignore(),默認行爲是取消請求
        // 如果調用proceed()或cancel(),Webview 將在內存中保存響應結果且對相同的"host:port"不會再次調用 onReceivedClientCertRequest
        // 多數情況下,可通過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)

        // 給應用一個機會處理按鍵事件
        // 如果返回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總是消費按鍵事件,除非是系統按鍵或shouldOverrideKeyEvent返回true
        // 此方法在按鍵事件分派時被異步調用
        @Override
        public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
            super.onUnhandledKeyEvent(view, event);
        }

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

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

當webview支持雙指縮放時,onScaleChanged回調方法可以監聽webview的縮放係數。

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

        //通知應用有個已授權賬號自動登陸了
        @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對象的渲染程序消失的情況,要麼是因爲系統殺死了渲染器以回收急需的內存,要麼是因爲渲染程序本身崩潰了。
        // 通過使用這個API,您可以讓您的應用程序繼續執行,即使渲染過程已經消失了。
        @RequiresApi(api = Build.VERSION_CODES.O)
        @Override
        public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) {
            //RenderProcessGoneDetail : 此類提供了有關渲染進程退出原因的更具體信息。應用程序可以使用它來決定如何處理這種情況。
            //RenderProcessGoneDetail提供了兩個方法
            //didCrash():指示是否觀察到呈現進程崩潰,或者是否被系統終止。
            //rendererPriorityAtExit():返回在渲染器退出時設置的渲染器優先級。
            if(!detail.didCrash()){
                //由於系統內存不足,渲染器被終止。
                //通過創建新的WebView實例,應用程序可以正常恢復
                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)

        //通知主應用程序加載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則顯示報告複選框
            }
            super.onSafeBrowsingHit(view, request, threatType, callback);
        }

以上的註釋是由谷歌官方文檔翻譯而來,可悲的是我嘗試了7.0、8.0、8.1的手機仍然沒有執行到該方法。沒有發現此方法的執行時機。

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

當加載https網頁的情況下,默認不進行證書校驗,當服務器要求webview必須進行證書校驗的時候,默認情況下加載網頁是一片空白,這個時候我們需要在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();//服務器返回的域名
                    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相關方法

1.onProgressChanged(WebView view, int newProgress)
這是webview加載網頁的進度監聽,常用作於添加瀏覽器的進度條。

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

2.onReceivedTitle(WebView view, String title)

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

3.onReceivedIcon(WebView view, Bitmap icon)

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

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

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

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

private View customView;//默認view
private FrameLayout fullscreenContainer;//全屏view
/** 視頻全屏參數 */
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);
}

這裏還需要注意的是,點擊返回鍵應該退出全屏

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

6.onCreateWindow和onCloseWindow

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


        //通知主機主機應用WebView關閉了,並在需要的時候從view系統中移除它。
        //此時,WebCore已經停止窗口中的所有加載進度,並在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);
        }

不太清楚它的執行時機,其他博客的解釋是:請求獲得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。
網上也是有相關博客 js中三種彈出框

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

        //JS相關操作,會在頁面關閉或刷新調用,觸發事件時,彈出對話框是否關閉,確定則關閉頁面,取消則保持該頁面。
        @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本地緩存數據庫
        @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定位權限提示框

        @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.來自網頁的權限請求

        @Override
        public void onPermissionRequest(PermissionRequest request) {

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

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

默認是拒絕所有請求,開發者可以根據需求授予指定網頁的權限請求。

14.onJsTimeout()

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

15.onConsoleMessage

        //三個參數的方法已經廢棄,被一個參數的方法替代
        @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);
        }

可以捕獲到網頁打印在控制檯的日誌,有利於日誌的跟蹤。

16.getDefaultVideoPoster()

        //Html中,視頻(video)控件在沒有播放的時候將給用戶展示一張“海報”圖片(預覽圖)。
        // 其預覽圖是由Html中video標籤的poster屬性來指定的。如果開發者沒有設置poster屬性, 則可以通過這個方法來設置默認的預覽圖。
        @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()

        //播放視頻時,在第一幀呈現之前,需要花一定的時間來進行數據緩衝。
        //ChromeClient可以使用這個函數來提供一個在數據緩衝時顯示的視圖。
        // 例如,ChromeClient可以在緩衝時顯示一個轉輪動畫。
        @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調用JS代碼

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

第一步:準備好html靜態頁面,寫好JS方法

<!DOCTYPE html>
<html>
<head> <meta charset="utf-8">
    <title>Carson_Ho</title> // JS代碼
    <script>
   function callJS(){
      alert("Android調用了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;
    }
}

效果展示:


弊端:會刷新當前頁面,執行效率慢。

(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;
    }
}

優點:不會刷新當前頁面
弊端:Andorid4.4之後才能使用(不過現在4.4之前的手機幾乎已被淘汰)

  • JS調用Android代碼

方法一:採用addJavascriptInterface映射

步驟一:定義接口

public class AndoridToJsInterface {

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

}

AndoridToJsInterface 類專門負責聲明接口,接口方法必須添加“@JavascriptInterface”修飾,該註解將接口暴露給JS。其作用是爲JS代碼提供調用的接口。JS代碼想要調用Android代碼聲明的接口,就必須給JS創建一個映射

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

第一個參數是AndoridToJsInterface 對象,第二個參數就是AndoridToJsInterface對象的別名,也就是給JS使用的映射。

現在開始編寫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()">點擊調用Android的hello方法</button>
</body>
</html>

優點:使用簡單
缺點:註解使Android的接口暴露出來,是一個高危漏洞

方法二:採用shouldOverrideUrlLoading攔截URl

編寫好JS代碼

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

其中"android://webview?name=zhangsan&age=22"是事先預定好的格式,然後在點擊網頁上的按鈕觸發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);
        }

優點:沒有方法一的安全漏洞
缺點:使用複雜,並且將所有的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);
        }

優點:沒有方法一的安全漏洞
缺點:使用複雜,並且將所有的JS處理都放在onJsAlert、onJsConfirm、onJsPrompt中略顯臃腫。

JS交互總結:

  • Android調用JS代碼推薦使用evaluateJavascript,他的好處顯而易見,既不會重新刷新頁面,也可以獲取返回值
  • 三種JS調用Android代碼的方法都不推薦使用,addJavascriptInterface映射的方式是一個高危漏洞,其他兩種使用太過複雜,這裏推薦使用JsBridge實現JS交互。

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

https://github.com/lzyzsd/JsBridge

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章