Android Native APP开发笔记:使用WebView控件加载网页

目的

WebView是一个比较常用的控件,功能上也比较单一,就是用来加载网页的,可以加载远程的网页,也可以加载本地网页文件。简单来说就相当于一个浏览器。这篇文章将对WebView使用相关内容做个简单记录。

官方文档:https://developer.android.google.cn/guide/webapps

基础使用

WebView控件使用寄来挺简单,无非是在视图中添加该控件,然后在代码中设置控件相关属性并加载要加载的网页,最后需要开启相关权限。下面是个简单的示例:

activity_main.xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 省略若干... >

    <!-- 添加WebView对象 -->
    <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 文件:

<?xml version="1.0" encoding="utf-8"?>
<manifest 省略若干... >

	<!-- 下面一行使APP可以访问网络内容 -->
    <uses-permission android:name="android.permission.INTERNET" />

    <application
    	<!-- 使用没有标题栏的主题来去除标题栏 -->
    	android:theme="@style/Theme.AppCompat.NoActionBar"
    	<!-- 下面一行使WebView可以访问基于HTTP协议的明文内容 -->
        android:usesCleartextTraffic="true"
        <!-- 省略若干... -->
    </application>

</manifest>

在这里插入图片描述

WebView使用基本上要涉及的东西都在上面演示中展示了,主要分为三个方面:

  • 在界面中放置WebView控件;
  • 在代码中设置WebView功能及需要加载的内容;
  • 在APP配置中启用相关权限;

前面演示中配置加载的网页可以使用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 = "<html><body><p>Hello world</p></body></html>";
String encodedHtml = Base64.encodeToString(html.getBytes(), Base64.NO_PADDING);
myWebView.loadData(encodedHtml, "text/html", "base64");

或者下面形式:

String html = "<html><body><p>Hello world</p></body></html>";
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之间交互

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>传递信息给Native</button>
    <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应用

默认情况下Web中JS打印输出到控制台的信息( console.log() )都会在Android Studio的Logcat窗口中显示。
在这里插入图片描述
我这里真机调试一直没问题,使用模拟器有时候就不行,不过一般也不会用模拟器来开啦。

处理页面重绘

默认情况下Android中页面尺寸改变或旋转时视图会重新绘制,在这里通常体现为屏幕旋转时WebView加载的页面恢复为了初始状态,这很多时候不符合我们预期的需求,所以需要稍加处理:

AndroidManifest.xml 文件中添加 android:configChanges="orientation|screenSize"

<?xml version="1.0" encoding="utf-8"?>
<manifest 省略若干... >
	<!-- 省略若干... -->
    <application
        <!-- 省略若干... -->
        <activity 
        	省略若干...
        	android:configChanges="orientation|screenSize" >
			<!-- 省略若干... -->
		</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的。

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Naisu Xu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值