1. 程式人生 > >Android——JsBridge實戰(二)

Android——JsBridge實戰(二)

概述

在進行具體編碼前 ,我們先分析下一般商業APP對WebView的需求:

  • 可載入本地和雲端H5
  • 擁有cookie持久能力
  • 新增公共引數
  • 回退前進功能
  • Js與本地navtive互動
  • 擁有載入預設錯誤頁面能力
  • 載入網頁可展現進度
  • 支援https

為了滿足以上常用功能,大致對webview相關知識進行描述。

WebViewClient

WebViewClient主要輔助WebView執行處理各種響應請求事件的,比如:

  • onLoadResource
  • onPageStart
  • onPageFinish
  • onReceiveError
  • onReceivedHttpAuthRequest
  • shouldOverrideUrlLoading

載入失敗頁面,攔截加入的header頭必須用到WebViewClient,由於Android無法攔截h5本身ajax的請求,所以對header同步不是很好,建議大家對於ajax請求採用cookie形式,以防止url引數服務端無法獲取的問題。

  • 加入header 一般直接使用webView.load(url, header)

為了方便上層開發者呼叫,可以將此code加入到WebViewClient 的shouldOverrideUrlLoading中執行 ;

public boolean shouldOverrideUrlLoading
(WebView view, String url) { if(this.onPageHeaders(url) != null) { view.loadUrl(url, this.onPageHeaders(url)); } return super.shouldOverrideUrlLoading(view, url); }
  • 錯誤頁面也是複寫WebViewClient的onReceivedError() 來加入自定義的抽象onPageError(),如下:
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
    view.loadUrl(this
.onPageError(failingUrl)); }

onPageHeaders()便是上層抽象出來的介面,方便我們直接加入header,而onPageError()是方便指定載入錯誤頁面,那麼在activity中就是這樣了,

 mProgressBarWebView.setWebViewClient(new    CustomWebViewClient(mProgressBarWebView.getWebView()) {

        @Override
        public String onPageError(String url) {
            return "file:///android_asset/error.html";
        }

        @Override
        public Map<String, String> onPageHeaders(String url) {
            return CookieManger.getHeader(getContext());
        }
});
  • 設定只讓本應用程式的webview載入網頁而不呼叫外部瀏覽器
    解決方法:
    設定WebViewClient,並重寫WebViewClient的shouldOverrideUrlLoading方法返回true; WebViewClient的shouldOverrideUrlLoading方法的預設實現是直接返回false的。
mWebView.setWebViewClient(new WebViewClient(){
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        view.loadUrl(url);
        return true;
    }
});

WebChromeClient

主要輔助WebView處理JavaScript的對話方塊、網站Logo、網站title、load進度等處理。

  • onCloseWindow(關閉WebView)
  • onCreateWindow()
  • onJsAlert ()
  • onJsPrompt
  • onJsConfirm
  • onProgressChanged
  • onReceivedIcon
  • onReceivedTitle
  • onShowCustomView

WebView只是用來處理一些html的頁面內容,只用WebViewClient就行了,如果需要更豐富的處理效果,比如JS、進度條等,就要用到WebChromeClient。因為這次功能要用載入進度,不得不說它。

  • 為了加入頂部的載入進度條,複寫WebChromeClient中onProgressChanged,在這裡更改我們加入的ProgressBar的進度,你也可以設定網頁標題,甚至可以全屏!
public class CustomWebChromeClient extends WebChromeClient {
    private NumberProgressBar mProgressBar;

    public CustomWebChromeClient(NumberProgressBar progressBar) {
    this.mProgressBar = progressBar;
    }

    public void onProgressChanged(WebView view, int newProgress) {
         // newProgress=100 網頁載入完成
        if(newProgress >= 95) {
            this.mProgressBar.setVisibility(8);
        } else {
            if(this.mProgressBar.getVisibility() == 8) {
                this.mProgressBar.setVisibility(0);
            }

            this.mProgressBar.setProgress(newProgress);
        }

        super.onProgressChanged(view, newProgress);
    }

   //獲取tittle
    @Override
    public void onReceivedTitle(WebView view, String title) {
        super.onReceivedTitle(view, title);
    }

    //全屏
    @Override
    public void onShowCustomView(View view, CustomViewCallback callback) {
        super.onShowCustomView(view, callback);
    }
}

好了準備好了同步Header和進度條之後,就的考慮cookie同步問題

  • CookieSync

CookieManager
CookieManager是用來管理Cookie的,主要來管理cookie相關,提供如下API

  • setAcceptCookie()
  • setCookie()
  • getCookie(String url);
  • removeSessionCookies();
    *hasCookies()
  • removeAllCookie()

CookieSyncManager
CookieSyncManagerl繼承WebSyncManager,來管理同步cookie相關,主要有以下API

  • resetSync()
  • stopSync()
  • sync()
  • syncFromRamToFlash()
  • checkInstanceIsAllowed()

