WebView是一个比较常用的控件,功能上也比较单一,就是用来加载网页的,可以加载远程的网页,也可以加载本地网页文件。简单来说就相当于一个浏览器。这篇文章将对WebView使用相关内容做个简单记录。
官方文档:https://developer.android.google.cn/guide/webapps
WebView控件使用寄来挺简单,无非是在视图中添加该控件,然后在代码中设置控件相关属性并加载要加载的网页,最后需要开启相关权限。下面是个简单的示例:
activity_main.xml
文件:
<androidx.constraintlayout.widget.ConstraintLayout 省略若干... >
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
文件:
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.webkit.WebSettings;
import android.webkit.WebView;
public class MainActivity extends AppCompatActivity {
@SuppressLint("SetJavaScriptEnabled") // 忽略使能JS警告
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); //隐藏状态栏
setContentView(R.layout.activity_main);
WebView myWebView = findViewById(R.id.webview); // 获取WebView控件
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true); // 使加载的网页可以运行JS代码
myWebView.loadUrl("https://html5test.com/"); // 加载网页链接
// https://html5test.com/是一个测试Html功能兼容性的网站
// 你要是喜欢也可以使用https://www.baidu.com/等
}
}
AndroidManifest.xml
文件:
<manifest 省略若干... >
<uses-permission android:name="android.permission.INTERNET" />
<application
android:usesCleartextTraffic="true"
application>
manifest>
WebView使用基本上要涉及的东西都在上面演示中展示了,主要分为三个方面:
前面演示中配置加载的网页可以使用JS代码,通常来说现在的网页为了获得更好的效果或是更多的功能通常都会有JS代码,所以这个功能基本上都需要打开。需要注意的是这会降低安全性,具体使用时需要根据实际情况来设置。
上面的 WebSettings 除了可以用来设置是否启用JS外,还有非常多的功能可以设置,比如是否可以缩放,是否可以跨域,是否可以缓存等功能,这些都可能是在一定应用下比较重要的功能。详细的内容可以参考下面链接:
https://developer.android.google.cn/reference/kotlin/android/webkit/WebSettings
WebView使用上比较常用的需要考虑的就是处理网页导航。
在上面的演示中,如果网页中有别的链接,点击这些链接时默认会调用系统的浏览器来打开这些链接。很多时候我们可能比想要通过浏览器来打开,而是通过当前的WebView来打开这些链接,这就需要在代码中对WebView控件进行一些设置了:
import android.webkit.WebViewClient;
WebView myWebView = findViewById(R.id.webview);
myWebView.setWebViewClient(new WebViewClient()); // 就是这一行
当进行上面设置后WebView就像浏览器一样可以在页面间跳转,这写操作都会留下历史访问记录,我们可以使用WebView控件的 goBack()
和 goForward()
方法来向后或向前浏览历史记录。在Android中因为Android设备有返回键,所以通常将返回键和 goBack()
方法绑定使用,比如下面这样:
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.KeyEvent;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class MainActivity extends AppCompatActivity {
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView myWebView = findViewById(R.id.webview);
myWebView.setWebViewClient(new WebViewClient()); // 在当前WebView打开新链接
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
myWebView.loadUrl("https://blog.csdn.net/Naisu_kun");
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
WebView myWebView = findViewById(R.id.webview);
// 如果点击了返回键,并且myWebView当前可以返回,则执行其goBack()方法
if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
myWebView.goBack();
return true;
}
// 不满足前面条件则执行系统默认行为
return super.onKeyDown(keyCode, event);
}
}
上面的 WebViewClient
封装了WebView控件各个事件的回调,比如其中的 shouldOverrideUrlLoading
就是来处理url跳转相关事务。
加载本地网页最简单的就是以字符串形式加载:
String html = "Hello world
";
String encodedHtml = Base64.encodeToString(html.getBytes(), Base64.NO_PADDING);
myWebView.loadData(encodedHtml, "text/html", "base64");
或者下面形式:
String html = "Hello world
";
String baseUrl = "https://example.com/";
myWebView.loadDataWithBaseURL(baseUrl, html, "text/html", null, baseUrl);
当然上面方式通常只是简单的页面才用用,更多的是把网页文件打包到项目中,然后直接把文件加载到WebView中。比如我们可以在项目的 项目目录/app/src/main/
下建立 assets
目录,然后将网页文件都放在该目录下,然后在代码中使用下面式来加载:
myWebView.loadUrl("file:///android_asset/index.html");
// 上面的file:///android_asset/这个路径默认指的就是 项目目录/app/src/main/assets/
上面方式因为同源策略(跨域),默认情况下是不允许JS加载其它资源的,得进行设置:
// 设置在WebView内部是否允许通过file url加载的 Js代码读取其他的本地文件
// webSettings.setAllowFileAccessFromFileURLs(true);
// 设置WebView内部是否允许通过 file url 加载的 Javascript 可以访问其他的源(包括http、https等源)
webSettings.setAllowUniversalAccessFromFileURLs(true);
当然上面方式被认为是不安全的,现在官方推荐使用 WebViewAssetLoader
来加载本地网页。这种方式的原理是把WebView发出的所有请求进行拦截,然后使用 WebViewAssetLoader
来分辨处理是加载本地资源还是远程资源。对例程的代码进行调整演示:
package com.example.myapplication;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.webkit.WebViewAssetLoader;
import androidx.webkit.WebViewClientCompat;
import android.annotation.SuppressLint;
import android.net.Uri;
import android.os.Bundle;
import android.view.KeyEvent;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
public class MainActivity extends AppCompatActivity {
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView myWebView = findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
// WebViewClientCompat就等同于WebViewClient,封装了WebView控件各个事件的回调
// 重写该类的shouldInterceptRequest方法,该方法对发出的请求进行拦截处理
// 这里就将该拦截转给WebViewAssetLoader来处理
class LocalContentWebViewClient extends WebViewClientCompat {
private final WebViewAssetLoader mAssetLoader;
LocalContentWebViewClient(WebViewAssetLoader assetLoader) {
mAssetLoader = assetLoader;
}
@Override
@RequiresApi(21)
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
return mAssetLoader.shouldInterceptRequest(request.getUrl());
}
@Override
@SuppressWarnings("deprecation") // to support API < 21
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
return mAssetLoader.shouldInterceptRequest(Uri.parse(url));
}
}
// 创建WebViewAssetLoader对象
// 设置资源路径/assets/,即 项目目录/app/src/main/assets/
final WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder()
.addPathHandler("/assets/", new WebViewAssetLoader.AssetsPathHandler(this))
.build();
// 加载上面处理
myWebView.setWebViewClient(new LocalContentWebViewClient(assetLoader));
// 这里的https://appassets.androidplatform.net是个默认的路径
// 下面最终其实访问的是 项目目录/app/src/main/assets/index.html 文件
myWebView.loadUrl("https://appassets.androidplatform.net/assets/index.html");
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
WebView myWebView = findViewById(R.id.webview);
if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
myWebView.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
}
WebViewAssetLoader
也可以混合加载本地的和远程的资源,比如下面这样:
final WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder()
.setDomain("example.com") // 远程地址域名
.addPathHandler("/assets/", new WebViewAssetLoader.AssetsPathHandler(this))
.build();
上面的设置下 WebViewAssetLoader
会先从本地寻找资源进行加载,如果本地找不到就从远程加载。
WebViewAssetLoader
更多内容可以参考下面链接:
https://developer.android.google.cn/reference/kotlin/androidx/webkit/WebViewAssetLoader
Web和Native之间交互主要依赖WebView的 addJavascriptInterface
方法。参考下面演示:
class WebAppInterface {
@JavascriptInterface
public void webToNative(String str) {
System.out.println(str);
}
@JavascriptInterface
public String nativeToWeb() {
return "msg: native to web";
}
}
// 使用addJavascriptInterface方法将WebAppInterface对象传递给web的"native"对象
myWebView.addJavascriptInterface(new WebAppInterface(), "native");
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
head>
<body>
<button>传递信息给Nativebutton>
<br>
<button>从Native获取信息button>
<div>div>
<script>
btns = document.querySelectorAll('button');
btns[0].onclick = () => {
native.webToNative('msg: web to native');
};
btns[1].onclick = () => {
let msg = native.nativeToWeb();
document.querySelector('div').innerText = msg;
};
script>
body>
html>
另外在Java中也可以直接调用web中的JS方法,主要使用下面方式:
webView.loadUrl(“javascript:methodName(parameterValues)”)
上面的 methodName
指的是绑定在 window
对象上的方法,可以不传入参数或者传入参数,参数通常为数值或是字符串,传入字符串时需要加引号转义,比如下面例子:
// 传入数值
webView.loadUrl("javascript:console.log(233)");
// 传入字符串
String str = "Hello Naisu!";
webView.loadUrl("javascript:console.log('" + str + "')");
上面方式调用JS方法是没有返回值的,可以使用 evaluateJavascript
方式调用,这样可以接收返回值:
webView.evaluateJavascript("jsfunc()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
// value即返回值
}});
默认情况下Web中JS打印输出到控制台的信息( console.log()
)都会在Android Studio的Logcat窗口中显示。
我这里真机调试一直没问题,使用模拟器有时候就不行,不过一般也不会用模拟器来开啦。
默认情况下Android中页面尺寸改变或旋转时视图会重新绘制,在这里通常体现为屏幕旋转时WebView加载的页面恢复为了初始状态,这很多时候不符合我们预期的需求,所以需要稍加处理:
AndroidManifest.xml
文件中添加 android:configChanges="orientation|screenSize"
:
<manifest 省略若干... >
<application
activity>
application>
manifest>
MainActivity.java
文件:
// ...
public class MainActivity extends AppCompatActivity {
private WebView myWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
if (savedInstanceState == null) {
myWebView.loadUrl("...");
}
}
@Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
myWebView.saveState(outState); // 保存状态
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
myWebView.restoreState(savedInstanceState); // 恢复状态
}
}
WebView控件控件的使用比较简单,大多数会遇到的问题都是与安全相关的各种权限问题,基本上都只要打开相关的权限即可。更多内容可以参考官方文档。
虽然文章标题说了Native APP,不过我其实是准备用WebView来做Hybrid App的。