Android WebView使用简介

本文目录
1.加载不同url的webview的方式
2.对返回键的监听,实现网页后退
3.如何防止WebView内存泄漏
4.WebSetting-对WebView进行配置和管理
5.Android调用js
6.js调用Android
7.WebView的优化
源码见文末
一:加载WebView

1)加载一个网页
  webView.loadUrl("https://www.baidu.com/");2)加载app包中的本地html页面
  webView.loadUrl("file:///android_asset/hello.html");3)加载手机本地的html页面
webView.loadUrl("content://com.android.htmlfileprovider/sdcard/hello.html");

二:对返回键的监听,实现网页后退

  /**
     * 对返回键的监听,实现网页后退
     */
    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        mWebView?.let {
            if (keyCode == KeyEvent.KEYCODE_BACK && it.canGoBack()) {
                it.goBack()
                return true
            }
        }
        return super.onKeyDown(keyCode, event)
    }

三:防止WebView内存泄漏
(1)不要在xml里面定义WebView,而是在Activity使用代码构建,使用ApplicationContext。
(2)在Activity销毁的时候,先让WebView加载空内容,然后rootView移除WebView,再销毁WebView,最后置空。

    /**
     * 代码动态添加WebView
     * 在onCreate把webview添加到Framlayout中
     */
     
    private fun addWebView() {
        val params = LinearLayout.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        )
        mWebView = WebView(applicationContext)
        mWebView?.layoutParams = params
        //设置webview取消右边滚动条和水波纹效果
        mWebView?.overScrollMode = View.OVER_SCROLL_NEVER
        mWebView?.isVerticalScrollBarEnabled = false
        fl_web_view.addView(mWebView)
    }
    
     override fun onDestroy() {
        super.onDestroy()
        mWebView?.let {
            it.loadDataWithBaseURL(null, null, "text/html", "utf-8", null)
            //清除当前WebView访问的历史记录
            it.clearHistory()
            val parent = it.parent as ViewGroup
            //从父容器中移除WebView
            parent.removeView(it)
            it.destroy()
        }
    }

四:WebSetting-对WebView进行配置和管理

  /**
     * 初始化WebView
     */
    @SuppressLint("SetJavaScriptEnabled", "ObsoleteSdkInt", "JavascriptInterface")
    private fun initWebView() {
        if (TextUtils.isEmpty(url)) return
        mWebView?.let {
            it.loadUrl(url)
            //对webview进行配置和管理
            val webSettings = it.settings
            //设置与JS交互的权限
            webSettings.javaScriptEnabled = true
            //js调用Android对象
            it.addJavascriptInterface(JSInterface(), "AndroidObject")
            //设置允许JS弹窗
            webSettings.javaScriptCanOpenWindowsAutomatically = true
            //将图片调整到适合WebView的大小
            webSettings.useWideViewPort = true
            //缩放至屏幕大小
            webSettings.loadWithOverviewMode = true
            //设置编码格式
            webSettings.defaultTextEncodingName = "UTF-8"
            //启用地理定位
            webSettings.setGeolocationEnabled(true)
            //允许WebView使用File协议
            webSettings.allowFileAccess = true;
            //不保存密码
            webSettings.savePassword = false;
            //解决js跨域问题
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                webSettings.allowUniversalAccessFromFileURLs = true
            }
            //5.1以上默认禁止了https和http的混用,下面的设置是开启
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
            }
            //开启DOM storage API功能
            webSettings.domStorageEnabled = true
            //开启database storage API功能
            webSettings.databaseEnabled = true
            //开启Application Caches功能
            webSettings.setAppCacheEnabled(true)
            //设置Application Caches缓存目录,把内部私有缓存目录'/data/data/包名/cache/'作为WebView的AppCache的存储路径
            val cachePath = BaseApplication.mContext.cacheDir.path
            webSettings.setAppCachePath(cachePath)
            webSettings.setAppCacheMaxSize((10 * 1024 * 1024).toLong())
            //设置缓存的模式
            webSettings.cacheMode = getCacheMode()
            it.webViewClient = MyWebViewClient()
            it.webChromeClient = MyWebChromeClient()
        }

    }

