1.概述
首先,需要提出一个概念,那就是hybrid,主要意思就是native原生Android和h5混合开发。为什么要这样做呢?大家可以想象一下针对于同一个活动,如果使用纯native的开发方式,Android和iOS两边都要维护同一套界面甚至是逻辑,这样开发和维护的成本会很大,而使用hybrid的开发方式的话,让前端的同学去写一套界面和逻辑,对于native端来说只要使用对应的容器去展示就可以了(对于Android来说这个容器当然就是WebView)。那为什么不所有的页面都使用这种方式开发呢?因为使用h5来展示界面的话用户体验始终是不如native的,所以在这两者之间我们需要一个权衡。
介绍完了何为hybrid,我们来思考下面几个场景:
场景1:前端那边的页面有一个按钮,点击这个按钮需要显示一个native的组件(比如一个toast),或者点击这个按钮需要去在native端执行一个耗时的任务。
场景2:还是前端页面有一个按钮,点击这个按钮的逻辑是:如果登录了,则跳转到相应的界面,如果没有登录,则跳转到登录界面。而这个登录界面是我们native维护的。
看完上面两个场景,相信大家也发现了一个问题,hybrid这样的开发方式有一个问题需要解决,那就是前端和本地的通信。
下面将会给大家介绍active原生Android和h5之间的通信方式。
2.如何使用WebView
使用WebView控件 与其他控件的使用方法相同 在layout中使用一个”WebView”标签
WebView不包括导航栏,地址栏等完整浏览器功能,只用于显示一个网页
在WebView中加载Web页面,使用loadUrl()
注意在manifest文件中加入访问互联网的权限:
1. <uses-permission android:name="android.permission.INTERNET"/>
但是,在Android中点击一个链接,默认是调用手机上已经安装的浏览器程序来启动,因此想要通过WebView代为处理这个动作 ,那么需要通过WebViewClient
当然,我们也可以写一个类继承WebViewClient来对WebViewClient对象进行扩展
然后只需要将setWebViewClient的内容进行修改即可
另外出于用户习惯上的考虑 需要将WebView表现得更像一个浏览器,也就是需要可以回退历史记录,因此需要覆盖系统的回退键 goBack,goForward可向前向后浏览历史页面
例子1:WebViewClient的使用
布局代码activity_main.xml:
1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
2. xmlns:tools="http://schemas.android.com/tools"
3. android:layout_width="match_parent"
4. android:layout_height="match_parent"
5. android:paddingBottom="@dimen/activity_vertical_margin"
6. android:paddingLeft="@dimen/activity_horizontal_margin"
7. android:paddingRight="@dimen/activity_horizontal_margin"
8. android:paddingTop="@dimen/activity_vertical_margin"
9. tools:context="com.example.hybirddemo.MainActivity" >
10.
11. <WebView
12. android:layout_width="match_parent"
13. android:layout_height="match_parent"
14. android:id="@+id/webView" />
15.
16. </RelativeLayout>
MainActivity代码:
public class MainActivity extends Activity {
2.
3. private WebView webView;
4.
5. @Override
6. protected void onCreate(Bundle savedInstanceState) {
7. super.onCreate(savedInstanceState);
8. setContentView(R.layout.activity_main);
9.
10. // 获取webview控件
11. webView = (WebView) findViewById(R.id.webView);
12. //设置WebViewClient
13. /*webView.setWebViewClient(new MyWebViewClient());*/
14. //使用webview加载页面
15. webView.loadUrl("http://www.baidu.com");
16.
17. webView.setWebViewClient(new WebViewClient() {
18.
19. @Override
20. public boolean shouldOverrideUrlLoading(WebView view, String url) {
21. view.loadUrl(url);
22. return true;
23. }
24.
25. @Override
26. public void onPageStarted(WebView view, String url, Bitmap favicon) {
27. // TODO Auto-generated method stub
28. super.onPageStarted(view, url, favicon);
29. }
30.
31. @Override
32. public void onPageFinished(WebView view, String url) {
33. // TODO Aut (view, url);
34. }
35.
36. });
37.
38. }
39.
40. @Override
41. //覆盖系统的回退键
42. public boolean onKeyDown(int keyCode, KeyEvent event) {
43. if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
44. webView.goBack();
45. return true;
46. }
47.
48. return super.onKeyDown(keyCode, event);
49. }
50.
51. }
3.JavaScript和java的相互调用
WebSetting用处非常大,通过WebSetting可以使用Android原生的JavascriptInterface来进行js和java的通信。
例子2:JavaScript和java的相互调用
首先我们写一段html代码:
1. <!DOCTYPE html>
2. <html>
3. <head>
4. <title>MyHtml.html</title>
5.
6. <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
7. <meta http-equiv="description" content="this is my page">
8. <meta http-equiv="content-type" content="text/html; charset=UTF-8">
9.
10. <!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
11. <script type="text/javascript">
12. function showToast(toast) {
13. javascript:control.showToast(toast);
14. }
15. function log(msg) {
16. consolse.log(msg);
17. }
18. </script>
19. </head>
20.
21. <body>
22. <input type="button" value="toast" onclick="showToast('hello world!')">
23. </body>
24. </html>
这是一个很简单的html5页面,里面有一个button,点击这个button就执行js脚本中的showToast方法。
那么这个showToast方法做了什么呢?
可以看到control.showToast,这个是什么我们后面再说,下面看我们Android工程中的java代码。编写布局文件activity_main.xml
布局的内容很简单,就是嵌套一个WebView控件
编写MainActivity.java代码
1. package com.example.hybirddemo;
2.
3. import android.annotation.SuppressLint;
4. import android.app.Activity;
5. import android.os.Bundle;
6. import android.util.Log;
7. import android.view.Menu;
8. import android.view.MenuItem;
9. import android.webkit.JavascriptInterface;
10. import android.webkit.WebSettings;
11. import android.webkit.WebView;
12. import android.widget.Toast;
13.
14. public class MainActivity extends Activity {
15.
16. private WebView webView;
17.
18. @Override
19. protected void onCreate(Bundle savedInstanceState) {
20. super.onCreate(savedInstanceState);
21. setContentView(R.layout.activity_main);
22. // 获取webview控件
23. webView = (WebView) findViewById(R.id.webView);
24. // 获取WebView的设置
25. WebSettings webSettings = webView.getSettings();
26. // 将JavaScript设置为可用,这一句话是必须的,不然所做一切都是徒劳的
27. webSettings.setJavaScriptEnabled(true);
28. //给webview添加JavaScript接口
29. webView.addJavascriptInterface(new JsInterface(), "control");
30. //通过webview加载html页面
31. webView.loadUrl("file:///android_asset/MyHtml.html");
32.
33. }
34. public class JsInterface{
35. @JavascriptInterface
36. public void showToast(String toast) {
37. Toast.makeText(MainActivity.this,toast , Toast.LENGTH_SHORT).show();
38. Log.d("html", "show toast success");
39. }
40. public void log(final String msg) {
41. webView.post(new Runnable() {
42.
43. @Override
44. public void run() {
45. webView.loadUrl("javascript log("+"'"+msg+"'"+")");
46.
47. }
48. });
49. }
50. }
51. }
上面的代码主要做了以下的步骤:
a) 获取webview控件
b) 获取webview的设置,将JavaScript设置为可用,打开JavaScript的通道
c) 在Android程序中建立接口 ,并编写相关逻辑
再去看之前js脚本中的那个showToast()方法
这里的control就是我们的那个interface,调用了interface的showToast方法,很明显这里是js调用了Android的代码,输出了一个Toast
可以看到这个interface我们给它取名叫control,最后通过loadUrl加载页面。
可以看到先显示一个toast,然后调用log()方法,log()方法里调用了js脚本的log()方法, js的log()方法做的事就是在控制台输出msg,这里明显是Android调用了js的方法。
d) 给webview添加我们自己编写的JavaScript接口
通过WebView的addJavascriptInterface方法去注入一个我们自己写的interface。
e) 使用webview控件加载我们之前编写的html文件
在真实手机上运行程序,在控制台成功输出内容:
这样我们就完成了js和java的互调,是不是很简单。
4.Android中处理JS的警告,对话框等
在Android中处理JS的警告,对话框等需要对WebView设置WebChromeClient对象,并复写其中的onJsAlert,onJsConfirm,onJsPrompt方法可以处理javascript的常用对话框
例子3:在Android中处理javascript的对话框
1) 编写html页面布局
1. <%@LANGUAGE="JAVASCRIPT" CODEPAGE="936"%>
2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3. <html xmlns="http://www.w3.org/1999/xhtml">
4. <head>
5. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8 " />
6. <title>分别测试javascript的三种对话框</title>
7. <script language="javascript">
8. function ale()
9. {
10. alert("这是一个警告对话框!");
11. }
12. function firm()
13. {
14. if(confirm("更多信息请到我的博客去?"))
15. {
16. location.href="http://yarin.javaeye.com";
17. }
18. else
19. {
20. alert("你选择了不去!");
21. }
22. }
23. function prom()
24. {
25. var str=prompt("演示一个带输入的对话框","这里输入你的信息");
26. if(str)
27. {
28. alert("谢谢使用,你输入的是:"+ str)
29. }
30. }
31. </script>
32. </head>
33. <body>
34. <p>下面我们演示3种对话框</p>
35. <p>警告、提醒对话框</p>
36. <p>
37. <input type="submit" name="Submit" value="提交" onclick="ale()" />
38. </p>
39. <p>带选择的对话框</p>
40. <p>
41. <input type="submit" name="Submit2" value="提交" onclick="firm()" />
42. </p>
43. <p>要求用户输入的对话框</p>
44. <p>
45. <input type="submit" name="Submit3" value="提交" onclick="prom()" />
46. </p>
47. </body>
48. </html>
页面效果:
2) Android中布局的编写
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3. android:orientation="vertical"
4. android:layout_width="fill_parent"
5. android:layout_height="fill_parent"
6. >
7. <LinearLayout
8. android:orientation="horizontal"
9. android:layout_width="fill_parent"
10. android:layout_height="fill_parent"
11. android:animationCache="true"
12. android:layout_weight="9">
13. <EditText
14. android:id="@+id/EditText01"
15. android:layout_width="wrap_content"
16. android:layout_weight="9"
17. android:layout_height="wrap_content"
18. android:text="请输入网址"/>
19. <Button android:id="@+id/Button01"
20. android:layout_width="wrap_content"
21. android:layout_weight="1"
22. android:layout_height="wrap_content"
23. android:text="连接" />
24. </LinearLayout>
25. <WebView
26. android:id="@+id/WebView01"
27. android:layout_width="fill_parent"
28. android:layout_height="fill_parent"
29. android:layout_weight="1"
30. />
31. </LinearLayout>
3) 编写自定义对话框的布局
新建prom_dialog.xml文件,在其中自定义一个带输入的对话框由TextView和EditText构成
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3. android:gravity="center_horizontal"
4. android:orientation="vertical"
5. android:layout_width="fill_parent"
6. android:layout_height="wrap_content"
7. >
8. <TextView
9. android:id="@+id/TextView_PROM"
10. android:layout_width="fill_parent"
11. android:layout_height="wrap_content"/>
12. <EditText
13. android:id="@+id/EditText_PROM"
14. android:layout_width="fill_parent"
15. android:layout_height="wrap_content"
16. android:selectAllOnFocus="true"
17. android:scrollHorizontally="true"/>
18. </LinearLayout>
4) 获取WebView控件,并进行相关的设置
5) 复写onKeyDown方法,当用户按返回键时,返回上一个加载的页面
6) 给WebView设置setWebChromeClient,并复写其中的方法
1. // 设置WebChromeClient
2. mWebView.setWebChromeClient(new WebChromeClient() {
3.
4. @Override
5. // 处理javascript中的alert
6. public boolean onJsAlert(WebView view, String url, String message,
7. final JsResult result) {
8. // 构建一个Builder来显示网页中的对话框
9. Builder builder = new Builder(MainActivity.this);
10. builder.setTitle("提示对话框");
11. builder.setMessage(message);
12. builder.setPositiveButton(android.R.string.ok,
13. new AlertDialog.OnClickListener() {
14.
15. @Override
16. public void onClick(DialogInterface dialog,
17. int which) {
18. // TODO Auto-generated method stub
19. // 点击确定按钮之后,继续执行网页中的操作
20. result.confirm();
21. }
22. });
23. builder.setNegativeButton(android.R.string.cancel,
24. new OnClickListener() {
25.
26. @Override
27. public void onClick(DialogInterface dialog,
28. int which) {
29. result.cancel();
30.
31. }
32. });
33. builder.setCancelable(false);
34. builder.create();
35. builder.show();
36.
37. return true;
38.
39. }
40.
41. @Override
42. //处理javascript中的confirm
43. public boolean onJsConfirm(WebView view, String url,
44. String message, final JsResult result) {
45. Builder builder = new Builder(MainActivity.this);
46. builder.setTitle("带选择的对话框");
47. builder.setMessage(message);
48. builder.setPositiveButton(android.R.string.ok,
49. new AlertDialog.OnClickListener() {
50. public void onClick(DialogInterface dialog, int which) {
51. result.confirm();
52. }
53. });
54. builder.setNegativeButton(android.R.string.cancel,
55. new DialogInterface.OnClickListener() {
56. public void onClick(DialogInterface dialog, int which) {
57. result.cancel();
58. }
59. });
60. builder.setCancelable(false);
61. builder.create();
62. builder.show();
63. return true;
64. }
65.
66. @Override
67. // 处理javascript中的prompt
68. // message为网页中对话框的提示内容
69. // defaultValue在没有输入时,默认显示的内容
70. public boolean onJsPrompt(WebView view, String url, String message,
71. String defaultValue, final JsPromptResult result) {
72. // 自定义一个带输入的对话框由TextView和EditText构成
73. LayoutInflater layoutInflater = LayoutInflater
74. .from(MainActivity.this);
75. final View dialogView = layoutInflater.inflate(
76. R.layout.prom_dialog, null);
77.
78. // 设置TextView对应网页中的提示信息
79. ((TextView) dialogView.findViewById(R.id.TextView_PROM))
80. .setText(message);
81. // 设置EditText对应网页中的输入框
82. ((EditText) dialogView.findViewById(R.id.EditText_PROM))
83. .setText(defaultValue);
84. //构建一个Builder来显示网页中的对话框
85. Builder builder = new Builder(MainActivity.this);
86. //设置弹出框标题
87. builder.setTitle("带输入的对话框");
88. //设置弹出框的布局
89. builder.setView(dialogView);
90. //设置按键的监听
91. builder.setPositiveButton(android.R.string.ok,
92. new AlertDialog.OnClickListener() {
93.
94. @Override
95. public void onClick(DialogInterface dialog,
96. int which) {
97.
98. // 点击确定之后,取得输入的值,传给网页处理
99. String value = ((EditText) dialogView
100. .findViewById(R.id.EditText_PROM))
101. .getText().toString();
102. result.confirm(value);
103. }
104.
105. });
106.
107. builder.setNegativeButton(android.R.string.cancel,
108. new OnClickListener() {
109.
110. @Override
111. public void onClick(DialogInterface dialog,
112. int which) {
113. // TODO Auto-generated method stub
114. result.cancel();
115. }
116. });
117.
118. builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
119. public void onCancel(DialogInterface dialog) {
120. result.cancel();
121. }
122. });
123. builder.show();
124. return true;
125. }
126.
127. @Override
128. //设置网页加载的进度条
129. public void onProgressChanged(WebView view, int newProgress) {
130. MainActivity.this.getWindow().setFeatureInt(Window.FEATURE_PROGRESS, newProgress *100);
131. super.onProgressChanged(view, newProgress);
132. }
133.
134. @Override
135. public void onReceivedTitle(WebView view, String title) {
136. MainActivity.this.setTitle(title);
137. super.onReceivedTitle(view, title);
138. }
139.
140. });
141. mButton.setOnClickListener(new View.OnClickListener() {
142.
143. @Override
144. public void onClick(View v) {
145. //取得编辑框中我们输入的内容
146. String url = mEditText.getText().toString().trim();
147. //判断输入的内容是不是网址
148. if(URLUtil.isNetworkUrl(url)){
149. //装载网址
150. mWebView.loadUrl(url);
151. }else{
152. mEditText.setText("输入网址错误,请重新输入");
153. }
154. }
155. });
156.
157. }
图1 dialog.html页面
图2 javascript的警告对话框
图3 javascript的confirm对话框
图4 javascript的prompt对话框
总结:在这个项目中,使用setWebChromeClient方法来为WebView设置一个WebChromeClient对象,来辅助WebView来处理Javascript的对话框等,图4是我们自定义的对话框,图2和图3我们都只需要监听按钮的点击事件,然后通过confirm和cancel方法将我们的操作传递给Javascript进行处理。当你在图1的界面,点击第一个按钮时,会打开图2的对话框,点击第二个按钮时,会打开图3的对话框,同时在这里点击确定,会跳转到另一个页面,当点击第三个按钮时,会打开图4对话框,并且可以输入内容。