基本介绍
MUI首页:http://dev.dcloud.net.cn/mui/
HTML5+SDK首页:http://www.html5plus.org/doc/h5p.html
DCloud首页:http://www.dcloud.io/
注入
一开始一直很好奇,mui的类似plus.device.getVolume()
这样的三级方法是怎么注入的,因为安卓中注入时mWebView.addJavascriptInterface(object, name)
中的name是不能有“.”的:
//这种写法是错误的,注入的方法会调用不了
mWebView.addJavascriptInterface(object, 'plus.device');
今天无意中在调试的时候发现,输入plus.device.getVolume
时返回的不是[native code]
,而是具体的代码,第一反应就是这个代码是在js里面写好的,而不是注入的,但是查看前端代码没有发现类似的js,然后就猜想这个js是不是放到安卓那边去了,于是就到HTML5+sdk中找到一个名为pdr.jar的文件,最后还真在里面发现了一个压缩过的js:
打开一看,还真是,所有的plus.xxx.xxx代码都在里面,只是因为加密了,看起来比较费力。
通过全局搜索找到了如下代码:
具体是怎么把这个js注入进去的呢?具体完整流程我没有走通,但是整体思路应该是下面这样的。
在io.dcloud.feature.b.java
有如下方法,以下方法将all.js的内容读取到了this.f
里面:
/* 77 */ public b(ICore paramICore) { super(paramICore, "featuremgr", IMgr.MgrType.FeatureMgr);
/* */ try
/* */ {
/* 80 */ if ((BaseInfo.ISDEBUG) && (DHFile.isExist("/sdcard/dcloud/all.js")))
/* 81 */ this.f = new String(PlatformUtil.getFileContent("/sdcard/dcloud/all.js", 2));
/* */ else
/* 83 */ this.f = new String(PlatformUtil.getFileContent(BaseInfo.sRuntimeJsPath, 1));
/* */ }
/* */ catch (IOException localIOException)
/* */ {
/* 87 */ localIOException.printStackTrace();
/* */ }
/* */ }
然后就是这个方法:
/* */ private String a(IApp paramIApp, IFrameView paramIFrameView)
/* */ {
/* 211 */ IWebview localIWebview = paramIFrameView.obtainWebView();
/* 212 */ String str1 = "__load__plus__";
/* 213 */ StringBuffer localStringBuffer = new StringBuffer("javascript:");
/* 214 */ localStringBuffer.append("function ").append(str1).append("(){try{");
/* 215 */ localStringBuffer.append("window._____isDebug_____=" + BaseInfo.ISDEBUG + ";");
/* 216 */ localStringBuffer.append("window._____platform_____=1;");
/* 217 */ localStringBuffer.append(this.f); // 这里很关键的代码,将all.js的内容进行了拼接
// 省略大部分代码
/* 268 */ return localStringBuffer.toString();
/* */ }
在WebLoadEvent
中的onPageFinished
调用了一个名为loadAllJSContent
的方法:
/* */ private void loadAllJSContent(WebView paramWebView, String paramString1, String paramString2)
/* */ {
/* 327 */ onLoadPlusJSContent(paramWebView, paramString1, paramString2);
/* */
/* 329 */ onPreloadJSContent(paramWebView, paramString1, paramString2);
/* */
/* 331 */ onPlusreadyEvent(paramWebView, paramString1, paramString2);
/* */
/* 333 */ onExecuteEvalJSStatck(paramWebView, paramString1, paramString2);
/* */ }
好家伙,找了这么久终于找到mui注册事件的代码了:
/* */ private void onPlusreadyEvent(WebView paramWebView, String paramString1, String paramString2)
/* */ {
/* 273 */ StringBuffer localStringBuffer1 = new StringBuffer();
/* 274 */ localStringBuffer1.append(String.format("javascript:(function(){if((!window.plus) || (window.plus && (!window.plus.isReady))){window.__load__plus__&&window.__load__plus__();}var e = document.createEvent('HTMLEvents');var evt = '%s';e.initEvent(evt, false, true);/*console.log('dispatch ' + evt + ' event');*/document.dispatchEvent(e);})();", new Object[] { "plusready" }));
/* 275 */ StringBuffer localStringBuffer2 = new StringBuffer();
/* 276 */ localStringBuffer2.append(String.format("(function (){var b,c,d,e,a=document.getElementsByTagName('iframe');if(a && a.length) for(b=0;b<a.length;b++)c=a[b],d=c.contentWindow.document.createEvent('HTMLEvents'),e='%s',d.initEvent(e,!1,!0),c.contentWindow.plus=window.plus,c.contentWindow.document.dispatchEvent(d)})();", new Object[] { "plusready" }));
/* 277 */ completeLoadJs(paramWebView, paramString1, paramString2, new String[] { localStringBuffer1.toString(), localStringBuffer2.toString() }, "(function(){/*console.log('plusready event loading href=' + location.href);*/if(location.__page__load__over__){return 2;}if(location.href.indexOf('%s') >= 0 || (location.href.startsWith && location.href.startsWith('data:'))){if(location.__plusready__){if(!location.__plusready__event__){location.__plusready__event__=true;return 1;}else{return 2;}}}else if(location.__plusready__event__){return 2;} return 0;})();", new String[] { paramString1 });
/* */ }
这其中,最关键的代码:
var e = document.createEvent('HTMLEvents');
e.initEvent('plusready', false, true);
document.dispatchEvent(e);
交互
这里简单介绍几个mui交互的方法:
plus.bridge.execSync
execSync: function(service, action, args, fn) {
var json, sync, ret;
if (T.IOS == T.platform) {
try {
if (json = T.stringify([[window.__HtMl_Id__, service, action, null , args]]),
sync = B.synExecXhr,
sync.open("post", "http://localhost:13131/cmds", !1),
sync.setRequestHeader("Content-Type", "multipart/form-data"),
sync.send(json),
fn)
return fn(sync.responseText)
} catch (e) {
console.log("sf:" + action + "-" + service)
}
return window.eval(sync.responseText)
}
return T.ANDROID == T.platform ? (ret = window.prompt(T.stringify(args), "pdr:" + T.stringify([service, action, !1])),
fn ? fn(ret) : eval(ret)) : void 0
},
根据字面意思理解,这个方法是JS同步执行一个原生方法,看代码最后发现,这个竟然是用prompt来实现的:
prompt(JSON.stringify([]), 'pdr:'+JSON.stringify(['Device', 'getVolume', false]))
// 输出:"(function(){return 0.46666667;})()"
可能是因为prompt这个方法是同步的原因吧。
有一些并不是all.js中的
比如plus.device.xxx,plus.screen.xxx,这些在all.js中并不存在,这些方法(或者属性)应该是动态生成的。
系统事件的实现
说明:
本小节写在上面all.js注入机制之前,所以当时还不明白mui的事件是如何注入的,已经明白的读者可以跳过本小节。
mui的很多事件可以像浏览器自带的事件那样,直接使用addEventListener完美监听,一直想知道它底层是如何实现的,不过到目前为止还没有搞透,主要是不但没有源码,而且源码都是混淆过的,反编译之后很难看懂。
document.addEventListener( "netchange", netchangeCallback, capture );
通过在prd.jar中搜索netchange
,最后只是找到了一个注册系统事件的方法,在io.dcloud.common.a.d.java
里面:
/* */ public void registerSysEventListener(ISysEventListener paramISysEventListener, ISysEventListener.SysEventType paramSysEventType)
/* */ {
/* 908 */ if (this.y == null) {
/* 909 */ this.y = new HashMap(1);
/* */ }
/* 911 */ ArrayList localArrayList = (ArrayList)this.y.get(paramSysEventType);
/* 912 */ if (localArrayList == null) {
/* 913 */ localArrayList = new ArrayList();
/* 914 */ this.y.put(paramSysEventType, localArrayList);
/* */ }
/* 916 */ localArrayList.add(paramISysEventListener);
/* */ }
这些方法都在io.dcloud.common.DHInterface.IApp
的抽象类里面,d.java实现了IApp,另外,在io.dcloud.common.adapter.ui.WebLoadEvent.java
中找到如下代码:
/* 368 */ if (i != 0) {
/* 369 */ localObject = String.format("javascript:(function(){var b=document.createEvent('HTMLEvents');var a='%s';b.url='%s';b.href='%s';b.initEvent(a,false,true);console.error(a);document.dispatchEvent(b);})();", new Object[] { "error", this.mAdaWebview.getOriginalUrl(), this.mAdaWebview.errorPageUrl });
/* 370 */ this.mAdaWebview.executeScript((String)localObject);
/* 371 */ this.mAdaWebview.errorPageUrl = null;
/* 372 */ this.mAdaWebview.hasErrorPage = false;
/* */ }
可以发现,mui可能也是使用js自定义事件然后触发的。
另外有一个io.dcloud.common.adapter.ui.AdaWebview.executeScript
方法,貌似执行js都是都过这个方法的,有一段代码是这样的:
/* 416 */ public MessageHandler.IMessages executeScriptListener = new MessageHandler.IMessages()
/* */ {
/* */ public void execute(Object paramAnonymousObject) {
/* 419 */ String str = (String)paramAnonymousObject;
/* */
/* 423 */ AdaWebview.this.mWebViewImpl.loadUrl("javascript:" + str);
/* */ }
/* 416 */ };
另外,在pdr.jar
中的all.js
中也没有搜索到像cordova中用到的online
、offline
的字符串,所以,mui执行js可能是采用传统的loadUrl方式来实现的。