1. 程式人生 > >騰訊X5WebView+JsBridge互動及WebView載入進度條效果實現

騰訊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