首先,需要提出一个概念,那就是 hybrid,主要意思就是 native 原生 Android 和 h5 混合开发。为什么要这样做呢?大家可以想象一下针对于同一个活动,如果使用纯 native 的开发方式,Android 和 iOS 两边都要维护同一套界面甚至是逻辑,这样开发和维护的成本会很大,而使用 hybrid 的开发方式的话,让前端的同学去写一套界面和逻辑,对于 native 端来说只要使用对应的容器去展示就可以了 (对于 Android 来说这个容器当然就是 WebView)。那为什么不所有的页面都使用这种方式开发呢?因为使用 h5 来展示界面的话用户体验始终是不如 native 的,所以在这两者之间我们需要一个权衡。
介绍完了何为 hybrid,我们来思考下面几个场景:
场景 1: 前端那边的页面有一个按钮,点击这个按钮需要显示一个 native 的组件 (比如一个 toast),或者点击这个按钮需要去在 native 端执行一个耗时的任务。
场景 2: 还是前端页面有一个按钮,点击这个按钮的逻辑是:如果登录了,则跳转到相应的界面,如果没有登录,则跳转到登录界面。而这个登录界面是我们 native 维护的。
看完上面两个场景,相信大家也发现了一个问题,hybrid 这样的开发方式有一个问题需要解决,那就是前端和本地的通信。
下面将会给大家介绍 active 原生 Android 和 h5 之间的通信方式。
使用 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:
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingBottom="@dimen/activity_vertical_margin"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- tools:context="com.example.hybirddemo.MainActivity" >
- <WebView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/webView" />
- </RelativeLayout>
MainActivity 代码:
- public class MainActivity extends Activity {
- private WebView webView;
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- // 获取webview控件
- webView = (WebView) findViewById(R.id.webView);
- //设置WebViewClient
- /*webView.setWebViewClient(new MyWebViewClient());*/
- //使用webview加载页面
- webView.loadUrl("http://www.baidu.com");
- webView.setWebViewClient(new WebViewClient() {
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, String url) {
- view.loadUrl(url);
- return true;
- }
- @Override
- public void onPageStarted(WebView view, String url, Bitmap favicon) {
- // TODO Auto-generated method stub
- super.onPageStarted(view, url, favicon);
- }
- @Override
- public void onPageFinished(WebView view, String url) {
- // TODO Aut (view, url);
- }
- });
- }
- @Override
- //覆盖系统的回退键
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
- webView.goBack();
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
- }
WebSetting 用处非常大, 通过 WebSetting 可以使用 Android 原生的 JavascriptInterface 来进行 js 和 java 的通信。
例子 2:JavaScript 和 java 的相互调用
首先我们写一段 html 代码:
- <!DOCTYPE html>
- <html>
- <head>
- <title>
- MyHtml.html
- </title>
- <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
- <meta http-equiv="description" content="this is my page">
- <meta http-equiv="content-type" content="text/html; charset=UTF-8">
- <!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
- <script type="text/javascript">
- function showToast(toast) {
- javascript: control.showToast(toast);
- }
- function log(msg) {
- consolse.log(msg);
- }
- </script>
- </head>
- <body>
- <input type="button" value="toast" onclick="showToast('hello world!')">
- </body>
- </html>
这是一个很简单的 html5 页面,里面有一个 button,点击这个 button 就执行 js 脚本中的 showToast 方法。
那么这个 showToast 方法做了什么呢?
可以看到 control.showToast,这个是什么我们后面再说,下面看我们 Android 工程中的 java 代码。
编写布局文件 activity_main.xml
布局的内容很简单,就是嵌套一个 WebView 控件
编写 MainActivity.java 代码
- public class MainActivity extends Activity {
- private WebView webView;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- // 获取webview控件
- webView = (WebView) findViewById(R.id.webView);
- //设置WebViewClient
- /*webView.setWebViewClient(new MyWebViewClient());*/
- //使用webview加载页面
- webView.loadUrl("http://www.baidu.com");
- webView.setWebViewClient(new WebViewClient() {
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, String url) {
- view.loadUrl(url);
- return true;
- }
- @Override
- public void onPageStarted(WebView view, String url, Bitmap favicon) {
- // TODO Auto-generated method stub
- super.onPageStarted(view, url, favicon);
- }
- @Override
- public void onPageFinished(WebView view, String url) {
- // TODO Aut (view, url);
- }
- });
- }
- @Override
- //覆盖系统的回退键
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
- webView.goBack();
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
- }
上面的代码主要做了以下的步骤:
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 的互调,是不是很简单。
在 Android 中处理 JS 的警告,对话框等需要对 WebView 设置 WebChromeClient 对象, 并复写其中的 onJsAlert,onJsConfirm,onJsPrompt 方法可以处理 javascript 的常用对话框
例子 3:在 Android 中处理 javascript 的对话框
1) 编写 html 页面布局
- <%@LANGUAGE="JAVASCRIPT" CODEPAGE="936" %>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8 " />
- <title>
- 分别测试javascript的三种对话框
- </title>
- <script language="javascript">
- function ale() {
- alert("这是一个警告对话框!");
- }
- function firm() {
- if (confirm("更多信息请到我的博客去?")) {
- location.href = "http://yarin.javaeye.com";
- } else {
- alert("你选择了不去!");
- }
- }
- function prom() {
- var str = prompt("演示一个带输入的对话框", "这里输入你的信息");
- if (str) {
- alert("谢谢使用,你输入的是:" + str)
- }
- }
- </script>
- </head>
- <body>
- <p>
- 下面我们演示3种对话框
- </p>
- <p>
- 警告、提醒对话框
- </p>
- <p>
- <input type="submit" name="Submit" value="提交" onclick="ale()" />
- </p>
- <p>
- 带选择的对话框
- </p>
- <p>
- <input type="submit" name="Submit2" value="提交" onclick="firm()" />
- </p>
- <p>
- 要求用户输入的对话框
- </p>
- <p>
- <input type="submit" name="Submit3" value="提交" onclick="prom()" />
- </p>
- </body>
- </html>
页面效果:
2) Android 中布局的编写
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:animationCache="true"
- android:layout_weight="9">
- <EditText
- android:id="@+id/EditText01"
- android:layout_width="wrap_content"
- android:layout_weight="9"
- android:layout_height="wrap_content"
- android:text="请输入网址"/>
- <Button android:id="@+id/Button01"
- android:layout_width="wrap_content"
- android:layout_weight="1"
- android:layout_height="wrap_content"
- android:text="连接" />
- </LinearLayout>
- <WebView
- android:id="@+id/WebView01"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- />
- </LinearLayout>
3) 编写自定义对话框的布局
新建 prom_dialog.xml 文件,在其中自定义一个带输入的对话框由 TextView 和 EditText 构成
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:gravity="center_horizontal"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content">
- <TextView
- android:id="@+id/TextView_PROM"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"/>
- <EditText
- android:id="@+id/EditText_PROM"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:selectAllOnFocus="true"
- android:scrollHorizontally="true"/>
- </LinearLayout>
4) 获取 WebView 控件,并进行相关的设置
5) 复写 onKeyDown 方法,当用户按返回键时,返回上一个加载的页面
6) 给 WebView 设置 setWebChromeClient,并复写其中的方法
- // 设置WebChromeClient
- mWebView.setWebChromeClient(new WebChromeClient() {
- @Override
- // 处理javascript中的alert
- public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
- // 构建一个Builder来显示网页中的对话框
- Builder builder = new Builder(MainActivity.this);
- builder.setTitle("提示对话框");
- builder.setMessage(message);
- builder.setPositiveButton(android.R.string.ok,
- new AlertDialog.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // TODO Auto-generated method stub
- // 点击确定按钮之后,继续执行网页中的操作
- result.confirm();
- }
- });
- builder.setNegativeButton(android.R.string.cancel,
- new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- result.cancel();
- }
- });
- builder.setCancelable(false);
- builder.create();
- builder.show();
- return true;
- }
- @Override
- //处理javascript中的confirm
- public boolean onJsConfirm(WebView view, String url,
- String message, final JsResult result) {
- Builder builder = new Builder(MainActivity.this);
- builder.setTitle("带选择的对话框");
- builder.setMessage(message);
- builder.setPositiveButton(android.R.string.ok,new AlertDialog.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- result.confirm();
- }
- });
- builder.setNegativeButton(android.R.string.cancel,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- result.cancel();
- }
- });
- builder.setCancelable(false);
- builder.create();
- builder.show();
- return true;
- }
- @Override
- // 处理javascript中的prompt
- // message为网页中对话框的提示内容
- // defaultValue在没有输入时,默认显示的内容
- public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) {
- // 自定义一个带输入的对话框由TextView和EditText构成
- LayoutInflater layoutInflater = LayoutInflater.from(MainActivity.this);
- final View dialogView = layoutInflater.inflate(R.layout.prom_dialog, null);
- // 设置TextView对应网页中的提示信息
- ((TextView) dialogView.findViewById(R.id.TextView_PROM)).setText(message);
- // 设置EditText对应网页中的输入框
- ((EditText) dialogView.findViewById(R.id.EditText_PROM)).setText(defaultValue);
- //构建一个Builder来显示网页中的对话框
- Builder builder = new Builder(MainActivity.this);
- //设置弹出框标题
- builder.setTitle("带输入的对话框");
- //设置弹出框的布局
- builder.setView(dialogView);
- //设置按键的监听
- builder.setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // 点击确定之后,取得输入的值,传给网页处理
- String value = ((EditText) dialogView.findViewById(R.id.EditText_PROM)).getText().toString();
- result.confirm(value);
- }
- });
- builder.setNegativeButton(android.R.string.cancel, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // TODO Auto-generated method stub
- result.cancel();
- }
- });
- builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
- public void onCancel(DialogInterface dialog) {
- result.cancel();
- }
- });
- builder.show();
- return true;
- }
- @Override
- //设置网页加载的进度条
- public void onProgressChanged(WebView view, int newProgress) {
- MainActivity.this.getWindow().setFeatureInt(Window.FEATURE_PROGRESS, newProgress *100);
- super.onProgressChanged(view, newProgress);
- }
- @Override
- public void onReceivedTitle(WebView view, String title) {
- MainActivity.this.setTitle(title);
- super.onReceivedTitle(view, title);
- }
- });
- mButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- //取得编辑框中我们输入的内容
- String url = mEditText.getText().toString().trim();
- //判断输入的内容是不是网址
- if(URLUtil.isNetworkUrl(url)){
- //装载网址
- mWebView.loadUrl(url);
- }else{
- mEditText.setText("输入网址错误,请重新输入");
- }
- }
- });
- }
图 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 对话框,并且可以输入内容。
来源: