Android原生与JS交互总结

您所在的位置:网站首页 vue与android交互 Android原生与JS交互总结

Android原生与JS交互总结

2022-06-10 15:34| 来源: 网络整理| 查看: 265

[TOC]前言

这里说的交互,指的是采用官方提供的方法,其它实现方式(如拦截url,拦截prompt)不在本文描述范围之内。

JS调用原生

通过给方法添加@JavascriptInterface注解,然后通过mWebView.addJavascriptInterface(object, name)将刚才那个方法所在的类注入JS,然后js就可以直接通过name.方法名()来调用刚才那个方法。

示例

获取包名:

mWebView.addJavascriptInterface(new Object() { @JavascriptInterface public String getPackageName() { return mWebView.getContext().getPackageName(); } }, "test");

然后在js里面执行test.getPackageName()就可以获取当前apk的包名了:

上面偷懒直接采用匿名内部类的方式,正常情况下一般不会这么写。

重载与参数类型转换

众所周知,js不支持重载,Java有,那么注入到js的java方法是否支持重载呢?

下面以获取versionCode和versionName为例来验证注入JS时的重载与类型转换问题。

测试代码//TestJavaScript.java public class TestJavaScript { private Activity mActivity; public TestJavaScript(Activity mActivity) { this.mActivity = mActivity; } /** * 获取版本号 * @return */ @JavascriptInterface public String getVersion() throws NameNotFoundException { Log.i("info", "进入第1个getVersion方法"); return getVersion(null); } /** * 获取版本号 * @return */ @JavascriptInterface public String getVersion(String type) throws NameNotFoundException { Log.i("info", "进入第2个getVersion方法"); Log.i("info", "type:"+type); if(type == null || "".equals(type)) type = "name"; PackageInfo info = mActivity.getPackageManager().getPackageInfo(mActivity.getPackageName(), 0); if("name".equals(type)) return info.versionName; else if("code".equals(type)) return info.versionCode+""; else return "type error"; } } //WelcomeActivity.java mWebView.addJavascriptInterface(new TestJavaScript(this), "test");

说明:type为name时返回versionName,type为code时返回versionCode,当然这里这样写仅仅是为了测试,实际环境中这样写的人肯定是找骂,哈哈。

开始测试

以上例子测试代码:

test.getVersion(); // 返回1.0 进入第1个getVersion方法 test.getVersion(null); // 返回1.0 进入第2个getVersion方法,获取到的type为null test.getVersion(undefined); // 返回 type error,进入第2个方法,获取到的type为字符串形式的"undefined" test.getVersion('name'); // 返回1.0, test.getVersion('code'); // 返回1 test.getVersion('code', 1); //提示 Error calling method on NPObject 错误 test.getVersion(222); // 提示 type error,进入第二个方法,获取到的是字符串的"222"

假如只有下面这一个方法:

@JavascriptInterface public String getVersion(int a) { Log.i("info", "进入int型getVersion方法,参数:" + a); return "进入int型getVersion方法"; }

测试时:

test.getVersion(123); // 接收到参数为123 test.getVersion('abc'); // 依然可以进入这个int型的方法,但是接收到的参数为0

假如只有下面这2个方法:

@JavascriptInterface public String getVersion(String a) { Log.i("info", "进入String型getVersion方法,参数:" + a); return "进入String型getVersion方法"; } @JavascriptInterface public String getVersion(int a) { Log.i("info", "进入int型getVersion方法,参数:" + a); return "进入int型getVersion方法"; }

测试时:

test.getVersion(123); // 进入int型方法,接收到参数为123 test.getVersion('abc'); // 进入string型方法,接收到参数为'abc'

再假设有如下代码:

@JavascriptInterface public void testParam(String param1, String param2) { System.out.println(param1 == null ? "@NULL" : param1); System.out.println(param2 == null ? "@NULL" : param2); }

js测试代码:

