整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Android WebView中Java与JavaS

Android WebView中Java与JavaScript的交互方式与安全策略

ndroid在加载网页H5时,会用到WebView组件,以实现服务端可配置,灵活定义Action。

这个时候就需要Java与JavaScript进行交互,典型的应用场景就是电商类APP,如京东和淘宝。

WebView支持JavaScript这些交互动作,那么问题来了,我们怎么实现Java编写的安卓程序与JavaScript编写的网页进行交互呢?下面给大家介绍下,如何实现Java与JavaScript之间的相互调用。

在Java层调用JavaScript的方式:

Java中调用JavaScript有两种方式,同步和异步:

  1. 同步阻塞UI线程的方式: webView.loadUrl("javascript:funtion()")

  2. 异步非阻塞方式: 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代码:

  1. 使用 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";
}

安全方面策略

  1. 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 功能:

Java 调用 Js

第一步,在网页中使用 Js 定义提供给 Java 访问的方法,就像普通方法定义一样,如:

第二步,在 Java 代码中按照 “javascript:XXX” 的 Url 格式使用 WebView 加载访问即可:

注意:String 类型的参数需要使用单引号 “’” 包裹,数组类型的参数则不用,如:javascript:javaCallJs([01, 02, 03]),其他复杂类型的参数可以转换为 Json 字符串的形式传递。

Js 调用 Java

第一步,在 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 文件中添加如下类似规则 (有关类名按需修改):

Url 拦截

除了上面这种 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