Полагается ли addJavascriptInterface() на getClass()?

Я попытался проследить код, чтобы увидеть, как реализовано addJavascriptInterface() на WebView, но он погружается в код native, который в основном калечит мою способность понять, что происходит.

В частности, я пытаюсь определить, зависит ли средство JNI (?), с помощью которого addJavascriptInterface() организует обратный вызов в код Java, getClass() как часть стратегии отражения, чтобы сопоставить ссылки на методы в исходном коде JavaScript с реализациями в Java. Я предполагаю, что это необходимо, и возможно, я ищу не в том месте, но я его не вижу.

Может ли кто-нибудь указать мне код, в котором используются внедренные объекты Java, чтобы мы могли увидеть, как это реализовано?

Спасибо!


ОБНОВЛЕНИЕ

Чтобы уточнить, я имею в виду использование getClass() для объекта, переданного addJavascriptInterface().


person CommonsWare    schedule 26.09.2013    source источник
comment
Еще один намек на то, что он, вероятно, использует отражение: для него требуется правило -keep ProGuard.   -  person James Wald    schedule 27.09.2013
comment
Вы пытались реализовать экземпляр JavaScriptInterface и установить точку останова в одном из его методов, чтобы JavaScript в браузере вызывал этот метод? Изучение трассировки стека этого вызова при достижении точки останова может быть поучительным.   -  person Streets Of Boston    schedule 27.09.2013
comment
@StreetsOfBoston: Ну, я не могу установить точку останова на самом getClass(), если только я не настрою исходный код Android, прикрепленный к Eclipse (что возможно, просто мне не нужно возиться с этим). Установка точки останова в другом месте может привести меня к тому, где этот материал находится в коде, но не даст прямого ответа. Хорошие идеи, хотя - я попробую это сегодня.   -  person CommonsWare    schedule 27.09.2013
comment
@CommonsWare, кстати, что означает ваше местоположение, что вы находитесь в космосе?   -  person Pacerier    schedule 17.06.2015


Ответы (4)


Код, который, я думаю, вам нужен, находится в external/webkit/Source/WebCore/bridge/jni/. Там есть два основных подкаталога, jsc и v8, представляющие два движка Javascript, которые использует Android. Поскольку V8 — это двигатель, который использовался совсем недавно и в течение некоторого времени, мы будем придерживаться его.

Я предполагаю, что вы смогли успешно отследить Java-сторону кода, чтобы перейти от WebView.addJavascriptInterface() к BrowserFrame.nativeAddJavaScriptInterface(), я опущу эти детали. Собственная сторона подхватывается AddJavaScriptInterface() в external/webkit/Source/WebKit/android/jni/WebCoreFrameBridge.cpp, где объект Java, передаваемый приложением, окончательно привязывается к фрейму WebKit с помощью bindToWindowObject().

Я пытаюсь определить, означает ли JNI, что addJavascriptInterface() организует обратный вызов в код Java, полагается на 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, прежде чем этот объект, переданный приложением, будет связан, jobject, первоначально переданный в собственный код через JNI, оборачивается в класс JavaInstance, а затем преобразуется в NPObject, который является окончательным объектом, привязанным к WebKit. Источник V8 NPObject находится по адресу: external/webkit/Source/WebCore/bridge/jni/v8/JavaNPObjectV8.cpp

Мы можем видеть в реализации NPObject, что вызовы всегда извлекают JavaInstance обратно и вызывают там методы. Если вы посмотрите на такие примеры, как JavaNPObjectHasMethod() или JavaNPObjectInvoke, вы заметите, что часто появляется следующая строка:

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

Это возвращает созданную ими оболочку JavaClass, но если вы посмотрите на конструктор JavaClassJobjectV8 и связанные с ним методы, вы увидите те знакомые вызовы отражения к объекту Java с использованием JNIEnv (включая фактический вызов JNI getClass() в Далвик).

Таким образом, когда связанный фрейм WebKit вызывает метод, он находит связанный NPObject, который извлекает свою оболочку JavaInstance, которая, в свою очередь, использует отражение JNI для получения доступа к методам Java. Цепочку ответственности здесь немного сложнее соблюдать, поэтому дайте мне знать, достаточно ли того, что уже предоставлено, чтобы ответить на ваши вопросы.

person devunwired    schedule 27.09.2013
comment
Отличная рецензия! Я не знал, что проекты external/ содержали подобные специфичные для Android хаки — я думал, что они ближе к чистому исходному коду. К сожалению, это означает, что моя текущая попытка «Радуйся, Мария» закрыть addJavascriptInterface() утечку безопасности не удалась. Но лучше выяснить это раньше, чем позже, чтобы снова порыться в моей шляпе и посмотреть, смогу ли я найти кролика. Большое спасибо за вашу помощь! - person CommonsWare; 27.09.2013
comment
@CommonsWare, вы когда-нибудь находили здесь кролика? Оглядываясь вокруг, чтобы узнать, придумал ли кто-нибудь решение для устранения утечки безопасности в версиях Android до 4.2. - person cottonBallPaws; 20.12.2013
comment
@littleFluffyKitty: ты когда-нибудь находил здесь кролика? -- К сожалению, я живу без кроликов. - person CommonsWare; 20.12.2013