test.testParam('abc'); // Error: Error calling method on NPObject. test.testParam('abc', undefined); // js没报错,后台输出 abc undefined(注意这个undefined是加了双引号的) test.testParam('abc', null); // js没报错,后台输出 abc @NULL test.testParam('abc', 'cbd'); // js 没报错,后台输出 abc cbd

还有一些小测试,懒得逐一贴代码了,直接上结论。

重要结论

Java注入js时是区分重载的,但是由于存在类型转换,所以并不会完全像Java那样严格区分,换句话说,对于参数个数的不同会严格区分(前后端参数个数不同甚至会报错),但是对于参数类型的不同则没那么严格,如果能够找到正确类型的方法,那么会优先进入这个方法,如果找不到,则进入参数个数相同、类型不同的其它方法,会自动进行类型转换。

参数类型转换规则

假如只有一个参数类型是String的方法:

null 自动转 java 的 null;undefined 自动转 java 的 字符串 "undefined";123 自动转 "123";true 自动转 "true";'abc' 转换正常的 "abc";

假如只有一个参数类型是int的方法:

null 自动转 0;undefined 自动转 0;'abc' 自动转 0;true/false 全部自动转 0;123 转换正常的 123;

其它类型,比如时间、对象等就没测试了,因为实际使用中,string和int这2个足够用了。

为避免一些不必要的麻烦,建议注入方法时少用重载,尽量避免不同类型参数自动转换,毕竟多一事不如少一事,多取几个名字就是了。

Error calling method on NPObject

错误如下:

Error: Error calling method on NPObject.

如果是在调用原生方法出现这个错误,一般有3个原因:

由于参数个数不正确导致的,上面的例子也提到了,假如方法只有1个参数,但是你传了2个参数,或者定义了2个参数,但是你只传了1个参数,那么就会报这个错误;代码必须运行在UI线程中(如调用mWebView.goBack()),即mActivity.runOnUiThread;原生方法内部出错,比如常见的空指针异常等,都会引起这个错误; 切勿使用包装类型

假设有如下方法:

@JavascriptInterface public String testInt(Integer a) { return "int:"+a; } @JavascriptInterface public String testBoolean(Boolean a) { return "boolean:"+a; }

调用:

test.testInt(123); // 输出"int:null" test.testBoolean(true); // 输出"boolean:null" test.testBoolean(false); // 输出"boolean:null"

可以发现,如果Android这边参数使用了包装类型会导致参数接收不到,必须使用基本类型,把上面的Integer和Boolean换成int和boolean就没问题了。

注入有效期

只需要对webview全局注入一次,无需针对每个页面重新注入,一次注入“永久”生效。

有人会问,为啥cordova/phonegap、mui等都必须在某一事件触发之后才能调用原生提供的方法呢?也就是为啥它们是针对具体页面注入的?这个是因为它们实现机制和我们这里不一样,而且还有很多其它方面考虑,后续有机会再着重讨论这个问题。

JavascriptInterface注解漏洞

Android4.2开始增加@JavascriptInterface注解,目的是为了解决一个 漏洞 ,在4.2版本之后,只有添加了这个注解的方法才会被注入JS。

iframe问题

对iframe的支持不同设备不一样,有的会注入到iframe里面,有的不会。

//TODO 本小节有待完善。

远程调试提示问题

即使对象注入成功了,在控制台输入的时候,test会有提示,但是test.再往后面就没有提示了,这是正常现象,不要以为没有注入成功。

原生调用JS

一般都是通过mWebView.loadUrl('javascript:xxx')来执行一段JS,据说这个方法有一个bug,就是执行的时候如果输入法是弹出的,执行后输入法会自动消失,我暂未亲测。

这个方法最大的缺点是无法优雅的解决异步回调问题,鉴于此,Android4.4开始增加了如下方法:

mWebView.evaluateJavascript(script, resultCallback)

有了这个方法就可以非常方便的实现js的异步回调,但是毕竟低于Android4.4的版本还是有比较大的份额,所以一般还是得自己另行解决异步回调的问题。

//TODO 这里还有待完善



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3