欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

腾讯X5WebView+JsBridge交互及WebView加载进度条效果实现

程序员文章站 2022-03-30 21:12:06
...

最近在项目开发中有不少页面需要采用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) {

            }
        });
    }
}

这样子使用起来就方便多了;效果如下:

腾讯X5WebView+JsBridge交互及WebView加载进度条效果实现

源码地址:https://pan.baidu.com/s/1XIVHFxpTT7tnjGgtKjKC-g