接著我們就可以這樣操作來實現cookie同步了,

  CookieManager cookieManager = CookieManager.getInstance();
  // 接受伺服器cookie
  cookieManager.setAcceptCookie(true);
  //移除之前的cookie
  cookieManager.removeSessionCookie();
  // 注入cookies
  List<String> cookies = getCookies(customCookies);
  for (String cookie : cookies) {
      cookieManager.setCookie(uri.getHost(), cookie);
  }
  // 同步cookie
  CookieSyncManager.getInstance().sync();

這裡需要注意棒棒糖以上的會出現無法同步問題那麼請這樣做

if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    cookieManager.flush();
} else {
    CookieSyncManager.getInstance().sync();
}

經測試,完美!

你可能想問?我想自定義像header一樣加入一些自定義cookie,行,沒問題,繼續看!

public static List<String> createCustomCookies() {
    List<String> cookies = new ArrayList<>();
    cookies.add(“author ” + "= " + "tamic");
    cookies.add(“data” + "= " + "2016.8.15");
    cookies.add(“key” + "= " + 4fdfsfd34dfdfswer");
    cookies.add(“chanel” + "= " + "簡書");
    return cookies;
}

很可能會遇到處理快取問題,設定快取webView快取模式!這裡在普及下相關姿勢!

* 快取模式

webview快取模式有5種,具體方式:

  • LOAD_CACHE_ONLY: 不使用網路,只讀取本地快取資料
  • LOAD_DEFAULT: 根據cache-control決定是否從網路上取資料。
  • LOAD_CACHE_NORMAL: API level 17中已經廢棄, 從API level 11開始作用同LOAD_DEFAULT模式
  • LOAD_NO_CACHE: 不使用快取,只從網路獲取資料.
  • LOAD_CACHE_ELSE_NETWORK,只要本地有,無論是否過期,或者no-cache,都使用快取中的資料。

www.baidu.com的cache-control為no-cache,在模式LOAD_DEFAULT下,無論如何都會從網路上取資料,如果沒有網路,就會出現錯誤頁面;在LOAD_CACHE_ELSE_NETWORK模式下,無論是否有網,只要本地有快取,都會載入快取。本地沒有快取時才從網路上獲取,
這個和Http快取一致,我不在過多介紹,如果你想自定義快取策略和時間,可以嘗試下,

  • 清除快取

CacheManager來處理webview快取相關:

clearCache(boolean)
CacheManager.clear

在4.4以上的此api已經無法使用,也就是說快取清空涉及安全,需要你自己去實現,就類似picasso, okhttp快取,一樣要開發者自我去實現。
當然也可以這樣:

WebView.clearCache(true);

清空歷史記錄

mWebview.clearHistory();

需要在onPageFinished()的方法之後呼叫

  • webview支援https
webView.setWebViewClient(new SSLTolerentWebViewClient());
webView.loadUrl(myhttps url);

複寫WebViewClient的nReceivedSslError函式,執行handler.cancel();給使用者感知的話,來個對話方塊授權下就行了額

private class SSLTolerentWebViewClient extendsWebViewClient {
    public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
        AlertDialog.Builder builder = new AlertDialog.Builder(Tab1Activity.this);
        AlertDialog alertDialog = builder.create();
        String message = "SSL Certificate error.";
        switch (error.getPrimaryError()) {
            case SslError.SSL_UNTRUSTED:
                message = "The certificate authority is not trusted.";
            break;
            case SslError.SSL_EXPIRED:
                message = "The certificate has expired.";
            break;
            case SslError.SSL_IDMISMATCH:
                message = "The certificate Hostname mismatch.";
            break;
            case SslError.SSL_NOTYETVALID:
                message = "The certificate is not yet valid.";
            break;
    }

    message += " Do you want to continue anyway?";
    alertDialog.setTitle("SSL Certificate Error");
    alertDialog.setMessage(message);
    alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            // Ignore SSL certificate errors
            handler.proceed();
        }
    });

    alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() {
       @Override
        public void onClick(DialogInterface dialog, int which) {
            handler.cancel();
        }
    });
    alertDialog.show();
  }
}

* ProgressBarWebView

學習了上面基礎知識,我這裡就開始進行自定義的進度條ProgressBarWebView的封裝了,這裡我直接對BridgeWebView進行擴充套件。下面是主要部分。

 public class ProgressBarWebView extends LinearLayout {
     static final String TAG = ProgressBarWebView.class.getSimpleName();
     private NumberProgressBar mProgressBar;
     private BridgeWebView mWebView;

     public ProgressBarWebView(Context context) {
         super(context);
         this.init(context, (AttributeSet)null);
     }

     public ProgressBarWebView(Context context, AttributeSet attrs) {
         super(context, attrs);
         this.init(context, attrs);
     }

     @TargetApi(11)
     public ProgressBarWebView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         this.init(context, attrs);
     }

     @TargetApi(21)
     public ProgressBarWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         this.init(context, attrs);
     }

