diff --git a/examples/android/binder/java/io/grpc/binder/cpp/exampleclient/native.cc b/examples/android/binder/java/io/grpc/binder/cpp/exampleclient/native.cc index 2f0e7b91426..f9e7b5b5901 100644 --- a/examples/android/binder/java/io/grpc/binder/cpp/exampleclient/native.cc +++ b/examples/android/binder/java/io/grpc/binder/cpp/exampleclient/native.cc @@ -24,6 +24,13 @@ extern "C" JNIEXPORT jstring JNICALL Java_io_grpc_binder_cpp_exampleclient_ButtonPressHandler_native_1entry( JNIEnv* env, jobject /*this*/, jobject application) { + if (grpc::experimental::InitializeBinderChannelJavaClass(env)) { + __android_log_print(ANDROID_LOG_INFO, "DemoClient", + "InitializeBinderChannelJavaClass succeed"); + } else { + __android_log_print(ANDROID_LOG_INFO, "DemoClient", + "InitializeBinderChannelJavaClass failed"); + } static bool first = true; static std::shared_ptr channel; if (first) { @@ -34,7 +41,7 @@ Java_io_grpc_binder_cpp_exampleclient_ButtonPressHandler_native_1entry( "io.grpc.binder.cpp.exampleserver.ExportedEndpointService", std::make_shared< grpc::experimental::binder::UntrustedSecurityPolicy>()); - return env->NewStringUTF("Clicked 1 time"); + return env->NewStringUTF("Clicked 1 time, channel created"); } else { auto stub = helloworld::Greeter::NewStub(channel); grpc::ClientContext context; diff --git a/include/grpcpp/create_channel_binder.h b/include/grpcpp/create_channel_binder.h index a724e871fae..6a9f6b478fe 100644 --- a/include/grpcpp/create_channel_binder.h +++ b/include/grpcpp/create_channel_binder.h @@ -69,6 +69,15 @@ std::shared_ptr CreateCustomBinderChannel( std::shared_ptr security_policy, const ChannelArguments& args); +/// EXPERIMENTAL Finds internal binder transport Java code. To create channels +/// in threads created in native code, it is required to call this function +/// once beforehand in a thread that is not created in native code. +/// See +/// https://developer.android.com/training/articles/perf-jni#faq:-why-didnt-findclass-find-my-class +/// for details of this limitation. +/// Returns true when the initialization is successful. +bool InitializeBinderChannelJavaClass(void* jni_env_void); + } // namespace experimental } // namespace grpc diff --git a/src/core/ext/transport/binder/client/channel_create.cc b/src/core/ext/transport/binder/client/channel_create.cc index eb5d800505f..24ecf5e1fa2 100644 --- a/src/core/ext/transport/binder/client/channel_create.cc +++ b/src/core/ext/transport/binder/client/channel_create.cc @@ -86,13 +86,9 @@ std::shared_ptr CreateCustomBinderChannel( // TODO(mingcl): Consider if we want to delay the connection establishment // until SubchannelConnector start establishing connection. For now we don't // see any benifits doing that. - // clang-format off - grpc_binder::CallStaticJavaMethod(static_cast(jni_env_void), - "io/grpc/binder/cpp/NativeConnectionHelper", - "tryEstablishConnection", - "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", - application, std::string(package_name), std::string(class_name), connection_id); - // clang-format on + grpc_binder::TryEstablishConnection(static_cast(jni_env_void), + application, package_name, class_name, + connection_id); // Set server URI to a URI that contains connection id. The URI will be used // by subchannel connector to obtain correct endpoint binder from @@ -121,6 +117,11 @@ std::shared_ptr CreateCustomBinderChannel( return channel; } +bool InitializeBinderChannelJavaClass(void* jni_env_void) { + return grpc_binder::FindNativeConnectionHelper( + static_cast(jni_env_void)) != nullptr; +} + } // namespace experimental } // namespace grpc @@ -149,6 +150,11 @@ std::shared_ptr CreateCustomBinderChannel( return {}; } +bool InitializeBinderChannelJavaClass(void* jni_env_void) { + GPR_ASSERT(0); + return {}; +} + } // namespace experimental } // namespace grpc diff --git a/src/core/ext/transport/binder/client/jni_utils.cc b/src/core/ext/transport/binder/client/jni_utils.cc index a9ee67882cf..b8395b2e2c2 100644 --- a/src/core/ext/transport/binder/client/jni_utils.cc +++ b/src/core/ext/transport/binder/client/jni_utils.cc @@ -24,66 +24,56 @@ namespace grpc_binder { -void CallStaticJavaMethod(JNIEnv* env, const std::string& clazz, - const std::string& method, const std::string& type, - jobject application, const std::string& pkg, - const std::string& cls) { - jclass cl = env->FindClass(clazz.c_str()); - if (cl == nullptr) { - gpr_log(GPR_ERROR, "No class %s", clazz.c_str()); - } - - jmethodID mid = env->GetStaticMethodID(cl, method.c_str(), type.c_str()); - if (mid == nullptr) { - gpr_log(GPR_ERROR, "No method id %s", method.c_str()); +jclass FindNativeConnectionHelper(JNIEnv* env) { + auto do_find = [env]() { + jclass cl = env->FindClass("io/grpc/binder/cpp/NativeConnectionHelper"); + if (cl == nullptr) { + return cl; + } + jclass global_cl = static_cast(env->NewGlobalRef(cl)); + GPR_ASSERT(global_cl != nullptr); + return global_cl; + }; + static jclass connection_helper_class = do_find(); + if (connection_helper_class != nullptr) { + return connection_helper_class; } - - env->CallStaticVoidMethod(cl, mid, application, - env->NewStringUTF(pkg.c_str()), - env->NewStringUTF(cls.c_str())); + // Some possible reasons: + // * There is no Java class in the call stack and this is not invoked + // from JNI_OnLoad + // * The APK does not correctly depends on the helper class, or the + // class get shrinked + gpr_log(GPR_ERROR, + "Cannot find binder transport Java helper class. Did you invoke " + "grpc::experimental::InitializeBinderChannelJavaClass correctly " + "beforehand?"); + // TODO(mingcl): Maybe it is worth to try again so the failure can be fixed + // by invoking this function again at a different thread. + return nullptr; } -void CallStaticJavaMethod(JNIEnv* env, const std::string& clazz, - const std::string& method, const std::string& type, - jobject application, const std::string& pkg, - const std::string& cls, const std::string& conn_id) { - jclass cl = env->FindClass(clazz.c_str()); - if (cl == nullptr) { - gpr_log(GPR_ERROR, "No class %s", clazz.c_str()); - } - - jmethodID mid = env->GetStaticMethodID(cl, method.c_str(), type.c_str()); - if (mid == nullptr) { - gpr_log(GPR_ERROR, "No method id %s", method.c_str()); - } - - env->CallStaticVoidMethod( - cl, mid, application, env->NewStringUTF(pkg.c_str()), - env->NewStringUTF(cls.c_str()), env->NewStringUTF(conn_id.c_str())); -} +void TryEstablishConnection(JNIEnv* env, jobject application, + absl::string_view pkg, absl::string_view cls, + absl::string_view conn_id) { + std::string method = "tryEstablishConnection"; + std::string type = + "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/" + "lang/String;)V"; -jobject CallStaticJavaMethodForObject(JNIEnv* env, const std::string& clazz, - const std::string& method, - const std::string& type) { - jclass cl = env->FindClass(clazz.c_str()); + jclass cl = FindNativeConnectionHelper(env); if (cl == nullptr) { - gpr_log(GPR_ERROR, "No class %s", clazz.c_str()); - return nullptr; + return; } jmethodID mid = env->GetStaticMethodID(cl, method.c_str(), type.c_str()); if (mid == nullptr) { gpr_log(GPR_ERROR, "No method id %s", method.c_str()); - return nullptr; } - jobject object = env->CallStaticObjectMethod(cl, mid); - if (object == nullptr) { - gpr_log(GPR_ERROR, "Got null object from Java"); - return nullptr; - } - - return object; + env->CallStaticVoidMethod(cl, mid, application, + env->NewStringUTF(std::string(pkg).c_str()), + env->NewStringUTF(std::string(cls).c_str()), + env->NewStringUTF(std::string(conn_id).c_str())); } } // namespace grpc_binder diff --git a/src/core/ext/transport/binder/client/jni_utils.h b/src/core/ext/transport/binder/client/jni_utils.h index ea884ed7bc1..24fd9c08d5d 100644 --- a/src/core/ext/transport/binder/client/jni_utils.h +++ b/src/core/ext/transport/binder/client/jni_utils.h @@ -21,24 +21,21 @@ #include -#include +#include "absl/strings/string_view.h" namespace grpc_binder { -// For now we hard code the arguments of the Java function because this is only -// used to call that single function. -void CallStaticJavaMethod(JNIEnv* env, const std::string& clazz, - const std::string& method, const std::string& type, - jobject application, const std::string& pkg, - const std::string& cls); -void CallStaticJavaMethod(JNIEnv* env, const std::string& clazz, - const std::string& method, const std::string& type, - jobject application, const std::string& pkg, - const std::string& cls, const std::string& conn_id); - -jobject CallStaticJavaMethodForObject(JNIEnv* env, const std::string& clazz, - const std::string& method, - const std::string& type); +// Finds NativeConnectionHelper Java class and caches it. This is useful because +// FindClass only works when there is a Java class in the call stack. Typically +// user might want to call this once in a place that is called from Java (ex. +// JNI_OnLoad) so subsequent BinderTransport code can find Java class +jclass FindNativeConnectionHelper(JNIEnv* env); + +// Calls Java method NativeConnectionHelper.tryEstablishConnection +void TryEstablishConnection(JNIEnv* env, jobject application, + absl::string_view pkg, absl::string_view cls, + absl::string_view conn_id); + } // namespace grpc_binder #endif diff --git a/src/core/ext/transport/binder/java/io/grpc/binder/cpp/NativeConnectionHelper.java b/src/core/ext/transport/binder/java/io/grpc/binder/cpp/NativeConnectionHelper.java index adc67e9d0e8..5c2c33775a7 100644 --- a/src/core/ext/transport/binder/java/io/grpc/binder/cpp/NativeConnectionHelper.java +++ b/src/core/ext/transport/binder/java/io/grpc/binder/cpp/NativeConnectionHelper.java @@ -20,8 +20,9 @@ import java.util.HashMap; import java.util.Map; /** - * This class will be invoked by gRPC binder transport internal implementation to perform operations - * that are only possible in Java + * This class will be invoked by gRPC binder transport internal implementation (from + * src/core/ext/transport/binder/client/jni_utils.cc) to perform operations that are only possible + * in Java */ final class NativeConnectionHelper { // Maps connection id to GrpcBinderConnection instances