【问题标题】:Does addJavascriptInterface() rely upon getClass()?addJavascriptInterface() 是否依赖于 getClass()?
【发布时间】:2013-09-26 23:03:20
【问题描述】:

我试图通过代码追踪addJavascriptInterface() 上的WebView 是如何实现的,但它会深入到native 代码中,这基本上削弱了我了解正在发生的事情的能力。

具体来说,我正在尝试确定 addJavascriptInterface() 安排回调 Java 代码的 JNI(?) 方法是否依赖 getClass() 作为反射策略的一部分,将 JavaScript 源代码中的方法引用映射到Java中的实现。我会假设它必须,并且maybe I am searching in the wrong place,但我没有看到它。

谁能指出使用注入 Java 对象的代码,以便我们了解它是如何实现的?

谢谢!


更新

澄清一下,我的意思是在传递给addJavascriptInterface()的对象上使用getClass()

【问题讨论】:

  • 另一个暗示它可能确实使用了反射:它需要一个 -keep ProGuard 规则。
  • 您是否尝试过实现 JavaScriptInterface 实例,并在其中一个方法中设置断点并让浏览器中的 JavaScript 调用该方法?在断点命中时检查该调用的堆栈跟踪可能很有启发性。
  • @StreetsOfBoston:嗯,我不能在getClass() 本身上设置断点,除非我使用附加到 Eclipse 的 Android 源代码来安装东西(这是可能的,只是我不需要弄乱)。在其他地方设置断点可能会引导我了解这些东西在代码中的位置,但它不会直接提供答案。好主意,不过——我今天会试试这些东西。
  • @CommonsWare,顺便说一句,your location 意味着你在“太空”中是什么意思?

标签: android android-webview android-source dalvik


【解决方案1】:

我认为您需要的代码位于external/webkit/Source/WebCore/bridge/jni/。那里有两个主要的子目录,jscv8 代表 Android 使用的两个 Javascript 引擎。由于 V8 是最近使用过一段时间的引擎,我们会坚持使用它。

我假设您能够成功跟踪代码的 Java 端以从 WebView.addJavascriptInterface()BrowserFrame.nativeAddJavaScriptInterface(),我将省略这些细节。 native 端由AddJavaScriptInterface() 中的external/webkit/Source/WebKit/android/jni/WebCoreFrameBridge.cpp 拾取,其中应用传入的Java 对象最终以bindToWindowObject() 绑定到WebKit 框架。

我正在尝试确定 addJavascriptInterface() 安排回调 Java 代码的 JNI 方法是否依赖于 getClass() 作为反射策略的一部分

简短的回答是肯定的。他们在传统的 JNI 代码周围使用了很多包装器,但是如果您查看它们的内部,就会发现 JNIEnv 上用于进行反射的访问器。他们在 V8 中创建的包装器是:

external/webkit/Source/WebCore/bridge/jni/v8/JavaInstanceJobjectV8.cpp external/webkit/Source/WebCore/bridge/jni/v8/JavaClassJobjectV8.cpp external/webkit/Source/WebCore/bridge/jni/v8/JavaMethodJobjectV8.cpp

回到WebCoreFrameBridge.cpp,在绑定应用程序传入的那个对象之前,原来通过JNI传递到本机代码中的jobject被包装在一个JavaInstance类中,然后转换为一个NPObject,这是绑定到 WebKit 的最终对象。 V8 NPObject 的源代码位于: external/webkit/Source/WebCore/bridge/jni/v8/JavaNPObjectV8.cpp

我们可以在NPObject 实现中看到,调用总是将JavaInstance 提取出来并在那里调用方法。如果您查看JavaNPObjectHasMethod()JavaNPObjectInvoke 之类的示例,您会注意到以下行经常出现:

instance->getClass()->methodsNamed(name)

这将返回他们创建的 JavaClass 包装器,但如果您查看 JavaClassJobjectV8 构造函数和相关方法,您会看到那些使用 JNIEnv 对 Java 对象的熟悉反射调用(包括 实际 JNI getClass()调用Dalvik)。

