使用WebView开发的坑很多,这是众所周知的。本文分别对WebView的三个基本控件(俗称三剑客WebViewClient
,WebChromeClient
,WebView
)做了一些封装,方便使用,避免掉坑里。
主要有以下功能:
- 自定义出错页面,并实现重新加载事件
- 全屏播放视频
- 封装更加简单易用生命周期api,使用这些生命周期的方法可以避免很多与H5交互的坑
CustomWebViewClient
在WebViewClient
中主要是对onReceivedError
方法进行重写。这里面的逻辑这样的:
- 出错的url如果跟打开的url是一样的,那么这个时候显示自定义的出错页面。这个自定义页面是一个本地静态html。放在assets目录下。
- 如果这个出错的url就是本地的静态文件,那么也显示自定义访问出错页面。
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { if (view == null) { return; // 极端情况下可能出现,加个保险 } boolean isMainFrame = view.getUrl() != null && view.getUrl().equals(failingUrl); String url = view.getUrl(); //这判断非常重要,避免打开一个页面时,显示出来了内容,然后有被重定向到一个无效地址 if (isMainFrame || url.equalsIgnoreCase(CustomWebView.CUSTOM_ERROR_PAGE)) {//或者加载本地时也发生错误 if (view instanceof CustomWebView) { ((CustomWebView) view).onErrorView(url); } } else { Log.d(TAG, "did not show error view! reload url:" view.getUrl()); } Log.d(TAG, "onReceivedError-> errorCode=" errorCode ",desc=" description ",failingUrl=" failingUrl); }
CustomWebChromeClient
在这里主要是实现视频全屏播放的逻辑,重写onShowCustomView()
和onHideCustomView()
方法
@Override public void onShowCustomView(View view, CustomViewCallback callback) { if (view == null) { return; } mActivity = AppUtils.getCurrActivity(view.getContext()); Activity activity = mActivity; if (activity == null || activity.isFinishing()) { LogUtils.e("must use activity context to show video view!"); return; } if (mCustomView != null && callback != null) { callback.onCustomViewHidden(); return; } try { view.setKeepScreenOn(true); } catch (SecurityException e) { LogUtils.d("WebView is not allowed to keep the screen on"); } mOriginalOrientation = activity.getRequestedOrientation(); FrameLayout decor = (FrameLayout) activity.getWindow().getDecorView(); mFullscreenContainer = new FrameLayout(activity.getApplicationContext());// mFullscreenContainer.setBackgroundColor(ContextCompat.getColor(activity.getApplicationContext(), android.R.color.black)); mCustomView = view; mFullscreenContainer.addView(mCustomView, COVER_SCREEN_PARAMS); decor.addView(mFullscreenContainer, COVER_SCREEN_PARAMS); activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); setFullscreen(true, true);// mCurrentView.setVisibility(View.GONE); if (view instanceof FrameLayout) { if (((FrameLayout) view).getFocusedChild() instanceof VideoView) { mVideoView = (VideoView) ((FrameLayout) view).getFocusedChild(); mVideoView.setOnErrorListener(new VideoCompletionListener()); mVideoView.setOnCompletionListener(new VideoCompletionListener()); } } mCustomViewCallback = callback; } @Override public void onHideCustomView() { if (mCustomView == null || mCustomViewCallback == null) { return; } LogUtils.d("onHideCustomView");// mCurrentView.setVisibility(View.VISIBLE); try { mCustomView.setKeepScreenOn(false); } catch (SecurityException e) { LogUtils.d("WebView is not allowed to keep the screen on"); } setFullscreen(false, false); Activity activity = mActivity; if (activity == null || activity.isFinishing()) { return; } FrameLayout decor = (FrameLayout) activity.getWindow().getDecorView(); if (decor != null) { decor.removeView(mFullscreenContainer); } if (API < Build.VERSION_CODES.KITKAT) { try { mCustomViewCallback.onCustomViewHidden(); } catch (Throwable ignored) { } } mFullscreenContainer = null; mCustomView = null; if (mVideoView != null) { mVideoView.setOnErrorListener(null); mVideoView.setOnCompletionListener(null); mVideoView = null; } activity.setRequestedOrientation(mOriginalOrientation); mActivity = null; }
CustomWebView
在CustomWebView中封装了生命周期方法,resume()
,pause()
,destroy()
这几个方法对应于Activity或者Fragment中的生命周期方法。同时还自定义访问出错页面。
有了以上三个基本控件的封装,那么使用起来就非常简单了。
代码语言:javascript复制 public class WebViewActivity extends AppCompatActivity { ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_web_view); ... mWebView.loadUrl(url); mWebView.setWebViewClient(new CustomWebViewClient()); mWebView.setWebChromeClient(new CustomWebChromeClient() { @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); mProgressBar.setProgress(newProgress); mProgressBar.setVisibility(newProgress >= 100 ? View.GONE : View.VISIBLE); } }); } ... @Override protected void onPause() { super.onPause(); mWebView.pause(); } @Override protected void onResume() { super.onResume(); mWebView.resume(); }}
其他注意
由于WebViewActivity
中有实现视频全屏播放的功能,那么在CustomWebView
中的初始化中需要对WebView
作以下配置
void settings() { WebSettings setting = getSettings(); setting.setJavaScriptEnabled(true); setting.setJavaScriptCanOpenWindowsAutomatically(true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { setting.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); } setting.setJavaScriptCanOpenWindowsAutomatically(true); setting.setAllowFileAccess(false);// setting.setAllowFileAccessFromFileURLs(true); setting.setBuiltInZoomControls(false); setting.setSupportZoom(false); setting.setDisplayZoomControls(false); setting.setSaveFormData(false); setting.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS); setting.setCacheMode(WebSettings.LOAD_NO_CACHE);// 不使用缓存 setting.setDefaultTextEncodingName("UTF-8"); setting.setRenderPriority(WebSettings.RenderPriority.HIGH); // 自适应屏幕 setting.setUseWideViewPort(true); setting.setLoadWithOverviewMode(true); setting.setDomStorageEnabled(true);//应该设置为true,否则视频的缩略图会被放大 }
以上配置都在实际开发中得到验证的,一般来说,这样配置是可以满足很多需求的。
另外如果需要显示全屏,那么需要在WebViewActivity
的manifiest
中的configChanges
属性配置如下:
<activity android:name=".WebViewActivity" android:label="Web" android:configChanges="orientation|screenSize|keyboardHidden" android:screenOrientation="portrait"/>
本文所有代码均在https://github.com/wecodexyz/CustomWebView