完整项目:https://github.com/snailycy/android_jsbridge
1.1 配置WebView
public void configWebView() {
try {
WebSettings settings = this.mWebView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
settings.setDatabaseEnabled(true);
settings.setBuiltInZoomControls(false);
settings.setDomStorageEnabled(true);
settings.setAppCacheEnabled(true);
//设置localStorage存储路径
String localStorageDBPath = this.mWebView.getContext().getFilesDir().getAbsolutePath();
settings.setDatabasePath(localStorageDBPath);
this.mWebView.setWebViewClient(new JSWebViewClient(this));
this.mWebView.setWebChromeClient(new JSWebChromeClient(this));
} catch (Exception e) {
LogUtils.e(TAG, "configWebView error.");
}
}
1.2 安卓端拦截js的请求在WebChromeClient类中的onJsAlert方法中处理
注:如果是用addJavascriptInterface的方式接受js请求,那么在android 4.2系统以下版本有js注入漏洞(在4.2及以上系统时引入@JavascriptInterface可避免)
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
if (message.startsWith(JS_REQUEST_PREFIX)) {
if (this.jsBridge == null) {
result.cancel();
return true;
}
parseJSProtocol(message);
result.cancel();
return true;
}
return super.onJsAlert(view, url, message, result);
}
1.3 自定义JS 请求协议:myjsbridge:///request?class=指定调用的类名&method=指定调用的方法名¶ms=指定的参数&callId=指定的请求ID
解析时按照协议格式分别解析出类名,方法名,参数,callId
/**
* 解析JS协议
*
* @param message: myjsbridge:///request?class=指定调用的类名&method=指定调用的方法名¶ms=指定的参数&callId=指定的请求ID
*/
private void parseJSProtocol(String message) {
String[] tokens = message.substring(JS_REQUEST_PREFIX.length()).split("&");
String target = null;
String method = null;
String params = null;
long callID = -1;
for (String token : tokens) {
String[] pair = token.split("=");
if (pair.length != 2) {
continue;
}
try {
String key = pair[0];
String value = Uri.decode(pair[1]);
if (JS_REQUEST_CLASS_KEY.equals(key)) {
target = value;
} else if (JS_REQUEST_METHOD_KEY.equals(key)) {
method = value;
} else if (JS_REQUEST_PARAMETERS_KEY.equals(key)) {
params = value;
} else if (JS_REQUEST_CALL_ID_KEY.equals(key)) {
callID = Long.parseLong(value);
}
} catch (Exception e) {
// Ignores.
}
}
if (target != null && method != null && callID >= 0) {
this.jsBridge.requestAndroid(target, method, params, callID);
}
}
1.4 拿到对应的类名,方法名,参数后通过发射调用对应的jsapi
/**
* 由JS发起的对android端的请求
*
* @param className 类名
* @param methodName 方法名
* @param params 参数
* @param callID 请求ID
*/
public void requestAndroid(final String className, final String methodName,
final String params, final long callID) {
this.mWebView.post(new Runnable() {
@Override
public void run() {
try {
//拼接全类名: 包名.jsapi.className
String fullClassName = mWebView.getContext().getPackageName() + ".jsapi" + "." + className;
Class<?> cls = Class.forName(fullClassName);
//JSAPI 方法形参为(JSBridge jsbridge,long callId,JSONObject params)
Method declaredMethod = cls.getDeclaredMethod(methodName, JSBridge.class,
Long.class, JSONObject.class);
Object instance = cls.newInstance();
//将请求参数转换成JSONObject
JSONObject requestParams;
try {
requestParams = new JSONObject(params);
} catch (JSONException e) {
requestParams = new JSONObject();
}
//反射调用JSAPI
declaredMethod.invoke(instance, JSBridge.this, callID, requestParams);
} catch (Exception e) {
reportError(callID);
}
}
});
LogUtils.d(TAG, "requestAndroid : " + className + " , " + methodName + " , " + params);
}
1.5 jsapi demo
public class JSUIControl {
public void showToast(JSBridge jsBridge, Long callId, JSONObject requestParams) {
String content = requestParams.optString("content");
Toast.makeText(jsBridge.getActivity(), content, Toast.LENGTH_LONG).show();
//回调JS
jsBridge.reportSuccess(callId);
}
}
1.6 jsapi处理完逻辑后,将结果回调给js
/**
* 回调JS
*
* @param callID 请求ID (由JS请求android端时带过来的请求ID)
* @param type JSAPI执行成功与否
* @param params 回传参数
*/
private void callbackJS(long callID, JSCallbackType type, String params) {
try {
if (callID < 0) {
return;
}
//组装回调js
StringBuilder js = new StringBuilder("javascript:");
js.append(MY_JS_BRIDGE);
js.append(".callbackFromNative(");
js.append(callID);
js.append(",");
js.append(type.getValue());
if (TextUtils.isEmpty(params)) {
js.append(",{});");
} else {
js.append(",");
js.append(params);
js.append(");");
}
String callbackJS = js.toString();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//4.4及以上使用evaluateJavascript
this.mWebView.evaluateJavascript(callbackJS, null);
} else {
this.mWebView.loadUrl(callbackJS);
}
LogUtils.d(TAG, "callbackJS : " + callbackJS);
} catch (Exception e) {
//ignore
}
}
1.7 js端使用alert方式调用android接口:
var json = JSON.stringify({"content":"js call native!"});
alert("myjsbridge:///request?class=JSUIControl&method=showToast¶ms="+
encodeURIComponent(json)+"&callId=1");
使用:
//1.实例化JSBridge,配置WebView
JSBridge jsBridge = new JSBridge(this, webview);
jsBridge.configWebView();
//2.WebView 加载网页资源
webview.loadUrl("file:///android_asset/demo.html");
然后结合业务,自定义jsapi