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