Вот что я получил:

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

это идет к:

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

mProvider — это интерфейс типа WebViewProvider, как он объявлен в классе 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;
}

и вот где он использует Reflection. Если во время создания экземпляра пользовательского класса возникает исключение, 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);
}

Я думаю, это то, что вы ищете :)

person Eng.Fouad    schedule 27.09.2013
comment
Моя интерпретация вопроса: используется ли отражение, когда javascript обменивается данными после того, как запрос перемещается с использованием BrowserFrame#stringByEvaluatingJavaScriptFromString(String)? Глядя на ваш ответ, я думаю, что моя интерпретация далека от истины. - person Vikram; 27.09.2013
comment
Извините, я должен был уточнить более оригинально (и только что сделал это в редактировании): мне интересно знать, будет ли getClass() вызываться для объекта, переданного в addJavascriptInterface(). - person CommonsWare; 27.09.2013

Это скорее комментарий, чем ответ, но я не могу добавить трассировку стека в комментарии. Итак, вот оно:

При установке точки останова в объекте, который служит реализацией интерфейса 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). Затем он исчезает в нативном коде (nativeServiceFuncPtrQueue) и снова появляется в Java (nativeServiceFuncPtrQueue --> JSReady).

Этот стек взят с Nexus 10 под управлением 4.3.

На собственном уровне происходит что-то, что перемещает выполнение из вызова nativeServiceFuncPtrQueue непосредственно в метод Java экземпляра JavaScriptInterface в Java.

В настоящее время интерфейс JavaScriptInterface должен аннотировать каждый метод, который он публикует в JavaScript (аннотация метода @JavaScriptInterface). Возможно, это генерирует некоторые мосты JNI на лету, вызывая из Native в Java.

Интересно, как эта трассировка стека выглядела бы на более старом устройстве, где аннотации @JavaScriptInterface не нужны.

person Streets Of Boston    schedule 27.09.2013
comment
В Android 4.1 трассировка стека еще менее информативна, она переходит от WebViewCore.nativeMouseClick() прямо к моему методу. И хотя я, кажется, могу сказать Eclipse, что мне нужна точка останова на getClass(), на самом деле он не приостанавливает поток — он просто замедляет работу. - person CommonsWare; 27.09.2013
comment
Установка точки останова в методе делает все ужасно медленным. Невозможно переопределить «общедоступный класс‹?› getClass()», даже при использовании классов интерфейсов/прокси/обработчиков вызовов. Насколько мне известно, не существует «чистого» способа получить информацию об экземпляре без вызова его метода getClass(). С этого момента вы можете генерировать на основе информации о методе соответствующие привязки JNI для прямого вызова Java. - person Streets Of Boston; 28.09.2013

из Понимание веб-представления Android addjavascriptinterface : "Метод WebView.addJavascriptInterface отправляет сообщение экземпляру WebViewCore :

mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, аргумент); В WebViewCore.java есть куча перегруженных методов с именем sendMessage , но нам на самом деле не нужно знать, какой именно вызывается, поскольку они делают почти одно и то же. Есть даже приятный комментарий, который намекает нам, что мы находимся в правильном месте! Все они делегируют экземпляр EventHub, который является некоторым внутренним классом. Этот метод оказывается синхронизированным и отправляет сообщение экземпляру Handler, что является хорошим признаком того, что он, вероятно, выполняется в другом потоке, но для полноты картины давайте выясним!

Этот обработчик создается в EventHub.transferMessages, который вызывается из WebViewCore.initialize. Здесь есть еще несколько прыжков, но в конце концов я обнаружил, что это вызывается из run в WebCoreThread (подкласс Runnable), который создается вместе с новым потоком прямо здесь.

  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.Объект

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

Возвращает класс времени выполнения этого объекта. Возвращенный объект класса — это объект, заблокированный статическими синхронизированными методами представляемого класса. Фактический тип результата — Class, где |X| является стиранием статического типа выражения, для которого вызывается getClass. Например, в этом фрагменте кода приведение не требуется:

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

Возвращает: объект класса, который представляет класс среды выполнения этого объекта. См. Также: Спецификация языка Java, третье издание (15.8.2 Литералы классов)

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

С точки зрения Dalvik, я думаю, вы просто регистрируете обратный вызов JNI через findClass, подобный этому, из JNIHelp.c :

 /*
 * 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;
}

В заключение моя идея взята из родных библиотек:

//Get jclass with env->FindClass

так что, возможно, вместо getClass можно использовать FindClass...

person Community    schedule 30.09.2013