如果android和js交互的话,那就是要通过一个控件WebView。如果js要调android中方法的话,要通过JavascriptInterface,百度一下就能看到很多资料,这里不详述。
在API17之前的话,是不需要加@JavascriptInterface注释的,但是在17版本后,需要在方法上加上@JavascriptInterface。
看个简单的例子:
这是html的代码,点击按钮会触发JS方法:showAndroidToast,在这个方法中会调用android的方法。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html dir="ltr" xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
<script type="text/javascript">
function showAndroidToast(toast) {
window.Android.showToast(toast);
}
</script>
</head>
<body>
<input onclick="showAndroidToast('Hello world!')" type="button" value="Say hello"/>
</body>
</html>
看下android的代码:
public class MainActivity extends Activity {
private WebView mWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webview);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(new JavaScriptInterface(this), "Android");
mWebView.loadUrl("file:///android_asset/test.html");
}
}
public class JavaScriptInterface {
private Context mContext;
public JavaScriptInterface(Context c) {
mContext = c;
}
/**
* Show a toast from the web page
*/
@JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
}
运行起来的效果:
这里有更具体的说明:http://www.igooda.cn/jzjl/20141212737.html
js调用Java
调用格式为window.jsInterfaceName.methodName(parameterValues) 此例中我们使用的是control作为注入接口名称。注意,java提供给js的方法不能混淆!!
function toastMessage(message) {
window.control.toastMessage(message)
}
function sumToJava(number1, number2){
window.control.onSumResult(number1 + number2)
}
Java调用JS
webView调用js的基本格式为 webView.loadUrl(“javascript:methodName(parameterValues)”)
大多数人都知道WebView存在一个漏洞,见WebView中接口隐患与手机挂马利用,虽然该漏洞已经在Android 4.2上修复了,即使用@JavascriptInterface代替addJavascriptInterface,但是由于兼容性和安全性问题,基本上我们不会再利用Android系统为我们提供的addJavascriptInterface方法或者@JavascriptInterface注解来实现,所以我们只能另辟蹊径,去寻找既安全,又能实现兼容Android各个版本的方案。
那这里就要用到JSBridge。首先我们来了解一下为什么要使用JSBridge,在开发中,为了追求开发的效率以及移植的便利性,一些展示性强的页面我们会偏向于使用h5来完成,功能性强的页面我们会偏向于使用native来完成,而一旦使用了h5,为了在h5中尽可能的得到native的体验,我们native层需要暴露一些方法给js调用,比如,弹Toast提醒,弹Dialog,分享等等,有时候甚至把h5的网络请求放着native去完成,而JSBridge做得好的一个典型就是微信,微信给开发者提供了JSSDK,该SDK中暴露了很多微信native层的方法,比如支付,定位等。
下面这篇文章是介绍如何利用window.prompt来建立两端通信的:
Android JSBridge的原理与实现
该文章里面的代码。。。。真是惨不忍睹赌,所以我都贴出代码吧。
里面定的协议是:jsbridge://className:callbackAddress/methodName?jsonObj
public class MainActivity extends AppCompatActivity {
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = (WebView) findViewById(R.id.webview);
//此方法用来注册哪些类可以供JS调用
JSBridge.register("bridge", BridgeImpl.class);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebChromeClient(new JSBridgeWebChromeClient());
//把myWebView加载html
webView.loadUrl("file:///android_asset/one.html");
}
}
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>JSBridge</title>
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1, user-scalable=no"/>
<script src="file:///android_asset/JSBridge.js" type="text/javascript"></script>
<script type="text/javascript"></script>
<style></style>
</head>
<body>
<div><h3>JSBridge 测试</h3></div>
<ul class="list">
<li>
<div>
<button onclick="JSBridge.call('bridge','showToast',{'msg':'Hello JSBridge'},function(res){alert(JSON.stringify(res))})">
测试showToast
</button>
</div>
</li>
<br/></ul>
<ul class="list">
<li>
<div>
<button onclick="JSBridge.call('bridge','testThread',{},function(res){alert(JSON.stringify(res))})">
测试子线程回调
</button>
</div>
</li>
<br/></ul>
</body>
</html>
(function (win) {
var hasOwnProperty = Object.prototype.hasOwnProperty;
var JSBridge = win.JSBridge || (win.JSBridge = {});
var JSBRIDGE_PROTOCOL = 'JSBridge';
//然后有一个Inner类,里面有我们的call和onFinish方法。
var Inner = {
callbacks: {},
call: function (obj, method, params, callback) {
console.log(obj+" "+method+" "+params+" "+callback);
var port = Util.getPort();
console.log(port);
this.callbacks[port] = callback;
var uri=Util.getUri(obj,method,params,port);
console.log(uri);
window.prompt(uri, "");
},
onFinish: function (port, jsonObj){
var callback = this.callbacks[port];
callback && callback(jsonObj);
delete this.callbacks[port];
},
};
//一个Util类,里面有三个方法,getPort()用于随机生成port,getParam()用于生成json字符串
//getUri()用于生成native需要的协议uri,里面主要做字符串拼接的工作
var Util = {
getPort: function () {
return Math.floor(Math.random() * (1 << 30));
},
getUri:function(obj, method, params, port){
params = this.getParam(params);
var uri = JSBRIDGE_PROTOCOL + '://' + obj + ':' + port + '/' + method + '?' + params;
return uri;
},
getParam:function(obj){
if (obj && typeof obj === 'object') { return JSON.stringify(obj); } else { return obj || ''; }
}
};
for (var key in Inner) {
if (!hasOwnProperty.call(JSBridge, key)) {
JSBridge[key] = Inner[key];
}
}
})(window);
public class JSBridge {
private static Map<String, HashMap<String, Method>> exposedMethods = new HashMap<>();
public static void register(String exposedName, Class<? extends IBridge> clazz) {
if (!exposedMethods.containsKey(exposedName)) {
try {
exposedMethods.put(exposedName, getAllMethod(clazz));
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static HashMap<String, Method> getAllMethod(Class injectedCls) throws Exception {
HashMap<String, Method> mMethodsMap = new HashMap<>();
Method[] methods = injectedCls.getDeclaredMethods();
for (Method method : methods) {
String name;
if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) || (name = method.getName()) == null) {
continue;
}
Class[] parameters = method.getParameterTypes();
if (null != parameters && parameters.length == 3) {
if (parameters[0] == WebView.class && parameters[1] == JSONObject.class && parameters[2] == Callback.class) {
mMethodsMap.put(name, method);
}
}
}
return mMethodsMap;
}
public static String callJava(WebView webView, String uriString) {
String methodName = "";
String className = "";
String param = "{}";
String port = "";
if (!TextUtils.isEmpty(uriString) && uriString.startsWith("JSBridge")) {
Uri uri = Uri.parse(uriString);
className = uri.getHost();
param = uri.getQuery();
port = uri.getPort() + "";
String path = uri.getPath();
if (!TextUtils.isEmpty(path)) {
methodName = path.replace("/", "");
}
}
if (exposedMethods.containsKey(className)) {
HashMap<String, Method> methodHashMap = exposedMethods.get(className);
if (methodHashMap != null && methodHashMap.size() != 0 && methodHashMap.containsKey(methodName)) {
Method method = methodHashMap.get(methodName);
if (method != null) {
try {
method.invoke(null, webView, new JSONObject(param), new Callback(webView, port));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return null;
}
}
public class JSBridgeWebChromeClient extends WebChromeClient {
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
result.confirm(JSBridge.callJava(view, message));
return true;
}
}
public interface IBridge {
}
public class Callback {
private static Handler mHandler = new Handler(Looper.getMainLooper());
private static final String CALLBACK_JS_FORMAT = "javascript:JSBridge.onFinish('%s', %s);";
private String mPort;
private WeakReference<WebView> mWebViewRef;
public Callback(WebView view, String port) {
mWebViewRef = new WeakReference<>(view);
mPort = port;
}
public void apply(JSONObject jsonObject) {
final String execJs = String.format(CALLBACK_JS_FORMAT, mPort, String.valueOf(jsonObject));
if (mWebViewRef != null && mWebViewRef.get() != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
mWebViewRef.get().loadUrl(execJs);
}
});
}
}
}
public class BridgeImpl implements IBridge {
public static void showToast(WebView webView, JSONObject param, final Callback callback) {
String message = param.optString("msg");
Toast.makeText(webView.getContext(), message, Toast.LENGTH_SHORT).show();
if (null != callback) {
try {
JSONObject object = new JSONObject();
object.put("key", "value");
object.put("key1", "value1");
callback.apply(getJSONObject(0, "ok", object));
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void testThread(WebView webView, JSONObject param, final Callback callback) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
JSONObject object = new JSONObject();
object.put("key", "value");
callback.apply(getJSONObject(0, "ok", object));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
}).start();
}
private static JSONObject getJSONObject(int code, String msg, JSONObject result) {
JSONObject object = new JSONObject();
try {
object.put("code", code);
object.put("msg", msg);
object.putOpt("result", result);
return object;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
}
代码都上了,效果的话就和那篇文章里是一样的。
当然,除了上面这种方法以外,还有其他的方法,通过拦截WebViewClient的shouldOverrideUrlLoading方法,主要是用来拦截url,看下代码:
public class MainActivity extends Activity {
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = (WebView) findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new MyWebViewClient());
//如果不设置webchromeClient的话,会导致js的alert无法弹出
webView.setWebChromeClient(new WebChromeClient());
//把myWebView加载html
webView.loadUrl("file:///android_asset/ios.html");
}
}
ios.html:
<html>
<head>
<title>WebView和Native通信</title>
<meta charset="UTF8">
<script type="text/javascript">
function nativeCallback(strJSON) {
var description = "";
console.log("-------------------------");
for(var i in strJSON){
var property=strJSON[i];
description+=i+" = "+property+"\n";
}
alert(description);
return "this";
}
function callNative () {
var msgIframe = document.createElement('iframe');
msgIframe.style.display = 'none';
var msgBody = {"params" : {"id":"hello"},"callback" : ""};
var strBody = JSON.stringify(msgBody);
msgIframe.src = "epaysdk://paysuccess?msg=" + strBody;
document.documentElement.appendChild(msgIframe);
setTimeout(callback(), 0);
function callback() {
alert('dssdsdsdsd');
}
}
</script>
</head>
<body>
<label id="log"></label>
<div style="margin-top:90px">
<button onclick=callNative()>调用原生方法</button>
</div>
</body>
</html>
public class MyWebViewClient extends WebViewClient {
private JSONObject obj;
@Override
public WebResourceResponse shouldInterceptRequest(final WebView view, WebResourceRequest request) {
Log.d("tag", "shouldInterceptRequest--------url:" + request.getUrl().toString() + " , " + request.getUrl().getScheme());
try {
obj = new JSONObject();
obj.put("code", "1");
obj.put("msg", "success");
view.post(new Runnable() {
@Override
public void run() {
Log.d("tag","evaluateJavascript");
view.evaluateJavascript("nativeCallback(" + obj.toString() + ")", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Log.d("tag","onReceiveValue:"+value);
}
});
}
});
} catch (Exception e) {
e.printStackTrace();
}
return super.shouldInterceptRequest(view, request);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.d("tag", "shouldOverrideUrlLoading-------url:" + url);
return super.shouldOverrideUrlLoading(view, url);
}
}