Andorid瀏覽器:Native與H5交互的那些事

目錄
一,簡介
二,WebView,WebSettings、WebViewClient、WebChromeClient方法
三,Native與Js交互
四,騰訊瀏覽服務X5內核
五,X5內核WebView +全屏視頻 + JsBridge框架javascript

一,簡介

這部分主要介紹下 WebView,WebView 是一個用來顯示 Web 網頁的控件,繼承自 AbsoluteLayout,和使用系統其餘控件沒什麼區別,只是 WeView 控件方法比較多比較豐富。由於它就是一個微型瀏覽器,包含一個瀏覽器該有的基本功能,例如:滾動、縮放、前進、後退下一頁、搜索、執行 Js等功能。html

在 Android 4.4 以前使用 WebKit 做爲渲染內核,4.4 以後採用 chrome 內核。Api 使用兼容低版本。前端

二,WebView,WebSettings、WebViewClient、WebChromeClient方法

主要包含 WebView 的使用方法。 咱們基於這些方法能擴展不少其餘功能,例如:JsBridge、緩存等。
WebView方法:html5

方法 說明
void loadUrl(String url) 加載網絡連接 url
boolean canGoBack() 判斷 WebView 當前是否能夠返回上一頁
goBack() 回退到上一頁
boolean canGoForward() 判斷 WebView 當前是否能夠向前一頁
goForward() 回退到前一頁
onPause() 相似 Activity 生命週期,頁面進入後臺不可見狀態
pauseTimers() 方法面向全局整個應用程序的webview,它會暫停全部webview的layout,parsing,JavaScript Timer。當程序進入後臺時,該方法的調用能夠下降CPU功耗。
onResume() 在調用 onPause()後,能夠調用該方法來恢復 WebView 的運行
resumeTimers() 恢復pauseTimers時的全部操做。(注:pauseTimers和resumeTimers 方法必須一塊兒使用,不然再使用其它場景下的 WebView 會有問題)
destroy() 銷燬 WebView
clearHistory() 清除當前 WebView 訪問的歷史記錄。
clearCache(boolean includeDiskFiles): 清空網頁訪問留下的緩存數據。須要注意的時,因爲緩存是全局的,因此只要是WebView用到的緩存都會被清空,即使其餘地方也會使用到。該方法接受一個參數,從命名便可看出做用。若設爲false,則只清空內存裏的資源緩存,而不清空磁盤裏的。
reload() 從新加載當前請求
setLayerType(int layerType, Paint paint) 設置硬件加速、軟件加速
removeAllViews() 清除子view。
clearSslPreferences(): 清除ssl信息
clearMatches() 清除網頁查找的高亮匹配字符
removeJavascriptInterface(String interfaceName) 刪除interfaceName 對應的注入對象
addJavascriptInterface(Object object,String interfaceName) 注入 java 對象
setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled) 設置垂直方向滾動條。
setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled) 設置橫向滾動條
loadUrl(String url, Map additionalHttpHeaders) 加載制定url並攜帶http header數據
stopLoading() 中止 WebView 當前加載
freeMemory() 釋放內存,不過貌似很差用。
clearFormData() 清除自動完成填充的表單數據。須要注意的是,該方法僅僅清除當前表單域自動完成填充的表單數據,並不會清除WebView存儲到本地的數據。

WebSettings方法java

