今天调查一个线上Bug,发现是WebView中的一小段javascript,会直接调用到后台APK的一个Java事件,最后导致java中nullpointexception。
感兴趣的是,WebView中的javascript如何调用APK中的java方法。
一个例子:
通过JS取得Android的GPS数据
第一步,WebKit的准备
首先,给与WebKit的javascript的执行许可
- publicvoidonCreate(Bundleicicle){
- super.onCreate(icicle);
- WebViewwv=newWebView(this);
- wv.getSettings().setJavaScriptEnabled(true);//JS利用OK
- setContentView(wv);
- }
然后,塞入自己的javascript拦截器
- JsObjjo=newJsObj(this);
- wv.addJavascriptInterface(jo,"roid");
第二步,定义自己的javascript拦截器
- classJsObj{
- privateContextcon;
- publicJsObj(Contextcon){
- this.con=con;
- }
- publicStringgps(Stringtop,Stringend){
- LocationManagerlocman=(LocationManager)
- con.getSystemService(Context.LOCATION_SERVICE);
- Locationloc=locman.getCurrentLocation("gps");
- intlat=(int)(loc.getLatitude()*1000000);
- intlon=(int)(loc.getLongitude()*1000000);
- returntop+"緯度:"+lat+",経度:"+lon+end;
- }
- }
第三步,定义一个可运行的html
- <html>
- <head><title>JScallsAndroidMethod</title></head>
- <body>
- <h1>JSonAndroid</h1>
- <scripttype="text/javascript">
- document.write(roid.gps("<i>","</i>"));
- </script>
- </body>
- </html>
在这个代码里面,可以用roid.gps的方法调用第二步定义的java函数
最后,全部的代码
- packagecom.adamrocker.android.web;
- importandroid.app.Activity;
- importandroid.content.Context;
- importandroid.location.Location;
- importandroid.location.LocationManager;
- importandroid.os.Bundle;
- importandroid.webkit.WebView;
- publicclassWebkitTestextendsActivity{
- /**Calledwhentheactivityisfirstcreated.*/
- @Override
- publicvoidonCreate(Bundleicicle){
- super.onCreate(icicle);
- WebViewwv=newWebView(this);
- wv.getSettings().setJavaScriptEnabled(true);
- JsObjjo=newJsObj(this);
- wv.addJavascriptInterface(jo,"roid");
- setContentView(wv);
- wv.loadUrl("http://www.adamrocker.com/android/js2android.html");
- }
- classJsObj{
- privateContextcon;
- publicJsObj(Contextcon){
- this.con=con;
- }
- publicStringgps(Stringtop,Stringend){
- LocationManagerlocman=(LocationManager)con
- .getSystemService(Context.LOCATION_SERVICE);
- Locationloc=locman.getCurrentLocation("gps");
- intlat=(int)(loc.getLatitude()*1000000);
- intlon=(int)(loc.getLongitude()*1000000);
- returntop+"緯度:"+lat+",経度:"+lon+end;
- }
- }
- }
未完
我还想知道为什么,在webview里面定义一个JSObject,就可以连接javascript和后台函数
他们之间是如何通信的?
我稍微调查了一下WebView的底层代码,webview初期化的时候
- /*InitializeprivatedatawithintheWebCorethread.
- */
- privatevoid[More...]initialize(){
- /*InitializeourprivateBrowserFrameclasstohandleall
- *frame-relatedfunctions.Weneedtocreateanewviewwhich
- *inturncreatesaClevelFrameViewandattachesittotheframe.
- */
- mBrowserFrame=newBrowserFrame(mContext,this,mCallbackProxy,
- mSettings,mJavascriptInterfaces);
- mJavascriptInterfaces=null;
- //SyncthenativesettingsandalsocreatetheWebCorethreadhandler.
- mSettings.syncSettingsAndCreateHandler(mBrowserFrame);
- //CreatethehandlerandtransfermessagesfortheIconDatabase
- WebIconDatabase.getInstance().createHandler();
- //CreatethehandlerforWebStorage
- WebStorage.getInstance().createHandler();
- //CreatethehandlerforGeolocationPermissions.
- GeolocationPermissions.getInstance().createHandler();
- //ThetransferMessagescallwilltransferallpendingmessagestothe
- //WebCorethreadhandler.
- mEventHub.transferMessages();
- //SendamessagebacktoWebViewtotellitthatwehavesetupthe
- //WebCorethread.
- if(mWebView!=null){
- Message.obtain(mWebView.mPrivateHandler,
- WebView.WEBCORE_INITIALIZED_MSG_ID,
- mNativeClass,0).sendToTarget();
- }
- }
生成了显示用对象
mBrowserFrame
而此对象的所有操作事件,都会被
mEventHub截获
而mEventHub会将请求发送给真正需要处理的MessageStub。 通过messageName
- Transferallmessagestothenewlycreatedwebcorethreadhandler.
- privatevoid[More...]transferMessages(){
- mTid=Process.myTid();
- mSavedPriority=Process.getThreadPriority(mTid);
- mHandler=newHandler(){
- @Override
- publicvoid[More...]handleMessage(Messagemsg){
- if(DebugFlags.WEB_VIEW_CORE){
- Log.v(LOGTAG,(msg.what<REQUEST_LABEL
- ||msg.what
- >VALID_NODE_BOUNDS?Integer.toString(msg.what)
- :HandlerDebugString[msg.what
- -REQUEST_LABEL])
- +"arg1="+msg.arg1+"arg2="+msg.arg2
- +"obj="+msg.obj);
- }
- switch(msg.what){
- caseWEBKIT_DRAW:
- webkitDraw();
所以你要问我他们是怎么通信的
我只能说是线程间通信。