因此,当绑定的 WebKit 框架调用方法时,它会找到关联的 NPObject,然后提取其 JavaInstance 包装器,然后使用 JNI 反射来访问 Java 方法。这里的监管链有点难以遵循,所以如果已经提供的内容足以回答您的问题,请告诉我。

【讨论】:

  • 优秀的文章!我没有意识到external/ 项目包含这样的特定于 Android 的 hack——我认为它们更接近纯上游代码。不幸的是,这意味着我目前试图关闭addJavascriptInterface() 安全漏洞的“万岁玛丽”尝试被枪杀。但是,最好早点发现,再翻找我的帽子,看看能不能找到一只兔子。非常感谢您的帮助!
  • @CommonsWare 你在这里找到过兔子吗?环顾四周,看看是否有人提出了解决 4.2 之前版本的 android 安全漏洞的解决方案
  • @littleFluffyKitty:“你在这里找到过兔子吗?” -- 不幸的是,我过着没有兔子的生活。
【解决方案2】:

这是我得到的:

WebView wv = ...;
wv.addJavascriptInterface(object, name);

这是:

public void addJavascriptInterface(Object object, String name) {
    checkThread();
    mProvider.addJavascriptInterface(object, name);
}

mProviderWebViewProvider 类型的接口,因为它在WebView 类中声明:

//-------------------------------------------------------------------------
// Private internal stuff
//-------------------------------------------------------------------------

private WebViewProvider mProvider;

我能看到的唯一实例化它的方法是ensureProviderCreated()

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

getFactory() 实现为:

private static synchronized WebViewFactoryProvider getFactory() {
    return WebViewFactory.getProvider();
}

getProvider() 实现为:

static synchronized WebViewFactoryProvider getProvider() {
    // For now the main purpose of this function (and the factory abstraction) is to keep
    // us honest and minimize usage of WebViewClassic internals when binding the proxy.
    if (sProviderInstance != null) return sProviderInstance;

    sProviderInstance = getFactoryByName(DEFAULT_WEB_VIEW_FACTORY);
    if (sProviderInstance == null) {
        if (DEBUG) Log.v(LOGTAG, "Falling back to explicit linkage");
        sProviderInstance = new WebViewClassic.Factory();
    }
    return sProviderInstance;
}

getFactoryByName() 实现为:

private static WebViewFactoryProvider getFactoryByName(String providerName) {
    try {
        if (DEBUG) Log.v(LOGTAG, "attempt to load class " + providerName);
        Class<?> c = Class.forName(providerName);
        if (DEBUG) Log.v(LOGTAG, "instantiating factory");
        return (WebViewFactoryProvider) c.newInstance();
    } catch (ClassNotFoundException e) {
        Log.e(LOGTAG, "error loading " + providerName, e);
    } catch (IllegalAccessException e) {
        Log.e(LOGTAG, "error loading " + providerName, e);
    } catch (InstantiationException e) {
        Log.e(LOGTAG, "error loading " + providerName, e);
    }
    return null;
}

这里是它使用反射的地方。如果在实例化自定义类的过程中发生异常,将使用WebViewClassic.Factory() 代替。以下是它的实现方式:

static class Factory implements WebViewFactoryProvider,  WebViewFactoryProvider.Statics {
    @Override
    public String findAddress(String addr) {
        return WebViewClassic.findAddress(addr);
    }
    @Override
    public void setPlatformNotificationsEnabled(boolean enable) {
        if (enable) {
            WebViewClassic.enablePlatformNotifications();
        } else {
            WebViewClassic.disablePlatformNotifications();
        }
    }

    @Override
    public Statics getStatics() { return this; }

