参考
记一次印象深刻的Bug追踪过程 - 简书
WebView 源码分析 - 简书
我们在androidstudio里面点击addJavascriptInterface方法进入源代码:
public void addJavascriptInterface(Object object, String name) {
checkThread();
mProvider.addJavascriptInterface(object, name);
}
发现是mProvider调用了方法,接下来我们就寻找mProvider的实例化地方
webview.java关键代码
private void ensureProviderCreated() {
checkThread();
if (mProvider == null) {
// As this can get called during the base class constructor chain, pass the minimum
// number of dependencies here; the rest are deferred to init().
mProvider = getFactory().createWebView(this, new PrivateAccess());
}
}
@UnsupportedAppUsage
private static WebViewFactoryProvider getFactory() {
return WebViewFactory.getProvider();
}
@UnsupportedAppUsage
private final Looper mWebViewThread = Looper.myLooper();
WebViewFactory.java 关键代码
/** @hide */
private static final String CHROMIUM_WEBVIEW_FACTORY =
"com.android.webview.chromium.WebViewChromiumFactoryProviderForQ";
private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
private static final String LOGTAG = "WebViewFactory";
static WebViewFactoryProvider getProvider() {
synchronized (sProviderLock) {
//......省略
try {
Class providerClass = getProviderClass();
Method staticFactory = null;
try {
staticFactory = providerClass.getMethod(
CHROMIUM_WEBVIEW_FACTORY_METHOD, WebViewDelegate.class);
} catch (Exception e) {
if (DEBUG) {
Log.w(LOGTAG, "error instantiating provider with static factory method", e);
}
}
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactoryProvider invocation");
try {
sProviderInstance = (WebViewFactoryProvider)
staticFactory.invoke(null, new WebViewDelegate());
if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
return sProviderInstance;
} catch (Exception e) {
Log.e(LOGTAG, "error instantiating provider", e);
throw new AndroidRuntimeException(e);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
}
}
private static Class getProviderClass() {
Context webViewContext = null;
Application initialApplication = AppGlobals.getInitialApplication();
try {
try {
webViewContext = getWebViewContextAndSetProvider();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
try {
initialApplication.getAssets().addAssetPathAsSharedLibrary(
webViewContext.getApplicationInfo().sourceDir);
ClassLoader clazzLoader = webViewContext.getClassLoader();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
WebViewLibraryLoader.loadNativeLibrary(clazzLoader,
getWebViewLibrary(sPackageInfo.applicationInfo));
try {
return getWebViewProviderClass(clazzLoader);
} finally {
}
} catch (ClassNotFoundException e) {
throw new AndroidRuntimeException(e);
} finally {
}
} catch (MissingWebViewPackageException e) {
throw new AndroidRuntimeException(e);
}
}
/**
* @hide
*/
public static Class getWebViewProviderClass(ClassLoader clazzLoader)
throws ClassNotFoundException {
return (Class) Class.forName(CHROMIUM_WEBVIEW_FACTORY,
true, clazzLoader);
}
发现调用com.android.webview.chromium.WebViewChromiumFactoryProviderForQ的create方法
package com.android.webview.chromium;
class WebViewChromiumFactoryProviderForQ extends WebViewChromiumFactoryProvider {
public static WebViewChromiumFactoryProvider create(android.webkit.WebViewDelegate delegate) {
return new WebViewChromiumFactoryProviderForQ(delegate);
}
protected WebViewChromiumFactoryProviderForQ(android.webkit.WebViewDelegate delegate) {
super(delegate);
}
}
上文中一开始 我们发现webview的ensureProviderCreated()方法调用了
mProvider =getFactory().createWebView(this, new PrivateAccess());
进入父类代码WebViewChromiumFactoryProvider类查看createWebView方法,由此我们发现了
WebViewChromium wvc = new WebViewChromium(this, webView, privateAccess);具体代码如下:
/**
* Constructor called by the API 22 version of {@link WebViewFactory} and later.
*/
public WebViewChromiumFactoryProvider(android.webkit.WebViewDelegate delegate) {
initialize(WebViewDelegateFactory.createProxyDelegate(delegate));
}
........省略...
@Override
public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
WebViewChromium wvc = new WebViewChromium(this, webView, privateAccess);
synchronized (mLock) {
if (mWebViewsToStart != null) {
mWebViewsToStart.add(new WeakReference(wvc));
}
}
return wvc;
}
接下来进入WebViewChromium 我们查看具体的webview 方法实现,webview方法都是WebViewChromium代理实现的,下面我们看下最常用的addJavascriptInterface方法调用了mAwContents.addPossiblyUnsafeJavascriptInterface
@Override
public void addJavascriptInterface(final Object obj, final String interfaceName) {
if (checkNeedsPost()) {
mRunQueue.addTask(new Runnable() {
@Override
public void run() {
addJavascriptInterface(obj, interfaceName);
}
});
return;
}
Class extends Annotation> requiredAnnotation = null;
if (mAppTargetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
requiredAnnotation = JavascriptInterface.class;
}
mAwContents.addPossiblyUnsafeJavascriptInterface(obj, interfaceName, requiredAnnotation);
}
AwContent在哪里创建的呢?
仔细查找WebViewChromium这个类,我们发现AwContent是在initForReal方法中被创建的。而initForReal调用来自init方法。可是,init方法是在哪里调用的呢?答案是:WebView。看下面代码倒数第三行:mProvider.init(javaScriptInterfaces, privateBrowsing);即是调用WebViewChromium的init()方法 然后在init()方法里面调用initForReal();
protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
Map javaScriptInterfaces, boolean privateBrowsing) {
super(context, attrs, defStyleAttr, defStyleRes);
// WebView is important by default, unless app developer overrode attribute.
if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
}
if (context == null) {
throw new IllegalArgumentException("Invalid context argument");
}
if (mWebViewThread == null) {
throw new RuntimeException(
"WebView cannot be initialized on a thread that has no Looper.");
}
sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
Build.VERSION_CODES.JELLY_BEAN_MR2;
checkThread();
ensureProviderCreated();
mProvider.init(javaScriptInterfaces, privateBrowsing);
// Post condition of creating a webview is the CookieSyncManager.getInstance() is allowed.
CookieSyncManager.setGetInstanceIsAllowed();
}
OK,继续往下,看AwContent是怎么创建的。
private void initForReal() {
AwContentsStatics.setRecordFullDocument(sRecordWholeDocumentEnabledByApi
|| mAppTargetSdkVersion < Build.VERSION_CODES.LOLLIPOP);
mAwContents = new AwContents(mFactory.getBrowserContextOnUiThread(), mWebView, mContext,
new InternalAccessAdapter(), new WebViewNativeDrawGLFunctorFactory(),
mContentsClientAdapter, mWebSettings.getAwSettings(),
new AwContents.DependencyFactory() {
@Override
public AutofillProvider createAutofillProvider(
Context context, ViewGroup containerView) {
return mFactory.createAutofillProvider(context, mWebView);
}
});
if (mAppTargetSdkVersion >= Build.VERSION_CODES.KITKAT) {
// On KK and above, favicons are automatically downloaded as the method
// old apps use to enable that behavior is deprecated.
AwContents.setShouldDownloadFavicons();
}
if (mAppTargetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
// Prior to Lollipop, JavaScript objects injected via addJavascriptInterface
// were not inspectable.
mAwContents.disableJavascriptInterfacesInspection();
}
// TODO: This assumes AwContents ignores second Paint param.
mAwContents.setLayerType(mWebView.getLayerType(), null);
}
接下来我们继续看Awcontents类中的addPossiblyUnsafeJavascriptInterface方法具体实现
private void setNewAwContents(long newAwContentsPtr) {
if (mNativeAwContents != 0) {
destroyNatives();
mContentViewCore = null;
mWebContents = null;
mNavigationController = null;
}
.....
mContentViewCore = createAndInitializeContentViewCore(
mContainerView, mContext, mInternalAccessAdapter, nativeWebContents,
new AwGestureStateListener(), mContentViewClient, mZoomControls, mWindowAndroid);
nativeSetJavaPeers(mNativeAwContents, this, mWebContentsDelegate, mContentsClientBridge,
mIoThreadClient, mInterceptNavigationDelegate);
mWebContents = mContentViewCore.getWebContents();
mNavigationController = mWebContents.getNavigationController();
installWebContentsObserver();
mSettings.setWebContents(nativeWebContents);
nativeSetDipScale(mNativeAwContents, (float) mDIPScale);
mContentViewCore.onShow();
}
private static ContentViewCore createAndInitializeContentViewCore(ViewGroup containerView,
Context context, InternalAccessDelegate internalDispatcher, long nativeWebContents,
GestureStateListener gestureStateListener,
ContentViewClient contentViewClient,
ContentViewCore.ZoomControlsDelegate zoomControlsDelegate,
WindowAndroid windowAndroid) {
ContentViewCore contentViewCore = new ContentViewCore(context);
contentViewCore.initialize(containerView, internalDispatcher, nativeWebContents,
windowAndroid);
contentViewCore.addGestureStateListener(gestureStateListener);
contentViewCore.setContentViewClient(contentViewClient);
contentViewCore.setZoomControlsDelegate(zoomControlsDelegate);
return contentViewCore;
}
/**
* @see ContentViewCore#addPossiblyUnsafeJavascriptInterface(Object, String, Class)
*/
public void addPossiblyUnsafeJavascriptInterface(Object object, String name,
Class extends Annotation> requiredAnnotation) {
if (isDestroyed()) return;
mContentViewCore.addPossiblyUnsafeJavascriptInterface(object, name, requiredAnnotation);
}
继续进入ContentViewCore.java查看addPossiblyUnsafeJavascriptInterface方法
public void addJavascriptInterface(Object object, String name) {
addPossiblyUnsafeJavascriptInterface(object, name, JavascriptInterface.class);
}
public void addPossiblyUnsafeJavascriptInterface(Object object, String name,
Class extends Annotation> requiredAnnotation) {
if (mNativeContentViewCore != 0 && object != null) {
mJavaScriptInterfaces.put(name, new Pair
看方法名,nativeAddJavascriptInterface看起来最终调用来自于Native,继续往下看:
private native void nativeAddJavascriptInterface(int nativeContentViewCoreImpl, Object object,
String name, Class requiredAnnotation, HashSet
接下来看C++代码,这里的中间调用过程没有深究,但最终应该是来到了这里:
static void AddJavascriptInterface(JNIEnv *env, jobject obj, jint nativeFramePointer,
jobject javascriptObj, jstring interfaceName)
{
#ifdef ANDROID_INSTRUMENT
TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter);
#endif
WebCore::Frame* pFrame = 0;
if (nativeFramePointer == 0)
pFrame = GET_NATIVE_FRAME(env, obj);
else
pFrame = (WebCore::Frame*)nativeFramePointer;
LOG_ASSERT(pFrame, "nativeAddJavascriptInterface must take a valid frame pointer!");
JavaVM* vm;
env->GetJavaVM(&vm);
LOGV("::WebCore:: addJSInterface: %p", pFrame);
#if USE(JSC)
// Copied from qwebframe.cpp
JSC::JSLock lock(false);
WebCore::JSDOMWindow *window = WebCore::toJSDOMWindow(pFrame);
if (window) {
JSC::Bindings::RootObject *root = pFrame->script()->bindingRootObject();
JSC::Bindings::setJavaVM(vm);
// Add the binding to JS environment
JSC::ExecState* exec = window->globalExec();
JSC::JSObject *addedObject = WeakJavaInstance::create(javascriptObj,
root)->createRuntimeObject(exec);
const jchar* s = env->GetStringChars(interfaceName, NULL);
if (s) {
// Add the binding name to the window's table of child objects.
JSC::PutPropertySlot slot;
window->put(exec, JSC::Identifier(exec, (const UChar *)s,
env->GetStringLength(interfaceName)), addedObject, slot);
env->ReleaseStringChars(interfaceName, s);
checkException(env);
}
}
#endif // USE(JSC)
#if USE(V8)
if (pFrame) {
const char* name = JSC::Bindings::getCharactersFromJStringInEnv(env, interfaceName);
NPObject* obj = JSC::Bindings::JavaInstanceToNPObject(new JSC::Bindings::JavaInstance(javascriptObj));
pFrame->script()->bindToWindowObject(pFrame, name, obj);
// JavaInstanceToNPObject calls NPN_RetainObject on the
// returned one (see CreateV8ObjectForNPObject in V8NPObject.cpp).
// BindToWindowObject also increases obj's ref count and decrease
// the ref count when the object is not reachable from JavaScript
// side. Code here must release the reference count increased by
// JavaInstanceToNPObject.
_NPN_ReleaseObject(obj);
JSC::Bindings::releaseCharactersForJString(interfaceName, name);
}
#endif
}
这里的代码量较大,如果使用的是JSC引擎 ,我们主要关注下面这一行代码:
window->put(exec, JSC::Identifier(exec, (const UChar *)s, env->GetStringLength(interfaceName)), addedObject, slot);
最终数据的处理原来来自于C++端的window对象,这又是什么呢?继续看:
WebCore::JSDOMWindow *window = WebCore::toJSDOMWindow(pFrame);
这是在WebCore命名空间下面的JSDOMWindow对象,至此我们将一个android的java对象加入到了javascript的dom对象 完成了原生到js对象的注入全部过程
如果是V8引擎
pFrame->script()->bindToWindowObject(pFrame, name, obj);
将android的java对象转换为NPObject 并绑定到javascript的window对象中