Android WebView开发全攻略

WebView是一个基于WebKit引擎、展现Web页面的控件,Android的WebView在低版本和高版本采用了不同的WebKit版本内核

基本使用

WebView的最简单的使用方式即是直接显示网页内容,首先别忘了添加网络权限


<uses-permission android:name="android.permission.INTERNET" />
  <WebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
   webview.loadUrl("http://www.baidu.com")
        webview.webViewClient = object : WebViewClient() {
            override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
                //在本WebView中直接显示网页
                view.loadUrl(url)
                return true
            }
        }

WebView常用方法

状态

webview.onResume() //激活WebView为活跃状态,能正常执行网页的响应
 
webview.onPause() //当页面被失去焦点被切换到后台不可见状态,执行

webview.pauseTimers() //当应用程序(存在webview)被切换到后台时,这个方法针对的是全局的webview,它会暂停所有webview的布局显示、解析、延时,从而降低CPU功耗

webview.resumeTimers() //恢复pauseTimers状态

//销毁webview,先从父容器移除,再销毁
father.removeView(webview)
webview.destroy() 

前进或后退网页

//是否可以后退
webview.canGoBack()
//后退网页
webview.goBack()

//是否可以前进
webview.canGoForward()
//前进网页
webview.goForward()

//以当前的index为起始点前进或者后退到历史记录中指定的steps
//如果steps为负数则为后退,正数则为前进
webview.goBackOrForward(steps)

常见用法:在Activity中处理该Back键事件

  override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        if ((keyCode == KEYCODE_BACK) && webview.canGoBack()) {
            webview.goBack()
            return true
        }
        return super.onKeyDown(keyCode, event)
    }

清除缓存

//由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序.
webview.clearCache(true)

//清除当前webview访问的历史记录
webview.clearHistory()

//这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据
webview.clearFormData()

WebSettings类
作用:对WebView进行配置和管理
下面是一些常规设置

 val webSettings: WebSettings = webview.settings
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { //解决网页中图片显示不出问题
            webSettings.mixedContentMode = WebSettings.LOAD_DEFAULT
        }
        webSettings.javaScriptEnabled = true //支持JS
        webSettings.domStorageEnabled = true //支持DOM Storage
        webSettings.defaultTextEncodingName = "utf-8" //设置编码格式
        webSettings.pluginState = WebSettings.PluginState.ON// 支持插件
        webSettings.loadsImagesAutomatically = true //支持自动加载图片
        webSettings.setSupportZoom(true) //支持缩放,默认为true。是下面那个的前提。
        webSettings.builtInZoomControls = true //设置内置的缩放控件。若为false,则该WebView不可缩放
        webSettings.displayZoomControls = false //隐藏原生的缩放控件
        webSettings.databaseEnabled = true // 数据库存储API是否可用
        webSettings.layoutAlgorithm = WebSettings.LayoutAlgorithm.SINGLE_COLUMN //WebView底层布局算法
        webSettings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK //设置缓存,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。本地没有缓存时才从网络上获取
        webSettings.allowFileAccess = true //设置可以访问文件
        webSettings.javaScriptCanOpenWindowsAutomatically = true //支持通过JS打开新窗口

        //设置自适应屏幕,两者合用
        webSettings.useWideViewPort = true //将图片调整到适合WebView的大小
        webSettings.loadWithOverviewMode = true // 缩放至屏幕的大小

WebViewClient类
作用:处理各种通知、请求事件
(1)shouldOverrideUrlLoading()
作用:打开网页时,不调用系统浏览器进行打开,而是在本WebView中直接显示
(2)onPageStarted()
作用:开始载入页面时调用此方法,在这里我们可以设定一个loading的页面,告诉用户程序正在等待网络响应
(3)onPageFinished()
作用:在页面加载结束时调用,我们可以关闭loading 条,切换程序动作
(4)onLoadResource()
作用:在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次
(5)onReceivedSslError()
作用:处理https请求,WebView默认是不处理https请求的,页面显示空白

 webview.webViewClient = object : WebViewClient() {
            override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
                //使用WebView加载显示url
                view.loadUrl(url)
                return true
            }

            override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
                //设定加载开始的操作
            }

            override fun onPageFinished(view: WebView?, url: String?) {
                //设定加载结束的操作
            }

            override fun onLoadResource(view: WebView?, url: String?) {
                //设定加载资源的操作
            }

            override fun onReceivedError(
                view: WebView?,
                errorCode: Int,
                description: String?,
                failingUrl: String?
            ) {
                when (errorCode) {
                    HttpStatus.NOT_FOUND -> view!!.loadUrl("file:///android_assets/error_handle.html")
                }
            }

            override fun onReceivedSslError(
                view: WebView?,
                handler: SslErrorHandler?,
                error: SslError?
            ) {
                // 接受所有网站的证书,忽略SSL错误,执行访问网页
                handler?.proceed()
//                handler?.cancel() //表示挂起连接,为默认方式
//                handler?.handleMessage(msg!!) //可做其他处理
            }
        }

