Android网络应用之在WebView中构建Web应用程序

assets 文件夹下的html访问方式:"file:///android_asset/index.html"

如果要将Web应用程序(或只是网页)作为客户端应用程序的一部分提供,可以使用WebView进行操作。 WebView类是Android的View类的扩展,它允许您将网页显示为活动布局的一部分。 它不包括完全开发的Web浏览器的任何功能,例如导航控件或地址栏。 默认情况下,所有WebView都会显示一个网页。
使用WebView的常见情况是您希望在应用程序中提供可能需要更新的信息(例如最终用户协议或用户指南)。 在您的Android应用程序中,您可以创建一个包含WebView的活动,然后使用它来显示在线托管的文档。
WebView可以帮助的另一种情况是,如果您的应用程序向用户提供总是需要Internet连接来检索数据(如电子邮件)的数据。 在这种情况下,您可能会发现在您的Android应用程序中构建WebView更容易,该应用程序显示具有所有用户数据的网页,而不是执行网络请求,然后解析数据并将其呈现在Android布局中。 相反,您可以设计适合Android设备的网页,然后在Android应用程序中实现加载网页的WebView。
本文档介绍了如何开始使用WebView,以及如何处理页面导航,以及如何将Web页面中的JavaScript绑定到Android应用程序中的客户端代码

将WebView添加到您的应用程序

要将WebView添加到应用程序中,只需将<WebView>元素添加到活动布局中即可。 例如,这里是WebView填充屏幕的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<WebView  xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/webview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
/>

要在WebView中加载网页,请使用loadUrl()。 例如:

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.loadUrl("http://www.baidu.com");

然而,在此之前,您的应用程序必须能够访问Internet。 要获取Internet访问权限,请在清单文件中请求INTERNET权限。 例如:.

<manifest ... >
    <uses-permission android:name="android.permission.INTERNET" />
    ...
</manifest>

这就是显示网页的基本WebView所需要的。

在WebView中使用JavaScript

如果您计划在WebView中加载的网页使用JavaScript,则必须为WebView启用JavaScript。 启用JavaScript后,您还可以在应用程序代码和JavaScript代码之间创建接口。

启用JavaScript

默认情况下,WebView中的JavaScript被禁用。 您可以通过附加到WebView的WebSettings启用它。 您可以使用getSettings()检索WebSettings,然后使用setJavaScriptEnabled()启用JavaScript。

例如:

WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);

WebSettings可以访问您可能会发现有用的各种其他设置。 例如,如果您正在开发专门针对Android应用程序中的WebView设计的Web应用程序,则可以使用setUserAgentString()定义自定义用户代理字符串,然后在网页中查询自定义用户代理,以验证 请求您的网页的客户端实际上是您的Android应用程序。

将JavaScript代码绑定到Android代码

在开发专门针对Android应用程序中的WebView设计的Web应用程序时,您可以在JavaScript代码和客户端Android代码之间创建界面。 例如,您的JavaScript代码可以调用Android代码中的方法来显示对话框,而不是使用JavaScript的alert()函数。

要绑定JavaScript和Android代码之间的新界面,请调用addJavascriptInterface(),传递一个类实例以绑定到您的JavaScript和JavaScript可以调用以访问该类的接口名称。

例如,您可以在Android应用程序中包含以下类:

public class WebAppInterface {
    Context mContext;

    /** Instantiate the interface and set the context */
    WebAppInterface(Context c) {
        mContext = c;
    }

    /** Show a toast from the web page */
    @JavascriptInterface
    public void showToast(String toast) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
    }
}

注意:如果将targetSdkVersion设置为17或更高版本,则必须将@JavascriptInterface注释添加到您希望JavaScript可用的任何方法(该方法也必须是公共的)。 如果您不提供注释,则在Android 4.2或更高版本上运行时,您的网页无法访问该方法。
在此示例中,WebAppInterface类允许网页使用showToast()方法创建Toast消息。