方法 說明
setJavaScriptEnabled(boolean flag) 是否支持 Js 使用
setCacheMode(int mode) 設置 WebView 的緩存模式
setAppCacheEnabled(boolean flag) 是否啓用緩存模式
setAppCachePath(String appCachePath) Android 私有緩存存儲,若是你不調用setAppCachePath方法,WebView將不會產生這個目錄
setGeolocationEnabled 是否支持定位
setGeolocationDatabasePath 設置定位數據庫路徑
setSupportZoom(boolean support) 是否支持縮放。
setTextZoom(int textZoom) 設置頁面中文本縮放百分比,默認100%
setAllowFileAccess(boolean allow) 是否容許加載本地 html 文件/false
setDatabaseEnabled(boolean flag) 是否開啓數據庫緩存
setDomStorageEnabled(boolean flag) 是否開啓DOM緩存
setUserAgentString(String ua) 設置 UserAgent 屬性
setLoadsImagesAutomatically(boolean flag) 支持自動加載圖片
boolean getLoadsImagesAutomatically() 是否支持自動加載圖片
setMixedContentMode 設置混合模式,當一個安全的https請求加載不安全的http時
setLoadWithOverviewMode 設置webview加載在override模式,就是縮小內容以適應屏幕寬,當加載的內容寬大於Webview控制的寬時
setDisplayZoomControls 設置webview是否顯示縮放控制按鈕

項目中使用設置:android

 WebSettings settings = mWebView.getSettings();
        settings.setJavaScriptEnabled(true);
        settings.setDatabaseEnabled(false);
        settings.setSaveFormData(false);
        settings.setBuiltInZoomControls(true);
        if (Build.VERSION.SDK_INT >= 11)
            settings.setDisplayZoomControls(false);
        settings.setAllowFileAccess(true);
        settings.setUseWideViewPort(true);
        settings.setLoadWithOverviewMode(true);
        settings.setDomStorageEnabled(true);
        settings.setAppCacheEnabled(true);
        settings.setGeolocationEnabled(true);
        settings.setGeolocationDatabasePath(getActivity().getCacheDir().toString());
        settings.setUserAgentString(settings.getUserAgentString() + " " + DeviceUtil.getUserAgent(getActivity()));
        // android 5.0以上默認不支持Mixed Content
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            settings.setMixedContentMode(
                    WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
        }
複製代碼

WebViewClient方法:
主要做用:主要幫助WebView處理各類通知、請求事件的web

方法 說明
onPageStarted(WebView view, String url, Bitmap favicon) WebView 開始加載頁面時回調,一次Frame加載對應一次回調
onLoadResource(WebView view, String url) WebView 加載頁面資源時會回調,每個資源產生的一次網絡加載,除非本地有當前 url 對應有緩存,不然就會加載
shouldInterceptRequest(WebView view, String url) WebView 能夠攔截某一次的 request 來返回咱們本身加載的數據,這個方法在後面緩存會有很大做用
shouldOverrideUrlLoading(WebView view, String url) 是否在 WebView 內加載頁面,返回true表明當前Webview完成這次加載,false表明交給系統瀏覽器加載
onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) WebView ssl 訪問證書出錯,handler.cancel()取消加載,handler.proceed()對然錯誤也繼續加載
onPageFinished(WebView view, String url) WebView 完成加載頁面時回調,一次Frame加載對應一次回調
onReceivedError(WebView view, int errorCode, String description, String failingUrl) WebView 訪問 url 出錯

WebChromeClient方法
要輔助WebView處理Javascript的對話框、網站圖標、網站title、加載進度等chrome

方法 說明
onConsoleMessage(String message, int lineNumber,String sourceID) 輸出 Web 端日誌
onProgressChanged(WebView view, int newProgress) 當前 WebView 加載網頁進度
onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) 處理 JS 中的 Prompt對話框
onJsAlert(WebView view, String url, String message, JsResult result) Js 中調用 alert() 函數,產生的對話框
onReceivedTitle(WebView view, String title): 接收web頁面的 Title
onReceivedIcon(WebView view, Bitmap icon) 接收web頁面的icon
View getVideoLoadingProgressView() 獲取在全屏視頻正在緩衝的同時顯示的視圖。宿主應用程序能夠重寫此方法,以提供包含旋轉器或相似物的視圖。
onShowCustomView(View view, CustomViewCallback callback) 網頁中有H5播放flash video的時候按下全屏按鈕將會調用到這個方法,通常用做設置網頁播放全屏操做
onHideCustomView() 取消全屏方法

三,Native與Js交互

首先說Native調用js代碼,或者說執行js代碼很簡單:數據庫

