Web Apps
实现一个Android应用的两个基本方式是:客户端应用和Web应用。前者使用Android SDK开发并且以一个APK方式安装在用户的设备上,后者使用标准的Web开发,不需要在用户设备上安装,通过一个Web浏览器获取。
如果我们为Android设备提供一个基于web的app,我们可以确定的是主流的Android浏览器(WebView框架)允许我们指定视窗和样式属性,确保我们的网页在所有屏幕配置上都能够呈现出合适的尺寸和大小。
我们不应该开发一个app仅仅用来展示我们的网站。相反的,网页应该嵌入在我们的app中。我们甚至可以在Android应用和网页之间定义一个接口,以此允许我们网页中的JavaScript可以调用Android应用的APIs。
使Web Apps支持不同的屏幕
因为各种Android设备有不同的屏幕大小及像素密度,我们在web设计的时候应该考虑这些因素,以便我们的网页总能以合适的大小展现出来。
当网页的目标设备是Android设备的时候,有两个方面的因素需要我们考虑:
Viewport:Viewpost是一个矩形区域,它为我们的网页提供一个可绘制区域。我们可以指定不同的Viewpost属性,例如大小和初始缩放百分比。最重要的是Viewport的宽度,它定义了这个网页视图的横向可用像素数(可用CSS像素的数量)。
屏幕密度:Web类和Android大部分的网页浏览器将CSS像素值转换为独立设备像素值。因此一个网页在中等密度的屏幕上(160dpi)呈现出相同的可感知尺寸。如果图形是我们网页设计重要元素,我们应该更加考虑出现在不同密度屏幕上的缩,因为一个300像素宽的图片在一个320dpi的屏幕上将被按比例放大(每CSS像素使用更多的物理像素),这将会导致伪影(模糊像素)。
使用WebView构建Web Apps
如果我们想实现一个web应用程序(或只是一个网页)作为一个客户端应用程序的一部分,我们可以使用WebView。WebView类是Android中View类的扩展,它允许我们将网页作为我们布局的一部分进行展示。它不包含非常完善的web浏览器所具备的任何特性,比如导航控件或一个地址栏。WebView默认能做的是显示一个网页。
一个常见的场景,比如我们想要在应用程序中展示一些信息,这些需要更新,如一个终端用户协议或用户指南,此时使用WebView是有益的。我们可以创建一个仅含有一个WebView的Activity来展示在线托管的文档。
另一个场景,如果我们的应用提供给用户的数据总是需要一个网络连接来检索数据,比如邮件,此时使用WebView是有用的。我们将会发现在应用中使用WebView来显示用户的所有数据比执行一个网络请求、解析数据、布局展示更加简单方便。我们可以为Android设备设计一个网页,然后在Android应用中实现一个WebView中加载这个网页。
向应用中添加一个WebView
1.布局中添加WebView
2.代码中找到WebView,并以url为参数调用WebView对象的loadUrl()方法
3.添加网络权限
在WebView中使用JavaScript
如果要加载的网页使用到的JavaScript,我们必须使WebView启用JavaScript。一旦JavaScript被启用,我们可以在我们的应用代码和JavaScript代码之间创建接口。
启用JavaScript
WebView默认禁用JavaScript。我们可以通过WebView的WebSettings来启用它。我们可以通过getSettings()
方法来获取WebSettings,然后通过setJavaScriptEnabled()
方法来启用它。
例如:
WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
WebSettings提供了大量非常有用的其他设置。例如,如果我们开发一个Web应用程序,它被设计为仅用WebView,我们可以通过setUserAgentString()
方法定义一个用户代理字符串,然后在网页中查询这个用户代理字符串,来验证用户请求网页实际是请求我们的Android应用。
JavaScript代码绑定到Android代码
我们可以在JavaScript代码和客户端的Android代码之间创建一个接口,例如我们的JavaScript代码可以调用我们的Android代码来显示一个Dialog,而不是使用JavaScript的alert()
方法。
在二者之间绑定接口需要调用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或更高,必须在JavaScript可以调用的方法上添加@JavascriptInterface
注解,该方法也必须使公有的。若不添加,Android 4.2或更高设备上该方法将无法执行。
WebAppInterface类允许网页使用showToast()
方法来创建一个吐司信息。我们可以如下将该类绑定至JavaScript。
WebView webView = (WebView) findViewById(R.id.webview);
webView.addJavascriptInterface(new WebAppInterface(this), "Android");
这为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中不需要初始化这个接口,WebView自动使它可用于您的web页面。
提示:绑定在JavaScript的对象运行在其他线程,而不再它被构造的线程。
注意:使用addJavascriptInterface()
方法允许JavaScript控制我们的Android应用,它非常有用但也产生了严重的安全问题。例如在WebView中的HTML是不可靠的(例如HTML的一部分或所有由未知的人或进程提供),然后攻击者可以执行你的包括HTML客户端代码,并且可能选择攻击任意代码。就此,我们不应该使用addJavascriptInterface()
方法,除非我们写的所有HTML和JavaScript出现在我们的WebView中。我们也不应该允许用户在我们的WebView中导航至那些不属于我们的网页中(相反,默认允许用户的默认浏览器应用打开外部链接,用户的网页浏览器打开所有的URL链接)。
处理页面导航
当用户在我们的WebView中点击一个链接的时候,默认处理方式是Android运行一个应用来处理所有的URLs。通常,是默认网页浏览器来打开和加载这个目标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")) {
// This is my web site, so do not override; let my WebView load the page
return false;
}
// Otherwise, the link is not for a page on my site, so launch another Activity that handles URLs
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(通常允许WebView加载这个网页)。如果不匹配,创建一个Intent来启动默认Activity来处理URL。
浏览网页的历史
让我们的WebView重复加载了多个URL,它自动地累积浏览过的历史,我们可以可以通过goBack()
和goForward()
方法来前进或者后退这些历史网页。
如下展示了在Activity中使用设备返回键来回退历史网页:
@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);
}
Android 4.4的WebView(需要注意的地方)
Android4.4引进了全新的基于Chrominum的WebView。这个变化升级了WebView的性能。所有的在Android4.4或更高版本的Android设备使用WebView的Apps将继承这些优化。