您可以将此类绑定到使用addJavascriptInterface()在WebView中运行的JavaScript,并将其命名为Android。 例如:

WebView webView = (WebView) findViewById(R.id.webview);
webView.addJavascriptInterface(new WebAppInterface(this), "android");

这将为WebView中运行的JavaScript创建一个名为Android的界面。 此时,您的Web应用程序可以访问WebAppInterface类。 例如,以下是一些HTML和JavaScript,当用户单击按钮时,使用新界面创建吐司信息:

<input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" />

<script type="text/javascript">
    function showAndroidToast(toast) {
        android.showToast(toast);
    }
</script>

没有必要从JavaScript初始化Android界面。 WebView自动使您的网页可用。 所以,点击按钮,showAndroidToast()函数使用Android界面来调用WebAppInterface.showToast()方法。
注意:绑定到您的JavaScript的对象在另一个线程中运行,而不是在其构造的线程中运行。
注意:使用addJavascriptInterface()允许JavaScript控制您的Android应用程序。 这可能是非常有用的功能或危险的安全问题。 当WebView中的HTML不可信(例如,一部分或全部HTML由未知人员或进程提供)时,攻击者可以包括执行客户端代码的HTML,以及攻击者选择的任何代码。 因此,除非您写入WebView中显示的所有HTML和JavaScript,否则不应使用addJavascriptInterface()。 您也不应该允许用户导航到您自己的WebView内的其他网页(而不是允许用户的默认浏览器应用程序打开外部链接) - 默认情况下,用户的Web浏览器会打开所有URL链接,因此 只有当您按照以下部分的描述处理页面导航时,请小心)

处理页面导航

当用户从WebView中的网页单击链接时,默认行为是启动一个处理URL的应用程序。 通常,默认Web浏览器打开并加载目标URL。 但是,您可以覆盖WebView的此行为,因此在WebView中打开链接。 然后,您可以允许用户通过WebView维护的网页历史向后和向后浏览。
要打开用户点击的链接,只需使用setWebViewClient()为您的WebView提供一个WebViewClient。 例如:

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebViewClient(new WebViewClient());

就这样 现在 用户所有点击的链接都会在你的WebView

如果您想要更多地控制加载点击的链接的位置,请创建自己的WebViewClient来覆盖shouldOverrideUrlLoading()方法。 例如:

private class MyWebViewClient extends WebViewClient {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            if (Uri.parse(url).getHost().equals("www.example.com")) {
                // 这是我的网站,所以不要重写; 让我的WebView加载页面
                return false;
            }
            // 否则,该链接不是我的网站上的页面,因此启动另一个处理URL的活动
            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
            startActivity(intent);
            return true;
        }
    }

然后为WebView创建一个新的WebViewClient实例:

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebViewClient(new MyWebViewClient());

现在当用户单击链接时,系统调用shouldOverrideUrlLoading(),它检查URL主机是否与特定域匹配(如上定义)。 如果它匹配,那么该方法返回false,以便不覆盖URL加载(它允许WebView像往常一样加载URL)。 如果URL主机不匹配,则创建Intent以启动用于处理URL的默认活动(解析为用户的默认Web浏览器)。

浏览网页历史记录

当您的WebView覆盖URL加载时,它会自动累积访问的网页的历史记录。 您可以通过goBack()和goForward()向后和向前浏览历史记录。
例如,您的“活动”可以使用“设备返回”按钮向后导航:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    // Check if the key event was the Back button and if there's history
    if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
        myWebView.goBack();
        return true;
    }
    // If it wasn't the Back key or there's no web page history, bubble up to the default
    // system behavior (probably exit the activity)
    return super.onKeyDown(keyCode, event);
}

如果用户访问实际的网页历史记录,canGoBack()方法返回true。 同样,您可以使用canGoForward()来检查是否存在转发历史记录。 如果您不执行此检查,则一旦用户到达历史记录的末尾,goBack()或goForward()不执行任何操作。