Native 調用 Js:mWebView.loadUrl(js);
複製代碼

Js代碼調用Native的代碼,一共有三種方式:json

  1. 使用系統方法 addJavascriptInterface 注入 java 對象來實現。---這是標準方式
  2. 利用 WebViewClient 中 shouldOverrideUrlLoading (WebView view, String url) 接口,攔截操做。這個就是不少公司在用的 scheme 方式,經過制定url協議,雙方各自解析,使用iframe來調用native代碼,實現互通。 --- 這是最經常使用的方式
  3. 利用 WebChromeClient 中的 onJsAlert、onJsConfirm、onJsPrompt 提示接口,一樣也是攔截操做。--- 不經常使用

標準方式使用清單:

//開啓Js可用
mWebView.getSettings().setJavaScriptEnabled(true);

// 建立要注入的 Java 類
public class NativeInterface {

    private Context mContext;

    public NativeInterface(Context context) {
        mContext = context;
    }

   // 使用JavascriptInterface註解標註的方法能夠被JS代碼調用
    @JavascriptInterface
    public void hello() {
        Toast.makeText(mContext, "hello", Toast.LENGTH_SHORT).show();
    }

    @JavascriptInterface
    public void hello(String params) {
        Toast.makeText(mContext, params, Toast.LENGTH_SHORT).show();
    }

    @JavascriptInterface
    public String getAndroid() {
        Toast.makeText(mContext, "getAndroid", Toast.LENGTH_SHORT).show();
        return "Android data";
    }

}

// WebView 注入便可,第二個參數是注入的java對象的別名
mWebView.addJavascriptInterface(new NativeInterface(this), "AndroidNative");

//Js編寫
<script>
    function callHello(){
        AndroidNative.hello();
    }

    function callHello1(){
        // 在js代碼中別名AndroidNative就至關於java中實例對象new NativeInterface(this),可調用方法
        AndroidNative.hello('hello Android');
    }

    function callAndroid(){
        var temp = AndroidNative.getAndroid();
        console.log(temp);
        alert(temp);
    }  
</script>
複製代碼

最經常使用的方式使用清單:
shouldOverrideUrlLoading接口下,攔截js回調操做,使用scheme協議,即scheme://host[:post]/path/jsonparams,雙方定義格式解析

假如 :協定的格式爲 appname://jsbridge/call/func/jsonparams
jsonparams 的是格式爲:
{ "callback" : "js用於區分此操做的標識", // 這一項是必須的
"key1": value1,
"key2": value2,
"key3": value3
……
}

  // js獲取userinfo爲例:
  // func = userinfo
  // jsonparams 的是格式爲:
  //  { "callback" : "cbUserInfo_1"
  //  }
  @Override
  public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (!TextUtils.isEmpty(url) && url.startsWith("appname://jsbridge/call/")) {
         String[] values = url.replace("appname://jsbridge/call/""").split("/"2);
         String func = values[0];
         String params = values[1];
         //  1,js調用native代碼,處理Native端的邏輯
         switch(func) {
             case "userinfo":
                JSONObject job = new JSONObject(params);
                String callback = job.optString("callback"); // 此處是「cbUserInfo_1」
                // 客戶端獲取一些信息等等
                JSONObject result = new JSONObject();
                result.put("isLogin",Config.isLogin());
                result.put("nickName", Config.getUserName());
                result.put("userId", Config.getUserId());
                result.put("headimgUrl", Config.getPicUrl());
              /// 等等

                // 2, Native調用js代碼將處理的結果返回給js,Js須要用callback區分這次請求   

                String js = "callback 和 params組成js";
// 這裏須要把callback和params組成js,須要和前端商定,而後使用loadUrl("javascript:" + js) 就能夠將結果傳給js
                 if (mWebView != null) {
                       mWebView.loadUrl("javascript:" + js);
                 }
             break;
         }
         return true;
    }
  }
複製代碼

以上就是JsBridge的原理,固然,在業務複雜時能夠進行封裝

四,騰訊瀏覽服務X5內核

