在Android开发中,WebView的使用频率越来越高,这里跟大家分享下WebView使用中的一些技巧或者注意点
11月21日更新:
修复Android 6.0网页title获取的bug,具体见-2.网页title的获取
11月4日更新:
新增-9.WebView视频全屏播放
1.WebView顶部展示加载进度
自定义控件,继承WebView
public class ErmWebView extends WebView {
private ProgressBar progressbar;
public ErmWebView(Context context, AttributeSet attrs) {
super(context, attrs);
progressbar = new ProgressBar(context, null, android.R.attr.progressBarStyleHorizontal);
progressbar.setProgressDrawable(context.getResources().getDra(R.drawable.style_progressbar_web));//设置样式
progressbar.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 5, 0, 0));
addView(progressbar);
setWebChromeClient(new WebChromeClient());
}
public class WebChromeClient extends android.webkit.WebChromeClient {
@Override
public void onProgressChanged(WebView view, int newProgress) {//进度
if (newProgress == 100) {
progressbar.setVisibility(GONE);
} else {
if (progressbar.getVisibility() == GONE)
progressbar.setVisibility(VISIBLE);
progressbar.setProgress(newProgress);
}
super.onProgressChanged(view, newProgress);
}
}
2.网页title的获取
需要在WebChromeClient的onReceivedTitle()中获取,其他api获取不可靠
webview控件在Android6.0上有一个bug,那就是onReceivedTitle()会调用两次,一次为网页的url,一次为网页真正的title,故这里需要做一个过滤
mWebview.setWebChromeClient(new WebChromeClient(){
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
//由于webview在title在6.0上会调用两次,故这里过滤掉title为url的那次
if (!getUrl().contains(title)) mTitleBar.setTitle(title);//设置title
}
});
3.本地图片(文件)选择
web页面中经常需要用户选取手机中的图片(文件)上传,这种默认的js调用,在iOS和PC端是OK的,但是Android端需要处理,否则不会做任何操作
private ValueCallback<Uri> mUploadMessage;//回调图片选择,4.4以下
private ValueCallback<Uri[]> mUploadCallbackAboveL;//回调图片选择,5.0以上
mWebview.setWebChromeClient(new WebChromeClient(){
//由于是隐藏API,故没有@Override注解
// For Android 3.0+单参数
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
if (getContext() instanceof Activity) {
((Activity) getContext()).startActivityForResult(Intent.createChooser(i, "File Chooser"), FILE_SELECT_CODE);
}
}
// For Android 3.0+多参数
public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("*/*");
if (getContext() instanceof Activity) {
((Activity) getContext()).startActivityForResult(Intent.createChooser(i, "File Browser"), FILE_SELECT_CODE);
}
}
// For Android 4.1
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
if (getContext() instanceof Activity) {
((Activity) getContext()).startActivityForResult(Intent.createChooser(i, "File Chooser"), FILE_SELECT_CODE);
}
}
// For Android 5.0+
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
mUploadCallbackAboveL = filePathCallback;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("*/*");
if (getContext() instanceof Activity) {
((Activity) getContext()).startActivityForResult(Intent.createChooser(i, "File Browser"), FILE_SELECT_CODE);
}
return true;
}
}
//将选择结果回调给网页
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != Activity.RESULT_OK) {
if (Build.VERSION.SDK_INT >= 21) //5.0以上版本处理
mWebview.getUploadCallbackAboveL().onReceiveValue(null);//取消选择必须回调null,否则web处于阻塞状态,无法继续操作
else
mWebview.getUploadMessage().onReceiveValue(null);
return;
}
switch (requestCode) {
case FILE_SELECT_CODE: {
if (Build.VERSION.SDK_INT >= 21) {//5.0以上版本处理
Uri uri = data.getData();
Uri[] uris = new Uri[]{uri};
mWebview.getUploadCallbackAboveL().onReceiveValue(uris);//回调给js
} else {//4.4以下处理
Uri uri = data.getData();
Logger.i(uri.toString());
mWebview.getUploadMessage().onReceiveValue(uri);
}
}
break;
}
}
- 以上代码在大部分手机测试通过,但不排除个别机型存在bug的可能
4.宽度自适应
如果网页比较宽,webView就可以左右滑动,用户一屏看不到所有的内容,体验会比较差,我们可以设置页面自适应
WebSettings settings = getSettings();
settings.setJavaScriptEnabled(true);//开启js
settings.setUseWideViewPort(true);//宽度自适应
settings.setLoadWithOverviewMode(true);
settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
- html中的图片需要自适应,否则可能会出现图片特别宽,导致整个页面无法自适应
5.自定义网页加载出错页面
对于客户端来说,加载出错包括 网络出错+网页Load出错.默认的出错页面比较丑,并且会直接显示URL,导致我们的url暴露.
mWebview.setWebViewClient(new WebViewClient() {
@SuppressWarnings("deprecation")
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {//低版本
mWebview.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);//去除默认的404页面
mEmpty.showErrorType("网页加载出错,请点击重试");
}
@TargetApi(android.os.Build.VERSION_CODES.M)//编译版本>23
@Override
public void onReceivedError(WebView view, WebResourceRequest req, WebResourceError rerr) {
onReceivedError(view, rerr.getErrorCode(), rerr.getDescription().toString(), req.getUrl().toString());
}
});
- 多次测试之后,上面这样写才能捕捉到所有加载出错的回调,并且在大部分机型及API版本测试通过.
6.返回键处理
用户希望能够返回到上一个网页,而不是直接退出当前webActivity
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
mLeftTv.setVisibility(View.VISIBLE);
if (keyCode == KeyEvent.KEYCODE_BACK && mWebview.canGoBack()) {
mWebview.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
7.退出WebView
WebView的底层调用WebKit内核,加载也会另开线程,所以当WebView所在的Activity退出时,WebView内部的组建和线程可能并没有销毁,导致持续占用资源,甚至视频或者音频还在播放
@Override
protected void onPause() {
super.onPause();
if (mWebview != null) mWebview.onPause();//退出关闭webview
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mWebview != null) mWebview.destroy();//退出关闭webview
}
- 以上代码在大部分手机上含视频播放的web页面测试通过,视频及视频声音不会再播放.但音频web页面退出未测试
8.与JS代码互相调用
有时候JS与原生互相调用各自的方法
mWebview.addJavascriptInterface(new InfoJs(), "infojs");//增加js交互的方法
public class InfoJs {
@JavascriptInterface
public void showToast(String s) {
ToastUtils.showToast("展示toast " + s);
}
}
- JS调原生也可以用URL重定向来调,这样iOS比较好实现.
- JS与原生的交互,网上有很多资料,写的比较详细,如:android中Webview与javascript的交互(互相调用)
9.WebView视频全屏播放
webview虽然默认支持全屏播放的事件,但是在大部分手机上都是无法全屏的,要么点击全屏是空白,要么没有任何反应,好在WebView有提供全屏及取消全屏的回调事件,所以如果需要支持WebView的全屏播放,就需要处理一下:
1.布局文件中增加一个与WebView同等级的FrameLayout,用于装载全屏的Video控件
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<FrameLayout
android:id="@+id/video"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
2.处理全屏及取消全屏事件:
private class CustomWebViewChromeClient extends WebChromeClient{
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
fullScreen();
mWebview.setVisibility(View.GONE);
mVideo.setVisibility(View.VISIBLE);
mVideo.addView(view);
mCallBack=callback;
mWebTitle.setVisibility(View.GONE);//如果有titleBar,一并隐藏
super.onShowCustomView(view, callback);
}
@Override
public void onHideCustomView() {
fullScreen();
if (mCallBack!=null){
mCallBack.onCustomViewHidden();
}
mWebview.setVisibility(View.VISIBLE);
mVideo.removeAllViews();
mVideo.setVisibility(View.GONE);
mWebTitle.setVisibility(View.VISIBLE);//如果有titleBar,一并显示出来
super.onHideCustomView();
}
}
private void fullScreen() {//强制切换屏幕方向
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}
关于作者
- 简 书:uncochen
- github:ChenZhen
- 新浪微博:@Chen丶振
- Email:18620156376@163.com