为啥含有艾滋病毒在空气中失活的血液在外界容易失活

Android的Webview中,javascript如何调用java方法
今天调查一个线上Bug,发现是WebView中的一小段javascript,会直接调用到后台APK的一个事件,最后导致java中nullpointexception。
感兴趣的是,WebView中的javascript如何调用APK中的java方法。
一个例子:
&&&&&&& 通过JS取得的GPS数据
第一步,WebKit的准备
首先,给与WebKit的javascript的执行许可
public void onCreate(Bundle icicle) {&
& super.onCreate(icicle);&
& WebView wv = new WebView(this);&
& wv.getSettings().setEnabled(true);//JS利用OK&
& setContentView(wv);&
然后,塞入自己的javascript拦截器
JsObj jo = new JsObj(this);&
wv.addJavascriptInterface(jo, &roid&);&
第二步,定义自己的javascript拦截器
class JsObj {&
& private C&
& public JsObj(Context con) {&
&&& this.con =&
& public String gps(String top, String end) {&
&&& LocationManager locman = (LocationManager)&&
&&&&&&&& con.getSystemService(Context.LOCATION_SERVICE);&
&&& Location loc = locman.getCurrentLocation(&gps&);&
&&& int lat = (int) (loc.getLatitude() * 1000000);&
&&& int lon = (int) (loc.getLongitude() * 1000000);&
&&& return top + &緯度:& + lat + &, 経度: & + lon +&
第三步,定义一个可运行的html
& &head&&title&JS calls Android Method&/title&&/head&&
&&& &h1&JS on Android&/h1&&
&&& &script type=&text/javascript&&&
&&&&& document.write(roid.gps(&&i&&, &&/i&&));&
&&& &/script&&
& &/body&&
在这个代码里面,可以用roid.gps的方法调用第二步定义的java函数
最后,全部的代码
package com.adamrocker.android.&
import android.app.A&
import android.content.C&
import android.location.L&
import android.location.LocationM&
import android.os.B&
import android.webkit.WebV&
public class WebkitTest extends Activity {&
& /** Called when the activity is first created. */&
& @Override&
&& public void onCreate(Bundle icicle) {&
&&&&& super.onCreate(icicle);&
&&&&& WebView wv = new WebView(this);&
&&&&& wv.getSettings().setJavaScriptEnabled(true);&
&&&&& JsObj jo = new JsObj(this);&
&&&&& wv.addJavascriptInterface(jo, &roid&);&
&&&&& setContentView(wv);&
&&&&& wv.loadUrl(&&);&
&& class JsObj {&
&&&&& private C&
&&&&& public JsObj(Context con) {&
&&&&&&&& this.con =&
&&&&& public String gps(String top, String end) {&
&&&&&&&& LocationManager locman = (LocationManager) con&
&&&&&&&&&&&&& .getSystemService(Context.LOCATION_SERVICE);&
&&&&&&&& Location loc = locman.getCurrentLocation(&gps&);&
&&&&&&&& int lat = (int) (loc.getLatitude() * 1000000);&
&&&&&&&& int lon = (int) (loc.getLongitude() * 1000000);&
&&&&&&&& return top + &緯度:& + lat + &, 経度: & + lon +&
我还想知道为什么,在webview里面定义一个JSObject,就可以连接javascript和后台函数
他们之间是如何通信的?
&我稍微调查了一下WebView的底层代码,webview初期化的时候
/* Initialize private data within the WebCore thread.
&private void& [More ...] initialize() {&
&&&& /* Initialize our private BrowserFrame class to handle all
&&&&& * frame-related functions. We need to create a new view which
&& * in turn creates a C level FrameView and attaches it to the frame.
&&&& mBrowserFrame = new BrowserFrame(mContext, this, mCallbackProxy,&
&&&&&&&&&&&& mSettings, mJavascriptInterfaces);&
&&&& mJavascriptInterfaces =&
&&&& // Sync the native settings and also create the WebCore thread handler.&
&&&& mSettings.syncSettingsAndCreateHandler(mBrowserFrame);&
&&&& // Create the handler and transfer messages for the IconDatabase&
&&&& WebIconDatabase.getInstance().createHandler();&
&&&& // Create the handler for WebStorage&
&&&& WebStorage.getInstance().createHandler();&
&&&& // Create the handler for GeolocationPermissions.&
&&&& GeolocationPermissions.getInstance().createHandler();&
&&&& // The transferMessages call will transfer all pending messages to the&
&&&& // WebCore thread handler.&
&&&& mEventHub.transferMessages();&
&&&& // Send a message back to WebView to tell it that we have set up the&
&&&& // WebCore thread.&
&&&& if (mWebView != null) {&
&&&&&&&& Message.obtain(mWebView.mPrivateHandler,&
&&&&&&&&&&&&&&&& WebView.WEBCORE_INITIALIZED_MSG_ID,&
&&&&&&&&&&&&&&&& mNativeClass, 0).sendToTarget();&
生成了显示用对象
mBrowserFrame
而此对象的所有操作事件,都会被
mEventHub截获
而mEventHub会将请求发送给真正需要处理的MessageStub。 通过messageName
Transfer all messages to the newly created webcore thread handler.&
& private void& [More ...] transferMessages() {&
&&&&& mTid = Process.myTid();&
&&&&& mSavedPriority = Process.getThreadPriority(mTid);&
&&&&& mHandler = new Handler() {&
&&&&&&&&& @Override&
&&&&&&&&& public void& [More ...] handleMessage(Message msg) {&
&&&&&&&&&&&&& if (DebugFlags.WEB_VIEW_CORE) {&
&&&&&&&&&&&&&&&&& Log.v(LOGTAG, (msg.what & REQUEST_LABEL&
&&&&&&&&&&&&&&&&&&&&&&&&& || msg.what&
&&&&&&&&&&&&&&&&&&&&&&&&& & VALID_NODE_BOUNDS ? Integer.toString(msg.what)&
&&&&&&&&&&&&&&&&&&&&&&&&& : HandlerDebugString[msg.what&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& - REQUEST_LABEL])&
&&&&&&&&&&&&&&&&&&&&&&&&& + & arg1=& + msg.arg1 + & arg2=& + msg.arg2&
&&&&&&&&&&&&&&&&&&&&&&&&& + & obj=& + msg.obj);&
&&&&&&&&&&&&& }&
&&&&&&&&&&&&& switch (msg.what) {&
&&&&&&&&&&&&&&&&& case WEBKIT_DRAW:&
&&&&&&&&&&&&&&&&&&&&& webkitDraw();&
作者:nanjingjiangbiao在WebView中如何让JS与Java安全地互相调用
共 51539 人围观,
发现评论数个
在现在安卓应用原生开发中,为了追求开发的效率以及移植的便利性,使用WebView作为业务内容展示与交互的主要载体是个不错的折中方案。那么在这种Hybrid(混合式) App中,难免就会遇到页面JS需要与Java相互调用,调用Java方法去做那部分网页JS不能完成的功能。网上的方法可以告诉我们这个时候我们可以使用addjavascriptInterface来注入原生接口到JS中,但是在安卓4.2以下的系统中,这种方案却我们的应用带来了很大的安全风险。攻击者如果在页面执行一些非法的JS(诱导用户打开一些钓鱼网站以进入风险页面),极有可能反弹拿到用户手机的shell权限。接下来攻击者就可以在后台默默安装木马,完全洞穿用户的手机。详细的攻击过程可以见乌云平台的这份报告:。安卓4.2及以上版本(API &= 17),在注入类中为可调用的方法添加@JavascriptInterface注解,无注解的方法不能被调用,这种方式可以防范注入漏洞。那么有没一种安全的方式,可以完全兼顾安卓4.2以下版本呢?答案就是使用prompt,即WebChromeClient 输入框弹出模式。我们参照&&这篇文章给出的解决方案, 但它JS下的方法有点笨拙, 动态生成JS文件过程也并没有清晰,且加载JS文件的时机也没有准确把握。那么如何改造才能便利地在JS代码中调用Java方法,并且安全可靠呢?下面提到的源码及项目可以在这找到。一、动态地生成将注入的JS代码JsCallJava在构造时,将要注入类的public且static方法拿出来,逐个生成方法的签名,依据方法签名先将方法缓存起来,同时结合方法名称与静态的HostApp-JS代码动态生成一段将要注入到webview中的字符串。JsCallJava.java1
public JsCallJava (String injectedName, Class injectedCls) {
mMethodsMap = new HashMap();
Method[] methods = injectedCls.getDeclaredMethods();
StringBuilder sb = new StringBuilder("javascript:(function(b){console.log(\"HostApp initialization begin\");var a={queue:[],callback:function(){var d=Array.prototype.slice.call(arguments,0);var c=d.shift();var e=d.shift();this.queue[c].apply(this,d);if(!e){delete this.queue[c]}}};");
for (Method method : methods) {
if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) || (sign = genJavaMethodSign(method)) == null) {
mMethodsMap.put(sign, method);
sb.append(String.format("a.%s=", method.getName()));
sb.append("function(){var f=Array.prototype.slice.call(arguments,0);if(f.length&1){throw\"HostApp call error, message:miss method name\"}var e=[];for(var h=1;h<f.h++){var c="f[h];var" j="typeof"e[e.length]="j;if(j==\" function\"){var"="" d="a.queue.a.queue[d]=c;f[h]=d}}var" g="JSON.parse(prompt(JSON.stringify({method:f.shift(),types:e,args:f})));if(g.code!=200){throw\" hostapp"="" call="" error,="" code:\"+g.code+\",="" message:\"+g.result}return="" g.result};object.getownpropertynames(a).foreach(function(d){var="" c.apply(a,[d].concat(array.prototype.slice.call(arguments,0)))}}});b." + injectedName + "=a;console.log(\"HostApp initialization end\")})(window);");
mPreloadInterfaceJS = sb.toString();
} catch(Exception e){
Log.e(TAG, "init js error:" + e.getMessage());
private String genJavaMethodSign (Method method) {
String sign = method.getName();
Class[] argsTypes = method.getParameterTypes();
int len = argsTypes.
if (len & 1 || argsTypes[0] != WebView.class) {
Log.w(TAG, "method(" + sign + ") must use webview to be first parameter, will be pass");
return null;
for (int k = 1; k & k++) {
Class cls = argsTypes[k];
if (cls == String.class) {
sign += "_S";
} else if (cls == int.class ||
cls == long.class ||
cls == float.class ||
cls == double.class) {
sign += "_N";
} else if (cls == boolean.class) {
sign += "_B";
} else if (cls == JSONObject.class) {
sign += "_O";
} else if (cls == JsCallback.class) {
sign += "_F";
sign += "_P";
从上面可以看出,类的各个方法名称被拼接到前后两段静态压缩的JS代码当中,那么这样生成的完整清晰的HostApp-JS片段是怎样的呢? 我们假设HostJsScope类中目前只定义了toast、alert、getIMSI这三个公开静态方法,那么完整的片段就是下面这样:HostApp JS片段1
(function(global){
console.log("HostApp initialization begin");
var hostApp = {
queue: [],
callback: function () {
var args = Array.prototype.slice.call(arguments, 0);
var index = args.shift();
var isPermanent = args.shift();
this.queue[index].apply(this, args);
if (!isPermanent) {
delete this.queue[index];
hostApp.toast = hostApp.alert = hostApp.getIMSI = function () {
var args = Array.prototype.slice.call(arguments, 0);
if (args.length & 1) {
throw "HostApp call error, message:miss method name";
var aTypes = [];
for (var i = 1;i & args.i++) {
var arg = args[i];
var type = typeof
aTypes[aTypes.length] =
if (type == "function") {
var index = hostApp.queue.
hostApp.queue[index] =
var res = JSON.parse(prompt(JSON.stringify({
method: args.shift(),
types: aTypes,
args: args
if (res.code != 200) {
throw "HostApp call error, code:" + res.code + ", message:" + res.
return res.
Object.getOwnPropertyNames(hostApp).forEach(function (property) {
var original = hostApp[property];
if (typeof original === 'function'&&property!=="callback") {
hostApp[property] = function () {
return original.apply(hostApp,
[property].concat(Array.prototype.slice.call(arguments, 0)));
global.HostApp = hostA
console.log("HostApp initialization end");
})(window);
其实在JsCallJava初始化时我们拼接的只是上面第15行&hostApp.toast = hostApp.alert = hostApp.getIMSI = function ()&这段。目的是将所有JS层调用函数嫁接到一个匿名函数1中,而后利用拦截技术,遍历hostApp下所有的函数,拿出对应的函数名,然后将hostApp下所有的函数调用嫁接到另一个匿名函数2,这样做的目的是hostApp下函数调用时首先执行匿名函数2,匿名函数2将对应的函数名作为第一个参数然后再调用匿名函数1,这样匿名函数1中就能区分执行时调用来源。实现了JS层调用入口统一,返回出口统一的结构体系。二、HostApp JS片段注入时机步骤一说明了HostApp-JS片段的拼接方法,同时JS片段拼接是在JsCallJava初始化完成的,而JsCallJava初始化是在实例化InjectedChromeClient对象时发起的。InjectedChromeClient.java1
public InjectedChromeClient (String injectedName, Class injectedCls) {
mJsCallJava = new JsCallJava(injectedName, injectedCls);
从步骤一的代码,我们知道JsCallJava拼接出来的JS代码暂时被存到mPreloadInterfaceJS字段中。那么我们何时把这段代码串注入到Webview的页面空间内呢?答案是页面加载进度变化的过程中。InjectedChromeClient.java1
public void onProgressChanged (WebView view, int newProgress) {
if (newProgress &= 25) {
mIsInjectedJS = false;
} else if (!mIsInjectedJS) {
view.loadUrl(mJsCallJava.getPreloadInterfaceJS());
mIsInjectedJS = true;
Log.d(TAG, " inject js interface completely on progress " + newProgress);
super.onProgressChanged(view, newProgress);
从上面我们可以看出,注入的时机是准确把握在进度大于25%时。如果在OnPageFinished注入,页面document.ready的初始回调会等待时间过长,详细的原因我们会在后面讲到。三、页面调用Java方法执行的过程OK,上面两步解决了动态生成与成功注入的两大问题,接下来就要处理JS具体的调用过程。上面,我们知道页面调用Java方法时,匿名js函数在拼接好参数后prompt json数据。prompt消息被Java层的WebChromeClient.onJsPrompt拦截到。InjectedChromeClient.java1
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
result.confirm(mJsCallJava.call(view, message));
return true;
而JsCallJava.call的具体实现如下。JsCallJava.java1
public String call(WebView webView, String jsonStr) {
if (!TextUtils.isEmpty(jsonStr)) {
JSONObject callJson = new JSONObject(jsonStr);
String methodName = callJson.getString("method");
JSONArray argsTypes = callJson.getJSONArray("types");
JSONArray argsVals = callJson.getJSONArray("args");
String sign = methodN
int len = argsTypes.length();
Object[] values = new Object[len + 1];
int numIndex = 0;
String currT
values[0] = webV
for (int k = 0; k & k++) {
currType = argsTypes.optString(k);
if ("string".equals(currType)) {
sign += "_S";
values[k + 1] = argsVals.isNull(k) ? null : argsVals.getString(k);
} else if ("number".equals(currType)) {
sign += "_N";
numIndex = numIndex * 10 + k + 1;
} else if ("boolean".equals(currType)) {
sign += "_B";
values[k + 1] = argsVals.getBoolean(k);
} else if ("object".equals(currType)) {
sign += "_O";
values[k + 1] = argsVals.isNull(k) ? null : argsVals.getJSONObject(k);
} else if ("function".equals(currType)) {
sign += "_F";
values[k + 1] = new JsCallback(webView, argsVals.getInt(k));
sign += "_P";
Method currMethod = mMethodsMap.get(sign);
if (currMethod == null) {
return getReturn(jsonStr, 500, "not found method(" + methodName + ") with valid parameters");
if (numIndex & 0) {
Class[] methodTypes = currMethod.getParameterTypes();
Class currC
while (numIndex & 0) {
currIndex = numIndex - numIndex / 10 * 10;
currCls = methodTypes[currIndex];
if (currCls == int.class) {
values[currIndex] = argsVals.getInt(currIndex - 1);
} else if (currCls == long.class) {
values[currIndex] = Long.parseLong(argsVals.getString(currIndex - 1));
values[currIndex] = argsVals.getDouble(currIndex - 1);
numIndex /= 10;
return getReturn(jsonStr, 200, currMethod.invoke(null, values));
} catch (Exception e) {
if (e.getCause() != null) {
return getReturn(jsonStr, 500, "method execute error:" + e.getCause().getMessage());
return getReturn(jsonStr, 500, "method execute error:" + e.getMessage());
return getReturn(jsonStr, 500, "call data empty");
这是一个完整的解析匹配过程,会依据js层传入的方法名、参数类型列表再次生成方法签名,与之前初始化构造好的缓存对象中的方法匹配。匹配成功后则判断js调用参数类型中是否有number类型,如果有依据Java层方法的定义决定是取int、long还是double类型的值。最后使用调用值列表和方法对象反射执行,返回函数执行的结果。这里有几点需要注意:方法反射执行时会将当前WebView的实例放到第一个参数,方便在HostJsScope静态方法依据Context拿到一些相关上下文信息;注入类(如HostJsScope)静态方法的参数定义可使用的类型有int/long/double、String、boolean、JSONObject、JsCallback,对应于js层传入的类型为number、string、boolean、object、function,注意number数字过大时(如时间戳),可能需要先转为string类型(Java方法中参数也须定义为String),避免精度丢失;Java方法的返回值可以是void 或 能转为字符串的类型(如int、long、String、double、float等)或&可序列化的自定义类型;如果执行失败或找不到调用方法时,Java层会将异常信息传递到JS层, JS匿名函数中会throw抛出错误;
刚表态过的朋友 ()
站长推荐 /5
全部都是百度网盘下载,包括Android Studio、SDK Tools、反编译工具、调试工具……
支持老罗Android视频,学习Android技术!学习是一种信仰!
技术GG还在愁如何赚安币?不要说我没有告诉你们攻略哦~
这个线上翻译活动将是一个长期做下去的活动...
又到了一年一度大总结的时刻了;今年你收获,失去或者成长了什么?你会为自己过去的2016打多少分?
Powered by您所在的位置: &
在WebView中如何让JS与Java安全地互相调用
在WebView中如何让JS与Java安全地互相调用
书呆子精神院
在现在安卓应用原生开发中,为了追求开发的效率以及移植的便利性,使用WebView作为业务内容展示与交互的主要载体是个不错的折中方案。那么在 这种Hybrid(混合式) App中,难免就会遇到页面JS需要与Java相互调用,调用Java方法去做那部分网页JS不能完成的功能。
在现在安卓应用原生开发中,为了追求开发的效率以及移植的便利性,使用WebView作为业务内容展示与交互的主要载体是个不错的折中方案。那么在 这种Hybrid(混合式) App中,难免就会遇到页面JS需要与Java相互调用,调用Java方法去做那部分网页JS不能完成的功能。
网上的方法可以告诉我们这个时候我们可以使用addjavascriptInterface来注入原生接口到 JS中,但是在安卓4.2以下的系统中,这种方案却我们的应用带来了很大的安全风险。攻击者如果在页面执行一些非法的JS(诱导用户打开一些钓鱼网站以进 入风险页面),极有可能反弹拿到用户手机的shell权限。接下来攻击者就可以在后台默默安装木马,完全洞穿用户的手机。详细的攻击过程可以见乌云平台的 这份报告:。
安卓4.2及以上版本(API &=
17),在注入类中为可调用的方法添加@JavascriptInterface注解,无注解的方法不能被调用,这种方式可以防范注入漏洞。那么有没一种 安全的方式,可以完全兼顾安卓4.2以下版本呢?答案就是使用prompt,即WebChromeClient 输入框弹出模式。
这篇文章给出的解决方案, 但它JS下的方法有点笨拙, 动态生成JS文件过程也并没有清晰,且加载JS文件的时机也没有准确把握。那么如何改造才能便利地在JS代码中调用Java方法,并且安全可靠呢?
下面提到的源码及项目可以在这找到。
一、动态地生成将注入的JS代码
JsCallJava在构造时,将要注入类的public且static方法拿出来,逐个生成方法的签名,依据方法签名先将方法缓存起来,同时结合方法名称与静态的HostApp-JS代码动态生成一段将要注入到webview中的字符串。
public&JsCallJava&(String&injectedName,&Class&injectedCls)&{&&&&&try&{&&&&&&&&&mMethodsMap&=&new&HashMap&String,&Method&();&&&&&&&&&&&&&&&&&&Method[]&methods&=&injectedCls.getDeclaredMethods();&&&&&&&&&StringBuilder&sb&=&new&StringBuilder(&javascript:(function(b){console.log(\&HostApp&initialization&begin\&);var&a={queue:[],callback:function(){var&d=Array.prototype.slice.call(arguments,0);var&c=d.shift();var&e=d.shift();this.queue[c].apply(this,d);if(!e){delete&this.queue[c]}}};&);&&&&&&&&&&for&(Method&method&:&methods)&{&&&&&&&&&&&&&String&&&&&&&&&&&&&&if&(method.getModifiers()&!=&(Modifier.PUBLIC&|&Modifier.STATIC)&||&(sign&=&genJavaMethodSign(method))&==&null)&{&&&&&&&&&&&&&&&&&continue;&&&&&&&&&&&&&}&&&&&&&&&&&&&mMethodsMap.put(sign,&method);&&&&&&&&&&&&&sb.append(String.format(&a.%s=&,&method.getName()));&&&&&&&&&}&&&&&&&&&&sb.append(&function(){var&f=Array.prototype.slice.call(arguments,0);if(f.length&1){throw\&HostApp&call&error,&message:miss&method&name\&}var&e=[];for(var&h=1;h&f.h++){var&c=f[h];var&j=typeof&c;e[e.length]=j;if(j==\&function\&){var&d=a.queue.a.queue[d]=c;f[h]=d}}var&g=JSON.parse(prompt(JSON.stringify({method:f.shift(),types:e,args:f})));if(g.code!=200){throw\&HostApp&call&error,&code:\&+g.code+\&,&message:\&+g.result}return&g.result};Object.getOwnPropertyNames(a).forEach(function(d){var&c=a[d];if(typeof&c===\&function\&&&d!==\&callback\&){a[d]=function(){return&c.apply(a,[d].concat(Array.prototype.slice.call(arguments,0)))}}});b.&&+&injectedName&+&&=a;console.log(\&HostApp&initialization&end\&)})(window);&);&&&&&&&&&mPreloadInterfaceJS&=&sb.toString();&&&&&}&catch(Exception&e){&&&&&&&&&Log.e(TAG,&&init&js&error:&&+&e.getMessage());&&&&&}&}&&private&String&genJavaMethodSign&(Method&method)&{&&&&&String&sign&=&method.getName();&&&&&Class[]&argsTypes&=&method.getParameterTypes();&&&&&int&len&=&argsTypes.&&&&&if&(len&&&1&||&argsTypes[0]&!=&WebView.class)&{&&&&&&&&&Log.w(TAG,&&method(&&+&sign&+&&)&must&use&webview&to&be&first&parameter,&will&be&pass&);&&&&&&&&&return&null;&&&&&}&&&&&for&(int&k&=&1;&k&&&&k++)&{&&&&&&&&&Class&cls&=&argsTypes[k];&&&&&&&&&if&(cls&==&String.class)&{&&&&&&&&&&&&&sign&+=&&_S&;&&&&&&&&&}&else&if&(cls&==&int.class&||&&&&&&&&&&&&&cls&==&long.class&||&&&&&&&&&&&&&cls&==&float.class&||&&&&&&&&&&&&&cls&==&double.class)&{&&&&&&&&&&&&&sign&+=&&_N&;&&&&&&&&&}&else&if&(cls&==&boolean.class)&{&&&&&&&&&&&&&sign&+=&&_B&;&&&&&&&&&}&else&if&(cls&==&JSONObject.class)&{&&&&&&&&&&&&&sign&+=&&_O&;&&&&&&&&&}&else&if&(cls&==&JsCallback.class)&{&&&&&&&&&&&&&sign&+=&&_F&;&&&&&&&&&}&else&{&&&&&&&&&&&&&sign&+=&&_P&;&&&&&&&&&}&&&&&}&&&&&return&&}&
从上面可以看出,类的各个方法名称被拼接到前后两段静态压缩的JS代码当中,那么这样生成的完整清晰的HostApp-JS片段是怎样的呢?
我们假设HostJsScope类中目前只定义了toast、alert、getIMSI这三个公开静态方法,那么完整的片段就是下面这样:
(function(global){&&&&&console.log(&HostApp&initialization&begin&);&&&&&var&hostApp&=&{&&&&&&&&&queue:&[],&&&&&&&&&callback:&function&()&{&&&&&&&&&&&&&var&args&=&Array.prototype.slice.call(arguments,&0);&&&&&&&&&&&&&var&index&=&args.shift();&&&&&&&&&&&&&var&isPermanent&=&args.shift();&&&&&&&&&&&&&this.queue[index].apply(this,&args);&&&&&&&&&&&&&if&(!isPermanent)&{&&&&&&&&&&&&&&&&&delete&this.queue[index];&&&&&&&&&&&&&}&&&&&&&&&}&&&&&};&&&&&hostApp.toast&=&hostApp.alert&=&hostApp.getIMSI&=&function&()&{&&&&&&&&&var&args&=&Array.prototype.slice.call(arguments,&0);&&&&&&&&&if&(args.length&&&1)&{&&&&&&&&&&&&&throw&&HostApp&call&error,&message:miss&method&name&;&&&&&&&&&}&&&&&&&&&var&aTypes&=&[];&&&&&&&&&for&(var&i&=&1;i&&&args.i++)&{&&&&&&&&&&&&&var&arg&=&args[i];&&&&&&&&&&&&&var&type&=&typeof&&&&&&&&&&&&&&aTypes[aTypes.length]&=&&&&&&&&&&&&&&if&(type&==&&function&)&{&&&&&&&&&&&&&&&&&var&index&=&hostApp.queue.&&&&&&&&&&&&&&&&&hostApp.queue[index]&=&&&&&&&&&&&&&&&&&&args[i]&=&&&&&&&&&&&&&&}&&&&&&&&&}&&&&&&&&&var&res&=&JSON.parse(prompt(JSON.stringify({&&&&&&&&&&&&&method:&args.shift(),&&&&&&&&&&&&&types:&aTypes,&&&&&&&&&&&&&args:&args&&&&&&&&&})));&&&&&&&&&&if&(res.code&!=&200)&{&&&&&&&&&&&&&throw&&HostApp&call&error,&code:&&+&res.code&+&&,&message:&&+&res.&&&&&&&&&}&&&&&&&&&return&res.&&&&&};&&&&&&&&&&&&&&&&&&&&&&&&&&&&Object.getOwnPropertyNames(hostApp).forEach(function&(property)&{&&&&&&&&&var&original&=&hostApp[property];&&&&&&&&&&if&(typeof&original&===&'function'&&property!==&callback&)&{&&&&&&&&&&&&&hostApp[property]&=&function&()&{&&&&&&&&&&&&&&&&&return&original.apply(hostApp,&&[property].concat(Array.prototype.slice.call(arguments,&0)));&&&&&&&&&&&&&};&&&&&&&&&}&&&&&});&&&&&global.HostApp&=&hostA&&&&&console.log(&HostApp&initialization&end&);&})(window);&
其实在JsCallJava初始化时我们拼接的只是上面第15行 hostApp.toast = hostApp.alert = hostApp.getIMSI = function ()
这段。目的是将所有JS层调用函数嫁接到一个匿名函数1中,而后利用拦截技术,遍历hostApp下所有的函数,拿出对应的函数名,然后将hostApp 下所有的函数调用嫁接到另一个匿名函数2,这样做的目的是hostApp下函数调用时首先执行匿名函数2,匿名函数2将对应的函数名作为第一个参数然后再 调用匿名函数1,这样匿名函数1中就能区分执行时调用来源。实现了JS层调用入口统一,返回出口统一的结构体系。
二、HostApp JS片段注入时机
步骤一说明了HostApp-JS片段的拼接方法,同时JS片段拼接是在JsCallJava初始化完成的,而JsCallJava初始化是在实例化InjectedChromeClient对象时发起的。
public&InjectedChromeClient&(String&injectedName,&Class&injectedCls)&{&&&&&mJsCallJava&=&new&JsCallJava(injectedName,&injectedCls);&}&
从步骤一的代码,我们知道JsCallJava拼接出来的JS代码暂时被存到mPreloadInterfaceJS字段中。那么我们何时把这段代码串注入到Webview的页面空间内呢?答案是页面加载进度变化的过程中。
@Override&public&void&onProgressChanged&(WebView&view,&int&newProgress)&{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&if&(newProgress&&=&25)&{&&&&&&&&&mIsInjectedJS&=&false;&&&&&}&else&if&(!mIsInjectedJS)&{&&&&&&&&&view.loadUrl(mJsCallJava.getPreloadInterfaceJS());&&&&&&&&&mIsInjectedJS&=&true;&&&&&&&&&Log.d(TAG,&&&inject&js&interface&completely&on&progress&&&+&newProgress);&&&&&&}&&&&&super.onProgressChanged(view,&newProgress);&}&
从上面我们可以看出,注入的时机是准确把握在进度大于25%时。如果在OnPageFinished注入,页面document.ready的初始回调会等待时间过长,详细的原因我们会在后面讲到。
三、页面调用Java方法执行的过程
OK,上面两步解决了动态生成与成功注入的两大问题,接下来就要处理JS具体的调用过程。上面,我们知道页面调用Java方法时,匿名js函数在拼 接好参数后prompt json数据。prompt消息被Java层的WebChromeClient.onJsPrompt拦截到。
@Override&public&boolean&onJsPrompt(WebView&view,&String&url,&String&message,&String&defaultValue,&JsPromptResult&result)&{&&&&&result.confirm(mJsCallJava.call(view,&message));&&&&&return&true;&}&
而JsCallJava.call的具体实现如下。
public&String&call(WebView&webView,&String&jsonStr)&{&&&&&if&(!TextUtils.isEmpty(jsonStr))&{&&&&&&&&&try&{&&&&&&&&&&&&&JSONObject&callJson&=&new&JSONObject(jsonStr);&&&&&&&&&&&&&String&methodName&=&callJson.getString(&method&);&&&&&&&&&&&&&JSONArray&argsTypes&=&callJson.getJSONArray(&types&);&&&&&&&&&&&&&JSONArray&argsVals&=&callJson.getJSONArray(&args&);&&&&&&&&&&&&&String&sign&=&methodN&&&&&&&&&&&&&int&len&=&argsTypes.length();&&&&&&&&&&&&&Object[]&values&=&new&Object[len&+&1];&&&&&&&&&&&&&int&numIndex&=&0;&&&&&&&&&&&&&String&currT&&&&&&&&&&&&&&values[0]&=&webV&&&&&&&&&&&&&&for&(int&k&=&0;&k&&&&k++)&{&&&&&&&&&&&&&&&&&currType&=&argsTypes.optString(k);&&&&&&&&&&&&&&&&&if&(&string&.equals(currType))&{&&&&&&&&&&&&&&&&&&&&&sign&+=&&_S&;&&&&&&&&&&&&&&&&&&&&&values[k&+&1]&=&argsVals.isNull(k)&?&null&:&argsVals.getString(k);&&&&&&&&&&&&&&&&&}&else&if&(&number&.equals(currType))&{&&&&&&&&&&&&&&&&&&&&&sign&+=&&_N&;&&&&&&&&&&&&&&&&&&&&&numIndex&=&numIndex&*&10&+&k&+&1;&&&&&&&&&&&&&&&&&}&else&if&(&boolean&.equals(currType))&{&&&&&&&&&&&&&&&&&&&&&sign&+=&&_B&;&&&&&&&&&&&&&&&&&&&&&values[k&+&1]&=&argsVals.getBoolean(k);&&&&&&&&&&&&&&&&&}&else&if&(&object&.equals(currType))&{&&&&&&&&&&&&&&&&&&&&&sign&+=&&_O&;&&&&&&&&&&&&&&&&&&&&&values[k&+&1]&=&argsVals.isNull(k)&?&null&:&argsVals.getJSONObject(k);&&&&&&&&&&&&&&&&&}&else&if&(&function&.equals(currType))&{&&&&&&&&&&&&&&&&&&&&&sign&+=&&_F&;&&&&&&&&&&&&&&&&&&&&&values[k&+&1]&=&new&JsCallback(webView,&argsVals.getInt(k));&&&&&&&&&&&&&&&&&}&else&{&&&&&&&&&&&&&&&&&&&&&sign&+=&&_P&;&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&}&&&&&&&&&&&&&&Method&currMethod&=&mMethodsMap.get(sign);&&&&&&&&&&&&&&&&&&&&&&&&&&&if&(currMethod&==&null)&{&&&&&&&&&&&&&&&&&return&getReturn(jsonStr,&500,&&not&found&method(&&+&methodName&+&&)&with&valid&parameters&);&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&&&&&&&&if&(numIndex&&&0)&{&&&&&&&&&&&&&&&&&Class[]&methodTypes&=&currMethod.getParameterTypes();&&&&&&&&&&&&&&&&&int&currI&&&&&&&&&&&&&&&&&Class&currC&&&&&&&&&&&&&&&&&while&(numIndex&&&0)&{&&&&&&&&&&&&&&&&&&&&&currIndex&=&numIndex&-&numIndex&/&10&*&10;&&&&&&&&&&&&&&&&&&&&&currCls&=&methodTypes[currIndex];&&&&&&&&&&&&&&&&&&&&&if&(currCls&==&int.class)&{&&&&&&&&&&&&&&&&&&&&&&&&&values[currIndex]&=&argsVals.getInt(currIndex&-&1);&&&&&&&&&&&&&&&&&&&&&}&else&if&(currCls&==&long.class)&{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&values[currIndex]&=&Long.parseLong(argsVals.getString(currIndex&-&1));&&&&&&&&&&&&&&&&&&&&&}&else&{&&&&&&&&&&&&&&&&&&&&&&&&&values[currIndex]&=&argsVals.getDouble(currIndex&-&1);&&&&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&&&numIndex&/=&10;&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&}&&&&&&&&&&&&&&return&getReturn(jsonStr,&200,&currMethod.invoke(null,&values));&&&&&&&&&}&catch&(Exception&e)&{&&&&&&&&&&&&&&&&&&&&&&&&&&if&(e.getCause()&!=&null)&{&&&&&&&&&&&&&&&&&return&getReturn(jsonStr,&500,&&method&execute&error:&&+&e.getCause().getMessage());&&&&&&&&&&&&&}&&&&&&&&&&&&&return&getReturn(jsonStr,&500,&&method&execute&error:&&+&e.getMessage());&&&&&&&&&}&&&&&}&else&{&&&&&&&&&return&getReturn(jsonStr,&500,&&call&data&empty&);&&&&&}&}&
这是一个完整的解析匹配过程,会依据js层传入的方法名、参数类型列表再次生成方法签名,与之前初始化构造好的缓存对象中的方法匹配。匹配成功后则 判断js调用参数类型中是否有number类型,如果有依据Java层方法的定义决定是取int、long还是double类型的值。最后使用调用值列表 和方法对象反射执行,返回函数执行的结果。这里有几点需要注意:
方法反射执行时会将当前WebView的实例放到第一个参数,方便在HostJsScope静态方法依据Context拿到一些相关上下文信息;
注入类(如HostJsScope)静态方法的参数定义可使用的类型有int/long/double、String、boolean、 JSONObject、JsCallback,对应于js层传入的类型为number、string、boolean、object、function, 注意number数字过大时(如时间戳),可能需要先转为string类型(Java方法中参数也须定义为String),避免精度丢失;
Java方法的返回值可以是void 或 能转为字符串的类型(如int、long、String、double、float等)或 可序列化的自定义类型;
如果执行失败或找不到调用方法时,Java层会将异常信息传递到JS层, JS匿名函数中会throw抛出错误;
四、HostApp在页面的使用
有了上面的准备工作,现在我们在页面中就可以很方便地使用HostApp了,而不需要加载任何依赖文件。如li标签的点击:
&class=&entry&&&&&&&onclick=&HostApp.alert('HostApp.alert');&HostApp.alert&&&&&&onclick=&HostApp.toast('HostApp.toast');&HostApp.toast&&&&&&onclick=&HostApp.testLossTime(new&Date().getTime()&+&'');&HostApp.testLossTime&&&&&&&onclick=&HostApp.toast(HostApp.getIMSI());&HostApp.getIMSI&&
但同时有一种业务情景时,页面初始加载完备时就应立即触发的调用,如果我们这样写:
document.addEventListener('DOMContentLoaded',&function()&{&&&&&HostApp.toast('document&ready&now');;&},&false);&
那么HostApp的调用极有可能不成功,因为端注入HostApp-JS片段的时机可能在document.ready前也可能在其后。那么如何解决这个矛盾的问题呢?
如果document.ready的时候HostApp
JS已经注入成功,这种情况OK没有问题。当document.ready的时候HostApp
JS还未开始注入,这种情景下我们的js脚本层就需要做出变动,即轮询状态,直到端注入成功或者超时(1.5s),再发生回调。具体实现如下(下面的是以 zepto.js的$.ready()函数改造为例)。
&&&$.fn&=&{&&&&&&&&&&ready:&function(callback,&jumpHostAppInject)&{&&&&&&&&&var&originCb&=&&&&&&&&&&var&mcounter&=&0;&&&&&&&&&&&&&&&&&&callback&=&function&()&{&&&&&&&&&&&&&if(!window.HostApp&&&&mcounter++&&&150)setTimeout(callback,&10);else&originCb($);&&&&&&&&&};&&&&&&&&&&&&&&&&&&if&(jumpHostAppInject)&{&&&&&&&&&&&&&callback&=&originCb;&&&&&&&&&}&&&&&&&&&if&(readyRE.test(document.readyState))&callback($);&else&document.addEventListener('DOMContentLoaded',&function()&{&&&&&&&&&&&&&&&&&callback($)&&&&&&&&&&&&&},&false);&&&&&&&&&return&this&&&&&},&&&&&...&&&&&...&};&
这样的机制也就解释了为什么不把Java层的JS注入放在OnPageFinish了,如果那样页面轮询的次数就会上升,等待的时间就会变长,而且有可能会超时。好了,有了上面的改动,页面初始加载完备时需要立即触发HostApp的调用,如下:
&type=&text/javascript&&&&&&$(function&()&{&&&&&&&&&HostApp.alert(&HostApp&ready&now&);&&&&&});&&
更多使用说明及完整源代码见:
【编辑推荐】
【责任编辑: TEL:(010)】
关于&&&&&&的更多文章
Java 8版本最大的改进就是Lambda表达式,其目的是使Java更易于为
讲师: 144人学习过讲师: 56人学习过讲师: 22人学习过
国际消费类电子产品展览会(International Consumer E
你心爱的那些移动游戏为什么会如此吸引你?就是那些你
本专题意在帮助想要了解Android的人能快速上手Android
20多年以来,《软件工程:实践者的研究方法》一书是最受学生和行业专业人员欢迎的软件工程指南。它在全面而系统、概括而清晰地介
Windows Phone专家
Android开发专家
51CTO旗下网站

我要回帖

更多关于 失活 的文章

 

随机推荐