腾讯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