WebView使用的一些注意事项

内容摘要

  • 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注册:

        <activity
            android:name=".activity.WebActivity"
            android:configChanges="keyboardHidden|screenSize|orientation"/>

有些网页没有回调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<String>()

    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);
    }

这里是有默认属性值的,具体为:

    <item name="webViewStyle">@style/Widget.WebView</item>

    <style name="Widget.WebView">
        <item name="focusable">true</item>
        <item name="focusableInTouchMode">true</item>
        <item name="scrollbars">horizontal|vertical</item>
    </style>

所以给出解决方法:

class MyWebView @JvmOverloads constructor(
        context: Context?,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = android.R.attr.webViewStyle
) : WebView(context, attrs, defStyleAttr) 

而知道是焦点引起的输入法问题(如EditTextView, CheckBox), 通过代码设置focusablefocusableInTouchMode也可以。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354