在Android4.4上的WebView

Android 4.4(API级别19)引入了基于Chromium的新版本的WebView。 此更改将针对HTML5,CSS3和JavaScript的WebView性能和标准支持进行升级,以配合最新的Web浏览器。 任何使用WebView的应用程序将在Android 4.4及更高版本上运行时继承这些升级。
本文档描述了如果将targetSdkVersion设置为“19”或更高版本,您应该注意的WebView的其他更改。
注意:如果您的targetSdkVersion设置为“18”或更低版本,则WebView将以“怪异”模式运行,以避免在尽可能接近的情况下,尽可能避免一些行为变化,同时仍然为应用程序提供性能和Web标准升级。 不过,请注意,Android 4.4中根本不支持单列和窄栏列布局和默认缩放级别,并且还可能存在尚未识别的其他行为差异,因此请确保在Android 4.4或更高版本上测试您的应用程序,即使 您将targetSdkVersion设置为“18”或更低。
为了帮助您解决在Android 4.4中将应用程序迁移到WebView时遇到的任何问题,您可以通过调用setWebContentsDebuggingEnabled()来启用桌面上的Chrome的远程调试。 WebView中的新功能允许您在WebView中运行时检查和分析Web内容,脚本和网络活动。 有关详细信息,请参阅Android上的远程调试。

用户代理更改

如果您根据用户代理向WebView提供内容,则应注意用户代理字符串稍有更改,现在包括Chrome版本:
Mozilla/5.0 (Linux; Android 4.4; Nexus 4 Build/KRT16H) AppleWebKit/537.36
(KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36
如果您需要检索用户代理,但不需要为应用程序存储或不想实例化WebView,则应使用静态方法getDefaultUserAgent()。 但是,如果您打算覆盖WebView中的用户代理字符串,则可能需要使用getUserAgentString()。

多线程和线程阻塞

如果您从除应用程序的UI线程之外的任何线程调用WebView方法,可能会导致意外的结果。 例如,如果您的应用程序使用多个线程,则可以使用runOnUiThread()方法来确保您的代码在UI线程上执行:
还要确保你永远不会阻止UI线程。 某些应用程序出现此错误的情况是等待JavaScript回调。 例如,不要使用这样的代码:

webView.loadUrl("javascript:fn()");
while(result == null) {
  Thread.sleep(100);
}

您可以使用一种新方法evaluateJavascript()来异步运行JavaScript。

自定义URL处理

新的WebView在请求资源并解析使用自定义URL方案的链接时,会附加其他限制。 例如,如果您实现回调,例如shouldOverrideUrlLoading()或shouldInterceptRequest(),则WebView将仅调用有效的URL。

如果您使用自定义URL方案或基本URL,并注意到您的应用程序在这些回调中接收的呼叫较少或在Android 4.4上无法加载资源,请确保请求指定符合RFC 3986的有效URL。

例如,新的WebView可能不会像这样的链接调用你的shouldOverrideUrlLoading()方法:
<a href="showProfile">Show Profile</a>
用户点击此链接的结果可能有所不同:
如果通过使用无效或空的基本URL调用loadData()或loadDataWithBaseURL()来加载页面,那么您将不会收到该页面上此类链接的shouldOverrideUrlLoading()回调。
注意:当您使用loadDataWithBaseURL()并且基本URL无效或设置为null时,正在加载的内容中的所有链接必须是绝对的。
如果您通过调用loadUrl()或通过loadDataWithBaseURL()提供有效的基本URL来加载页面,那么您将收到该页面上此类型链接的shouldOverrideUrlLoading()回调,但您收到的URL将是绝对的,相对于 当前页面。 例如,您收到的URL将是“http://www.example.com/showProfile”而不是“showProfile”。
而不是如上所示在链接中使用简单的字符串,您可以使用自定义方案,如下所示:
<a href="example-app:showProfile">Show Profile</a>
然后,您可以在您的shouldOverrideUrlLoading()方法中处理此URL,如下所示:

private static final String APP_SCHEME = "example-app:";

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith(APP_SCHEME)) {
        urlData = URLDecoder.decode(url.substring(APP_SCHEME.length()), "UTF-8");
        respondToData(urlData);
        return true;
    }
    return false;
}