五:Android调用js
(1)通过WebView的loadUrl
注意:该方式必须在webview加载完毕之后才能调用,也就是webviewClient的onPageFinished()方法回调之后,而且该方法的执行 会刷新界面,效率较低。

js代码:
function callJs(){
    alert("Android 调用了 js代码)
}
webView?.loadUrl("javascript:callJs()")

(2)通过webview的evaluateJavaScript
注意:比起第一种方法,效率更高,但是要在4.4之后才能使用

webView?.evaluateJavascript("javascript:callJs()") { 
// 这里直接拿到的是js代码的返回值
 toast(it) 
 }

六:js调用Android

1.通过webview的addJavaScriptInterface进行对象映射,缺点:4.2之前,存在漏洞。
解决方案:
对于Android 4.2以前,需要采用拦截prompt()的方式进行漏洞修复
对于Android 4.2以后,则只需要对被调用的函数以 @JavascriptInterface进行注解
2. shouldOverrideUrlLoading,不存在漏洞,需要提前定义好协议。如果需要返回值给js,需要手动调用js方法。JS不需要返回值的时候,可以采用这个方法。
3. onJsPrompt,没有漏洞,需要定义协议,可以返回值。
建议统一使用第三种方法。

js:
function clickPrompt(){
                var result=prompt("js://result?arg1=111&arg2=222");
                alert("demo " + result);
             }
  //js alert
        override fun onJsAlert(
            view: WebView?,
            url: String?,
            message: String?,
            result: JsResult?
        ): Boolean {
            AlertDialog.Builder(this@BaseWebViewActivity)
                .setTitle("JsAlert")
                .setMessage(message)
                .setPositiveButton("OK") { _, _ -> result?.confirm() }
                .setCancelable(false)
                .show()
            return true
        }


        override fun onJsPrompt(
            view: WebView?,
            url: String?,
            message: String?,
            defaultValue: String?,
            result: JsPromptResult?
        ): Boolean {
            //先判断页面是否关闭
            toast(message.toString())
            val uri = Uri.parse(message)
            if (uri.scheme == "js") {
                // 将需要返回的值通过该方式返回
                result?.confirm("js调用了Android的方法成功啦啦啦啦啦")
                return true
            }
            return super.onJsPrompt(view, url, message, defaultValue, result)
        }

七:WebView的优化
1.h5的缓存,资源预加载,资源拦截
资源预加载
预加载webview对象,首次初始化WebView会比第二次慢很多的原因:初始化后,即使webview已经释放,但是WebView的一些共享的对象依然是存在的,我们可以在Application里面提前初始化一个Webview的对象,然后可以直接loadurl加载资源
2.资源拦截:
可以将更新频率低的一些资源静态文件放在本地,拦截h5的资源网络请求并进行检测,如果检测到,就直接拿本地的资源进行替换即可
shouldInterceptRequest
3.Android9.0,已经禁止了webview使用http,怎么解决?
在manifest的Application标签下面使用:android:usesCleartextTraffic=“true”

最后:

package com.zx.base.webview

import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.net.Uri
import android.net.http.SslError
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.view.KeyEvent
import android.view.View
import android.view.ViewGroup
import android.webkit.*
import android.widget.LinearLayout
import androidx.appcompat.app.AlertDialog
import com.zx.base.BaseApplication
import com.zx.base.BaseToolBarActivity
import com.zx.base.R
import com.zx.common.AppScheme
import com.zx.ext.toast
import kotlinx.android.synthetic.main.activity_base_web_view.*
import org.json.JSONException
import org.json.JSONObject
import java.io.InputStream

abstract class BaseWebViewActivity : BaseToolBarActivity() {
    private var mWebView: WebView? = null
    private lateinit var url: String
    private val APP_CACHE_DIRNAME = "app_cache_dirname"


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //解决webview截长屏不全的问题
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            try {
                WebView.enableSlowWholeDocumentDraw()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        setContentView(R.layout.activity_base_web_view)
        addWebView()
        url = initUrl()
        initWebView()
    }


    /**
     * 代码动态添加WebView
     */
    private fun addWebView() {
        val params = LinearLayout.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        )
        mWebView = WebView(applicationContext)
        mWebView?.layoutParams = params
        //设置webview取消右边滚动条和水波纹效果
        mWebView?.overScrollMode = View.OVER_SCROLL_NEVER
        mWebView?.isVerticalScrollBarEnabled = false
        fl_web_view.addView(mWebView)
    }

    abstract fun initUrl(): String

    /**
     * 初始化WebView
     */
    @SuppressLint("SetJavaScriptEnabled", "ObsoleteSdkInt", "JavascriptInterface")
    private fun initWebView() {
        if (TextUtils.isEmpty(url)) return
        mWebView?.let {
            it.loadUrl(url)
            //对webview进行配置和管理
            val webSettings = it.settings
            //设置与JS交互的权限
            webSettings.javaScriptEnabled = true
            //js调用Android对象
            it.addJavascriptInterface(JSInterface(), "AndroidObject")
            //设置允许JS弹窗
            webSettings.javaScriptCanOpenWindowsAutomatically = true
            //将图片调整到适合WebView的大小
            webSettings.useWideViewPort = true
            //缩放至屏幕大小
            webSettings.loadWithOverviewMode = true
            //设置编码格式
            webSettings.defaultTextEncodingName = "UTF-8"
            //启用地理定位
            webSettings.setGeolocationEnabled(true)
            //允许WebView使用File协议
            webSettings.allowFileAccess = true;
            //不保存密码
            webSettings.savePassword = false;
            //解决js跨域问题
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                webSettings.allowUniversalAccessFromFileURLs = true
            }
            //5.1以上默认禁止了https和http的混用,下面的设置是开启
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
            }
            //开启DOM storage API功能
            webSettings.domStorageEnabled = true
            //开启database storage API功能
            webSettings.databaseEnabled = true
            //开启Application Caches功能
            webSettings.setAppCacheEnabled(true)
            //设置Application Caches缓存目录,把内部私有缓存目录'/data/data/包名/cache/'作为WebView的AppCache的存储路径
            val cachePath = BaseApplication.mContext.cacheDir.path
            webSettings.setAppCachePath(cachePath)
            webSettings.setAppCacheMaxSize((10 * 1024 * 1024).toLong())
            //设置缓存的模式
            webSettings.cacheMode = getCacheMode()
            it.webViewClient = MyWebViewClient()
            it.webChromeClient = MyWebChromeClient()
        }

    }


    /**
     * 页面刷新
     */
    fun reload() {
        if (mWebView != null && !TextUtils.isEmpty(url)) {
            mWebView?.loadUrl(url)
        }
    }

    /**
     * 获取webview缓存的模式
    LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
    LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取据。
    LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
    LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或no-cache,都使用缓存中的数据。
     */
    private fun getCacheMode(): Int {
        return WebSettings.LOAD_DEFAULT
    }

    /**
     * 销毁Webview
     * 在关闭了Activity时,如果Webview的音乐或视频,还在播放。就必须销毁Webview.
     * 注意:webview调用destory时,webview仍绑定在Activity上
     * 这是由于自定义webview构建时传入了该Activity的context对象,因此需要先从父容器中移除webview,然后再销毁webview:
     */
    override fun onDestroy() {
        super.onDestroy()
        mWebView?.let {
            it.loadDataWithBaseURL(null, null, "text/html", "utf-8", null)
            //清除当前WebView访问的历史记录
            it.clearHistory()
            val parent = it.parent as ViewGroup
            //从父容器中移除WebView
            parent.removeView(it)
            it.destroy()
        }
    }

    /**
     * 对返回键的监听,实现网页后退
     */
    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        mWebView?.let {
            if (keyCode == KeyEvent.KEYCODE_BACK && it.canGoBack()) {
                it.goBack()
                return true
            }
        }
        return super.onKeyDown(keyCode, event)
    }

    /**
     * WebViewClient
     * 作用:处理各种通知,请求事件,主要有:网页开始加载,加载结束,加载错误(如404),处理https请求
     */
    inner class MyWebViewClient : WebViewClient() {
        override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
            super.onPageStarted(view, url, favicon)
            //页面开始的时候,设置不加载图片
            view?.settings?.blockNetworkImage = true
        }

        //页面加载结束
        override fun onPageFinished(view: WebView?, url: String?) {
            super.onPageFinished(view, url)
            //页面加载结束,开始加载图片
            view?.settings?.blockNetworkImage = false
            val automatically = view?.settings?.loadsImagesAutomatically
            automatically?.let {
                if (!it) {
                    //设置wenView加载图片资源
                    view.settings.blockNetworkImage = false
                    view.settings.loadsImagesAutomatically = true
                }
            }
        }

        //只要加载html、js、css资源,会回调这个方法
        override fun onLoadResource(view: WebView?, url: String?) {
            super.onLoadResource(view, url)
        }

        //加载页面的服务器出现错误时(如404)调用。
        override fun onReceivedError(
            view: WebView,
            request: WebResourceRequest,
            error: WebResourceError
        ) {
            toast("加载错误")
//            when (error.errorCode) {
//                // 404 -> view.loadUrl("file:///android_assets/error_handle.html")
//            }
        }

        //webView默认是不处理https请求的,页面显示空白,需要进行如下设置
        override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
            handler.proceed()  // 接受所有网站的证书
            //handler.cancel(); // Android默认的处理方式
            //handleMessage(Message msg); // 进行其他处理
        }

        override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
            if (TextUtils.isEmpty(url)) return true
            //JS调用Android,对协议进行处理
            AppScheme.handleUrl(this@BaseWebViewActivity, url)
            return true
        }


        // 拦截资源 通常用于h5的首页页面,将常用的一些资源,放到本地
        override fun shouldInterceptRequest(
            view: WebView?,
            request: WebResourceRequest?
        ): WebResourceResponse? {
            if (request?.url.toString().contains("logo.gif")) {
                var inputStream: InputStream? = null
                inputStream = applicationContext.assets.open("images/test.png")
                return WebResourceResponse("image/png", "utf-8", inputStream)
            }
            return super.shouldInterceptRequest(view, request)
        }
    }

    private fun handleRequest(request: WebResourceRequest) {
        val uri = Uri.parse(request?.url.toString())
        // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
        //假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)
        if (TextUtils.equals(uri.scheme, "js")) {
            if (TextUtils.equals(uri.authority, "webview")) {
                toast(request?.url.toString())
                //获取参数
                val params = uri.queryParameterNames
                for (param in params) {
                    toast("key" + param + "value" + uri.getQueryParameter(param))
                }
            }
        }
    }

    /**
     * WebChromeClient
     * 可以得到网页加载的进度,网页的标题,网页的icon,js的一些弹框,
     */
    inner class MyWebChromeClient : WebChromeClient() {
        //网页加载的进度
        override fun onProgressChanged(view: WebView, progress: Int) {
            progress_bar.visibility = View.VISIBLE
            progress_bar.progress = progress
            if (progress >= 90) {
                progress_bar.visibility = View.GONE
            }
        }

        //网页的标题
        override fun onReceivedTitle(view: WebView, title: String) {
            super.onReceivedTitle(view, title)
            setTitle(title)
        }

        //js alert
        override fun onJsAlert(
            view: WebView?,
            url: String?,
            message: String?,
            result: JsResult?
        ): Boolean {
            AlertDialog.Builder(this@BaseWebViewActivity)
                .setTitle("JsAlert")
                .setMessage(message)
                .setPositiveButton("OK") { _, _ -> result?.confirm() }
                .setCancelable(false)
                .show()
            return true
        }


        override fun onJsPrompt(
            view: WebView?,
            url: String?,
            message: String?,
            defaultValue: String?,
            result: JsPromptResult?
        ): Boolean {
            //先判断页面是否关闭
            toast(message.toString())
            val uri = Uri.parse(message)
            if (uri.scheme == "js") {
                // 将需要返回的值通过该方式返回
                result?.confirm("js调用了Android的方法成功啦啦啦啦啦")
                return true
            }
            return super.onJsPrompt(view, url, message, defaultValue, result)
        }

    }

    inner class JSInterface {
        /**
         *
         * Js调用Android的方法
         */
        @JavascriptInterface
        fun hello(msg: String) {
            toast(msg)
        }

        @JavascriptInterface
        fun set(json: String) {
            try {
                // 显示分享按钮
                val obj = JSONObject(json)
                var url = obj.optString("url")
                val title = obj.optString("title")
            } catch (e: JSONException) {
            }

        }
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值