WebChromeClient类
作用:辅助 WebView 处理 Javascript 的对话框,网站图标,网站标题等等
(1)onProgressChanged()
作用:获得网页的加载进度并显示
(2)onReceivedTitle()
作用:获取Web页中的标题

      webview.webChromeClient = object : WebChromeClient() {
            override fun onProgressChanged(view: WebView?, newProgress: Int) {
                if (newProgress < 100)
                    progressbar.text = "${newProgress}%"
                else
                    progressbar.text = "100%"
            }

            override fun onReceivedTitle(view: WebView?, title: String?) {
                title_text.text = title
            }
        }

WebView与JS的交互

Android通过WebView调用 JS 代码

注意:JS代码调用一定要在 onPageFinished() 回调之后才能调用,否则不会调用

(1)通过WebView的loadUrl()

        // 先载入JS代码
        webview.loadUrl(url)
        // 调用javascript的callJS()方法,注意调用的JS方法名要对应上
        webview.loadUrl("javascript:callJS()")

(2)通过WebView的evaluateJavascript()
该方法比第一种方法效率更高、使用更简洁。因为该方法的执行不会使页面刷新,而第一种方法(loadUrl)会,该方法在Android 4.4 后才可使用,不过现在的安卓版本都在4.4以上了,所以这点不用在意了。如果需要返回值的话,请务必使用这个方法

  webview.evaluateJavascript("javascript:callJS()", object : ValueCallback<String> {
            override fun onReceiveValue(p0: String?) {
                //此处为 js 返回的结果
            }
        })

JS通过WebView调用 Android 代码
(1)通过WebView的addJavascriptInterface()进行对象映射

    //定义一个内部类
    inner class AndroidJSInterFace{
        // 定义JS需要调用的方法,被JS调用的方法必须加入@JavascriptInterface注解
        @JavascriptInterface
        fun hello(msg: String?) {
            runOnUiThread{
                title_text.text=msg
            }
        }
    }
        //AndroidJSInterFace类对象映射到js的test对象
        webview.addJavascriptInterface(AndroidJSInterFace(), "test") 

(2)在Android通过WebViewClient复写shouldOverrideUrlLoading()

     webview.webViewClient = object : WebViewClient() {
            override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
                // 根据协议的参数,判断是否是所需要的url
                // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
                //假如约定好的传入进来的url = "js://webview?arg1=111&arg2=222"
                val uri = Uri.parse(url)
                if (uri.scheme == "js" && uri.authority == "webview") {
                    // 执行JS所需要调用的逻辑
                    Log.d("TestLog", "js调用了Android的方法")
                    // 也可以在协议上带有参数并传递到Android上
                    val collection = uri.queryParameterNames
                }
                return true
            }
        }

如果JS想要得到Android方法的返回值,只能通过 WebView 的 loadUrl ()去执行 JS 方法把返回值传递回去,相关的代码如下:

 webview.loadUrl("javascript:returnResult($result)")

(3)通过 WebChromeClient 的onJsAlert()、onJsConfirm() 、onJsPrompt() 方法回调拦截JS对话框alert()、confirm()、prompt()消息

常用的拦截是:拦截 JS的prompt()方法
因为只有prompt()可以返回任意类型的值,操作最全面方便、更加灵活,而alert()没有返回值,confirm()只能返回两种状态(确定 / 取消)两个值

     webview.webChromeClient = object : WebChromeClient() {
            override fun onJsPrompt(
                view: WebView?,
                url: String?,
                message: String?,
                defaultValue: String?,
                result: JsPromptResult?
            ): Boolean { //输入框
                // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
                //假定传入进来的需要拦截的 url = "js://webview?arg1=111&arg2=222"
                val uri = Uri.parse(message)
                if (uri.scheme == "js" && uri.authority == "webview") {
                    // 执行JS所需要调用的逻辑
                    Log.d("WebviewTest", "js调用了Android的方法")
                    // 可以在协议上带有参数并传递到Android上
                    val collection = uri.queryParameterNames
                    //参数result:代表消息框的返回值(输入值)
                    result!!.confirm("js调用了Android的方法成功啦")
                    return true
                }
                return super.onJsPrompt(view, url, message, defaultValue, result)
            }

            override fun onJsAlert(
                view: WebView?,
                url: String?,
                message: String?,
                result: JsResult?
            ): Boolean { //警告框
                return super.onJsAlert(view, url, message, result)
            }

            override fun onJsConfirm(
                view: WebView?,
                url: String?,
                message: String?,
                result: JsResult?
            ): Boolean { //确认框
                return super.onJsConfirm(view, url, message, result)
            }
        }