騰訊瀏覽服務網址:https://x5.tencent.com/
接入指南:https://x5.tencent.com/tbs/guide/sdkInit.html
使用很簡單和系統的WebView同樣。

  1. 爲何要更換X5內核
    Android 4.4如下web內核是WebKit,4.4 以後採用 chrome 內核,當前端在開發時,會出現前端使用的新特性在低版本安卓手機上不能表現出來,因此須要統一內核

  2. X5內核的技術特性
    騰訊瀏覽服務是致力於優化移動端webview體驗的整套解決方案。該方案由SDK、手機QQ瀏覽器X5內核和X5雲端服務組成,解決移動端webview使用過程當中出現的一切問題,優化用戶的瀏覽體驗。同時,騰訊還將持續提供後續的更新和優化,爲開發者提供最新最優秀的功能和服務。
    其中,SDK是經過共享使用用戶手機上微信、手機QQ、空間等軟件已經下載好的X5內核,低成本實現對系統webview的替代。該SDK大小隻有200+K,接入時僅需修改幾行代碼。
    其中,X5內核相對於系統webview,具備下述明顯優點:
    1) 速度快:相比系統webview的網頁打開速度有30+%的提高;
    2) 省流量:使用雲端優化技術使流量節省20+%;
    3) 更安全:安全問題能夠在24小時內修復;
    4) 更穩定:通過億級用戶的使用考驗,CRASH率低於0.15%;
    5) 兼容好:無系統內核的碎片化問題,更少的兼容性問題;
    6) 體驗優:支持夜間模式、適屏排版、字體設置等瀏覽加強功能;
    7) 功能全:在Html五、ES6上有更完整支持;
    8) 更強大:集成強大的視頻播放器,支持視頻格式遠多於系統webview;
    9) 視頻和文件格式的支持x5內核多於系統內核
    10) 防劫持是x5內核的一大亮點

  3. 下載TbsSDK,會有不少文檔:

tbssdk
tbssdk
常見FQA
常見FQA
常見FQA
常見FQA

五,X5內核WebView +全屏視頻 + JsBridge框架

三個核心類封裝
三個核心類封裝
// 注意:代碼中的WebView,均爲騰訊的WebView
import com.tencent.smtt.sdk.WebChromeClient;
import com.tencent.smtt.sdk.WebView;
import com.tencent.smtt.sdk.WebChromeClient;

// X5WebView.java:繼承自騰訊的x5內核的WebView
public class X5WebView extends WebView {

    public static final String TAG = X5WebView.class.getSimpleName();

    private boolean addedJavascriptInterface;

    public X5WebView(Context context) {
        super(context);
        addedJavascriptInterface = false;
    }

    public X5WebView(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        addedJavascriptInterface = false;
    }

    public X5WebView(Context context, AttributeSet attributeSet, int i) {
        super(context, attributeSet, i);
        addedJavascriptInterface = false;
    }

    public X5WebView(Context context, AttributeSet attributeSet, int i, boolean b) {
        super(context, attributeSet, i, b);
        addedJavascriptInterface = false;
    }

    public X5WebView(Context context, AttributeSet attributeSet, int i, Map<String, Object> map, boolean b) {
        super(context, attributeSet, i, map, b);
        addedJavascriptInterface = false;
    }

    @Override
    @SuppressLint("SetJavaScriptEnabled")
    public void setWebChromeClient(WebChromeClient client) {
        getSettings().setJavaScriptEnabled(true);
        super.setWebChromeClient(client);
    }

    @Override
    public void loadData(String data, String mimeType, String encoding) {
        addJavascriptInterface();
        super.loadData(data, mimeType, encoding);
    }

    @Override
    public void loadDataWithBaseURL(String baseUrl, String data,
                                    String mimeType, String encoding,
                                    String historyUrl)
 
{
        addJavascriptInterface();
        super.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
    }

    @Override
    public void loadUrl(String url) {
        addJavascriptInterface();
        super.loadUrl(url);
    }

