标签:android webviewclient webchromeclient 同步 异步
大家都知道,通过WebView,我们可以在Android客户端,用Web开发的方式来开发我们的应用。
如果一个应用就是单纯一个WebView,所有的逻辑都只需要在网页上交互的话,那我们其实就只需要通过html和javascript来跟服务器交互就可以了。
但是很多情况下,我们的应用不是单纯一个WebView就可以了,有可能会需要运用到Android本身的应用,比如拍照,就需要调用Android本身的照像机等,要产生震动,在需要运用到手机特性的一些场景下,肯定需要这么一套机制在javascript和Android之间互相通信,包括同步和异步的方式,而这套机制就是本文中我想要介绍的。
一步一步来,我们先从最简单的地方讲起:
1)需要一个WebView去展现我们的页面,首先定义一个布局,非常简单,就是一个WebView,如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width= "match_parent"
android:layout_height= "match_parent"
android:orientation= "vertical">
<WebView
android:id="@+id/html5_webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>2)在对应的Activity中,对WebView进行一些初始化
mWebView = (WebView) findViewById(R.id. html5_webview ); WebSettings webSettings = mWebView.getSettings(); webSettings.setJavaScriptCanOpenWindowsAutomatically( true ); webSettings.setJavaScriptEnabled( true ); webSettings.setLayoutAlgorithm(LayoutAlgorithm. NORMAL ); mWebView.setWebChromeClient( new WebServerChromeClient()); mWebView.setWebViewClient( new WebServerViewClient()); mWebView.setVerticalScrollBarEnabled( false ); mWebView.requestFocusFromTouch(); mWebView.addJavascriptInterface( new AppJavascriptInterface(), "nintf" );
2.1)webSettings.setJavaScriptEnabled( true );
告诉WebView,让它能够去执行JavaScript语句。在一个交互的网页上,javascript是没办法忽略的。
2.2)mWebView.setWebChromeClient( new WebServerChromeClient()); 2.3)mWebView.setWebViewClient( new WebServerViewClient());WebChromeClient和WebViewClient是WebView应用中的两个最重要的类。
通过这两个类,WebView能够捕获到Html页面中url的加载,javascript的执行等的所有操作,从而能够在Android的原生环境中对这些来自网页上的事件进行判断,解析,然后将对应的处理结果返回给html网页。
这两个类是html页面和Android原生环境交互的基础,所有通过html页面来跟后台交互的操作,都在这两个类里面实现,在后面我们还会详细说明。
2.4)mWebView.addJavascriptInterface( new AppJavascriptInterface(), "nintf" );
将我们自定义的AppJavascriptInterface类,调用mWebView的addJavascriptInterface方法,可以将这个对象传递给mWebView中Window对象的nintf属性("nintf"这个属性名称是自定义的)之后,
就可以直接在javascript中调用这个Java对象的方法。
3)接下来,我们就先来看看在Html中的javascript是如何跟Android原生环境来交互的。
我们按照事件发生的顺序机制来看,这样有个先后的概念,理解起来会容易一点。
在这套机制中,提供了两种访问Android原生环境的方法,一种是同步的,一种是异步的。
同步的概念就是说,我在跟你交流的时候,如果我还没有收到你的回复,我是不能跟其他人交流的,我必须等在那里,一直等着你。
异步的概念就是说,我在跟你交流的时候,如果你还没有回复我,我还能够去跟其他人交流,而当我收到你的回复的时候,再去看看你的回复,应该要干些什么。
3.1)同步访问
在Javascript中,我们定义了这样一个方法,如下:
var exec = function (service, action, args) {
var json = {
"service" : service,
"action" : action
};
var result_str = prompt(JSON.stringify(json), args);
var result;
try {
result = JSON.parse(result_str);
} catch (e) {
console.error(e.message);
}
var status = result.status;
var message = result.message;
if (status == 0) {
return message;
} else {
console.error( "service:" + service + " action:" + action + " error:" + message);
}
}exec( "Toast", "makeTextShort" , JSON.stringify(text));
在这里,我们调用了prompt方法,通过这个方法,在WebView中定义的的WebChromeClient就会拦截到这样一个方法,具体代码如下:
class WebServerChromeClient extends WebChromeClient {
@Override
public boolean onJsPrompt(WebView view, String url, String message,
String defaultValue, JsPromptResult result) {
System.out.println( "onJsPrompt:defaultValue:" + defaultValue + "|" + url + "," + message);
JSONObject args = null ;
JSONObject head = null ;
try {
head = new JSONObject(message);
args = new JSONObject(defaultValue);
String execResult = mPluginManager.exec(head.getString(IPlugin.SERVICE),
head.getString(IPlugin.ACTION), args);
result.confirm(execResult);
return true;
...
}
}而当返回false的时候,则此事件会继续传递给WebView,由WebView来处理。
由于我们这里是要利用这个Prompt方法,来实现Javascript跟Android原生环境之间的同步访问,所以我们在这里会拦截这个事件进行处理。
在这里,通过message和defaultValue,我们可以拿到javascript中prompt方法两个参数的值,在这里,它们是Json数据,在这里进行解析之后,由PluginManager来进行处理,最后将结果返回给JsPromptResult的confirm方法中。
此结果就是javascript中prompt的返回值了。
而除了JsPrompt,还有类似Javascript中的Alert方法等,我们知道浏览器弹出的Alert窗口跟我们手机应用中窗口风格样式是很不一样的,而作为一个应用,风格肯定要有一套统一的标准,所以一般情况下,我们也会拦截WebView中的Alert窗口,这个逻辑也同样会是在这里处理,如下:
@Override
public boolean onJsAlert(WebView view, String url, String message,
final JsResult result) {
System. out .println("onJsAlert : url:" + url + " | message:" + message);
if (isFinishing()) {
return true ;
}
CustomAlertDialog.Builder customBuilderres = new CustomAlertDialog.Builder(DroidHtml5.this );
customBuilderres.setTitle( "信息提示" ).setMessage(message)
.setPositiveButton( "确定" , new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
result.confirm();
}
}).create().show();
return true ;
}
3.2)异步访问
同样的,我们会在Javascript中定义如下一个方法:
var exec_asyn = function(service, action, args, success, fail) {
var json = {
"service" : service,
"action" : action
};
var result = AndroidHtml5.callNative(json, args, success, fail);
}
我们会调用AndroidHtml5的callNative,此方法有四个参数:
a)json:是调用的服务和操作
b)args: 对应的参数数
c)success : 成功时的回调方
d)fail:失败时的回调方
典型的调用如下:
var success = function(data){};
var fail = functio(data){};
exec_asyn( "Contacts", "openContacts" , '{}', success, fail);var AndroidHtml5 = {
idCounter : 0, // 参数序列计数器
OUTPUT_RESULTS : {}, // 输出的结果
CALLBACK_SUCCESS : {}, // 输出的结果成功时调用的方法
CALLBACK_FAIL : {}, // 输出的结果失败时调用的方法
callNative : function (cmd, args, success, fail) {
var key = "ID_" + (++ this.idCounter);
window.nintf.setCmds(cmd, key);
window.nintf.setArgs(args, key);
if (typeof success != 'undefined'){
AndroidHtml5.CALLBACK_SUCCESS[key] = success;
} else {
AndroidHtml5.CALLBACK_SUCCESS[key] = function (result){};
}
if (typeof fail != 'undefined'){
AndroidHtml5.CALLBACK_FAIL[key] = fail;
} else {
AndroidHtml5.CALLBACK_FAIL[key] = function (result){};
}
//下面会定义一个Iframe,Iframe会去加载我们自定义的url,以androidhtml:开头
var iframe = document.createElement("IFRAME" );
iframe.setAttribute( "src" , "androidhtml://ready?id=" + key);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null ;
return this .OUTPUT_RESULTS[key];
},
callBackJs : function (result,key) {
this .OUTPUT_RESULTS[key] = result;
var obj = JSON.parse(result);
var message = obj.message;
var status = obj.status;
if (status == 0) {
if (typeof this.CALLBACK_SUCCESS[key] != "undefined"){
setTimeout( "AndroidHtml5.CALLBACK_SUCCESS['" +key+"']('" + message + "')", 0);
}
} else {
if (typeof this.CALLBACK_FAIL != "undefined") {
setTimeout( "AndroidHtml5.CALLBACK_FAIL['" +key+"']('" + message + "')" , 0);
}
}
}
};window.nintf.setCmds(cmd, key); window.nintf.setArgs(args, key);
public class AppJavascriptInterface implements java.io.Serializable {
private static Hashtable<String,String> CMDS = new Hashtable<String,String>();
private static Hashtable<String,String> ARGS = new Hashtable<String,String>();
@JavascriptInterface
public void setCmds(String cmds, String id) {
CMDS .put(id, cmds);
}
@JavascriptInterface
public void setArgs(String args, String id) {
ARGS .put(id, args);
}
public static String getCmdOnce(String id) {
String result = CMDS .get(id);
CMDS .remove(id);
return result;
}
public static String getArgOnce(String id) {
String result = ARGS .get(id);
ARGS .remove(id);
return result;
}
}class WebServerViewClient extends WebViewClient {
Handler myHandler = new Handler() {
...
};
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url != null && url.startsWith( "androidhtml")) {
String id = url.substring(url.indexOf( "id=" ) + 3);
JSONObject cmd = null ;
JSONObject arg = null ;
try {
String cmds = AppJavascriptInterface.getCmdOnce(id);
String args = AppJavascriptInterface.getArgOnce(id);
cmd = new JSONObject(cmds);
arg = new JSONObject(args);
} catch (JSONException e1) {
e1.printStackTrace();
return false ;
}
//另起线程处理请求
try {
AsynServiceHandler asyn = new AsynServiceHandlerImpl();
asyn.setKey(id);
asyn.setService(cmd.getString( "service" ));
asyn.setAction(cmd.getString( "action" ));
asyn.setArgs(arg);
asyn.setWebView( mWebView);
asyn.setMessageHandler( myHandler );
Thread thread = new Thread(asyn, "asyn_" + (threadIdCounter ++));
thread.start();
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true ;
}
//如果url不是以Androidhtml开头的,则由WebView继续去处理。
view.loadUrl(url);
return true ;
}
}我们可以看到,在这方法中,首先只有以androidhtml开头的url才会被拦截处理,而其他的url则还是由WebView进行处理。
而通过AppJavascriptInterface,我们将在Javascript中保存的cmds和args等数据都拿出来了,并由AsynServiceHandler新启一个线程去处理。
我们再来看看AsynServiceHandlerImpl是怎么实现的,
public class AsynServiceHandlerImpl implements AsynServiceHandler {
@Override
public void run() {
try {
final String responseBody = PluginManager.getInstance().exec(service, action,args);
handler.post( new Runnable() {
public void run() {
webView .loadUrl( "javascript:AndroidHtml5.callBackJs('"+responseBody+ "','" +key +"')" );
}
});
} catch (PluginNotFoundException e) {
e.printStackTrace();
}
}可以看到,当调用PluginManager操作完对应的命令和数据之后,会通过WebView的loadUrl方法,去执行AndroidHtml5的callBackJs方法。
通过key值,我们就可以在AndroidHtml5中的callBackJs方法中找回到对应的回调方法,进行处理。
因此,通过一次Iframe的构建,加载以androidhtml开头的url,再利用WebView的WebViewClient接口对象,我们就能够在Html页面中和Android原生环境进行异步的交互了。
在这一篇文章中,我们几处地方讲到了PluginManager这个类,这是一个管理HTML和Android原生环境交互接口的类。
因为如果把所有的逻辑都放在WebViewClient或者WebChromeClient这两个都来处理,这是不合理的,乱,复杂,看不懂。
所以我们需要把逻辑实现跟交互给分开来,这个机制才显得漂亮,实用,易操作。
Android与WebView的同步和异步访问机制,布布扣,bubuko.com
标签:android webviewclient webchromeclient 同步 异步
原文地址:http://blog.csdn.net/linmiansheng/article/details/38324013