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