    @Override
    public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
        return new WebViewClassic(webView, privateAccess);
    }

    @Override
    public GeolocationPermissions getGeolocationPermissions() {
        return GeolocationPermissionsClassic.getInstance();
    }

    @Override
    public CookieManager getCookieManager() {
        return CookieManagerClassic.getInstance();
    }

    @Override
    public WebIconDatabase getWebIconDatabase() {
        return WebIconDatabaseClassic.getInstance();
    }

    @Override
    public WebStorage getWebStorage() {
        return WebStorageClassic.getInstance();
    }

    @Override
    public WebViewDatabase getWebViewDatabase(Context context) {
        return WebViewDatabaseClassic.getInstance(context);
    }
}

现在返回mProvider = getFactory().createWebView(this, new PrivateAccess());,其中getFactory() 是自定义类(通过反射)或WebViewClassic.Factory

WebViewClassic.Factory#createWebView() 返回WebViewClassic,它是mProvider 类型的子类型。

WebViewClassic#addJavascriptInterface 实现为:

/**
 * See {@link WebView#addJavascriptInterface(Object, String)}
 */
@Override
public void addJavascriptInterface(Object object, String name) {
    if (object == null) {
        return;
    }
    WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
    arg.mObject = object;
    arg.mInterfaceName = name;
    mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
}

我想,这就是你要找的 :)

【讨论】:

  • 我对这个问题的解释:在使用BrowserFrame#stringByEvaluatingJavaScriptFromString(String) 浮动查询后,javascript 回传时是否使用反射?看了你的回答,我觉得我的解释很离谱。
  • 对不起,我本来应该澄清更多的(只是在编辑中做了):我很想知道是否会在传递给 addJavascriptInterface() 的对象上调用 getClass()
【解决方案3】:

这更像是一个评论而不是一个答案,但我无法在 cmets 中添加堆栈跟踪。就这样吧:

在作为 JavaScript 接口实现服务器的对象中设置断点时,这是我得到的示例堆栈跟踪:

16> WebViewCoreThread@830034675584, prio=5, in group 'main', status: 'RUNNING'
      at com.mediaarc.player.books.model.pagesource.service.EPubPageSourceService$JS.JSReady(EPubPageSourceService.java:1752)
      at android.webkit.JWebCoreJavaBridge.nativeServiceFuncPtrQueue(JWebCoreJavaBridge.java:-1)
      at android.webkit.JWebCoreJavaBridge.nativeServiceFuncPtrQueue(JWebCoreJavaBridge.java:-1)
      at android.webkit.JWebCoreJavaBridge.handleMessage(JWebCoreJavaBridge.java:113)
      at android.os.Handler.dispatchMessage(Handler.java:99)
      at android.os.Looper.loop(Looper.java:137)
      at android.webkit.WebViewCore$WebCoreThread.run(WebViewCore.java:814)
      at java.lang.Thread.run(Thread.java:841)

它以 Java 开头 (Thread.run --> handleMessage)。然后它消失在 Native 代码中(nativeServiceFuncPtrQueue),然后又出现在 Java 中(nativeServiceFuncPtrQueue --> JSReady)。

此堆栈来自运行 4.3 的 Nexus 10。

在 Native 层中发生了一些事情,它将执行从对 nativeServiceFuncPtrQueue 的调用直接转移到 Java 中 JavaScriptInterface 实例的 Java 方法。

现在,JavaScriptInterface 需要注释它发布到 JavaScript 的每个方法(@JavaScriptInterface 方法注释)。也许这会在运行中生成一些从 Native 调用到 Java 的 JNI 桥。

我想知道这个堆栈跟踪在不需要 @JavaScriptInterface 注释的旧设备上会是什么样子。

【讨论】:

  • 在 Android 4.1 上,堆栈跟踪信息更少,直接从 WebViewCore.nativeMouseClick() 跳转到我的方法。而且,虽然我似乎能够告诉 Eclipse 我想要在getClass() 上设置一个断点,但它实际上并没有暂停线程——它只是减慢了速度。
  • 在方法上设置断点会使一切变慢。没有办法覆盖'public Class> getClass()',即使在使用接口/代理/InvocationHandler 类时也是如此。据我所知,如果不调用实例的 getClass() 方法,就没有“干净”的方法来获取有关实例的信息。从那时起,您可以根据 Method 信息生成适当的 JNI 绑定以直接调用 Java。