如果您不能修改HTML,那么您可以使用loadDataWithBaseURL()并设置由自定义方案和有效主机(例如“example-app:// <valid_host_name> /”)组成的基本URL。 例如:
webView.loadDataWithBaseURL("example-app://example.co.uk/", HTML_DATA,
null, "UTF-8", null);

有效的主机名应符合RFC 3986的要求,并且最后包括尾部的斜杠很重要,否则可能会丢弃来自加载的页面的任何请求。

视口更改

不再支持视口target-densitydpi
以前,WebView支持一个名为target-densitydpi的视口属性,以帮助网页指定其预期的屏幕密度。 此属性不再受支持,您应该迁移到使用图像和CSS的标准解决方案,如WebView中的Pixel-Perfect UI中所述。

视口缩小时放大

以前,如果将视口宽度设置为小于或等于“320”的值,则将其设置为“device-width”,如果将视口高度设置为小于或等于WebView高度的值 将设置为“device-height”。 但是,当在新的WebView中运行时,会粘贴宽度或高度值,WebView放大以填充屏幕宽度

不支持多个视口标签

以前,如果您在网页中包含多个视口标签,则WebView将合并所有标签的属性。 在新的WebView中,仅使用最后一个视口,并忽略所有其他视口。

默认缩放已弃用

getDefaultZoom()和setDefaultZoom()用于获取和设置页面上的初始缩放级别已不再受支持,您应该在网页中定义相应的视口。

注意:Android 4.4及更高版本不支持这些API。 即使您的targetSdkVersion设置为“18”或更低,这些API也不起作用。
有关如何在HTML中定义视口属性的信息,请在WebView中阅读Pixel-Perfect UI。

如果您无法在HTML中设置视口的宽度,那么您应该调用setUseWideViewPort()来确保页面被赋予更大的视口。 例如:
WebSettings settings = webView.getSettings();
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
造型变化
背景CSS简写覆盖了背景大小
Chrome和其他浏览器的行为已经有一段时间了,但是如果您还指定了背景样式,那么WebView也将覆盖背景大小的CSS设置。 例如,这里的大小将被重置为默认值:
.some-class {
background-size: contain;
background: url('images/image.png') no-repeat;
}
修复是简单地切换两个属性。 以这种方式行事一段时间,但是如果您还指定了背景样式,那么WebView还将覆盖背景大小的CSS设置。 例如,这里的大小将被重置为默认值:
.some-class {
background: url('images/image.png') no-repeat;
background-size: contain;
}
尺寸为CSS像素而不是屏幕像素
以前,诸如window.outerWidth和window.outerHeight之类的大小参数在实际的屏幕像素中返回一个值。在新的WebView中,这些返回基于CSS像素的值。

通常不好的做法是尝试计算尺寸元素或其他计算的物理尺寸(以像素为单位)。但是,如果禁用缩放,并将初始化设置为1.0,则可以使用window.devicePixelRatio获取缩放比例,然后将CSS像素值乘以该像素值。相反,您还可以创建一个JavaScript绑定,以从WebView自身查询像素大小。

有关更多信息,请参阅quirksmode.org。

不再支持NARROW_COLUMNS和SINGLE_COLUMN
新的WebView不支持WebSettings.LayoutAlgorithm的NARROW_COLUMNS值。

注意:Android 4.4及更高版本不支持这些API。即使您的targetSdkVersion设置为“18”或更低,这些API也不起作用。

您可以通过以下方式处理此更改:

更改应用程序的样式:
如果您可以控制页面上的HTML和CSS,可能会发现改变内容的设计可能是最可靠的方法。例如,对于您引用许可证的屏幕,您可能需要在< pre>标签之间进行换行,您可以使用以下样式:
<pre style="word-wrap: break-word; white-space: pre-wrap;">
如果您尚未定义页面的视口属性,这可能会特别有用。
使用新的TEXT_AUTOSIZING布局算法:
如果您使用窄列作为使广泛的桌面版网站在移动设备上更易于阅读的方式,并且您无法更改HTML内容,则新的TEXT_AUTOSIZING算法可能是NARROW_COLUMNS的替代方案。
此外,新的WebView中也不支持SINGLE_COLUMN值(以前已不推荐使用)。

处理JavaScript中的触摸事件
如果您的网页直接处理WebView中的触摸事件,请确保您还处理了touchcancel事件。触摸屏有几种情况会被调用,如果没有收到,可能会导致问题:

一个元素被触摸(所以touchstart和touchmove被调用),页面被滚动,导致一个触摸屏被抛出。
触摸一个元素(touchstart被调用),但是没有调用event.preventDefault(),从而足够早就触发了触发事件(所以WebView假设你不想消耗触摸事件)。

调试Web程序

如果您使用运行Android 4.4或更高版本的设备测试您的网络应用程序,则可以使用Chrome开发人员工具在WebView中远程调试网页,同时继续支持旧版Android。 有关详细信息,请参阅Android上的远程调试。

如果您没有运行Android 4.4或更高版本的设备,则可以使用控制台JavaScript API调试JavaScript,并将输出消息查看到logcat。 如果您熟悉使用Firebug或Web Inspector调试网页,那么您可能熟悉使用控制台(如console.log())。 Android的WebKit框架支持大多数相同的API,因此您可以在Android浏览器或自己的WebView中调试时从网页接收日志。 本文档介绍如何使用控制台API进行调试。

在Android浏览器中使用控制台API

当调用控制台函数(在DOM的window.console对象中)时,输出将显示在logcat中。 例如,如果您的网页执行以下JavaScript:
console.log("Hello World");
然后logcat消息看起来像这样:
Console: Hello World http://www.example.com/hello.html :82
根据您使用的Android版本,邮件的格式可能会有所不同。 在Android 2.1及更高版本上,来自Android浏览器的控制台消息被标记为名称“browser”。 在Android 1.6及更低版本上,Android浏览器消息被标记为名称“WebCore”。

Android的WebKit不会实现其他桌面浏览器中可用的所有控制台API。 但是,您可以使用基本的文本记录功能:
console.log(String)
console.info(String)
console.warn(String)
console.error(String)
其他控制台功能不会引起错误,但可能与其他Web浏览器的期望不同。

在WebView中使用控制台API

在WebView中调试时,还支持上述所有控制台API。 如果您要定位Android 2.1(API级别7)及更高版本,则必须提供实现onConsoleMessage()方法的WebChromeClient,以便控制台消息出现在logcat中。 然后,使用setWebChromeClient()将WebChromeClient应用到WebView。

例如,为了支持API级别7,这是onConsoleMessage(String,int,String)的代码可能如下所示:

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebChromeClient(new WebChromeClient() {
  public void onConsoleMessage(String message, int lineNumber, String sourceID) {
    Log.d("MyApplication", message + " -- From line "
                         + lineNumber + " of "
                         + sourceID);
  }
});

但是,如果您的最低支持版本是API级别8或更高版本,那么您应该实现onConsoleMessage(ConsoleMessage)。 例如:

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebChromeClient(new WebChromeClient() {
  public boolean onConsoleMessage(ConsoleMessage cm) {
    Log.d("MyApplication", cm.message() + " -- From line "
                         + cm.lineNumber() + " of "
                         + cm.sourceId() );
    return true;
  }
});

ConsoleMessage还包括一个MessageLevel对象来指示正在传递的控制台消息的类型。 您可以使用messageLevel()来查询消息级别,以确定消息的严重性,然后使用适当的Log方法或采取其他适当的操作。

无论您是使用onConsoleMessage(String,int,String)还是onConsoleMessage(ConsoleMessage),当您在网页中执行控制台方法时,Android都会调用相应的onConsoleMessage()方法,以便您可以报告错误。 例如,使用上面的示例代码,将打印出如下所示的logcat消息:
Hello World -- From line 82 of http://www.example.com/hello.html

Web应用程序的最佳做法

开发用于移动设备的网页和网络应用程序与开发典型桌面网络浏览器的网页相比,具有不同的挑战。 为了帮助您开始使用,以下是您应遵循的实践列表,以便为Android和其他移动设备提供最有效的Web应用程序。
将移动设备重定向到您的网站的专用移动版本
使用服务器端重定向,您可以通过几种方法将请求重定向到您的网站的移动版本。 大多数情况下,这通过“嗅探”Web浏览器提供的用户代理字符串来完成。 要确定是否为您的站点提供移动版本,您应该只是在用户代理中查找与移动设备类似的“移动”字符串。 如果需要,您还可以识别用户代理字符串中的特定操作系统(例如“Android 2.1”)。
注意:应提供全尺寸网站(如平板电脑)的大屏幕Android设备不会在用户代理中包含“移动”字符串,而其余的用户代理字符串大部分相同。 因此,根据用户代理中是否存在“移动”字符串,交付您的网站的移动版本非常重要。
使用适合移动设备的有效标记DOCTYPE
用于移动网站的最常用的标记语言是XHTML Basic。 此标准确保您的网站在移动设备上最有效的特定标记。 例如,它不允许在移动设备上表现不佳的HTML框架或嵌套表。 与DOCTYPE一起,请务必为文档(如UTF-8)声明适当的字符编码。
例如:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"
"http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">
还要确保您的网页标记对声明的DOCTYPE有效。 使用验证器,例如http://validator.w3.org可用的验证器。
使用视口元数据来正确调整您的网页大小
在您的文档<head>中,您应该提供元数据,指定浏览器的视口如何呈现您的网页。 例如,您的视口元数据可以指定浏览器视口的高度和宽度,初始网页缩放以及目标屏幕密度。
例如:
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
有关如何为Android设备使用视口元数据的更多信息,请阅读从Web应用程序定位屏幕。
避免多个文件请求
由于移动设备的连接速度通常远低于台式机,所以您应该尽可能快地加载您的网页。加速它的一种方法是避免在<head>中加载额外的文件,如样式表和脚本文件。相反,直接在<head>(或<body>的末尾)直接在页面加载之前不需要的脚本提供您的CSS和JavaScript。或者,您应该通过使用Minify等工具压缩文件来优化文件的大小和速度。
使用垂直线性布局
避免用户在浏览网页时向左右滚动。上下滚动对用户来说更容易,并使您的网页更简单。
有关创建优秀移动Web应用程序的更全面的指南,请参阅W3C的移动网络最佳实践。关于提高网站速度(适用于移动和桌面)的其他指导,请参阅Yahoo!的卓越绩效指南和Google速度教程,让我们使网络更快。

以上为官方翻译,如有翻译不正确地方望指正。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 122,388评论 15 533
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 67,046评论 12 114
  • 前言 总结 Android WebView 常用的相关知识点,令包含以下干货内容分析:Js注入漏洞、WebView...
    無名小子的杂货铺阅读 55,625评论 17 154
  • 世界七大奇迹之一的马丘比丘的名头实在太大,以至于前来库斯科的游客都忽略了另一个世界之最——彩虹圣山,这个世界上最特...
    薄暮初阳阅读 651评论 11 15
  • 1.一个由小写字母组成的字符串可以看成一些同一字母的最大碎片组成的。例如,"aaabbaaac"是由下面碎片组成的...
    雨y飘零久阅读 34评论 0 0