Hybrid学习记录
一、初始化
1.加载
//方式1. 加载一个网页:
webView.loadUrl("http://www.google.com/");
//方式2:加载apk包中的html页面
webView.loadUrl("file:///android_asset/test.html");
//方式3:加载手机本地的html页面
webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");
//方式4:加载某一段代码
val body = "示例:这里有个img标签,地址是相对路径[外链图片转存失败(img-8vlE8Da6-1562229182334)(https://mp.csdn.net/uploads/allimg/130923/1FP02V7-0.png)]";
//baseUrl表示基础的网页,data表示要加载的内容,mimeType表示加载网页的类型
//encoding表示编码格式,historyUrl表示可用历史记录
webView.loadDataWithBaseURL("http://www.jcodecraeer.com", body, "text/html", "utf-8",null);
防止域控制不严格漏洞
settings.setAllowFileAccessFromFileURLs(false);
settings.setAllowUniversalAccessFromFileURLs(false);
2.暂停
override fun onPause() {
//通知内核尝试停止所有处理,如动画和地理位置,但是不能停止Js
webView.onPause()
//全应用程序的webView都会被暂停,降低CPU功耗
webView.pauseTimers()
//若加载的 html 里有JS 在执行动画等操作,需要关闭Javascript支持
settings.setJavaScriptEnabled(false)
super.onPause()
}
override fun onResume() {
//重新**WebView
webView.onResume()
//恢复pauseTimers状态
webView.resumeTimers()
//若加载的 html 里有JS 在执行动画等操作,重新进入开启Javascript支持
// 禁止 file 协议加载 JavaScript
if (url.startsWith("file://")){
settings.setJavaScriptEnabled(false)
}else{
settings.setJavaScriptEnabled(true)
}
super.onResume()
}
3.关闭
在关闭Activity时,如果WebView的音乐或视频,还在播放,就必须销毁Webview
override fun onDestroy() {
super.onDestroy()
//从当前父容器移除WebView,防止其持有Activity的引用
constraintLayout.removeView(webView)
//销毁WebView
webView.destroy();
}
4.导航
用户点击back键会有两种情况,有前一页的页面,那么就后退,否则退出程序
override fun onBackPressed() {
//是否可以后退一页,是则后退,否则退出程序
if (webView.canGoBack()){
webView.goBack()
}else{
finish()
}
}
前进一页也要先判断是否有前一页
if (webView.canGoForward()){
webView.goForward()
}
5.设置缓存
val settings = webView.getSettings()
if (OKHttpUtil.INSTANCE.isNetworkConnected()) {
// 默认根据cache-control决定是否从网络上取数据
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
} else {
// 没网,离线加载,优先加载缓存(即使已经过期)
settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
}
根据需要可以清除缓存
//清除整个应用程序的缓存,传入true表示同时内存与磁盘,false表示仅清除内存
webView.clearCache(true);
//调用方法时,注意是当前,也就是说当前的页面记录并不会被删除。
//这样的话我从A打开B同时调用该方法,此时当前页面是A,清空的是A的之前的记录,A的自身记录还在。
//因此要在当前页面是B的时候调用该方法。
//清除当前webview访问的历史记录
webView.clearHistory();
//这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据
webView.clearFormData();
6.设置存储
//开启 DOM storage API 功能 较大存储空间,使用简单,默认值 false
//不安全,任何人都能读取到它
settings.setDomStorageEnabled(true);
7.不使用系统游览器
webView.webViewClient = object : WebViewClient(){
/**
* 拦截页面加载,返回true表示宿主app拦截并处理了该url
* 否则返回false由当前WebView处理
*/
override fun shouldOverrideUrlLoading(
view: WebView,
request: WebResourceRequest
): Boolean {
//重定向返回true
//view.loadUrl(url);
//return true;
return false
}
}
8.loading
webView.webViewClient = object : WebViewClient(){
//开始载入页面加载loading
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap) {
progressBar.visibility = View.VISIBLE
}
//结束载入页面关闭loading
override fun onPageFinished(view: WebView, url: String) {
progressBar.visibility = View.GONE
}
}
9.加载异常
webView.webViewClient = object : WebViewClient(){
//加载资源时出错
override fun onReceivedError(
view: WebView,
request: WebResourceRequest,
error: WebResourceError
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
when(error.errorCode){
}
}
}
//http出错
override fun onReceivedHttpError(
view: WebView,
request: WebResourceRequest,
errorResponse: WebResourceResponse
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
when(errorResponse.statusCode){
}
}
}
}
10.弹窗提示
// 设置允许JS弹窗
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webView.webChromeClient = object : WebChromeClient() {
//获取页面标题
override fun onReceivedTitle(view: WebView, title: String) {
pageTitle = title
}
//弹出javascript的警告框
override fun onJsAlert(
view: WebView,
url: String,
message: String,
result: JsResult
): Boolean {
AlertDialog.Builder([email protected])
.setTitle("安全提示")
.setMessage(message)
.setCancelable(false)
.setPositiveButton("OK", DialogInterface.OnClickListener { dialog, which ->
result.confirm()
})
.show()
return true
}
//弹出javascript的确认框
override fun onJsConfirm(
view: WebView,
url: String,
message: String,
result: JsResult
): Boolean {
var isDetermine = true
AlertDialog.Builder([email protected])
.setTitle("确认提示")
.setMessage(message)
.setCancelable(false)
.setPositiveButton("确定", DialogInterface.OnClickListener { dialog, which ->
result.confirm()
})
.setNegativeButton("取消", DialogInterface.OnClickListener { dialog, which ->
result.cancel()
isDetermine = false
})
.show()
return isDetermine
}
//弹出javascript输入框,点击确认返回输入框中的值,点击取消返回 null
override fun onJsPrompt(
view: WebView,
url: String,
message: String,
defaultValue: String,
result: JsPromptResult
): Boolean {
val editText = EditText([email protected])
editText.setText(defaultValue)
AlertDialog.Builder([email protected])
.setTitle("确认提示")
.setView(editText)
.setCancelable(false)
.setPositiveButton("确定", DialogInterface.OnClickListener { dialog, which ->
result.confirm(editText.text.toString())
})
.setNegativeButton("取消", DialogInterface.OnClickListener { dialog, which ->
result.cancel()
})
.show()
return true
}
}
11.自适应屏幕
会与缩放操作冲突,一般不开启自适应屏幕
//将图片调整到适合webview的大小,默认值 false
settings.setUseWideViewPort(true);
//缩放至屏幕的大小,默认值 false
settings.setLoadWithOverviewMode(true);
12.缩放操作
//支持缩放,默认为true。是下面那个的前提。
settings.setSupportZoom(true);
//设置内置的缩放控件。若为false,则该WebView不可缩放
settings.setBuiltInZoomControls(true);
//隐藏原生的缩放控件,默认为true
settings.setDisplayZoomControls(false);
13.资源加载
// 是否自动加载图片
settings.setLoadsImagesAutomatically(true);
// 禁止加载网络图片
settings.setBlockNetworkImage(false);
// 禁止加载所有网络资源
settings.setBlockNetworkLoads(false);
二、交互
1.Javascript
// 是否支持Javascript,默认值false,在onResume()和onPause()中开启和关闭
// 禁止 file 协议加载 JavaScript
if (url.startsWith("file://")){
settings.setJavaScriptEnabled(false)
}else{
settings.setJavaScriptEnabled(true)
}
1.1Android调用js的方法
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript("javascript:changename()", ValueCallback {
})
}else{
webView.loadUrl("javascript:changename()")
}
1.2.js调用Android方法
先定义一个要映射给js的对象,要让js调用的方法要加上 @JavascriptInterface
public class WebTest {
private Context context;
public WebTest(Context context){
this.context = context;
}
@JavascriptInterface
public void show(String s) {
Toast.makeText(context,s,Toast.LENGTH_SHORT).show();
}
}
然后设置给前端,将WebTest对象映射到前端的mAndroid对象
webview.addJavascriptInterface(new WebTest(this),"mAndroid");
移除已注入的Javascript对象
//下次加载或刷新页面时生效
public void removeJavascriptInterface("mAndroid");
2.下载文件
webView.setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
//调用http下载
}
3.长按
webView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
WebView.HitTestResult result = ((WebView) view).getHitTestResult();
if(result != null){
switch (result.getType()){
case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
String imgUrl = result.getExtra();
...
return true;
...
}
}
return false;
}
});
HitTestResult 的Type
WebView.HitTestResult.UNKNOWN_TYPE //未知类型
WebView.HitTestResult.PHONE_TYPE //电话类型
WebView.HitTestResult.EMAIL_TYPE //电子邮件类型
WebView.HitTestResult.GEO_TYPE //地图类型
WebView.HitTestResult.SRC_ANCHOR_TYPE //超链接类型
WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE //带有链接的图片类型
WebView.HitTestResult.IMAGE_TYPE //单纯的图片类型
WebView.HitTestResult.EDIT_TEXT_TYPE //选中的文字类型
三、混淆
keepattributes *Annotation*
-keepattributes *JavascriptInterface*
-keep public class org.mq.study.webview.DemoJavaScriptInterface{
public <methods>;
}
如果是内部类:
-keepattributes *Annotation*
-keepattributes *JavascriptInterface*
-keep public class org.mq.study.webview.webview.DemoJavaScriptInterface$InnerClass{
public <methods>;
}
四、优化
WebView首次启动需要一个初始化的动作,会占用70-700ms,这里可以如下优化:
1.全局WebView
客户端启动时便初始化一个全局WebView待用,当用户访问页面时便使用这个全局Web体View。
@SuppressLint("StaticFieldLeak")
object WebViewUtil {
private lateinit var webView:WebView
private lateinit var application: Application
private lateinit var contextWrapper:MutableContextWrapper
fun init(application:Application){
this.application = application
contextWrapper = MutableContextWrapper(application)
webView = WebView(contextWrapper)
}
/**
* 进入Activity时重用WebView
*/
fun reuse(activityContext:Activity):WebView{
contextWrapper.baseContext = activityContext
return webView
}
/**
* 退出Activity时重置防止内存泄漏
*/
fun reset(){
contextWrapper.baseContext = application
}
}
在Application中初始化WebViewUtil
WebViewUtil.init(this)
在Activity布局文件中创建一个layout指定WebView位置
<FrameLayout
android:id="@+id/layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/guideline2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
在Activity中调用WebView
webView = WebViewUtil.reuse(this)
layout.addView(webView)
退出Activity时 Context 替换为 Application
override fun onDestroy() {
super.onDestroy()
layout.removeView(webView)
WebViewUtil.reset()
}
如果复用WebView会有历史记录存在,有两种处理方式
方式一,在onPageFinished中清除厉害记录
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
webView.clearHistory()
}
}
方式二,非首次进入不加载url
if (webView.url == null){
webView.loadUrl(url)
}
2.并行加载
不使用WebView的网络功能,而是在WebView初始化时并行网络操作,然后将数据加载到WebView中
2.1启动子线程请求页面主资源,子线程中不断将网络数据读取到内存中,也就是网络流(NetStream)和内存流(MemStream)之间的转换;
2.2当WebView初始化完成的时候,提供一个中间层BridgeStream来连接WebView和数据流;
2.3当WebView读取数据的时候,中间层BridgeStream会先把内存的数据读取返回后,再继续读取网络的数据。
3.预加载
静态页面:
WebView初始化前,提前获取首页页面,如果是第一次,获取的是完整的html文件,如不是则获取增量包
动态页面:
将H5文件分为静态模板和将要发生变化的数据块,默认先加载静态模板,有变化只更新变化的部分
参考自腾讯的VasSonic框架 https://github.com/Tencent/VasSonic
上一篇: VasSonic之流式拦截