Commit 103bf194 authored by torne's avatar torne Committed by Commit bot

Use combined native/manual JNI registration.

Add a new mode to the JNI generator which emits both native JNI exports
and manual registration code, and use this as the default mode (except
when compiling with clang as a clang bug prevents this from working at
present).

Native JNI exports are stripped from binaries by default to enforce that
the correct manual registration code is called (and to save increasing
the dynamic symbol table size), except for binaries that explicitly opt
in to using native exports (i.e. libwebviewchromium). Native exports are
not compatible with the crazy linker, so cannot be used universally.

The WebView-specific call to InitReplacementClassLoader, required by
native export mode, has been moved to base to make it easy for other
binaries to experiment with that mode.

Manual JNI registration can be disabled with a call to
base::android::DisableManualJniRegistration at the beginning of
JNI_OnLoad and this has been added to WebView. We plan to refactor the
Android library entry points to make it possible to avoid needing this
flag by just not calling JNI registration but the work is still ongoing;
the flag gets us the desired WebView startup time improvement in the
meantime.

BUG=442327

Review URL: https://codereview.chromium.org/920883002

Cr-Commit-Position: refs/heads/master@{#317434}
parent a26edbf7
......@@ -9,6 +9,10 @@
// This is called by the VM when the shared library is first loaded.
// Most of the initialization is done in LibraryLoadedOnMainThread(), not here.
JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
// WebView uses native JNI exports; disable manual JNI registration to
// improve startup peformance.
base::android::DisableManualJniRegistration();
if (!android_webview::OnJNIOnLoad(vm))
return -1;
......
......@@ -8,7 +8,6 @@
#include "android_webview/native/android_webview_jni_registrar.h"
#include "base/android/jni_android.h"
#include "base/android/jni_registrar.h"
#include "base/android/jni_utils.h"
#include "components/navigation_interception/component_jni_registrar.h"
#include "components/web_contents_delegate_android/component_jni_registrar.h"
#include "content/public/app/content_jni_onload.h"
......@@ -42,10 +41,6 @@ bool WebViewJNIOnLoadDelegate::RegisterJNI(JNIEnv* env) {
}
bool WebViewJNIOnLoadDelegate::Init() {
JNIEnv* env = base::android::AttachCurrentThread();
base::android::InitReplacementClassLoader(env,
base::android::GetClassLoader(env));
content::SetContentMainDelegate(new android_webview::AwMainDelegate());
// Initialize url lib here while we are still single-threaded, in case we use
......
......@@ -7,6 +7,9 @@
'dependencies': [
'android_webview_common',
],
'variables': {
'use_native_jni_exports': 1,
},
'conditions': [
[ 'android_webview_build==1', {
'dependencies': [
......
......@@ -6,6 +6,7 @@
#include "base/android/jni_android.h"
#include "base/android/jni_onload_delegate.h"
#include "base/android/jni_utils.h"
#include "base/android/library_loader/library_loader_hooks.h"
namespace base {
......@@ -25,6 +26,11 @@ bool BaseJNIOnLoadDelegate::RegisterJNI(JNIEnv* env) {
}
bool BaseJNIOnLoadDelegate::Init() {
JNIEnv* env = base::android::AttachCurrentThread();
base::android::InitReplacementClassLoader(env,
base::android::GetClassLoader(env));
return true;
}
......
......@@ -17,6 +17,8 @@ using base::android::GetClass;
using base::android::MethodID;
using base::android::ScopedJavaLocalRef;
bool g_disable_manual_jni_registration = false;
JavaVM* g_jvm = NULL;
// Leak the global app context, as it is used from a non-joinable worker thread
// that may still be running at shutdown. There is no harm in doing this.
......@@ -77,6 +79,15 @@ std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) {
namespace base {
namespace android {
bool IsManualJniRegistrationDisabled() {
return g_disable_manual_jni_registration;
}
void DisableManualJniRegistration() {
DCHECK(!g_disable_manual_jni_registration);
g_disable_manual_jni_registration = true;
}
JNIEnv* AttachCurrentThread() {
DCHECK(g_jvm);
JNIEnv* env = NULL;
......
......@@ -21,6 +21,13 @@ namespace android {
// Used to mark symbols to be exported in a shared library's symbol table.
#define JNI_EXPORT __attribute__ ((visibility("default")))
// Used to disable manual JNI registration in binaries that prefer to use native
// JNI exports for startup performance. This is not compatible with the crazy
// linker and so defaults to off. Call DisableManualJniRegistration at the very
// beginning of JNI_OnLoad to use this.
BASE_EXPORT bool IsManualJniRegistrationDisabled();
BASE_EXPORT void DisableManualJniRegistration();
// Contains the registration method information for initializing JNI bindings.
struct RegistrationMethod {
const char* name;
......
......@@ -889,7 +889,7 @@ jmethodID g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = NULL;""")
def GetJNINativeMethodsString(self):
"""Returns the implementation of the array of native methods."""
if self.options.native_exports:
if self.options.native_exports and not self.options.native_exports_optional:
return ''
template = Template("""\
static const JNINativeMethod kMethods${JAVA_CLASS}[] = {
......@@ -922,7 +922,7 @@ ${KMETHODS}
"""Returns the code for RegisterNatives."""
template = Template("""\
${REGISTER_NATIVES_SIGNATURE} {
${CLASSES}
${EARLY_EXIT}${CLASSES}
${NATIVES}
${CALLED_BY_NATIVES}
return true;
......@@ -934,9 +934,16 @@ ${CALLED_BY_NATIVES}
else:
signature += ')'
early_exit = ''
if self.options.native_exports_optional:
early_exit = """\
if (base::android::IsManualJniRegistrationDisabled()) return true;
"""
natives = self.GetRegisterNativesImplString()
called_by_natives = self.GetRegisterCalledByNativesImplString()
values = {'REGISTER_NATIVES_SIGNATURE': signature,
'EARLY_EXIT': early_exit,
'CLASSES': self.GetFindClasses(),
'NATIVES': natives,
'CALLED_BY_NATIVES': called_by_natives,
......@@ -945,7 +952,7 @@ ${CALLED_BY_NATIVES}
def GetRegisterNativesImplString(self):
"""Returns the shared implementation for RegisterNatives."""
if self.options.native_exports:
if self.options.native_exports and not self.options.native_exports_optional:
return ''
template = Template("""\
......@@ -1513,7 +1520,12 @@ See SampleForTests.java for more details.
option_parser.add_option('--native_exports', action='store_true',
help='Native method registration through .so '
'exports.')
option_parser.add_option('--native_exports_optional', action='store_true',
help='Support both explicit and native method'
'registration.')
options, args = option_parser.parse_args(argv)
if options.native_exports_optional:
options.native_exports = True
if options.jar_file:
input_file = ExtractJarInputFile(options.jar_file, options.input_file,
options.output_dir)
......
......@@ -43,6 +43,7 @@ class TestOptions(object):
self.cpp = 'cpp'
self.javap = 'javap'
self.native_exports = False
self.native_exports_optional = False
class TestGenerator(unittest.TestCase):
def assertObjEquals(self, first, second):
......@@ -1019,7 +1020,7 @@ class Foo {
test_data, 'org/chromium/example/jni_generator/Test', options)
self.assertGoldenTextEquals(jni_from_java.GetContent())
def testNativeExportsOption(self):
def runNativeExportsOption(self, optional):
test_data = """
package org.chromium.example.jni_generator;
......@@ -1054,9 +1055,18 @@ class Foo {
options = TestOptions()
options.jni_init_native_name = 'nativeInitNativeClass'
options.native_exports = True
options.native_exports_optional = optional
jni_from_java = jni_generator.JNIFromJavaSource(
test_data, 'org/chromium/example/jni_generator/SampleForTests', options)
self.assertGoldenTextEquals(jni_from_java.GetContent())
return jni_from_java.GetContent()
def testNativeExportsOption(self):
content = self.runNativeExportsOption(False)
self.assertGoldenTextEquals(content)
def testNativeExportsOptionalOption(self):
content = self.runNativeExportsOption(True)
self.assertGoldenTextEquals(content)
def testOuterInnerRaises(self):
test_data = """
......
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file is autogenerated by
// base/android/jni_generator/jni_generator.py
// For
// org/chromium/example/jni_generator/SampleForTests
#ifndef org_chromium_example_jni_generator_SampleForTests_JNI
#define org_chromium_example_jni_generator_SampleForTests_JNI
#include <jni.h>
#include "base/android/jni_generator/jni_generator_helper.h"
#include "base/android/jni_int_wrapper.h"
// Step 1: forward declarations.
namespace {
const char kSampleForTestsClassPath[] =
"org/chromium/example/jni_generator/SampleForTests";
// Leaking this jclass as we cannot use LazyInstance from some threads.
base::subtle::AtomicWord g_SampleForTests_clazz __attribute__((unused)) = 0;
#define SampleForTests_clazz(env) base::android::LazyGetClass(env, kSampleForTestsClassPath, &g_SampleForTests_clazz)
} // namespace
extern "C" {
static jint Init(JNIEnv* env, jobject jcaller);
__attribute__((visibility("default"), alias("Init")))
jint
Java_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_nativeInit(JNIEnv*
env, jobject jcaller);
static jint Init(JNIEnv* env, jobject jcaller);
__attribute__((visibility("default"), alias("Init")))
jint
Java_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_nativeInit(JNIEnv*
env, jobject jcaller);
}; // extern "C"
// Step 2: method stubs.
extern "C" {
__attribute__((visibility("default")))
jint
Java_org_chromium_example_jni_1generator_SampleForTests_nativeStaticMethod(JNIEnv*
env,
jobject jcaller,
jlong nativeTest,
jint arg1) {
Test* native = reinterpret_cast<Test*>(nativeTest);
CHECK_NATIVE_PTR(env, jcaller, native, "StaticMethod", 0);
return native->StaticMethod(env, jcaller, arg1);
}
__attribute__((visibility("default")))
jint
Java_org_chromium_example_jni_1generator_SampleForTests_nativeMethod(JNIEnv*
env,
jobject jcaller,
jlong nativeTest,
jint arg1) {
Test* native = reinterpret_cast<Test*>(nativeTest);
CHECK_NATIVE_PTR(env, jcaller, native, "Method", 0);
return native->Method(env, jcaller, arg1);
}
static base::subtle::AtomicWord g_SampleForTests_testMethodWithParam = 0;
static void Java_SampleForTests_testMethodWithParam(JNIEnv* env, jobject obj,
JniIntWrapper iParam) {
/* Must call RegisterNativesImpl() */
CHECK_CLAZZ(env, obj,
SampleForTests_clazz(env));
jmethodID method_id =
base::android::MethodID::LazyGet<
base::android::MethodID::TYPE_INSTANCE>(
env, SampleForTests_clazz(env),
"testMethodWithParam",
"("
"I"
")"
"V",
&g_SampleForTests_testMethodWithParam);
env->CallVoidMethod(obj,
method_id, as_jint(iParam));
jni_generator::CheckException(env);
}
static base::subtle::AtomicWord g_SampleForTests_testMethodWithParamAndReturn =
0;
static base::android::ScopedJavaLocalRef<jstring>
Java_SampleForTests_testMethodWithParamAndReturn(JNIEnv* env, jobject obj,
JniIntWrapper iParam) {
/* Must call RegisterNativesImpl() */
CHECK_CLAZZ(env, obj,
SampleForTests_clazz(env), NULL);
jmethodID method_id =
base::android::MethodID::LazyGet<
base::android::MethodID::TYPE_INSTANCE>(
env, SampleForTests_clazz(env),
"testMethodWithParamAndReturn",
"("
"I"
")"
"Ljava/lang/String;",
&g_SampleForTests_testMethodWithParamAndReturn);
jstring ret =
static_cast<jstring>(env->CallObjectMethod(obj,
method_id, as_jint(iParam)));
jni_generator::CheckException(env);
return base::android::ScopedJavaLocalRef<jstring>(env, ret);
}
static base::subtle::AtomicWord g_SampleForTests_testStaticMethodWithParam = 0;
static jint Java_SampleForTests_testStaticMethodWithParam(JNIEnv* env,
JniIntWrapper iParam) {
/* Must call RegisterNativesImpl() */
CHECK_CLAZZ(env, SampleForTests_clazz(env),
SampleForTests_clazz(env), 0);
jmethodID method_id =
base::android::MethodID::LazyGet<
base::android::MethodID::TYPE_STATIC>(
env, SampleForTests_clazz(env),
"testStaticMethodWithParam",
"("
"I"
")"
"I",
&g_SampleForTests_testStaticMethodWithParam);
jint ret =
env->CallStaticIntMethod(SampleForTests_clazz(env),
method_id, as_jint(iParam));
jni_generator::CheckException(env);
return ret;
}
static base::subtle::AtomicWord g_SampleForTests_testMethodWithNoParam = 0;
static jdouble Java_SampleForTests_testMethodWithNoParam(JNIEnv* env) {
/* Must call RegisterNativesImpl() */
CHECK_CLAZZ(env, SampleForTests_clazz(env),
SampleForTests_clazz(env), 0);
jmethodID method_id =
base::android::MethodID::LazyGet<
base::android::MethodID::TYPE_STATIC>(
env, SampleForTests_clazz(env),
"testMethodWithNoParam",
"("
")"
"D",
&g_SampleForTests_testMethodWithNoParam);
jdouble ret =
env->CallStaticDoubleMethod(SampleForTests_clazz(env),
method_id);
jni_generator::CheckException(env);
return ret;
}
static base::subtle::AtomicWord g_SampleForTests_testStaticMethodWithNoParam =
0;
static base::android::ScopedJavaLocalRef<jstring>
Java_SampleForTests_testStaticMethodWithNoParam(JNIEnv* env) {
/* Must call RegisterNativesImpl() */
CHECK_CLAZZ(env, SampleForTests_clazz(env),
SampleForTests_clazz(env), NULL);
jmethodID method_id =
base::android::MethodID::LazyGet<
base::android::MethodID::TYPE_STATIC>(
env, SampleForTests_clazz(env),
"testStaticMethodWithNoParam",
"("
")"
"Ljava/lang/String;",
&g_SampleForTests_testStaticMethodWithNoParam);
jstring ret =
static_cast<jstring>(env->CallStaticObjectMethod(SampleForTests_clazz(env),
method_id));
jni_generator::CheckException(env);
return base::android::ScopedJavaLocalRef<jstring>(env, ret);
}
}; // extern "C"
// Step 3: RegisterNatives.
static const JNINativeMethod kMethodsMyOtherInnerClass[] = {
{ "nativeInit",
"("
")"
"I",
reinterpret_cast<void*>(Java_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_nativeInit)
},
};
static const JNINativeMethod kMethodsMyInnerClass[] = {
{ "nativeInit",
"("
")"
"I",
reinterpret_cast<void*>(Java_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_nativeInit)
},
};
static const JNINativeMethod kMethodsSampleForTests[] = {
{ "nativeStaticMethod",
"("
"J"
"I"
")"
"I",
reinterpret_cast<void*>(Java_org_chromium_example_jni_1generator_SampleForTests_nativeStaticMethod)
},
{ "nativeMethod",
"("
"J"
"I"
")"
"I",
reinterpret_cast<void*>(Java_org_chromium_example_jni_1generator_SampleForTests_nativeMethod)
},
};
static bool RegisterNativesImpl(JNIEnv* env, jclass clazz) {
if (base::android::IsManualJniRegistrationDisabled()) return true;
base::subtle::Release_Store(&g_SampleForTests_clazz,
static_cast<base::subtle::AtomicWord>(env->NewWeakGlobalRef(clazz));
const int kMethodsMyOtherInnerClassSize =
arraysize(kMethodsMyOtherInnerClass);
if (env->RegisterNatives(MyOtherInnerClass_clazz(env),
kMethodsMyOtherInnerClass,
kMethodsMyOtherInnerClassSize) < 0) {
jni_generator::HandleRegistrationError(
env, MyOtherInnerClass_clazz(env), __FILE__);
return false;
}
const int kMethodsMyInnerClassSize = arraysize(kMethodsMyInnerClass);
if (env->RegisterNatives(MyInnerClass_clazz(env),
kMethodsMyInnerClass,
kMethodsMyInnerClassSize) < 0) {
jni_generator::HandleRegistrationError(
env, MyInnerClass_clazz(env), __FILE__);
return false;
}
const int kMethodsSampleForTestsSize = arraysize(kMethodsSampleForTests);
if (env->RegisterNatives(SampleForTests_clazz(env),
kMethodsSampleForTests,
kMethodsSampleForTestsSize) < 0) {
jni_generator::HandleRegistrationError(
env, SampleForTests_clazz(env), __FILE__);
return false;
}
return true;
}
extern "C" JNIEXPORT bool JNICALL
Java_org_chromium_example_jni_1generator_SampleForTests_nativeInitNativeClass(JNIEnv*
env, jclass clazz) {
return RegisterNativesImpl(env, clazz);
}
#endif // org_chromium_example_jni_generator_SampleForTests_JNI
......@@ -74,6 +74,12 @@ template("generate_jni") {
rebase_path(jni_generator_jarjar_file, root_build_dir),
]
}
if (!is_clang) {
# Clang builds currently fail with --native_exports_optional due to
# http://llvm.org/bugs/show_bug.cgi?id=22602 - only enable for gcc.
# http://crbug.com/442327
args += [ "--native_exports_optional" ]
}
}
config("jni_includes_${target_name}") {
......@@ -184,6 +190,12 @@ template("generate_jar_jni") {
"--includes",
rebase_path(jni_generator_include, root_build_dir),
]
if (!is_clang) {
# Clang builds currently fail with --native_exports_optional due to
# http://llvm.org/bugs/show_bug.cgi?id=22602 - only enable for gcc.
# http://crbug.com/442327
args += [ "--native_exports_optional" ]
}
}
}
......
......@@ -73,5 +73,13 @@
'<(DEPTH)/build/android/android_exports.gyp:android_exports',
],
}],
['clang==0', {
# Clang builds currently fail with --native_exports_optional due to
# http://llvm.org/bugs/show_bug.cgi?id=22602 - only enable for gcc.
# http://crbug.com/442327
'variables': {
'native_exports%': '--native_exports_optional',
},
}],
],
}
......@@ -92,6 +92,14 @@
'<(DEPTH)/build/android/android_exports.gyp:android_exports',
],
}],
['clang==0', {
# Clang builds currently fail with --native_exports_optional due to
# http://llvm.org/bugs/show_bug.cgi?id=22602 - only enable for gcc.
# http://crbug.com/442327
'variables': {
'native_exports%': '--native_exports_optional',
},
}],
],
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment