Android中使用WebView与JS交互全解析

xeyb1688 8年前
   <h3><strong>1.概述</strong></h3>    <p>首先,需要提出一个概念,那就是hybrid,主要意思就是native原生Android和h5混合开发。为什么要这样做呢?大家可以想象一下针对于同一个活动,如果使用纯native的开发方式,Android和iOS两边都要维护同一套界面甚至是逻辑,这样开发和维护的成本会很大,而使用hybrid的开发方式的话,让前端的同学去写一套界面和逻辑,对于native端来说只要使用对应的容器去展示就可以了(对于Android来说这个容器当然就是WebView)。那为什么不所有的页面都使用这种方式开发呢?因为使用h5来展示界面的话用户体验始终是不如native的,所以在这两者之间我们需要一个权衡。</p>    <p>介绍完了何为hybrid,我们来思考下面几个场景:</p>    <p>场景1:前端那边的页面有一个按钮,点击这个按钮需要显示一个native的组件(比如一个toast),或者点击这个按钮需要去在native端执行一个耗时的任务。</p>    <p>场景2:还是前端页面有一个按钮,点击这个按钮的逻辑是:如果登录了,则跳转到相应的界面,如果没有登录,则跳转到登录界面。而这个登录界面是我们native维护的。</p>    <p>看完上面两个场景,相信大家也发现了一个问题,hybrid这样的开发方式有一个问题需要解决,那就是前端和本地的通信。</p>    <p>下面将会给大家介绍active原生Android和h5之间的通信方式。</p>    <h3><strong>2.如何使用WebView</strong></h3>    <p>使用WebView控件 与其他控件的使用方法相同 在layout中使用一个”WebView”标签</p>    <p>WebView不包括导航栏,地址栏等完整浏览器功能,只用于显示一个网页</p>    <p>在WebView中加载Web页面,使用loadUrl()</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f54edb522fb4e48a6c128f0355349973.png"></p>    <p>注意在manifest文件中加入访问互联网的权限:</p>    <p>1.  <uses-permission android:name=”android.permission.INTERNET”/></p>    <p>但是,在Android中点击一个链接,默认是调用手机上已经安装的浏览器程序来启动,因此想要通过WebView代为处理这个动作 ,那么需要通过WebViewClient</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/dd88b7a075231bb57b000b2aca529c55.png"></p>    <p>当然,我们也可以写一个类继承WebViewClient来对WebViewClient对象进行扩展</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/085653cb550e8da8467011e616fed5fb.png"></p>    <p>然后只需要将setWebViewClient的内容进行修改即可</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/52473701e9a06025c7ecbe1b8636f575.png"></p>    <p>另外出于用户习惯上的考虑 需要将WebView表现得更像一个浏览器,也就是需要可以回退历史记录,因此需要覆盖系统的回退键 goBack,goForward可向前向后浏览历史页面</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/598d940a88a709c9c6cf149bbf41f9aa.png"></p>    <p><strong>例子1:WebViewClient的使用</strong></p>    <p>布局代码activity_main.xml:</p>    <pre>  <code class="language-java"><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></code></pre>    <p>MainActivity代码:</p>    <pre>  <code class="language-java">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);      }  }</code></pre>    <h3><strong>3.JavaScript和java的相互调用</strong></h3>    <p>WebSetting用处非常大,通过WebSetting可以使用Android原生的JavascriptInterface来进行js和java的通信。</p>    <p>例子2:JavaScript和java的相互调用</p>    <p>首先我们写一段html代码:</p>    <pre>  <code class="language-java"><!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></code></pre>    <p>这是一个很简单的html5页面,里面有一个button,点击这个button就执行js脚本中的showToast方法。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/3b13894e2cf696c65a60c6c2d8163eb0.png"></p>    <p>那么这个showToast方法做了什么呢?</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/5e56a6b900c2fc8640eadbf4a94b496c.png"></p>    <p>可以看到control.showToast,这个是什么我们后面再说,下面看我们Android工程中的java代码。</p>    <p><strong>编写布局文件activity_main.xml</strong></p>    <p>布局的内容很简单,就是嵌套一个WebView控件</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c281e72238f574a477e0935cb8959fd1.png"></p>    <p><strong>编写MainActivity.java代码</strong></p>    <pre>  <code class="language-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);       }  }</code></pre>    <p>上面的代码主要做了以下的步骤:</p>    <p>a) 获取webview控件</p>    <p>b) 获取webview的设置,将JavaScript设置为可用,打开JavaScript的通道</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f248c6fe441f86f41cab2a508c635b0f.png"></p>    <p>c) 在Android程序中建立接口 ,并编写相关逻辑</p>    <p>再去看之前js脚本中的那个showToast()方法</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/65a2f4cde08417d8a0e88341c4f0ed28.png"></p>    <p>这里的control就是我们的那个interface,调用了interface的showToast方法,很明显这里是js调用了Android的代码,输出了一个Toast</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/610f874b2c65e49728eba57700852beb.png"></p>    <p>可以看到这个interface我们给它取名叫control,最后通过loadUrl加载页面。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/6a483baf823d9e6826238eb7332970e9.png"></p>    <p>可以看到先显示一个toast,然后调用log()方法,log()方法里调用了js脚本的log()方法, js的log()方法做的事就是在控制台输出msg,这里明显是Android调用了js的方法。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/58b133de3630c072caeea6fdb28bad51.png"></p>    <p>d) 给webview添加我们自己编写的JavaScript接口</p>    <p>通过WebView的addJavascriptInterface方法去注入一个我们自己写的interface。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/b75c61018db9d796b44677e7b94e060d.png"></p>    <p>e) 使用webview控件加载我们之前编写的html文件</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/9ce9707fe78dacf66b9fbb9a863bd81a.png"></p>    <p>在真实手机上运行程序,在控制台成功输出内容:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/328f21ac4ca16d207da0357cbed56ac1.png"></p>    <p>这样我们就完成了js和java的互调,是不是很简单。</p>    <h3><strong>4.Android中处理JS的警告,对话框等</strong></h3>    <p>在Android中处理JS的警告,对话框等需要对WebView设置WebChromeClient对象,并复写其中的onJsAlert,onJsConfirm,onJsPrompt方法可以处理javascript的常用对话框</p>    <p>例子3:在Android中处理javascript的对话框</p>    <p><strong>1) 编写html页面布局</strong></p>    <pre>  <code class="language-java"><%@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></code></pre>    <p>页面效果:</p>    <p><img src="https://simg.open-open.com/show/45683dde2c8420d1b7d0600c5f8c060a.png"></p>    <p><strong>2) Android中布局的编写</strong></p>    <pre>  <code class="language-java"><?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></code></pre>    <p><strong>3) 编写自定义对话框的布局</strong></p>    <p>新建prom_dialog.xml文件,在其中自定义一个带输入的对话框由TextView和EditText构成</p>    <pre>  <code class="language-java"><?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></code></pre>    <p><strong>4) 获取WebView控件,并进行相关的设置</strong></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/b9577e76737c7df6b6f2b156a3958032.png"></p>    <p><strong>5) 复写onKeyDown方法,当用户按返回键时,返回上一个加载的页面</strong></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/44f8551fee1552735c8c75c5f5d59fe8.png"></p>    <p><strong>6) 给WebView设置setWebChromeClient,并复写其中的方法</strong></p>    <pre>  <code class="language-java">// 设置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("输入网址错误,请重新输入");           }        }     });  }</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/000c10a51fe09310b2b987cbd35de944.png"></p>    <p>图1 dialog.html页面</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/fa1e6c858b6bdb75ef44f8618c9a4964.png"></p>    <p>图2 javascript的警告对话框</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/2cc52b904c9623a43bba98dcc153c746.png"></p>    <p>图3 javascript的confirm对话框</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c8c96ed8dd9d3351dde5b0ca17e06d4f.png"></p>    <p>图4 javascript的prompt对话框</p>    <p>总结:在这个项目中,使用setWebChromeClient方法来为WebView设置一个WebChromeClient对象,来辅助WebView来处理Javascript的对话框等,图4是我们自定义的对话框,图2和图3我们都只需要监听按钮的点击事件,然后通过confirm和cancel方法将我们的操作传递给Javascript进行处理。当你在图1的界面,点击第一个按钮时,会打开图2的对话框,点击第二个按钮时,会打开图3的对话框,同时在这里点击确定,会跳转到另一个页面,当点击第三个按钮时,会打开图4对话框,并且可以输入内容。</p>    <p> </p>    <p>来自:http://www.androidchina.net/6109.html</p>    <p> </p>