騰訊X5WebView+JsBridge互動及WebView載入進度條效果實現
最近在專案開發中有不少頁面需要採用html的方式實現,自然而然就涉及到原生和js的互動問題,WebView也提供了addJavascriptInterface方法可以進行js的互動,實現也比較簡單,由於需要互動的地方比較多,還是沒有采用這種方式,使用了JsBridge第三方來實現,JsBridge用起來比較方便,可以主動給js傳送訊息,同時回調發送結果,也可以有js主動呼叫,同時回撥呼叫結果等;
JsBridge地址:https://github.com/lzyzsd/JsBridge
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private final String CODING = "UTF-8"; // 編碼格式 private String loadUrl = "file:///android_asset/demo.html"; private BridgeWebView mWebView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mWebView = (BridgeWebView) findViewById(R.id.web_view); //垂直滾動條不顯示 // webView.setVerticalScrollBarEnabled(false); IX5WebViewExtension x5WebViewExtension = mWebView.getX5WebViewExtension(); if (x5WebViewExtension != null) { x5WebViewExtension.setScrollBarFadingEnabled(false); } //如果訪問的頁面中有Javascript,則webview必須設定支援Javascript WebSettings settings = mWebView.getSettings(); // settings.setLayoutAlgorithm(LayoutAlgorithm.NARROW_COLUMNS); // settings.setUseWideViewPort(true); settings.setUseWideViewPort(true); //設定執行載入js // settings.setJavaScriptEnabled(true); // 允許js彈出視窗 settings.setJavaScriptCanOpenWindowsAutomatically(true); //設定編碼 settings.setDefaultTextEncodingName(CODING); //設定支援DomStorage settings.setDomStorageEnabled(true); // 實現8倍快取 settings.setAppCacheMaxSize(Long.MAX_VALUE); settings.setAllowFileAccess(true); // 開啟Application Cache功能 settings.setAppCacheEnabled(true); //取得快取路徑 String appCachePath = getApplicationContext().getCacheDir().getAbsolutePath(); // String chejusPath = getFilesDir().getAbsolutePath()+ APP_CACHE_DIRNAME; //設定路徑 //API 19 deprecated settings.setDatabasePath(appCachePath); // 設定Application caches快取目錄 settings.setAppCachePath(appCachePath); //是否啟用資料庫 settings.setDatabaseEnabled(true); //設定儲存模式 建議快取策略為,判斷是否有網路,有的話,使用LOAD_DEFAULT,無網路時,使用LOAD_CACHE_ELSE_NETWORK settings.setCacheMode(WebSettings.LOAD_DEFAULT); //設定不支援字型縮放 settings.setSupportZoom(false); //設定對應的cookie具體設定有子類重寫該方法來實現 setCookie(loadUrl); //還有一種是載入https的URL時在5.0以上載入不了,5.0以下可以載入,這種情況可能是網頁中存在非https得資源,在5.0以上是預設關閉,需要設定, // loadWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { settings.setAllowUniversalAccessFromFileURLs(true); } // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // WebView.setWebContentsDebuggingEnabled(true); // } // 設定具體WebViewClient mWebView.setWebViewClient(new MyWebViewClient(mWebView)); // set HadlerCallBack mWebView.setDefaultHandler(new myHadlerCallBack()); mWebView.setWebChromeClient(new WebChromeClient() { @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); } }); try { mWebView.loadUrl(loadUrl); mWebView.setBackgroundColor(0); } catch (Exception e) { e.printStackTrace(); } //js呼叫Android方法 如果頁面上面有多個的話,可以註冊多個方法 //submitFromWeb 要和js那邊定義的一樣就可以了 mWebView.registerHandler("submitFromWeb", new BridgeHandler() { @Override public void handler(String data, CallBackFunction function) { Toast.makeText(MainActivity.this, data, Toast.LENGTH_LONG).show(); //如果js那邊呼叫後又 進行回撥的話可以在這裡進行回撥的 function.onCallBack("submitFromWeb-----------------"); } }); UserInfo user = new UserInfo(); user.name = "SDU"; user.pwd = "123456"; //Android傳送訊息給js,也可以註冊多個 //functionInJs和js定義的要一致 mWebView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() { @Override public void onCallBack(String data) { //這裡也是可以進行js回傳的 } }); mWebView.send("hello"); } /** * 自定義的WebViewClient */ class MyWebViewClient extends BridgeWebViewClient { public MyWebViewClient(BridgeWebView webView) { super(webView); } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); } @Override public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { super.onReceivedError(view, request, error); } @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { super.onReceivedError(view, errorCode, description, failingUrl); } } /** * 自定義回撥 */ class myHadlerCallBack extends DefaultHandler { @Override public void handler(String data, CallBackFunction function) { } } /** * 設定對應的cookie具體設定有子類重寫該方法來實現 * "age=20;sex=1;time=today" */ protected void setCookie(String loadUrl) { UserInfo info = new UserInfo(); info.name = "1111"; info.pwd = "123456789"; Gson gs = new Gson(); String toJson = gs.toJson(info); synCookies(loadUrl, "user=" + toJson); } @Override protected void onDestroy() { super.onDestroy(); ViewParent parent = mWebView.getParent(); if (parent != null) { ((ViewGroup) parent).removeView(mWebView); } mWebView.stopLoading(); mWebView.getSettings().setJavaScriptEnabled(false); mWebView.clearHistory(); mWebView.clearView(); mWebView.removeAllViews(); mWebView.destroy(); mWebView = null; } /** * 設定Cookie * * @param url * @param cookie 格式:uid=21233 如需設定多個,需要多次呼叫 * synCookies(this, url, "age=20;sex=1;time=today"); */ protected void synCookies(String url, String cookie) { // if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { // CookieSyncManager.createInstance(this); // } CookieSyncManager.createInstance(this); CookieSyncManager.getInstance().sync(); CookieManager cookieManager = CookieManager.getInstance(); cookieManager.setAcceptCookie(true); cookieManager.setCookie(url, cookie);//cookies是在HttpClient中獲得的cookie // CookieSyncManager.getInstance().sync(); } }
通過呼叫registerHandler註冊處理程式,以便javascript呼叫它,在BridgeHandler回撥中,可以通過function.onCallBack()進行回撥的處理,就是說js主動呼叫原生,原生又可以立馬回撥js ;
/** * register handler,so that javascript can call it * 註冊處理程式,以便javascript呼叫它 * @param handlerName handlerName * @param handler BridgeHandler */ public void registerHandler(String handlerName, BridgeHandler handler) { if (handler != null) { // 新增至 Map<String, BridgeHandler> messageHandlers.put(handlerName, handler); } }
需要注意handlerName要和js那邊保持一致,其實是儲存在一個map集合中;
Map<String, BridgeHandler> messageHandlers = new HashMap<String, BridgeHandler>();
所有建議當頁面銷燬的是時候呼叫unregisterHandler將註冊處理程式登出掉,其實就是從messageHandlers集合中移除;
/** * unregister handler * * @param handlerName */ public void unregisterHandler(String handlerName) { if (handlerName != null) { messageHandlers.remove(handlerName); } }
呼叫callHandler方法可以主動給js傳送訊息,在CallBackFunction回撥中可以處理js的回撥;
/**
* call javascript registered handler
* 呼叫javascript處理程式註冊
* @param handlerName handlerName
* @param data data
* @param callBack CallBackFunction
*/
public void callHandler(String handlerName, String data, CallBackFunction callBack) {
doSend(handlerName, data, callBack);
}
可以多次呼叫registerHandler和callHandler方法,不過在使用的時候要注意不要去重寫shouldOverrideUrlLoading方法,如果重寫shouldOverrideUrlLoading不能進行js的互動;原生和js的互動是搞定了,但是發現使用系統的WebView載入效果不理想,載入慢,卡頓等;出現問題就需要去解決,就建議使用騰訊的X5WebView,但是X5WebView並沒有對js互動做很好的處理,還是使用的addJavascriptInterface來實現,如果不能處理好,將X5WebView整合進來,之前實現的js互動就白寫了,最後考慮,不採用遠端依賴的方式引入JsBridge,將JsBridge原始碼拷貝到專案中,讓JsBridge中的BridgeWebView extends X5WebView中的WebView,這樣子問題就解決了;
騰訊X5WebView地址:http://x5.tencent.com/tbs/guide/sdkInit.html
替換成騰訊X5WebView載入速度和效果好了很多,接下來是實現進度載入效果;這個效果的實現網上有些是採用這種方式實現的;
public class ProgressWebView extends WebView {
private ProgressBar mProgressBar;
public ProgressWebView(Context context, AttributeSet attrs) {
super(context, attrs);
mProgressBar = new ProgressBar(context, null,
android.R.attr.progressBarStyleHorizontal);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, 8);
mProgressBar.setLayoutParams(layoutParams);
Drawable drawable = context.getResources().getDrawable(
R.drawable.web_progress_bar_states);
mProgressBar.setProgressDrawable(drawable);
addView(mProgressBar);
setWebChromeClient(new WebChromeClient());
}
public class WebChromeClient extends android.webkit.WebChromeClient {
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress == 100) {
mProgressBar.setVisibility(GONE);
} else {
if (mProgressBar.getVisibility() == GONE)
mProgressBar.setVisibility(VISIBLE);
mProgressBar.setProgress(newProgress);
}
super.onProgressChanged(view, newProgress);
}
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
LayoutParams lp = (LayoutParams) mProgressBar.getLayoutParams();
lp.x = l;
lp.y = t;
mProgressBar.setLayoutParams(lp);
super.onScrollChanged(l, t, oldl, oldt);
}
}
自定義WebView,然後例項化一個ProgressBar,設定ProgressBar的樣式並將其新增到自定義的WebView中,這樣導致WebView和ProgressBar成為一個整體,會發現當WebView滑動的時候ProgressBar也會跟著滑動,這樣的效果肯定是不行的,需要的效果是不管WebView怎麼滑動,ProgressBar的位置是不變的,WebView和ProgressBar是彼此獨立的,ProgressBar位於WebView上面;可以採用下面這種在xml佈局中處理;
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal"
android:background="#0ca5f5">
</LinearLayout>
<ProgressBar
android:id="@+id/load_progress"
android:layout_width="match_parent"
android:layout_height="3dp"
style="?android:attr/progressBarStyleHorizontal"
android:progressDrawable="@drawable/progress_bar"
android:visibility="gone"/>
<com.isales.webtest.jsbridge.BridgeWebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
然後在setWebChromeClient()中onProgressChanged回撥中進行處理,設定setProgress已達到進度的改變;
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
//載入進度改變的時候回撥
if (newProgress == 100) {
//隱藏掉進度條
mProgress.setVisibility(View.GONE);
} else {
//顯示進度條並載入進度
mProgress.setVisibility(View.VISIBLE);
mProgress.setProgress(newProgress);
}
}
});
這樣子不管WebView怎麼滑動,ProgressBar的位置是不變的,效果是實現了,如果只有一個地方使用還好些,使用的地方多了,該實現方式還是比較麻煩的,可以使用下面的實現方式;
/**
*
* webview載入進度效果
*/
public class LoadWebView extends LinearLayout {
private ProgressBar mProgress;
private BridgeWebView mWebView;
private LoadInterface loadInfterface;
public LoadWebView(Context context) {
this(context, null);
}
public LoadWebView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LoadWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
/**
* 初始化
*
* @param context
*/
private void init(Context context) {
setOrientation(VERTICAL);
initProgressBar(context);
initWebView(context);
}
/**
* 初始化進度條
*
* @param context
*/
private void initProgressBar(Context context) {
mProgress = (ProgressBar) LayoutInflater.from(context).inflate(R.layout.progress_horizontal, null);
//設定最大進度
mProgress.setMax(100);
//設定當前進度
mProgress.setProgress(0);
//將progressbar新增到佈局中
addView(mProgress, LayoutParams.MATCH_PARENT, dip2px(3));
}
/**
* 初始化WebView
*
* @param context
*/
private void initWebView(Context context) {
mWebView = new BridgeWebView(context);
//設定滾動條不可用
IX5WebViewExtension x5WebViewExtension = mWebView.getX5WebViewExtension();
if (x5WebViewExtension != null) {
x5WebViewExtension.setScrollBarFadingEnabled(false);
}
WebSettings settings = mWebView.getSettings();
settings.setUseWideViewPort(true);
// 允許js彈出視窗
settings.setJavaScriptCanOpenWindowsAutomatically(true);
//設定編碼
settings.setDefaultTextEncodingName("UTF-8");
//設定支援DomStorage
settings.setDomStorageEnabled(true);
// 實現8倍快取
settings.setAppCacheMaxSize(Long.MAX_VALUE);
settings.setAllowFileAccess(true);
// 開啟Application Cache功能
settings.setAppCacheEnabled(true);
//取得快取路徑
String appCachePath = context.getCacheDir().getAbsolutePath();
// String chejusPath = getFilesDir().getAbsolutePath()+ APP_CACHE_DIRNAME;
//設定路徑
//API 19 deprecated
settings.setDatabasePath(appCachePath);
// 設定Application caches快取目錄
settings.setAppCachePath(appCachePath);
//是否啟用資料庫
settings.setDatabaseEnabled(true);
//設定儲存模式 建議快取策略為,判斷是否有網路,有的話,使用LOAD_DEFAULT,無網路時,使用LOAD_CACHE_ELSE_NETWORK
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
//設定不支援字型縮放
settings.setSupportZoom(false);
//設定對應的cookie具體設定有子類重寫該方法來實現
//還有一種是載入https的URL時在5.0以上載入不了,5.0以下可以載入,這種情況可能是網頁中存在非https得資源,在5.0以上是預設關閉,需要設定,
// loadWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
settings.setAllowUniversalAccessFromFileURLs(true);
}
//將webview新增到佈局中
LayoutParams lps = new LayoutParams(LayoutParams.MATCH_PARENT, 0, 1);
addView(mWebView, lps);
mWebView.setWebViewClient(new MyWebViewClient(mWebView));
// set HadlerCallBack
mWebView.setDefaultHandler(new myHadlerCallBack());
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
//載入進度改變的時候回撥
if (newProgress == 100) {
//隱藏掉進度條
mProgress.setVisibility(View.GONE);
} else {
//顯示進度條並載入進度
mProgress.setVisibility(View.VISIBLE);
mProgress.setProgress(newProgress);
}
//進行載入回撥
if (loadInfterface != null) {
loadInfterface.onProgressChanged(view, newProgress);
}
}
});
}
public void goForward() {
if (canGoForward()) {
mWebView.goForward();
}
}
public boolean canGoForward() {
return mWebView != null && mWebView.canGoForward();
}
/**
* 判斷是否可以返回
*
* @return
*/
public boolean canGoBack() {
return mWebView != null && mWebView.canGoBack();
}
/**
* 執行返回的動作
*/
public void goBack() {
if (canGoBack()) {
mWebView.goBack();
}
}
/**
* 載入url連結
*
* @param url
*/
public void loadUrl(String url) {
if (mWebView != null) {
//載入url連結
try {
mWebView.loadUrl(url);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 獲取當前的ProgressBar
*
* @return
*/
public ProgressBar getProgressBar() {
return mProgress;
}
/**
* 獲取當前的WebView
*
* @return
*/
public BridgeWebView getWebView() {
return mWebView;
}
/**
* 設定監聽回撥
*
* @param listener
*/
public void addOnWebViewLoadingListener(LoadInterface listener) {
this.loadInfterface = listener;
}
/**
* 自定義的WebViewClient
*/
class MyWebViewClient extends BridgeWebViewClient {
public MyWebViewClient(BridgeWebView webView) {
super(webView);
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
//載入完成回撥
if (loadInfterface != null) {
loadInfterface.onPageFinished(view, url);
}
}
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
}
}
/**
* 自定義回撥
*/
class myHadlerCallBack extends DefaultHandler {
@Override
public void handler(String data, CallBackFunction function) {
}
}
private int dip2px(int dip) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
}
}
自定義一個佈局容器,首先例項化一個ProgressBar,設定ProgressBar樣式、大小等,將其新增到佈局容器中,再去例項化一個WebView,同樣新增到佈局容器中,提供相應的方法供外部呼叫,在使用的時候直接引用該自定義容器即可;
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal"
android:background="#0ca5f5">
</LinearLayout>
<com.isales.webtest.widget.LoadWebView
android:id="@+id/load_webview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
/**
* webview載入進度效果
*/
public class EffectSecondActivity extends AppCompatActivity {
private String loadUrl = "file:///android_asset/demo.html";
private BridgeWebView webView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_effect_second);
LoadWebView loadWebview = (LoadWebView) findViewById(R.id.load_webview);
webView = loadWebview.getWebView();
loadWebview.loadUrl(loadUrl);
//設定載入監聽
loadWebview.addOnWebViewLoadingListener(new LoadInterface() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress == 100) {
//載入完成後進行js互動
//js呼叫Android方法 如果頁面上面有多個的話,可以註冊多個方法
//submitFromWeb 要和js那邊定義的一樣就可以了
webView.registerHandler("submitFromWeb", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
Toast.makeText(EffectSecondActivity.this, data, Toast.LENGTH_LONG).show();
//如果js那邊呼叫後又 進行回撥的話可以在這裡進行回撥的
function.onCallBack("submitFromWeb-----------------");
}
});
UserInfo user = new UserInfo();
user.name = "SDU";
user.pwd = "123456";
//Android傳送訊息給js,也可以註冊多個
//functionInJs和js定義的要一致
webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
@Override
public void onCallBack(String data) {
//這裡也是可以進行js回傳的
}
});
webView.send("hello");
}
}
@Override
public void onPageFinished(WebView view, String url) {
}
});
}
}
這樣子使用起來就方便多了;效果如下:
原始碼地址:https://pan.baidu.com/s/1XIVHFxpTT7tnjGgtKjKC-g