    @Override
    public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
        addJavascriptInterface();
        super.loadUrl(url, additionalHttpHeaders);
    }

    private void addJavascriptInterface() {
        if (!addedJavascriptInterface) {
            // Add javascript interface to be called when the video ends (must be done before page load)
            addJavascriptInterface(new Object() {
            }, "_VideoEnabledWebView"); // Must match Javascript interface name of VideoEnabledWebChromeClient
            addedJavascriptInterface = true;
        }
    }
// X5WebViewClient.java: 繼承自騰訊的x5內核的WebViewClient
public class X5WebViewClient extends WebViewClient {

}
// X5WebChromeClient.java :構建一個全屏顯示Video的WebChromeClient, 網上能夠找到實現,只是作了繼承X5的WebChromeClient
public class X5WebChromeClient extends WebChromeClient implements MediaPlayer.OnPreparedListenerMediaPlayer.OnCompletionListenerMediaPlayer.OnErrorListener {

    // 將被隱藏的View
    private View willHiddenView;
    // 將全屏顯示的ViewGroup
    private ViewGroup willDisplayedViewGroup;
    // 加載View
    private View loadingView;
    // 核心的WebView
    private X5WebView webView;
    // 進度條
    private ProgressBar mProgressBar;

    // 代表是否在使用自定義View 顯示全屏Video
    private boolean isVideoFullscreen;

    // 保存全屏Video容器
    private FrameLayout videoViewContainer;
    // 保存自定義View回調
    private IX5WebChromeClient.CustomViewCallback videoViewCallback;

    // 全屏Video回調
    private ToggledFullscreenCallback toggledFullscreenCallback;
    // 接收標題回調
    private onReceivedTitleCallback receivedTitleCallback;


    /**
     * Never use this constructor alone.
     * This constructor allows this class to be defined as an inline inner class in which the user can override methods
     */

    public X5WebChromeClient() {
    }

    /**
     * 構建一個全屏顯示Video的WebChromeClient
     *
     * @param willHiddenView           當video全屏時,activity佈局中的應該被隱藏的View
     * @param willDisplayedViewGroup   activity佈局中,將要顯示的 ViewGroup,特別是你想使此ViewGroup全屏顯示。
     */

    public X5WebChromeClient(View willHiddenView, ViewGroup willDisplayedViewGroup) {
        this.willHiddenView = willHiddenView;
        this.willDisplayedViewGroup = willDisplayedViewGroup;
        this.loadingView = null;
        this.webView = null;
        this.isVideoFullscreen = false;
    }

    /**
     * 構建一個全屏顯示Video的WebChromeClient
     *
     * @param willHiddenView          當video全屏時,activity佈局中的應該被隱藏的View
     * @param willDisplayedViewGroup  activity佈局中,將要顯示的 ViewGroup,特別是你想使此ViewGroup全屏顯示
     * @param loadingView             加載視頻時顯示的視圖(一般僅在API級別<11時使用)。必須被預先inflate,且無父佈局
     */

    public X5WebChromeClient(View willHiddenView, ViewGroup willDisplayedViewGroup, View loadingView) {
        this.willHiddenView = willHiddenView;
        this.willDisplayedViewGroup = willDisplayedViewGroup;
        this.loadingView = loadingView;
        this.webView = null;
        this.isVideoFullscreen = false;
    }

    /**
     * 構建一個全屏顯示Video的WebChromeClient
     *
     * @param willHiddenView         當video全屏時,activity佈局中的應該被隱藏的View
     * @param willDisplayedViewGroup activity佈局中,將要顯示的 ViewGroup,特別是你想使此ViewGroup全屏顯示
     * @param loadingView            加載視頻時顯示的視圖(一般僅在API級別<11時使用)。必須被預先inflate,且無父佈局
     * @param webView                X5WebView
     *
     * 經過它將使X5WebChromeClient檢測HTML5視頻結束事件並退出全屏。
     * 注意:爲了使HTML5視頻結束事件正常工做,網頁只能包含一個視頻標記。若是須要的話,能夠改進這一點(參見javascript代碼)。
     */

