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);
}
}

推荐阅读更多精彩内容