為了使webview能有後退功能!我遮蔽了長按事件,並且對返回鍵建進行了攔截。

mWebView.setOnLongClickListener(new OnLongClickListener() {
        public boolean onLongClick(View v) {
            return true;
        }
 });

this.mWebView.setOnKeyListener(new OnKeyListener() {
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        if(event.getAction() == 0 && keyCode == 4 &&      ProgressBarWebView.this.mWebView.canGoBack()) {
            ProgressBarWebView.this.mWebView.goBack();
            return true;
        } else {
            return false;
        }
    }
});

如果防止webview程式碼產生記憶體洩漏,請及時在activity銷燬時,清空webview

@Override
public void onDestroy() {
    super.onDestroyView();
    if (mProgressBarWebView.getWebView() != null) {
        mProgressBarWebView.getWebView().destroy();
    }
}

看了構造方法你已明白,裡面包含一個BridgeWebView和一個NumberProgressBar 成員屬性,
* 對JsBridge進行封裝

Java呼叫js程式碼:

public void registerHandler(final String handlerName, final JsHandler handler) {
    this.mWebView.registerHandler(handlerName, new BridgeHandler() {
        public void handler(String data, CallBackFunction function) {
             if(handler != null) {
                handler.OnHandler(handlerName, data, function);
             }
        }
    });
}

js呼叫Native

public void callHandler(final String handlerName, String javaData, final JavaCallHandler handler) {
    this.mWebView.callHandler(handlerName, javaData, new CallBackFunction() {
        public void onCallBack(String data) {
            if(handler != null) {
                handler.OnHandler(handlerName, data);
            }
        }
    });
}

為了方便上層呼叫我自己封裝的介面JavaCallHandler 和JsHandler 。

public interface JsHandler {
    void OnHandler(String var1, String var2, CallBackFunction var3);
}

public interface JavaCallHandler {
    void OnHandler(String var1, String var2);
}

關鍵的東西已經介紹完,接著使用我們封裝好的ProgressBarWebView。

  • 案列使用

初始化

ProgressBarWebView  mProgressBarWebView = (ProgressBarWebView)    findViewById(R.id.login_progress_webview);

設定自定義WebViewClient

mProgressBarWebView.setWebViewClient(new   CustomWebViewClient(mProgressBarWebView.getWebView()) {

    @Override
    public String onPageError(String url) {
        //指定網路載入失敗時的錯誤頁面
        return "file:///android_asset/error.html";
    }

    @Override
    public Map<String, String> onPageHeaders(String url) {
        // 可以加入header
        return null;
    }
});

載入指定Url

mProgressBarWebView.loadUrl("file:///android_asset/demo.html");

註冊Js回撥函式

ArrayList<String> mHandlerNames = new ArrayList<>();
mHandlers.add("login");
mHandlers.add("callNative");
mHandlers.add("callJs");
mHandlers.add("open");

回撥js的方法

mProgressBarWebView.registerHandlers(mHandlers, new JsHandler() {
        @Override
        public void OnHandler(String handlerName, String responseData, CallBackFunction function) {

            if (handlerName.equals("login")) {
                Toast.makeText(MainActivity.this, responseData, Toast.LENGTH_SHORT).show();
            } else if (handlerName.equals("callNative")) {
                Toast.makeText(MainActivity.this, responseData, Toast.LENGTH_SHORT).show();

                function.onCallBack("我在上海");
            } else if (handlerName.equals("callJs")) {
              Toast.makeText(MainActivity.this, responseData, Toast.LENGTH_SHORT).show();
                // 想呼叫你的方法:
                function.onCallBack("好的 這是圖片地址 :xxxxxxx");
            } if (handlerName.equals("open")) {
                mfunction = function;
                pickFile();
            }
        }
    });

Native呼叫js

 mProgressBarWebView.callHandler("callNative", "hello H5, 我是java", new JavaCallHandler() {
        @Override
        public void OnHandler(String handlerName, String jsResponseData) {
            Toast.makeText(MainActivity.this, "h5返回的資料:" + jsResponseData, Toast.LENGTH_SHORT).show();
        }
    });

Native傳送訊息給js

    mProgressBarWebView.send("哈嘍", new CallBackFunction() {
        @Override
        public void onCallBack(String data) {
            Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();
        }
    });
}

補充:

如果希望瀏覽的網頁後退而不是退出瀏覽器,需要WebView覆蓋URL載入,讓它自動生成歷史訪問記錄,那樣就可以通過前進或後退訪問已訪問過的站點。

//改寫物理按鍵——返回的邏輯
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if(keyCode==KeyEvent.KEYCODE_BACK){
        if(webView.canGoBack()){
            webView.goBack();//返回上一頁面
            return true;
        }else{
            System.exit(0);//退出程式
        }
    }
    return super.onKeyDown(keyCode, event);
}