    public X5WebChromeClient(View willHiddenView, ViewGroup willDisplayedViewGroup, View loadingView, X5WebView webView) {
        this.willHiddenView = willHiddenView;
        this.willDisplayedViewGroup = willDisplayedViewGroup;
        this.loadingView = loadingView;
        this.webView = webView;
        this.isVideoFullscreen = false;
    }

    /**
     * 手動 進去全屏時 調用
     * @param view
     * @param callback
     */

    @Override
    public void onShowCustomView(View view, IX5WebChromeClient.CustomViewCallback callback) {
        if (view instanceof FrameLayout) {
            PalLog.d(X5WebView.TAG, "onShowCustomView");
            // A video wants to be shown
            FrameLayout frameLayout = (FrameLayout) view;
            // 焦點View
            View focusedChild = frameLayout.getFocusedChild();
            // 保存Video相關變量
            this.isVideoFullscreen = true;
            this.videoViewContainer = frameLayout;
            this.videoViewCallback = callback;
            // 隱藏non-video的View
            willHiddenView.setVisibility(View.GONE);
            // 添加video的View並顯示
            willDisplayedViewGroup.addView(videoViewContainer, new RelativeLayout.LayoutParams(ActionBar.LayoutParams.MATCH_PARENT, ActionBar.LayoutParams.MATCH_PARENT));
            willDisplayedViewGroup.setVisibility(View.VISIBLE);

            // 焦點View是VideoView
            if (focusedChild instanceof VideoView) {
                // VideoView (typically API level <11)
                VideoView videoView = (VideoView) focusedChild;
                // VideoView設置準備監聽器,完成監聽器,錯誤監聽器
                videoView.setOnPreparedListener(this);
                videoView.setOnCompletionListener(this);
                videoView.setOnErrorListener(this);
            } else {
                // Usually android.webkit.HTML5VideoFullScreen$VideoSurfaceView, sometimes android.webkit.HTML5VideoFullScreen$VideoTextureView
                // HTML5VideoFullScreen (typically API level 11+)
                // Handle HTML5 video ended event
                if (webView != null && webView.getSettings().getJavaScriptEnabled()) {
                    // Run javascript code that detects the video end and notifies the interface
                    String js = "javascript:";
                    js += "_ytrp_html5_video = document.getElementsByTagName('video')[0];";
                    js += "if (_ytrp_html5_video !== undefined) {";
                    {
                        js += "function _ytrp_html5_video_ended() {";
                        {
                            js += "_ytrp_html5_video.removeEventListener('ended', _ytrp_html5_video_ended);";
                            js += "_VideoEnabledWebView.notifyVideoEnd();"// Must match Javascript interface name and method of VideoEnableWebView
                        }
                        js += "}";
                        js += "_ytrp_html5_video.addEventListener('ended', _ytrp_html5_video_ended);";
                    }
                    js += "}";
                    webView.loadUrl(js);
                }
            }

            // 最後通知全屏改變
            if (toggledFullscreenCallback != null) {
                toggledFullscreenCallback.toggledFullscreen(true);
            }
        }
    }

    @Override
    public void onShowCustomView(View view, int requestedOrientation, IX5WebChromeClient.CustomViewCallback callback) // Only available in API level 14+
    
{
        onShowCustomView(view, callback);
    }

    /**
     * 手動取消全屏時調用
     */

    @Override
    public void onHideCustomView() {
        PalLog.d(X5WebView.TAG, "onHideCustomView");
        // This method must be manually (internally) called on video end in the case of VideoView (typically API level <11)
        // This method must be manually (internally) called on video end in the case of HTML5VideoFullScreen (typically API level 11+) because it's not always called automatically
        // This method must be manually (internally) called on back key press (from this class' onBackPressed() method)
        if (isVideoFullscreen) {
            // Hide the video view, remove it, and show the non-video view
            willDisplayedViewGroup.setVisibility(View.GONE);//播放視頻的
            willDisplayedViewGroup.removeView(videoViewContainer);
            willHiddenView.setVisibility(View.VISIBLE);

            // Call back
            if (videoViewCallback != null) {
                videoViewCallback.onCustomViewHidden();
            }

            // Reset video related variables
            isVideoFullscreen = false;
            videoViewContainer = null;
            videoViewCallback = null;

            // Notify full-screen change
            if (toggledFullscreenCallback != null) {
                toggledFullscreenCallback.toggledFullscreen(false);
            }
        }
    }

