WebView使用的一些主要事项

内容摘要

  • shouldOverrideUrlLoading
  • 全屏播放视频
  • 无法播放视频问题
  • 重定向页面回退问题
  • 网页加载进度

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

转载于:https://www.jianshu.com/p/f351647180f4

猜你喜欢

转载自blog.csdn.net/weixin_33736832/article/details/91325566