原文鏈接:http://blog.csdn.net/qq_22329521/article/details/73610277
最近在處理android webView與js的通信上的問題买猖,作為總結(jié)
1.簡單篇
如何實現(xiàn)簡單的android 調(diào)用js 與js調(diào)用android
讓webview做一下操作
private void init(Context context){
WebSettings setting =getSettings();
setting.setJavaScriptEnabled(true);//支持js
setWebViewClient(new WebClient(this));
setWebChromeClient(new WebChromeClient());
//添加js調(diào)用android的方法這是關(guān)鍵 前者是個對象玉控,后者是個字符串 在js中是在window.android可以直接獲取到
addJavascriptInterface(new JavaScriptinterface(context,this),
"android");
//這是開啟js的調(diào)試下面再講
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
setWebContentsDebuggingEnabled(true);
}
}
public class JavaScriptinterface{
Context context;
public JavaScriptinterface(Context c) {
context = c;
}
//這個注解可以點擊進去看官方描述 是 帶有此注釋的標記可用于JavaScript代碼
@JavascriptInterface
public void toastMessage(String message) {
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
}
}
如果js 要調(diào)用這個方法調(diào)用window.anroid.toastMessage('即可')
<script>
function showToast() {
android.toastMessage('hhh');
}
</script>
注意這里的toastMessage 是JavaScriptinterface 中被@JavascriptInterface注解的toastMessage方法
這是js調(diào)用android中的方法
android 調(diào)用js 的方法
<script>
function jsalert(data) {
alert(data);
}
</script>
//java這樣調(diào)用即可 方法前需要加javascript
webview.loadUrl('javascript:jsalert('111')')
注意點
- 需要在主線程調(diào)用才會生效
- 如果在oncreate中直接調(diào)用loadurl不會生效原因是 webview需要加載完成才能調(diào)用webview加載完成的回調(diào)函數(shù)是在webclinet 中
- 如果alert沒效果高诺,需要在setWebChromeClient(new WebChromeClient());
String javascriptCommand='javascript:jsalert('111')';
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
this.loadUrl(javascriptCommand);
}else{
((Activity) getContext()).runOnUiThread(new Runnable() {
@Override
public void run() {
//此時已在主線程中,可以更新UI了
loadUrl(javascriptCommand);
}
});
}
}
public class JsWebClient extends WebViewClient {
public static final String TAG="JsWebClient";
private JsWebView jsWebView;
public JsWebClient(JsWebView jsWebView) {
this.jsWebView = jsWebView;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.e(TAG,url);
return super.shouldOverrideUrlLoading(view,url);
}
//當這個回調(diào)函數(shù)觸發(fā)了webview才可以發(fā)送消息
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
}
}
簡單封裝
js與android中的通信還是通過字符串來處理的
//傳遞過來
基本的數(shù)據(jù)格式大致為
{
type:type,//類型
funName:funName,//當js調(diào)android 讓android是否有回調(diào)方法到j(luò)s中的某個方法
option:option,//object牡拇,內(nèi)部帶個個參數(shù)
}
//回復js
{
type:'success'/'fail'...
data:data
}
@JavascriptInterface
public void postMessage(String data) {
try {
JSONObject jsonObject = new JSONObject(data);
String type = jsonObject.getString("type");
JSONObject option=jsonObject.getJSONObject("option");
String funName=jsonObject.getString('funName');
switch(type){
case 'abc':
break;
case 'bcd':
Object obj=....
....
dispathMessage(funName,obj,'success')
break;
}
} catch (JSONException e) {
e.printStackTrace();
if(!TextUtils.isEmpt(type)){
dispathMessage(funName,null,'fail')
}
}
}
//真正發(fā)送給js 的方法 loadurl是異步加載所以如果在web為加載完成發(fā)送無效果
public void dispathMessage(String funName,Object obj,String type){
if(TextUtils.isEmpt(type))return;
JSONObject jsonObject = new JSONObject();
try {
if(obj!=null)
jsonObject.put("data",new Gson().toJson(obj));
jsonObject.put("type",type);
} catch (JSONException e) {
e.printStackTrace();
}
final String javascriptCommand=String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, funName,jsonObject.toString());
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
this.loadUrl(javascriptCommand);
}else{
((Activity) getContext()).runOnUiThread(new Runnable() {
@Override
public void run() {
//此時已在主線程中导俘,可以更新UI了
loadUrl(javascriptCommand);
}
});
}
}
public class BridgeUtil {
final static String JS_HANDLE_MESSAGE_FROM_JAVA = "javascript:%s('%s')";
}
js的封裝還沒做但是大體是如上
框架分析
接下來看下github上的一個JsBridge這個框架 start約3000+的 他的實現(xiàn)比較完整
這是他的具體實現(xiàn) 代碼不多 其中assets中的js文件是為了注入他的封裝的js代碼 他的交互方式與上面不同具體實現(xiàn)
這是他封裝的webview
public class BridgeWebView extends WebView implements WebViewJavascriptBridge {
private final String TAG = "BridgeWebView";
//加載自己的js文件 趟畏,然后別人的html調(diào)用它的js來傳遞消息
public static final String toLoadJs = "WebViewJavascriptBridge.js";
//callId為鍵滩租,記錄回調(diào)的集合
Map<String, CallBackFunction> responseCallbacks = new HashMap<String, CallBackFunction>();
//本地注冊一個方法名讓js調(diào)用
Map<String, BridgeHandler> messageHandlers = new HashMap<String, BridgeHandler>();
BridgeHandler defaultHandler = new DefaultHandler();
//發(fā)送消息的對象律想,因為webview加載要時間所有他這里先用隊里存儲
private List<Message> startupMessage = new ArrayList<Message>();
public List<Message> getStartupMessage() {
return startupMessage;
}
//發(fā)送消息
private void queueMessage(Message m) {
//這邊只有只為null的時候才會進入發(fā)送消息,置為null是在BridgeWebViewClient的onPageFinished中
if (startupMessage != null) {
startupMessage.add(m);
} else {
dispatchMessage(m);
}
}
public class BridgeWebViewClient extends WebViewClient {
private BridgeWebView webView;
public BridgeWebViewClient(BridgeWebView webView) {
this.webView = webView;
}
//這就是他處理交互的另一個方式
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
try {
url = URLDecoder.decode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回數(shù)據(jù)
webView.handlerReturnData(url);
return true;
} else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //
webView.flushMessageQueue();
return true;
} else {
return super.shouldOverrideUrlLoading(view, url);
}
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
//這里是是webview加載完成做到的操作
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (BridgeWebView.toLoadJs != null) {
//在當前webview中加載一段自己的js文件
BridgeUtil.webViewLoadLocalJs(view, BridgeWebView.toLoadJs);
}
//
if (webView.getStartupMessage() != null) {
for (Message m : webView.getStartupMessage()) {
webView.dispatchMessage(m);
}
//這個里webview加載完成將隊列之為null 之后就會走dispatchMessage這個方法
webView.setStartupMessage(null);
}
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
}
}
我們觀察的他的封裝對象和交互方式
public class Message {
//每個message都有一個callbackid 傳遞給js ,js吧這個返回給客戶端然后客戶端在responseCallbacks中查找返回id的方法
private String callbackId; //callbackId
//回復id
private String responseId; //responseId
private String responseData; //responseData
private String data; //data of message
private String handlerName; //name of handler
}
BridgeUtil 對象封裝了js與android之間的數(shù)據(jù)傳遞格式根據(jù)format 處理后做相應(yīng)的操作
他的交互方式放在了BridgeWebViewClient的shouldOverrideUrlLoading 這個方法
shouldOverrideUrlLoading是當網(wǎng)頁的超鏈接相應(yīng)是回調(diào)到WebClinet的shouldOverrideUrlLoading方法中return true本地處理
false是調(diào)整相應(yīng)的鏈接
然后我們看他的js代碼
我們直接看他發(fā)送消息的代碼
//sendMessage add message, 觸發(fā)native處理 sendMessage
function _doSend(message, responseCallback) {
if (responseCallback) {
var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
responseCallbacks[callbackId] = responseCallback;
message.callbackId = callbackId;
}
//這就是message隊列 隊列的原因后面會將
sendMessageQueue.push(message);
//這個messagingIframe.src 設(shè)置這一串東西
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
//messagingIframe 是iframe元素 添加到element中設(shè)置為不可見 然后在發(fā)送數(shù)據(jù)中設(shè)置src
function _createQueueReadyIframe(doc) {
messagingIframe = doc.createElement('iframe');
messagingIframe.style.display = 'none';
doc.documentElement.appendChild(messagingIframe);
}
//在iframe的src屬性設(shè)置url webviewclient中的對url進行攔截做處理
public boolean shouldOverrideUrlLoading(WebView view, String url) {
try {
url = URLDecoder.decode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回數(shù)據(jù)
webView.handlerReturnData(url);
return true;
} else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //
webView.flushMessageQueue();
return true;
} else {
return super.shouldOverrideUrlLoading(view, url);
}
}
_doSent發(fā)送的方法里面的src的內(nèi)容最終會進入 webView.flushMessageQueue(); 因為字符串匹配的原因
//這里的_fetchQueue就是js中的_fetchQueue方法 實際調(diào)用了那個方法
final static String JS_FETCH_QUEUE_FROM_JAVA = "javascript:WebViewJavascriptBridge._fetchQueue();";
void flushMessageQueue() {
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() {
@Override
public void onCallBack(String data) {
// deserializeMessage
//js發(fā)送過來的數(shù)據(jù)分離出來相應(yīng)的去處理
List<Message> list = null;
try {
list = Message.toArrayList(data);
} catch (Exception e) {
e.printStackTrace();
return;
}
if (list == null || list.size() == 0) {
return;
}
for (int i = 0; i < list.size(); i++) {
Message m = list.get(i);
String responseId = m.getResponseId();
// 是否是response
if (!TextUtils.isEmpty(responseId)) {
CallBackFunction function = responseCallbacks.get(responseId);
String responseData = m.getResponseData();
function.onCallBack(responseData);
responseCallbacks.remove(responseId);
} else {
CallBackFunction responseFunction = null;
// if had callbackId
final String callbackId = m.getCallbackId();
if (!TextUtils.isEmpty(callbackId)) {
responseFunction = new CallBackFunction() {
@Override
public void onCallBack(String data) {
Message responseMsg = new Message();
responseMsg.setResponseId(callbackId);
responseMsg.setResponseData(data);
queueMessage(responseMsg);
}
};
} else {
responseFunction = new CallBackFunction() {
@Override
public void onCallBack(String data) {
// do nothing
}
};
}
BridgeHandler handler;
if (!TextUtils.isEmpty(m.getHandlerName())) {
handler = messageHandlers.get(m.getHandlerName());
} else {
handler = defaultHandler;
}
if (handler != null){
handler.handler(m.getData(), responseFunction);
}
}
}
}
});
}
}
function _fetchQueue() {
//將之前的message都序列出來一次發(fā)送到客戶端
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
//android can't read directly the return data, so we can reload iframe src to communicate with java
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
}
//將callback放到responseCallbacks
public void loadUrl(String jsUrl, CallBackFunction returnCallback) {
this.loadUrl(jsUrl);
responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback);
}
至于android 調(diào)用js代碼
//message中已經(jīng)包含了方法名
final static String JS_HANDLE_MESSAGE_FROM_JAVA = "javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');";
void dispatchMessage(Message m) {
String messageJson = m.toJson();
//escape special characters for json string
messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2");
messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\"");
String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
this.loadUrl(javascriptCommand);
}
}
查看js中
//提供給native調(diào)用,receiveMessageQueue 在會在頁面加載完后賦值為null,所以
function _handleMessageFromNative(messageJSON) {
console.log(messageJSON);
if (receiveMessageQueue && receiveMessageQueue.length > 0) {
receiveMessageQueue.push(messageJSON);
} else {
_dispatchMessageFromNative(messageJSON);
}
}
/提供給native使用,
function _dispatchMessageFromNative(messageJSON) {
setTimeout(function() {
//解析message
var message = JSON.parse(messageJSON);
var responseCallback;
//java call finished, now need to call js callback function
if (message.responseId) {
responseCallback = responseCallbacks[message.responseId];
if (!responseCallback) {
return;
}
responseCallback(message.responseData);
delete responseCallbacks[message.responseId];
} else {
//直接發(fā)送
if (message.callbackId) {
var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
_doSend({
responseId: callbackResponseId,
responseData: responseData
});
};
}
var handler = WebViewJavascriptBridge._messageHandler;
if (message.handlerName) {
//根據(jù)message中方法名去messageHandlers中去找,messageHandlers是js中注冊了的function集合
handler = messageHandlers[message.handlerName];
}
//查找指定handler
try {
handler(message.data, responseCallback);
} catch (exception) {
if (typeof console != 'undefined') {
console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception);
}
}
}
});
}
android 調(diào)試js代碼
之前代碼中有一段
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
setWebContentsDebuggingEnabled(true);
}
開啟后 在chrome瀏覽器中 輸入chrome://inspect 然后能找到當前應(yīng)用有個insert按鈕
然后就可以開始搞事了
以上是jsbridge具體實現(xiàn)的思路