WebView调起系统相机和相册

(1)初始化需要的变量

    private var uploadMessageAboveL: ValueCallback<Array<Uri>>? = null
    private var cameraFielPath: String? = null
    private var uploadMessage: ValueCallback<Uri>? = null
   webview.webChromeClient = object :WebChromeClient(){
            override fun onShowFileChooser(
                webView: WebView?,
                filePathCallback: ValueCallback<Array<Uri>>?,
                fileChooserParams: FileChooserParams?
            ): Boolean {
                uploadMessageAboveL = filePathCallback
                openImageChooserActivity()
                return true
            }
            // For Android < 3.0
            fun openFileChooser(valueCallback: ValueCallback<Uri>) {
                uploadMessage = valueCallback
                openImageChooserActivity()
            }
        }
    // 2.回调方法触发本地选择文件
    private fun openImageChooserActivity() {
        //拍照
        val imageStorageDir = File(
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
            "heart_image"
        )
        if (!imageStorageDir.exists()) {
            imageStorageDir.mkdirs()
        }
        cameraFielPath = imageStorageDir.toString() + File.separator + "IMG_" + System.currentTimeMillis()
            .toString() + ".jpg"
        val file = File(cameraFielPath)

        //需要显示应用的意图列表,这个list的顺序和选择菜单上的图标顺序是相关的,请注意。
        val cameraIntents: MutableList<Intent> = ArrayList()
        val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        val packageManager: PackageManager = packageManager
        //获取手机里所有注册相机接收意图的应用程序,放到意图列表里(无他相机,美颜相机等第三方相机)
        val listCam: List<ResolveInfo> = packageManager.queryIntentActivities(captureIntent, 0)
        for (res in listCam) {
            val packageName: String = res.activityInfo.packageName
            val i = Intent(captureIntent)
            i.component = ComponentName(res.activityInfo.packageName, res.activityInfo.name)
            i.setPackage(packageName)
            i.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file))
            cameraIntents.add(i)
        }

        //相册
        val i = Intent(Intent.ACTION_GET_CONTENT)
        i.action = Intent.ACTION_PICK
        i.type = "image/*"
        val chooserIntent = Intent.createChooser(i, "选择")
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toTypedArray())
        startActivityForResult(
            chooserIntent,
            101
        )
    }
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == 101) {
            val result = if (data == null || resultCode != Activity.RESULT_OK) null else data.data
            if (uploadMessageAboveL != null) {
                onActivityResultAboveL(requestCode, resultCode, data)
            } else if (uploadMessage != null) {
                uploadMessage?.onReceiveValue(result)
                uploadMessage = null
            }
            if (resultCode != RESULT_OK) {
                //这里uploadMessage跟uploadMessageAboveL在不同系统版本下分别持有了
                //WebView对象,在用户取消文件选择器的情况下,需给onReceiveValue传null返回值
                //否则WebView在未收到返回值的情况下,无法进行任何操作,文件选择器会失效
                if (uploadMessage != null) {
                    uploadMessage?.onReceiveValue(null)
                    uploadMessage = null
                } else if (uploadMessageAboveL != null) {
                    uploadMessageAboveL?.onReceiveValue(null)
                    uploadMessageAboveL = null
                }
            }
        }
    }
    // 4. 选择内容回调到Html页面
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    fun onActivityResultAboveL(requestCode: Int, resultCode: Int, intent: Intent?) {
        if (requestCode != 101 || uploadMessageAboveL == null)
            return
        var results = arrayOf<Uri>()
        var result: Uri? = null
        if (resultCode == Activity.RESULT_OK) {
            if (intent != null) {
                var dataString = intent.dataString
                var clipData = intent.clipData
                if (clipData != null) {
                    for (i in 0 until clipData.itemCount) {
                        var itemAt = clipData.getItemAt(i)
                        results[i] = itemAt.uri
                    }
                }
                if (dataString != null) {
                    results = arrayOf<Uri>(Uri.parse(dataString))
                }
                uploadMessageAboveL?.onReceiveValue(results)
                uploadMessageAboveL = null
            } else { //
                if (result == null && File(cameraFielPath).exists()) {
                    result = Uri.fromFile(File(cameraFielPath))
                }
                uploadMessageAboveL?.onReceiveValue(arrayOf(result!!))
                uploadMessageAboveL = null
            }
        }
    }

WebView响应下载点击事件

 webview.setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
            val uri = Uri.parse(url)
            val intent = Intent(Intent.ACTION_VIEW, uri)
            startActivity(intent)
        }

你可能感兴趣的:(笔记)