在Android端翻开H5页面,最简略的办法是运用WebView
直接加载H5页面的链接。假如H5页面包括的图片、视频等资源较大或网络状况不好时,会呈现加载很久的状况。能够将H5页面的资源打包放到项目的assets文件夹中或许在装置App后下载到设备上,再经过WebView
翻开。
本文简略介绍一下如安在安卓端运行本地服务保管网页,再经过WebView
翻开已保管的网页。
测验用网页
首要创建一个测验用的html,放在assets文件夹中,并经过代码写入到storage,代码如下:
- 测验用html:
<!DOCTYPE html>
<html lang=zh-CN>
<head>
<meta charset=utf-8>
<title>test</title>
<script>
function jsCallAndroidWithParams(){
JsInteractive.jsCallAndroidWithParams('Js params to android')
}
function androidCallJsWithParams(arg){
document.getElementById("message").innerHTML += (arg);
}
</script>
</head>
<body>
<div style="position:relative;left:40px;top:100px">
<p id='message' style="font-size:24px;position:relative;top:20px">receive:</p>
<button type="button" style="width:280px;height:88px;font-size:24px;position:relative;left:20px"
onclick="jsCallAndroidWithParams()">
jsCallAndroidWithParams
</button>
</div>
<div style="position:relative;left:40px;top:120px">
<img src="test_icon.jpg" alt="test image">
</div>
<div style="position:relative;left:40px;top:140px">
<video controls>
<source src="test_video.mp4" type="video/mp4"/>
</video>
</div>
</body>
</html>
- 示例页面(写入storage)
class LocalServerExampleActivity : AppCompatActivity() {
private lateinit var binding: LayoutLocalServerExampleActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = LayoutLocalServerExampleActivityBinding.inflate(layoutInflater).also { setContentView(it.root) }
copyTestHtmlToStorage()
}
private fun copyTestHtmlToStorage() {
val testHtmlParentDir = File(if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()) {
File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), packageName)
} else {
File(filesDir, packageName)
}, "storageweb")
if (!testHtmlParentDir.exists()) {
testHtmlParentDir.mkdirs()
}
val testHtmlFile = File(testHtmlParentDir, "local_server_example_index.html")
if (!testHtmlFile.exists()) {
val inputStream = assets.open("assetsweb/local_server_example_index.html")
val fileOutputStream = FileOutputStream(testHtmlFile)
val buffer = ByteArray(1024)
try {
var length: Int
while (inputStream.read(buffer).also { length = it } != -1) {
fileOutputStream.write(buffer, 0, length)
}
inputStream.close()
fileOutputStream.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
val testIconFile = File(testHtmlParentDir, "test_icon.jpg")
if (!testIconFile.exists()) {
val inputStream = assets.open("assetsweb/test_icon.jpg")
val fileOutputStream = FileOutputStream(testIconFile)
val buffer = ByteArray(1024)
try {
var length: Int
while (inputStream.read(buffer).also { length = it } != -1) {
fileOutputStream.write(buffer, 0, length)
}
inputStream.close()
fileOutputStream.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
val testVideoFile = File(testHtmlParentDir, "test_video.mp4")
if (!testVideoFile.exists()) {
val inputStream = assets.open("assetsweb/test_video.mp4")
val fileOutputStream = FileOutputStream(testVideoFile)
val buffer = ByteArray(1024)
try {
var length: Int
while (inputStream.read(buffer).also { length = it } != -1) {
fileOutputStream.write(buffer, 0, length)
}
inputStream.close()
fileOutputStream.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
写入后能够在Device Explorer中查看,如下图:
完成本地服务并翻开网页
NanoHttpd和AndServer是现在比较流行的,能够在Android端完成本地服务的库,接下来分别介绍下如何运用这两个库。
NanoHttpd
增加依靠
在项目app module的build.gradle中的dependencies中增加依靠:
dependencies {
implementation("org.nanohttpd:nanohttpd:2.3.1")
}
配置并发动服务器
自定义NanoHttpdServer
类承继NaoHTTPD
类定义端口号,并重写serve()
办法,示例代码如下:
-
NanoHttpdServer
类
class NanoHttpdServer(private val context: Context) : NanoHTTPD(8080) {
var openFromStorage = false
override fun serve(session: IHTTPSession?): Response {
var mimeType = "*/*"
session?.run {
try {
// 依据链接获取 mimeType
mimeType = URLConnection.getFileNameMap().getContentTypeFor(session.uri)
} catch (e: Exception) {
e.printStackTrace()
}
}
return try {
val uri = session?.uri
if (uri == null) {
super.serve(session)
} else {
if (openFromStorage) {
// 翻开存储空间内的H5资源
newChunkedResponse(Response.Status.OK, mimeType, FileInputStream(File(if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()) {
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), context.packageName)
} else {
File(context.filesDir, context.packageName)
}, uri)))
} else {
// 翻开assets下的H5资源
newChunkedResponse(Response.Status.OK, mimeType, context.assets.open(uri.substring(1)))
}
}
} catch (e: Exception) {
e.printStackTrace()
super.serve(session)
}
}
}
- 示例页面(重复部分省掉)
class LocalServerExampleActivity : AppCompatActivity() {
private lateinit var binding: LayoutLocalServerExampleActivityBinding
private var mainWebView: WebView? = null
private var nanoHttpdServer: NanoHttpdServer? = null
private val jsInteractive: JsInteractive = object : JsInteractive {
@JavascriptInterface
override fun jsCallAndroid() {
}
@JavascriptInterface
override fun jsCallAndroidWithParams(params: String) {
val message = "receive jsCallAndroidWithParams params:$params"
showSnakeBar(message)
}
@JavascriptInterface
override fun getPersonJsonArray() {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = LayoutLocalServerExampleActivityBinding.inflate(layoutInflater).also { setContentView(it.root) }
val insetsController = WindowCompat.getInsetsController(window, window.decorView)
insetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
insetsController.hide(WindowInsetsCompat.Type.systemBars())
mainWebView = WebView(this).apply {
layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
initWebViewSetting(this)
binding.webViewContainer.addView(this)
}
copyTestHtmlToStorage()
binding.btnOpenNanohttpd.setOnClickListener {
(nanoHttpdServer ?: NanoHttpdServer(this)).let {
nanoHttpdServer = it
if (!it.isAlive) {
it.start(NanoHTTPD.SOCKET_READ_TIMEOUT, true)
}
}
}
binding.btnCloseNanohttpd.setOnClickListener {
nanoHttpdServer?.run {
if (isAlive) {
closeAllConnections()
mainWebView?.run {
clearHistory()
loadDataWithBaseURL(null, "", "text/html", "utf-8", null)
}
}
}
}
binding.btnOpenAssetsWebsite.setOnClickListener {
if (nanoHttpdServer?.isAlive == true) {
nanoHttpdServer?.openFromStorage = false
mainWebView?.loadUrl("http://localhost:8080/assetsweb/local_server_example_index.html")
}
}
binding.btnOpenStorageWebsite.setOnClickListener {
if (nanoHttpdServer?.isAlive == true) {
nanoHttpdServer?.openFromStorage = true
mainWebView?.loadUrl("http://localhost:8080/storageweb/local_server_example_index.html")
}
}
}
override fun onDestroy() {
destroyWebView(mainWebView)
nanoHttpdServer?.stop()
nanoHttpdServer = null
super.onDestroy()
}
@SuppressLint("SetJavaScriptEnabled")
private fun initWebViewSetting(webView: WebView?) {
webView?.run {
settings.cacheMode = WebSettings.LOAD_DEFAULT
settings.domStorageEnabled = true
settings.allowContentAccess = true
settings.allowFileAccess = true
settings.useWideViewPort = true
settings.loadWithOverviewMode = true
settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
settings.javaScriptEnabled = true
settings.javaScriptCanOpenWindowsAutomatically = true
settings.setSupportMultipleWindows(true)
addJavascriptInterface(jsInteractive, "JsInteractive")
webChromeClient = object : WebChromeClient() {
override fun onProgressChanged(view: WebView, newProgress: Int) {
super.onProgressChanged(view, newProgress)
binding.pbWebLoadProgress.run {
post { progress = newProgress }
if (newProgress >= 100 && visibility == View.VISIBLE) {
postDelayed({ visibility = View.GONE }, 500)
}
}
}
}
webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
binding.pbWebLoadProgress.run {
post { visibility = View.VISIBLE }
}
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
view?.loadUrl("javascript:androidCallJsWithParams("${"message from LocalServerExampleActivity"}")")
}
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
return super.shouldInterceptRequest(view, request)
}
}
WebView.setWebContentsDebuggingEnabled(true)
}
}
private fun showSnakeBar(message: String) {
runOnUiThread {
Snackbar.make(binding.root, message, Snackbar.LENGTH_SHORT).show()
}
}
private fun destroyWebView(webView: WebView?) {
webView?.run {
clearHistory()
loadDataWithBaseURL(null, "", "text/html", "utf-8", null)
binding.webViewContainer.removeView(this)
destroy()
}
}
......
}
效果如图:
assets | storage |
---|---|
AndServer
增加依靠
- 在项目下的build.gradle中增加如下代码:
buildscript {
dependencies {
classpath("com.yanzhenjie.andserver:plugin:2.1.12")
}
}
- 在app module下的build.gradle中增加代码,如下:
plugins {
id("kotlin-kapt")
id 'com.yanzhenjie.andserver'
}
dependencies {
implementation("com.yanzhenjie.andserver:api:2.1.12")
kapt("com.yanzhenjie.andserver:processor:2.1.12")
}
配置并发动服务器
自定义AndServerConfig
类完成WebConfig
接口,重写onConfig()
办法增加assets和storage对应的delegate。配置端口并发动服务。示例代码如下:
-
AndServerConfig
类
@Config
class AndServerConfig : WebConfig {
override fun onConfig(context: Context?, delegate: WebConfig.Delegate?) {
context?.run { delegate?.addWebsite(AssetsWebsite(this, "/assetsweb/")) }
context?.run {
delegate?.addWebsite(StorageWebsite(File(if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()) {
File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), packageName)
} else {
File(filesDir, packageName)
}, "storageweb").absolutePath))
}
}
}
- 测验页面(重复部分省掉)
class LocalServerExampleActivity : AppCompatActivity() {
private lateinit var binding: LayoutLocalServerExampleActivityBinding
private var mainWebView: WebView? = null
private var andServer: Server? = null
......
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......
binding.btnOpenAndserver.setOnClickListener {
(andServer ?: AndServer.webServer(this).port(8080).timeout(10, TimeUnit.SECONDS).build()).let {
andServer = it
if (!it.isRunning) {
it.startup()
}
}
}
binding.btnCloseAndserver.setOnClickListener {
andServer?.run {
if (isRunning) {
shutdown()
mainWebView?.run {
clearHistory()
loadDataWithBaseURL(null, "", "text/html", "utf-8", null)
}
}
}
}
binding.btnOpenAssetsWebsite.setOnClickListener {
if (andServer?.isRunning == true) {
mainWebView?.loadUrl("http://localhost:8080/local_server_example_index.html")
}
}
binding.btnOpenStorageWebsite.setOnClickListener {
if (andServer?.isRunning == true) {
mainWebView?.loadUrl("http://localhost:8080/local_server_example_index.html")
}
}
}
......
}
效果如图:
assets | storage |
---|---|
示例
演示代码已在示例Demo中增加。