美文网首页
Android WebView加载本地缓存视频,首次播放下载到本

Android WebView加载本地缓存视频,首次播放下载到本

作者: beatzcs | 来源:发表于2019-07-23 11:44 被阅读0次

    视频播放流量费用是相当高昂的,所以在加载网页视频循环播放时,不做处理的话会花费很多的服务器流量费用。处理的策略是将网页的视频下载到本地,然后下次播放此视频的时候先检测本地是否有缓存当前视频,有的话就加载本地,没有就播放并下载。

    activity_main.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                    xmlns:tools="http://schemas.android.com/tools"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    tools:context="com.yusu.bigscreen.MainActivity">
    
        <WebView
                android:id="@+id/webView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>
    
    </RelativeLayout>
    

    加入网络和读写等权限:

        <uses-permission android:name="android.permission.INTERNET"/>
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
        <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
    

    读写权限动态申请:

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            applypermission(this)
        }
    
        fun applypermission(context: Activity) {
            if (Build.VERSION.SDK_INT >= 23) {
                var needapply = false
                for (i in allpermissions.indices) {
                    val chechpermission = ContextCompat.checkSelfPermission(
                        context.applicationContext,
                        allpermissions[i]
                    )
                    if (chechpermission != PackageManager.PERMISSION_GRANTED) {
                        needapply = true
                    }
                }
                if (needapply) {
                    Log.d("apply", "未授权,需申请")
                    ActivityCompat.requestPermissions(context, allpermissions, 1)
                } else {
                    Log.d("apply", "已授权,无需申请")
                    initWebview()
                }
            } else {
                initWebview()
            }
        }
    
        override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults)
            for (i in grantResults.indices) {
                if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                    //已授权
                    initWebview()
                } else {
                    Toast.makeText(this@MainActivity, "请开启授权", Toast.LENGTH_SHORT).show()
                }
            }
        }
    

    初始化WebView:

        //if(本地有)
        //{返回本地的流}
        //else(本地没有)
        //{则进行下载,返回网络流}
        fun initWebview() {
            var setting = webView.settings
            webView.webViewClient = object : WebViewClient() {
                override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
                    view.loadUrl(url)
                    return true
                }
    
                 // API 11 开始引入,API 21 弃用
                override fun shouldInterceptRequest(view: WebView?, url: String?): WebResourceResponse? {
                    if (url!!.contains(".mp4")) {
                        var miniType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(getFileExtensionFromUrl(url))
                        Log.d("miniType", miniType)
                        val file = File(Environment.getExternalStorageDirectory().path + "/video/" + encode(url, true))
                        if (file.exists()) {
                            Log.d("status2 == ", "文件已存在,加载缓存..." + file.absolutePath)
    
                            val header = HashMap<String, String>()
                            header["Access-Control-Allow-Origin"] = "*"
                            header["Access-Control-Allow-Headers"] = "Content-Type"
                            var resourceResponse =
                                WebResourceResponse(miniType, "", 200, "ok", header, FileInputStream(file))
                            return resourceResponse
                        } else {
                            Log.d("status2 == ", "文件未缓存,开始下载...")
    
                            Thread(object : Runnable {
                                override fun run() {
                                    createF(url)
                                }
                            }).start()
                        }
                    }
                    return super.shouldInterceptRequest(view, url)
                }
    
                 // API 21 开始引入
                override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
                    if (request?.url.toString().contains(".mp4")) {
                        var name = request?.url.toString()
                        var miniType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(getFileExtensionFromUrl(name))
                        Log.d("miniType", miniType)
                        val file = File(Environment.getExternalStorageDirectory().path + "/video/" + encode(name, true))
                        if (file.exists()) {
                            Log.d("status == ", "文件已存在,加载缓存..." + file.absolutePath)
    
                            val header = HashMap<String, String>()
                            header["Access-Control-Allow-Origin"] = "*"
                            header["Access-Control-Allow-Headers"] = "Content-Type"
                            var resourceResponse =
                                WebResourceResponse(miniType, "", 200, "ok", header, FileInputStream(file))
                            return resourceResponse
                        } else {
                            Log.d("status == ", "文件未缓存,开始下载...")
    
                            Thread(object : Runnable {
                                override fun run() {
                                    createF(name)
                                }
                            }).start()
                        }
                    }
                    return super.shouldInterceptRequest(view, request)
                }
            }
    
            setting.mediaPlaybackRequiresUserGesture = false
            setting.javaScriptEnabled = true
            setting.cacheMode = WebSettings.LOAD_DEFAULT
            //setting.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
            // 开启 DOM storage API 功能
            setting.domStorageEnabled = true
            //开启 database storage API 功能
            setting.databaseEnabled = true
    
            var cacheDirPath = filesDir.absolutePath + "cache/"
            //设置数据库缓存路径
            setting.databasePath = (cacheDirPath)
            //设置  Application Caches 缓存目录
            setting.setAppCachePath(cacheDirPath)
    
            //开启 Application Caches 功能
            setting.setAppCacheEnabled(true)
            setting.setAppCacheMaxSize(50 * 1024 * 1024)
    
            webView.loadUrl("网页链接")
        }
    

    shouldInterceptRequest 方法是用来监控所有的页面请求的,实现WebView拦截替换网络请求数据。
    当webview页面有资源请求的时候通知宿主应用,允许应用自己返回数据给webview。如果返回值是null,就正常加载返回的数据,否则就加载应用自己return的response给webview。注意,这个方法回调在子线程而不是UI线程,所以在操作私有数据或者view视图的时候要小心。

    相关方法

    • 获取文件的MimeType属性:
        fun getFileExtensionFromUrl(path: String): String {
            var url = path
            url = url.toLowerCase()
            if (!TextUtils.isEmpty(url)) {
                val fragment = url.lastIndexOf('#')
                if (fragment > 0) {
                    url = url.substring(0, fragment)
                }
                val query = url.lastIndexOf('?')
                if (query > 0) {
                    url = url.substring(0, query)
                }
                val filenamePos = url.lastIndexOf('/')
                val filename = if (0 <= filenamePos) url.substring(filenamePos + 1) else url
    
                // if the filename contains special characters, we don't
                // consider it valid for our matching purposes:
                if (!filename.isEmpty()) {
                    val dotPos = filename.lastIndexOf('.')
                    if (0 <= dotPos) {
                        return filename.substring(dotPos + 1)
                    }
                }
            }
            return ""
        }
    
    • 视频url使用MD5加密后作为存储的文件名称:
        fun encode(path: String, append: Boolean): String {
            try {
                val instance: MessageDigest = MessageDigest.getInstance("MD5")//获取md5加密对象
                val digest: ByteArray = instance.digest(path.toByteArray())//对字符串加密,返回字节数组
                var sb = StringBuffer()
                for (b in digest) {
                    var i: Int = b.toInt() and 0xff//获取低八位有效值
                    var hexString = Integer.toHexString(i)//将整数转化为16进制
                    if (hexString.length < 2) {
                        hexString = "0" + hexString//如果是一位的话,补0
                    }
                    sb.append(hexString)
                }
                val fileName = if (append) sb!!.toString() + ".mp4" else sb!!.toString()
                return fileName
            } catch (e: NoSuchAlgorithmException) {
                e.printStackTrace()
            }
            return ""
        }
    
    • 创建文件并写入:
        fun createF(path: String) {
            // 创建文件夹,在存储卡下
            val dirName = Environment.getExternalStorageDirectory().path + "/video"
            Log.d("dirName", dirName)
            val file = File(dirName)
            // 文件夹不存在时创建
            if (!file.exists()) {
                file.mkdir()
            }
    
            // 下载后的文件名
            val file1 = File(file, encode(path, false))
            downloadF(file, path, file1)
        }
    
        fun downloadF(dirFile: File, path: String, fileTemp: File) {
            try {
                var destFile = File(dirFile, encode(path, true))
                fileTemp.createNewFile()
                val url = URL(path)
                // 打开连接
                val conn = url.openConnection()
                // 打开输入流
                val ist = conn.getInputStream()
                // 创建字节流
                val bs = ByteArray(1024)
                var len = ist.read(bs)
                val os = FileOutputStream(fileTemp)
                // 写数据
                while (len != -1) {
                    os.write(bs, 0, len)
                    len = ist.read(bs)
                }
                Log.e("DOWNLOAD", "download-finish")
                destFile.createNewFile()
                var isRename = fileTemp.renameTo(destFile)
                if (isRename) {
                    fileTemp.delete()
                }
                // 完成后关闭流
                os.close()
                ist.close()
            } catch (e: Exception) {
                e.printStackTrace()
                Log.e("DOWNLOAD ERROR",  e.message)
            } finally {
    
            }
        }
    
    • 其他:
        override fun onResume() {
            super.onResume()
            webView.onResume()
        }
    
        override fun onPause() {
            super.onPause()
            webView.onPause()
        }
    

    相关文章

      网友评论

          本文标题:Android WebView加载本地缓存视频,首次播放下载到本

          本文链接:https://www.haomeiwen.com/subject/vhtolctx.html