addJavascriptInterface源码分析

Java层

我们先来看一下在代码中如何使用该功能代码如下:

WebView myWebView = (WebView) findViewById(R.id.myWebView);
myWebView.getSettings().setJavaScriptEnabled(true);
myWebView.addJavascriptInterface(new Object(){                    
    @JavascriptInterface 
    public String getIMEI(){
            return ((TelephonyManager) getSystemService(TELEPHONY_SERVICE)).getDeviceId();
        }
    }, "Android");
  • 第一行代码我们获取了一个WebView的实例。
  • 第二行代码我们设置在WebView能够执行JavaScript代码。
  • 第三行我们注册一个对象到WebView,这样我们在Javascript代码中可以使用Android.getIMEI来获取手机的IMEI信息了,如var imei = Android.getIMEI()。
揭秘addJavascriptInterface

注册对象
  • WebView.java
    位于/frameworks/base/core/java/android/webkit/WebView.java
private WebViewProvider mProvider;
...
public void addJavascriptInterface(Object object, String name) {
    checkThread();
    mProvider.addJavascriptInterface(object, name);
}

可以看到WebView中实际是调用了WebViewProvider类中对应的addJavascriptInterface方法。

  • WebViewProvider.java
    位于/frameworks/base/core/java/android/webkit/WebViewProvider.java
public interface WebViewProvider {
...
public void addJavascriptInterface(Object obj, String interfaceName);
public void removeJavascriptInterface(String interfaceName);
...
}

它只是一个接口,没有实现任何方法,那么是谁实现了该接口呢?通过分析我们知道是WebViewClassic这个类。

  • WebViewClassic.java
    位于/frameworks/base/core/java/android/webkit/WebViewClassic.java
public final class WebViewClassic implements WebViewProvider, WebViewProvider.ScrollDelegate,
WebViewProvider.ViewDelegate {
....
  @Override
  public void addJavascriptInterface(Object object, String name) {
   if (object == null) {
       return;
   }
   WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();

   arg.mObject = object;
   arg.mInterfaceName = name;

   // starting with JELLY_BEAN_MR1, annotations are mandatory for enabling access to
   // methods that are accessible from JS.
   if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
       arg.mRequireAnnotation = true;
   } else {
       arg.mRequireAnnotation = false;
   }
   mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
 }
...
}

首先构造一个JSInterfaceData变量用来存储对象和接口名信息。
其次判断SDK的版本如果是JELLY_BEAN_MR1(也就是4.3)之上的就需要加注释@JavascriptInterfacefa,因为这里涉及到使用addJavascriptInterface接口存在一些风险。
最后调用WebViewCore的sendMessage方法。

  • WebViewCore.java
    位于/frameworks/base/core/java/android/webkit/WebViewCore.java
private final EventHub mEventHub;
...
void sendMessage(int what, Object obj) {
    mEventHub.sendMessage(Message.obtain(null, what, obj));
}

这里的mEventHub实际上是WebViewCore.java中的一个内部类。

public class EventHub implements WebViewInputDispatcher.WebKitCallbacks {
    // Private handler for WebCore messages.
    private Handler mHandler;
    // Message queue for containing messages before the WebCore thread is
    // ready.
    private LinkedList<Message> mMessages = new LinkedList<Message>();
...
    private synchronized void sendMessage(Message msg) {
        if (mBlockMessages) {
            return;
        }
        if (mMessages != null) {
            mMessages.add(msg);
        } else {
            mHandler.sendMessage(msg);
        }
    }
...
}

这里mMessage是一个消息队列,队列中添加的消息最终也是通过mHandle发送出去的。在该内部类中重写了Handler的handeMessage方法。

  • Handler
    /frameworks/base/core/java/android/os/Handler.java
public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

final MessageQueue mQueue;
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
  • MessageQueue
    /frameworks/base/core/java/android/os/MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
...
if (needWake) {
        nativeWake(mPtr);
}
...
}
  • android_os_MessageQueue.cpp
    /frameworks/base/core/jni/android_os_MessageQueue.cpp
static JNINativeMethod gMessageQueueMethods[] = {
/* name, signature, funcPtr */
{ "nativeInit", "()I", (void*)android_os_MessageQueue_nativeInit },
{ "nativeDestroy", "(I)V", (void*)android_os_MessageQueue_nativeDestroy },
{ "nativePollOnce", "(II)V", (void*)android_os_MessageQueue_nativePollOnce },
{ "nativeWake", "(I)V", (void*)android_os_MessageQueue_nativeWake }
};

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jint ptr) {
  NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
  return nativeMessageQueue->wake();
}

