ndroid在加载网页H5时,会用到WebView组件,以实现服务端可配置,灵活定义Action。
这个时候就需要Java与JavaScript进行交互,典型的应用场景就是电商类APP,如京东和淘宝。
WebView支持JavaScript这些交互动作,那么问题来了,我们怎么实现Java编写的安卓程序与JavaScript编写的网页进行交互呢?下面给大家介绍下,如何实现Java与JavaScript之间的相互调用。
在Java层调用JavaScript的方式:
Java中调用JavaScript有两种方式,同步和异步:
同步阻塞UI线程的方式: webView.loadUrl("javascript:funtion()")
异步非阻塞方式: evaluateJavaScript()(在API level 19加入)
请看下面代码:
java中
// android 调用 JavaScript public void callJavaScript(View view) { if (Build.VERSION.SDK_INT >=Build.VERSION_CODES.KITKAT) { // 异步运行调用JavaScript的方式 mWebView.evaluateJavascript("javascript:callAlert()", new ValueCallback<String>() { @Override public void onReceiveValue(String value) { Log.e(TAG, "onReceiveValue" + value);//打印返回的值 } }); } else { if (mWebView !=null) { // 同步阻塞UI线程式调用 mWebView.loadUrl("javascript:callAlert()"); } } }
JavaScript中
function callAlert(){ alert("Android call html Alert function !"); return "这是需要返回的值"; }
在JavaScript调用Android中Java的方式
在JavaScript有三种方式可以调用java代码:
使用 mWebView.addJavascriptInterface(new JavaScriptInject(this), "JavaScriptInject")
java中
mWebView=findViewById(R.id.webview); mWebView.getSettings().setAllowFileAccessFromFileURLs(true); mWebView.addJavascriptInterface(new JavaScriptInject(this), "JavaScriptInject"); public class JavaScriptInject { private Context mContext; JavaScriptInject(Context context) { this.mContext=context; } // 被JS调用的方法必须加入@JavascriptInterface注解 @JavascriptInterface public void callAndroid(String string) { Toast.makeText(mContext,"javascript call Android", Toast.LENGTH_LONG).show(); Log.e("JavaScriptInject", "getAndroidInfo " + string); } }
JavaScript中
function callAndroid(){ JavaScriptInject.callAndroid("javascript call android"); }
2. 通过重写WebChromeClient中的回调方法拦截JS不同对话框消息:
onJsAlert(WebView view, String url, String message, JsResult result) 对应拦截 alert()
onJsConfirm(WebView view, String url, String message, JsResult result) 对应拦截 confirm()
onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) 对应拦截 prompt()
java中
mWebView.setWebChromeClient(new WebChromeClient() { @Override public boolean onJsAlert(WebView view, String url, String message, JsResult result) { Log.i(TAG,"onJsAlert"); return super.onJsAlert(view, url, message, result); } @Override public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { Log.i(TAG,"onJsConfirm"); return super.onJsConfirm(view, url, message, result); } @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { Log.i(TAG,"onJsPrompt:"+ message); return super.onJsPrompt(view, url, message, defaultValue, result); } });
JavaScript中
function clickPrompt(){ prompt("传递给Android的值"); } function clickConfirm(){ confirm("传递给Android的值"); } function clickAlert(){ alert("传递给Android的值"); }
webview页面中
<body> <button type="button" id="button3" onclick="clickPrompt()">点击调用Android onJsPrompt</button> <button type="button" id="button4" onclick="clickConfirm()">点击调用Android onJsConfirm</button> <button type="button" id="button5" onclick="clickAlert()">点击调用Android onJsAlert</button> </body>
3. 通过 WebViewClient 的 shouldOverrideUrlLoading 回调,拦截url。
Java中
mWebView.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // 解析该 url 的协议,如果检测到是预先约定好的协议,就调用相应方法 Log.e(TAG,"call shouldOverrideUrlLoading url : " + url); return true; } });
JavaScript中
function callLoadProtocol(){ // 在JS约定所需要的Url协议 document.location="http://www.360.cn?p=ppp&q=qqq"; }
安全方面策略
WebView的addJavascriptInterface方法在Android 4.2版本以下使用时可能出现的安全情况有:
WebView所在页面对外暴露,即AndroidManifest.xml设置了android:exported="true",会被其他应用恶意调起
网络劫持导致代码注入
2. 开发者需对加载的网页Url多加限制,禁止加载非合法url。
3. WebView对证书错误的页面会打开空白页。如果当遇到证书错误时调用proceed()函数,会忽略证书错误继续访问,导致https证书校验完全失效,存在安全隐患。
Java中
mWebView.setWebViewClient(new WebViewClient() { @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { handler.proceed();//使用proceed()忽略证书错误,会存在安全隐患 } }
4. WebView加载File协议的Url存在安全隐患,需要禁止File协议调用JavaScript,设置为false。如下即可拿到file:///sdcard/test.txt文件内容,这是很危险的情况。
Java中
mWebView.getSettings().setJavaScriptEnabled(true); mWebView.getSettings().setAllowFileAccessFromFileURLs(true);
JavaScript中
function getFileContent(){ var file="file:///sdcard/test.txt"; var xmlHttpReq=new XMLHttpRequest(); xmlHttpReq.onreadystatechange=function(){ if (xmlHttpReq.readyState==4) { //4: 请求已完成,且响应已就绪 alert(xmlHttpReq.responseText);//执行上传到远程 } } xmlHttpReq.open("GET",file,true); xmlHttpReq.send(); } getFileContent();
5. Android3.0以下,Android系统会默认通过searchBoxJavaBridge的Js接口给WebView添加一个JS映射对象:searchBoxJavaBridge对象。该接口可能被利用,实现远程任意代码,实现中可以进行删除该接口。
Java中
super.removeJavascriptInterface("searchBoxJavaBridge_");
6. 为了解决Android4.2中开启了辅助模式后,LocalActivityManager控制的Activity与AccessibilityInjector不兼容导致的崩溃问题可以在mWebView.getSettings().setJavaScriptEnabled之前执行下面代码。
Java中
public void fixedAccessibilityInjectorException() { if (Build.VERSION.SDK_INT==17) { try { Object webViewProvider=WebView.class.getMethod("getWebViewProvider").invoke(this); Method getAccessibilityInjector=webViewProvider.getClass().getDeclaredMethod("getAccessibilityInjector"); getAccessibilityInjector.setAccessible(true); Object accessibilityInjector=getAccessibilityInjector.invoke(webViewProvider); getAccessibilityInjector.setAccessible(false); Field mAccessibilityManagerField=accessibilityInjector.getClass().getDeclaredField("mAccessibilityManager"); mAccessibilityManagerField.setAccessible(true); Object mAccessibilityManager=mAccessibilityManagerField.get(accessibilityInjector); mAccessibilityManagerField.setAccessible(false); Field mIsEnabledField=mAccessibilityManager.getClass().getDeclaredField("mIsEnabled"); mIsEnabledField.setAccessible(true); mIsEnabledField.set(mAccessibilityManager, false); mIsEnabledField.setAccessible(false); } catch (Exception e) { e.printStackTrace(); } } }
7. 为了防止Js拿到Android对象后,通过反射(通过getClass()方法来得到Runtime实例)获取该对象的其他所有方法,从而导致信息泄露和恶意代码注入,需要过滤掉继承Object类的方法,包括getClass()方法。过滤方法包括:
getClass
hashCode
notify
notifyAll
equals
toString
wait
免责声明:转载自网络 不用于商业宣传 版权归原作者所有 侵权删
、javaScript介绍
JavaScript是一种基于对象和事件驱动的、并具有安全性能的脚本语言
(客户端语言)
JavaScript特点
向HTML页面中添加交互行为
脚本语言,语法和Java类似
解释性语言,边解释边执行
JavaScript组成:ECMAScript 、DOM、BOM
基本结构:
<script type="text/javascript">
<!—
JavaScript 语句;
—>
</script >
示例:
……
<title>初学JavaScript</title>
</head>
<body>
<script type="text/javascript">
document.write("初学JavaScript");
document.write("<h1>Hello,JavaScript</h1>");
</script>
</body>
</html>
注:<script>…</script>可以包含在文档中的任何地方,只要保证这些代码在被使用前已读取并加载到内存即可
执行原理:
外部JS文件:
<script src="export.js" type="text/javascript"></script>
直接在HTML标签中使用:
<input name="btn" type="button" value="弹出消息框"
onclick="javascript:alert('欢迎你');"/>
二、基本常见语法:
1、核心语法:同时声明和赋值变量
var catName="皮皮";
2、数据类型:
undefined:var width;
变量width没有初始值,将被赋予值undefined;
null:表示一个空值,与undefined值相等;
number:var iNum=23; //整数
var iNum=23.0; //浮点数
boolean:true 和false;
string:一组被引号(单引号或双引号)括起来的文本
var string1="This is a string";
3、typeof运算符:
typeof检测变量的返回值
typeof运算符返回值如下函数
undefined:变量被声明后,但未被赋值
string:用单引号或双引号来声明的字符串
boolean:true或false
number:整数或浮点数
object:javascript中的对象、数组和nul
4、String对象:
5、数组:
数组的常用属性和方法
类别 名称 描述
属性 length 设置或返回数组中元素的数目
方法 join( ) 把数组的所有元素放入一个字符串,通过一个的分隔符进行分隔
sort() 对数组排序
push() 向数组末尾添加一个或更多 元素,并返回新的长度
6、逻辑控制语句:
if(条件)
{
//JavaScript代码;
}
比于 Native App 和 Web App,Hybrid App 凭借其迭代灵活、控制自如、多端同步的优势在应用市场上越发显得优胜,主要得力于,其将变更频繁的部分产品功能使用 H5 开发并在客户端中借助 WebView 控件嵌入应用当中。所以,开发中我们总会遇到原生 Java 代码与网页中的 Js 代码之间相互调用从而产生的交互问题。
Java 与 Js 彼此调用的前提是设置 WebView 支持 JavaScript 功能:
第一步,在网页中使用 Js 定义提供给 Java 访问的方法,就像普通方法定义一样,如:
第二步,在 Java 代码中按照 “javascript:XXX” 的 Url 格式使用 WebView 加载访问即可:
注意:String 类型的参数需要使用单引号 “’” 包裹,数组类型的参数则不用,如:javascript:javaCallJs([01, 02, 03]),其他复杂类型的参数可以转换为 Json 字符串的形式传递。
第一步,在 Java 对象中定义 Js 访问的方法,如:
注意事项:提供给 Js 访问的属性和方法必须定义为 public 类型,并且添加注解 @JavascriptInterface。在 API 17 及更高版本的系统中,任何暴露给 Js 访问的 Java 接口都需要添加这个注解,否则会报异常:Uncaught TypeError: Object [object Object] has no method ‘XXX’。系统这种做法也是为了降低应用的安全隐患,因为在之前的版本中,Js 可以通过反射的方式访问注入 WebView 中的 Java 对象的 public 类型 field 和 method,从而随意修改宿主程序。
第二步,将提供给 Js 访问的接口内容所属的 Java 对象注入 WebView 中:
addJavascriptInterface(Object object, String name) 参数说明:object 表示 Js 访问的接口内容所在的 Java 对象;name 表示 Js 调用 Java 代码时的接口名称,与 Js 中的调用保持一致即可。
第三步,Js 按照指定的接口名访问 Java 代码,有如下两种写法:
这里简单提供一个可供测试的 Html 网页和 Activity 代码:
test.html:
MainActivity.java:
效果图:
注意:无论是 Java 调用 Js 还是 Js 调用 Java,只能通过参数传递数据,而无法获取彼此方法的返回值!解决方案就是额外添加一层回调来达到这个目的。比如 Java 调用 Js 的方法,Js 计算结束所得结果不能通过 return 语句返回给 Java 调用者,而是再回调 Java 的另一个方法,通过传参的形式传递给 Java。
1.使用 loadUrl() 方法实现 Java 调用 Js 功能时,必须放置在主线程中,否则会发生崩溃异常。比如修改上面的代码:
运行时会得到如下 logcat 异常信息:
java.lang.RuntimeException: java.lang.Throwable: A WebView method was called on thread 'Thread-18022'. All WebView methods must be called on the same thread.
如果真的在子线程中遇到调用 Js 的功能,也要将其转换到主线程中去:
2.Js 调用 Java 方法时,不是在主线程 (Thread Name:main) 中运行的,而是在一个名为 JavaBridge 的线程中执行的,通过如下代码可以测试:
所以这里需要注意的是,当 Js 调用 Java 时,如果需要 Java 继续回调 Js,千万别在 JavascriptInterface 方法体中直接执行 loadUrl() 方法,而是像前面一样进行线程切换操作。
3.代码混淆时,记得保持 JavascriptInterface 内容,在 proguard 文件中添加如下类似规则 (有关类名按需修改):
除了上面这种 Java 与 Js 互调方法的方式,还可以利用 WebView 拦截 Url 的方式实现原生应用与 H5 之间的交互动作。通过 WebViewClient 提供的接口拦截网页内诸如二级跳转的 Url 链接,便可以进行业务逻辑上的判断处理、Url 参数传递等功能,如:
注意:过去常用的 shouldOverrideUrlLoading(WebView view, String url) 方法已经被废弃。
通过 Java 与 Js 之间的交互可以做很多事情,比如获取网页中的图片,利用原生控件予以展示,类似响应微信公众号文章中的图片点击事件。参考代码如下:
作者博客地址:
http://yifeng.studio/2016/12/01/android-webview-java-js-interaction
*请认真填写需求信息,我们会在24小时内与您取得联系。