内容摘要
- shouldOverrideUrlLoading
- 全屏播放视频
- 无法播放视频问题
- 重定向页面回退问题
- 网页加载进度
- 自定义WebView弹不出输入法问题
shouldOverrideUrlLoading
/**
* Give the host application a chance to take over the control when a new
* url is about to be loaded in the current WebView. If WebViewClient is not
* provided, by default WebView will ask Activity Manager to choose the
* proper handler for the url. If WebViewClient is provided, return true
* means the host application handles the url, while return false means the
* current WebView handles the url.
* This method is not called for requests using the POST "method".
*
* @param view The WebView that is initiating the callback.
* @param url The url to be loaded.
* @return True if the host application wants to leave the current WebView
* and handle the url itself, otherwise return false.
*/
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
当url即将加载到
WebView
时给当前应用一个机会去接管控制。如果没有提供WebViewClient
(也就是没有调用WebView#setWebViewClient
方法) ,默认请求ActivityManager
选择一个合适的url处理器(比如系统浏览器去加载这个url)。如果设置了WebViewClient
,返回true
代表当前应用已经处理了url,返回false
意味着当前WebView
url。该方法对POST请求无效。
全屏播放视频
inner class MWebChromeClient : WebChromeClient() {
private var mActivityConfig: ActivityConfig? = null
override fun onShowCustomView(view: View, requestedOrientation: Int, callback: CustomViewCallback) {
this.onShowCustomView(view, callback)
}
override fun onShowCustomView(view: View?, callback: CustomViewCallback?) {
super.onShowCustomView(view, callback)
saveActivityConfiguration()
activity?.apply {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
}
if (mVideoView != null) {
callback?.onCustomViewHidden()
}
mVideoView = view
val parent = mVideoView?.parent as? ViewGroup
parent?.removeView(mVideoView)
mVideoContainer.addView(mVideoView)
mVideoContainer.visibility = View.VISIBLE
}
override fun onHideCustomView() {
super.onHideCustomView()
restoreActivityConfiguration()
mVideoContainer.removeAllViews()
mVideoView = null
mVideoContainer.visibility = View.INVISIBLE
}
private fun restoreActivityConfiguration() {
if (mActivityConfig != null) {
activity?.apply {
requestedOrientation = mActivityConfig!!.orientation
window.decorView.systemUiVisibility = mActivityConfig!!.systemUiVisibility
window.setFlags(mActivityConfig!!.flags, -1)
}
}
}
private fun saveActivityConfiguration() {
mActivityConfig = ActivityConfig(activity!!)
}
}
private class ActivityConfig(activity: Activity) {
internal val orientation = activity.requestedOrientation
internal val systemUiVisibility = activity.window.decorView.systemUiVisibility
internal val flags = activity.window.attributes.flags
}
基本思路就是在WebView
上面盖一层铺满整个屏幕(或者其他需求,如小窗播放)ViewGroup,全面时切换到横屏模式,将播放器添加到mVideoContainer
.
上面的代码对横屏切换时的Activity一些状态进行了保存,以便退出全屏后恢复全屏之前的状态. 另外,屏幕切换Activity会重走生命周期, 需要在AndroidManifest.xml注册:
有些网页没有回调onShowCustomView如何处理, 希望有大神支招
无法播放视频问题
// WebSettings.java
/**
* Configures the WebView's behavior when a secure origin attempts to load a resource from an
* insecure origin.
*
* By default, apps that target {@link android.os.Build.VERSION_CODES#KITKAT} or below default
* to {@link #MIXED_CONTENT_ALWAYS_ALLOW}. Apps targeting
* {@link android.os.Build.VERSION_CODES#LOLLIPOP} default to {@link #MIXED_CONTENT_NEVER_ALLOW}.
*
* The preferred and most secure mode of operation for the WebView is
* {@link #MIXED_CONTENT_NEVER_ALLOW} and use of {@link #MIXED_CONTENT_ALWAYS_ALLOW} is
* strongly discouraged.
*
* @param mode The mixed content mode to use. One of {@link #MIXED_CONTENT_NEVER_ALLOW},
* {@link #MIXED_CONTENT_ALWAYS_ALLOW} or {@link #MIXED_CONTENT_COMPATIBILITY_MODE}.
*/
public abstract void setMixedContentMode(int mode);
/**
* Used with {@link #setMixedContentMode}
*
* In this mode, the WebView will allow a secure origin to load content from any other origin,
* even if that origin is insecure. This is the least secure mode of operation for the WebView,
* and where possible apps should not set this mode.
*/
public static final int MIXED_CONTENT_ALWAYS_ALLOW = 0;
/**
* Used with {@link #setMixedContentMode}
*
* In this mode, the WebView will not allow a secure origin to load content from an insecure
* origin. This is the preferred and most secure mode of operation for the WebView and apps are
* strongly advised to use this mode.
*/
public static final int MIXED_CONTENT_NEVER_ALLOW = 1;
/**
* Used with {@link #setMixedContentMode}
*
* In this mode, the WebView will attempt to be compatible with the approach of a modern web
* browser with regard to mixed content. Some insecure content may be allowed to be loaded by
* a secure origin and other types of content will be blocked. The types of content are allowed
* or blocked may change release to release and are not explicitly defined.
*
* This mode is intended to be used by apps that are not in control of the content that they
* render but desire to operate in a reasonably secure environment. For highest security, apps
* are recommended to use {@link #MIXED_CONTENT_NEVER_ALLOW}.
*/
public static final int MIXED_CONTENT_COMPATIBILITY_MODE = 2;
android 5.0开始修改了内容的混合模式, 5.0之前默认为MIXED_CONTENT_ALWAYS_ALLOW
, 5.0开始改为默认MIXED_CONTENT_NEVER_ALLOW
, 因此需要开启混合模式, 鉴于安全性优先考虑MIXED_CONTENT_COMPATIBILITY_MODE
兼容模式.
val settings = mVebView.settings
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
settings.mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE
}
重定向页面回退问题
WebView具有记录浏览历史的功能,允许用户回退/前进到页面, 以回退为例, 通常这样处理:
class WebActivity: Activity {
private lateinit var mWebView:WebView
override fun onBackPressed() {
if (mWebView.canGoBack()) {
mWebView.goBack();
} else {
super.onBackPressed()
}
}
}
对于重定向的页面这样是有问题的. 以A
重定向到B
为例:
loadUrl(A)
时会重定向到B
, 最终显示的是B页面,于是A
,B
都在WebView的历史中. 如果回退到A
那么又会进行重定向到B
, 如此反复导致无法退出的死循环中.
android-21开始,新增了以下方法
android.webkit.WebViewClient#shouldOverrideUrlLoading(android.webkit.WebView, android.webkit.WebResourceRequest)
可以重写shouldOverrideUrlLoading
方法,通过WebResourceRequest#isRedirect
来判断是否为重定向的链接, 那5.0之前的系统怎么办? 况且这种方法如果将非重定向的链接存储, 那么假如初始便是加载了一个重定向的url, 这种情况也会被忽略. 所以判断链接是否为重定向的方式应该行不通的.
我们知道一个重定向的请求会携带一个Referer
的请求头, 表示从那个页面重定向过来的, 保存Referer
的那个url到栈中可以解决重定向问题.
WebView#getOriginalUrl
Added in API level 3
String getOriginalUrl ()
Gets the original URL for the current page. This is not always the same as the URL
passed to WebViewClient.onPageStarted because although the load for that URL has
begun, the current page may not have changed. Also, there may have been redirects
resulting in a different URL to that originally requested.
Returns the URL that was originally requested for the current page
获得当前页面的原始URL. 原始URL并非总是和传递给
WebViewClient.onPageStarted
的URL一样, 因为即使url已经开始加载, 而当前页面却没有改变. 此外,可能有重定向
导致与最初请求的URL不同
这个就是我们要的原始链接. 下面是我的处理方式(非完整代码):
class WebActivity: Activity {
private lateinit var mWebView: WebView
private val urlStack = LinkedList()
override fun onBackPressed(){
if(!canGoBack()) {
super.onBackPressed()
}
}
private fun canGoBack(): Boolean {
mWebView.stopLoading()
synchronized(urlStack) {
if (!urlStack.isEmpty()) {
urlStack.pop()
}
if (!urlStack.isEmpty()) {
mWebView.loadUrl(urlStack.pop())
return true
}
return false
}
}
inner class MWebClient : WebViewClient() {
@Suppress("DEPRECATION")
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
return shouldOverrideUrlLoading(view, request.url.toString())
}
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
if (url.startsWith("http://") || url.startsWith("https://")) {
val originalUrl = view.originalUrl
if (originalUrl != null && originalUrl != urlStack.peek()) {
urlStack.push(originalUrl)
}
return false
} else {
parseUrl(url)
return true
}
}
}
private fun parseUrl(url: String) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
if (intent.resolveActivity(context!!.packageManager) != null) {
try {
startActivity(intent)
} catch (e: SecurityException) {
e.printStackTrace()
}
}
}
}
网页加载进度
重写WebChromeClient#onProgressChanged
即可
class MWebChromeClient : WebChromeClient() {
override fun onProgressChanged(view: WebView?, newProgress: Int) {
when (newProgress) {
100 -> {
mProgress.visibility = View.INVISIBLE
stopRefresh()
}
else -> {
mProgress.visibility = View.VISIBLE
mProgress.progress = newProgress
}
}
}
}
自定义WebView弹不出输入法问题
一般自定义View都会重写三个构造方法, 默认传入attrs=null, defStyleAttr=0
即可。但是像
class MyWebView @JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : WebView(context, attrs, defStyleAttr)
这样操作自定义WebView时却弹不出输入法,直接使用WebView却可以?问题出在构造方法的调用上:
public WebView(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.webViewStyle);
}
这里是有默认属性值的,具体为:
- @style/Widget.WebView
所以给出解决方法:
class MyWebView @JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyleAttr: Int = android.R.attr.webViewStyle
) : WebView(context, attrs, defStyleAttr)
而知道是焦点引起的输入法问题(如EditTextView, CheckBox), 通过代码设置focusable
和focusableInTouchMode
也可以。