NativeMessageQueue继承自MessageQueue(android_os_MessageQueue)

在父类MessageQueue中有sp<Looper> mLooper;
void NativeMessageQueue::wake() {
  mLooper->wake();
}
  • Looper.cpp
    /frameworks/native/libs/utils/Looper.cpp
void Looper::wake() {
  #if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
  #endif

  ssize_t nWrite;
  do {
      nWrite = write(mWakeWritePipeFd, "W", 1);
  } while (nWrite == -1 && errno == EINTR);

  if (nWrite != 1) {
      if (errno != EAGAIN) {
          ALOGW("Could not write wake signal, errno=%d", errno);
      }
  }
}
处理消息

上面我们提到过内部类重写了handeMessage方法

mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
...
            case ADD_JS_INTERFACE:
                       JSInterfaceData jsData = (JSInterfaceData) msg.obj;
                       mBrowserFrame.addJavascriptInterface(jsData.mObject,
                       jsData.mInterfaceName, jsData.mRequireAnnotation);
                       break;
...
}

这里匹配到ADD_JS_INTERFACE这一消息类型,调用BrowserFrame中的addJavascriptInterface方法

  • BrowserFrame
    /frameworks/base/core/java/android/webkit/BrowserFrame.java
// Attached Javascript interfaces
private Map<String, JSObject> mJavaScriptObjects;
private Set<Object> mRemovedJavaScriptObjects;
...
public void addJavascriptInterface(Object obj, String interfaceName,
        boolean requireAnnotation) {
    assert obj != null;
    removeJavascriptInterface(interfaceName);
    mJavaScriptObjects.put(interfaceName, new JSObject(obj, requireAnnotation));
}

这里是用一个JSObject对象来包装注册对象。之后调用windowObjectCleared方法

/*
 * This method is called by WebCore to inform the frame that
 * the Javascript window object has been cleared.
 * We should re-attach any attached js interfaces.
 */
private void windowObjectCleared(int nativeFramePointer) {
    Iterator<String> iter = mJavaScriptObjects.keySet().iterator();
    while (iter.hasNext())  {
        String interfaceName = iter.next();
        JSObject jsobject = mJavaScriptObjects.get(interfaceName);
        if (jsobject != null && jsobject.object != null) {
            nativeAddJavascriptInterface(nativeFramePointer,
                    jsobject.object, interfaceName, jsobject.requireAnnotation);
        }
    }
    mRemovedJavaScriptObjects.clear();
}

这里通过调用JNI方法nativeAddJavascriptInterface来实现对象绑定

  • WebCoreFrameBridge
    /external/webkit/Source/WebKit/android/jni/WebCoreFrameBridge.cpp
static void AddJavascriptInterface(JNIEnv *env, jobject obj, jint nativeFramePointer,
    jobject javascriptObj, jstring interfaceName, jboolean requireAnnotation)
{
WebCore::Frame* pFrame = 0;
if (nativeFramePointer == 0)
    pFrame = GET_NATIVE_FRAME(env, obj);
else
    pFrame = (WebCore::Frame*)nativeFramePointer;
ALOG_ASSERT(pFrame, "nativeAddJavascriptInterface must take a valid frame pointer!");

JavaVM* vm;
env->GetJavaVM(&vm);
ALOGV("::WebCore:: addJSInterface: %p", pFrame);

if (pFrame) {
    RefPtr<JavaInstance> addedObject = WeakJavaInstance::create(javascriptObj,
            requireAnnotation);
    const char* name = getCharactersFromJStringInEnv(env, interfaceName);
    // Pass ownership of the added object to bindToWindowObject.
    NPObject* npObject = JavaInstanceToNPObject(addedObject.get());
    pFrame->script()->bindToWindowObject(pFrame, name, npObject);
    // bindToWindowObject calls NPN_RetainObject on the
    // returned one (see createV8ObjectForNPObject in V8NPObject.cpp).
    // bindToWindowObject also increases obj's ref count and decreases
    // the ref count when the object is not reachable from JavaScript
    // side. Code here must release the reference count increased by
    // bindToWindowObject.

    // Note that while this function is declared in WebCore/bridge/npruntime.h, for V8 builds
    // we use WebCore/bindings/v8/npruntime.cpp (rather than
    // WebCore/bridge/npruntime.cpp), so the function is implemented there.
    // TODO: Combine the two versions of these NPAPI files.
    NPN_ReleaseObject(npObject);
    releaseCharactersForJString(interfaceName, name);
}
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容