最近团队有需求将一个移动端网站打包成Android App,最初尝试过使用一些在线打包工具,但打包出来即难看又难用,所以还是撸起袖子自己干吧。需求无非是增加几个Tab栏,接入微博微信分享等社交功能,再用webview把网页加载进来,花了一点时间完成了,对常用的一些知识点做个总结。
webview的一些细节
处理页面导航
一般来说,在webview中点击一个链接之后,Android会打开一个能处理URL事件的APP,例如你默认的浏览器,如果我们想要自己去处理webview中网页的前进后退甚至更多的事情,就可以利用WebviewClient.
WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebViewClient(new WebViewClient());
竟然敢叫Native App,我们自然就不会只有一个页面,所以我们需要在用户点击某些链接时跳转到新的Activity,以及在页面开始加载时显示加载动画,页面结束加载时让动画结束,此时我们可以重载WebViewClient中的方法。
private WebViewClient mClient = new WebViewClient() {
public void onPageStarted(WebView view, String url, Bitmap favicon) {
mRefreshLayout.setRefreshing(true);
}
@Override
public void onPageFinished(WebView view, String url) {
if (mRefreshLayout.isRefreshing()) {
mRefreshLayout.setRefreshing(false);
}
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Intent intent = new Intent(getActivity(), DetailsActivity.class);
intent.putExtra(AppUtil.KEY_URL, url);
startActivity(intent);
return true;
}
};
}
除此之外,当然可以通过重载webviewclient做更多的事情,如自定义网页出错界面等等。
获取页面标题
我们通常可以通过实现WebChromeClient中的接口去获取与显示网页标题:
mWebView.setWebChromeClient(new WebChromeClient() {
public void onReceivedTitle(WebView view, String title){
mTitle.setText(title);
}
});
但是当你在一个webview中有不同链接跳转,再通过webview.goback()返回的时候,标题并不会更新,其实我们可以在WebviewClient中实现onPageFinish接口时通过view.getTitle去获取标题,此时goback时也是能获取到标题更新的。
UserAgent
网页的移动端通常有自己的导航栏设计,我们在native中增加了导航栏,如果此时继续让网页的导航栏加载出来,界面就会出现两个导航栏,所以我们需要告诉网页,访问并非来源于一个真正的浏览器,而是一个App,当网页识别到该访问时,即将自己的导航栏隐藏掉。我们可以通过下面这段代码获取并定制专属App的UserAgent.
WebSettings webSettings = mWebView.getSettings();
String userAgent = webSettings.getUserAgentString();
userAgent = userAgent.concat(" app和网页约定的字串");
webSettings.setUserAgentString(userAgent);
缓存
大多数需要访问网页的App,假设你已经成功访问了网页,那么下一次你断掉网络之后再访问,还是可以看到上次访问的页面,所以我们可以在有网络时去获取新数据,而没有网络时即从webview的缓存中去加载数据。webview有四种缓存模式,根据需要灵活使用。
if (AppUtil.isNetworkConnected(getActivity())) {
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
} else {
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
}
js交互
有时候我们需要在客户端与js代码做交互,在我的项目中,加载网页时,执行一段Js代码,返回用户是否登录的信息,在这里就简单的使用js让客户端弹toast作演示。
首先,定义一个类,用于让js代码调用:
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();
}
}
调用下面这段代码,将WebAppInterface与webview绑定起来:
WebView webView = (WebView) findViewById(R.id.webview);
webView.addJavascriptInterface(new WebAppInterface(this), "Android");
至此,js代码就可以和客户端通信了。
<input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" />
<script type="text/javascript">
function showAndroidToast(toast) {
Android.showToast(toast);
}
</script>
上面这段代码,是写在网页端的,如果我们想客户端主动插入js代码也是可以的,非常简单:
String js = "Android.showToast(\"Hello Android\")"
webview.loadurl(“javascript:”+js);
代码混淆
兴高采烈的打开混淆准备发布应用的时候发现,所有的js调用不生效了!打开proguard-rules.pro可以发现显著的提示:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
所以,我们必须将有webviwe和js交互的类作声明,且必须使用完整的包名路径:
-keepclassmembers class com.google.test.TestActivity {
public *;
}
同时4.2以上版本中我们需要对js调用接口作@javascriptinterface,声明,为了声明不丢失,我们还需要加上:
-keepattributes *Annotation*
-keepattributes *JavascriptInterface*