    /**
     * Video will start loading, only called in the case of VideoView (typically API level <11)
     * @return
     */

    @Override
    public View getVideoLoadingProgressView() {
        if (loadingView != null) {
            loadingView.setVisibility(View.VISIBLE);
            return loadingView;
        } else {
            return super.getVideoLoadingProgressView();
        }
    }

    /**
     * Video will start playing, only called in the case of VideoView (typically API level <11)
     * @param mp
     */

    @Override
    public void onPrepared(MediaPlayer mp) {
        PalLog.d(X5WebView.TAG, "onPrepared");
        if (loadingView != null) {
            loadingView.setVisibility(View.GONE);
        }
    }

    /**
     * Video finished playing, only called in the case of VideoView (typically API level <11)
     * @param mp
     */

    @Override
    public void onCompletion(MediaPlayer mp) {
        PalLog.d(X5WebView.TAG, "onCompletion");
        onHideCustomView();
    }

    /**
     *  Error while playing video, only called in the case of VideoView (typically API level <11)
     * @param mp
     * @param what
     * @param extra
     * @return
     */

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        // By returning false, onCompletion() will be called
        return false;
    }

    /**
     * 設置標題回調
     * @param callback
     */

    public void setOnReceivedTitleCallback(onReceivedTitleCallback callback) {
        receivedTitleCallback = callback;
    }

    /**
     * 設置進度條
     * @param progressBar
     */

    public void setProgressBar(ProgressBar progressBar) {
        this.mProgressBar = progressBar;
    }

    /**
     * 指示是否使用自定義視圖(一般爲全屏)顯示視頻
     *
     * @return true 使用自定義視圖(一般爲全屏)顯示視頻
     */

    public boolean isVideoFullscreen() {
        return isVideoFullscreen;
    }

    /**
     * 設置全屏切換回調
     *
     * @param callback 全屏切換回調
     */

    public void setOnToggledFullscreen(ToggledFullscreenCallback callback) {
        this.toggledFullscreenCallback = callback;
    }

    @Override
    public void onProgressChanged(WebView view, int newProgress) {
        super.onProgressChanged(view, newProgress);
        if (mProgressBar != null) {
            mProgressBar.setProgress(newProgress);
            if (newProgress == 100) {
                // onPageFinished
                mProgressBar.setVisibility(View.GONE);
            }
        }
    }

    @Override
    public void onReceivedTitle(WebView view, String title) {
        super.onReceivedTitle(view, title);
        if (receivedTitleCallback != null) {
            receivedTitleCallback.onReceivedTitle(title);
        }
    }

    /**
     * Notifies the class that the back key has been pressed by the user.
     * This must be called from the Activity's onBackPressed(), and if it returns false, the activity itself should handle it. Otherwise don't do anything.
     *
     * @return Returns true if the event was handled, and false if it is not (video view is not visible)
     */

    public boolean onBackPressed() {
        if (isVideoFullscreen) {
            onHideCustomView();
            return true;
        } else {
            return false;
        }
    }

    @Override
    public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {

        return true;
    }

    /**
     * 切換全屏,取消全屏時回調
     */

    public interface ToggledFullscreenCallback {
        public void toggledFullscreen(boolean fullscreen);
    }

    /**
     * 接收標題回調
     */

    public interface onReceivedTitleCallback {
        public void onReceivedTitle(String title);
    }
}
複製代碼
// Native調Js代碼的接口
public interface JSCallBack {
    // callback 和 params 是 上面「三,Native與Js交互# 最多見方式的使用清單」
    void jsCallback(String callback, String params);
    void jsCallback(String callback, Object object);
}
複製代碼