【解决方案4】:

来自Understanding Android's webview addjavascriptinterface:“WebView.addJavascriptInterface 方法向 WebViewCore 的实例发送消息:

mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);在 WebViewCore.java 中有一堆称为 sendMessage 的重载方法,但我们并不需要知道究竟调用了哪些方法,因为它们的作用几乎相同。甚至有一个很好的评论给我们一个提示,我们在正确的地方!所有这些都委托给 EventHub 的一个实例,它是一些内部类。这个方法被证明是同步的,并且正在向 Handler 的实例发送消息,这很好地表明它可能在另一个线程中运行,但为了完整起见,让我们找出来!

该处理程序在 EventHub.transferMessages 中实例化,它是从 WebViewCore.initialize 调用的。这里还有几跳,但最终我发现这是从 WebCoreThread(Runnable 的子类)中的 run 调用的,它在此处与新 Thread 一起实例化。” 在此处与新线程一起实例化。”

  synchronized (WebViewCore.class) {
            if (sWebCoreHandler == null) {
                // Create a global thread and start it.
                Thread t = new Thread(new WebCoreThread());
                t.setName(THREAD_NAME);
                t.start();
                try {
                    WebViewCore.class.wait();
                } catch (InterruptedException e) {
                    Log.e(LOGTAG, "Caught exception while waiting for thread " +
                           "creation.");
                    Log.e(LOGTAG, Log.getStackTraceString(e));
                }
            }
        }

换句话说,我认为这可能是调用链:

android.webkit.WebViewClassic

 4159    @Override
4160    public void More ...addJavascriptInterface(Object object, String name) {
4161
4162        if (object == null) {
4163            return;
4164        }
4165        WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
4166
4167        arg.mObject = object;
4168        arg.mInterfaceName = name;
4169
4170        // starting with JELLY_BEAN_MR1, annotations are mandatory for enabling access to
4171        // methods that are accessible from JS.
4172        if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
4173            arg.mRequireAnnotation = true;
4174        } else {
4175            arg.mRequireAnnotation = false;
4176        }
4177        mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
4178    }

android.webkit.WebViewCore

 static class JSInterfaceData {
827         Object mObject;
828         String mInterfaceName;
829         boolean mRequireAnnotation;
830     }

java.lang.Object

 37 public class Object {
38 
39     private static native void registerNatives();
40     static {
41         registerNatives();
42     }

返回此对象的运行时类。返回的 Class 对象是被表示类的静态同步方法锁定的对象。实际结果类型是 Class where |X|是调用 getClass 的表达式的静态类型的擦除。例如,此代码片段中不需要强制转换:

 Number n = 0; 
Class<? extends Number> c = n.getClass();

返回: Class 对象,表示此对象的运行时类。另请参阅:Java 语言规范,第三版(15.8.2 类文字)

 64 
65     public final native Class<?> getClass();

从 Dalvik 的角度来看,我认为您只是通过 findClass 从 JNIHelp.c 注册一个 JNI 回调:

 /*
 * Register native JNI-callable methods.
 *
 * "className" looks like "java/lang/String".
 */
int jniRegisterNativeMethods(JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    LOGV("Registering %s natives\n", className);
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        LOGE("Native registration unable to find class '%s', aborting\n",
            className);
        abort();
    }

    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        LOGE("RegisterNatives failed for '%s', aborting\n", className);
        abort();
    }

    (*env)->DeleteLocalRef(env, clazz);
    return 0;
}

总之我的想法来源于Native Libraries

//Get jclass with env->FindClass

所以也许 FindClass 可以用来代替 getClass...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-07-22
    • 2015-09-22
    • 2015-01-09
    • 2017-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-04
    相关资源
    最近更新 更多