下面重點介紹是Js調用Native代碼的封裝AppJsBridge.java

// IJSBridge是Js調用Native代碼的接口
public interface IJSBridge {
   // 見「三,Js與Native交互」
    void processJsBridge(String func, String params);
    void setJSCallBack(JSCallBack jsCallBack);
}
// AppJsBridge.java  是Js調用Native代碼的封裝
public class AppJSBridge implements IJSBridge {
    private static final String TAG = "AppJSBridge";

    public static final String KEY_CALLBACK = "callback";

   // Activity
    private BaseActivity mActivity;
    // 實際執行Native代碼的接口
    private OnInvokeCallback mOnInvokeCallback;
    // 客戶端執行js代碼,把結果傳給js
    private JSCallBack mJSCallBack;
    //當前頁面的host,用於判斷白名單
    private String mCurHost; 

    //白名單
    private List<JSBridgeSpecialBean> jsList;

    @Override
    public void setJSCallBack(JSCallBack jsCallBack) {
        mJSCallBack = jsCallBack;
    }

    /**
     * 是不是白名單
     */

    private boolean isJsBridgeSpecial(String func) {
        if (jsList == null || mCurHost == null)
            return false;
        for (JSBridgeSpecialBean jsSpecial : jsList) {
            if (jsSpecial.allow == null || jsSpecial.allow.isEmpty()) continue;
            if (jsSpecial.domain.equals(JSBridgeSpecialBean.TAG_ALL) ||
                    mCurHost.endsWith(jsSpecial.domain.toLowerCase())) {
                if (jsSpecial.allow.get(0).equals(JSBridgeSpecialBean.TAG_ALL))
                    return true;
                else if (jsSpecial.allow.contains(func))
                    return true;
            }
        }
        return false;
    }


    @Override
    public void processJsBridge(String func, String params, String callbackId) {
        PalLog.d(TAG, "func=" + func);
        PalLog.d(TAG, "params=" + params);
        if (!isJsBridgeSpecial(func)) {
            PalLog.e(TAG, func + " 不屬於白名單,禁止使用該JS接口");
            return;
        }
        switch (func) {

            case "deviceInfo"//獲取設備信息
                getDeviceInfo(params);
                break;
            case "userInfo"//獲取用戶信息
                getUserInfo(params);
                break;
            /// 等等其餘case
            default:
                PalLog.e(TAG, "沒法處理的事件: " + func);
                break;
        }
    }

    /**
     * 獲取用戶信息
     */

    private void getUserInfo(String params) {
        if (mJSCallBack == nullreturn;
        try {
            // 1,執行Native代碼
            JSONObject job = new JSONObject(params);
            String callback = job.optString(KEY_CALLBACK);
            // 從客戶端獲取一些信息等等
            JSONObject result = new JSONObject();
            result.put("isLogin", Config.isLogin());
            result.put("nickName", Config.getUserName());
            result.put("userId", Config.getUserId());
            result.put("headimgUrl", Config.getPicUrl());
            /// ....
            // 2,Native調用Js代碼,將結果回傳給JS
            mJSCallBack.jsCallback(callback, result.toString());
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    /**
     * 獲取設備信息
     */

    private void getDeviceInfo(String params) {
        if (mJSCallBack == nullreturn;
        try {
            // 1,執行Native代碼
            JSONObject job = new JSONObject(params);
            String callback = job.optString(KEY_CALLBACK);
            // 從客戶端獲取一些信息等等
            JSONObject result = new JSONObject();
            result.put("os""Android");
            result.put("version", Config.getVersion());
            result.put("deviceId", Config.getVersion());
            ///...
            // 2,Native調用Js代碼,將結果回傳給JS
            mJSCallBack.jsCallback(callback, result.toString());
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }
}
複製代碼
相關文章
相關標籤/搜索