Add an experimental binder transport API for pre-finding Java class (#27939)

Due to limitation of JVM, when user want to create binder channel in
threads created in unmanaged native code, they will need to call this
new API first to make sure Java helper classes can correctly be found.

Unused code in jni_utils.cc are also cleaned up in this commit
pull/27952/head
Ming-Chuan 3 years ago committed by GitHub
parent c79cdc0ae8
commit 1777ddf3c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      examples/android/binder/java/io/grpc/binder/cpp/exampleclient/native.cc
  2. 9
      include/grpcpp/create_channel_binder.h
  3. 20
      src/core/ext/transport/binder/client/channel_create.cc
  4. 86
      src/core/ext/transport/binder/client/jni_utils.cc
  5. 27
      src/core/ext/transport/binder/client/jni_utils.h
  6. 5
      src/core/ext/transport/binder/java/io/grpc/binder/cpp/NativeConnectionHelper.java

@ -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<grpc::Channel> 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;

@ -69,6 +69,15 @@ std::shared_ptr<grpc::Channel> CreateCustomBinderChannel(
std::shared_ptr<grpc::experimental::binder::SecurityPolicy> 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

@ -86,13 +86,9 @@ std::shared_ptr<grpc::Channel> 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<JNIEnv*>(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<JNIEnv*>(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<grpc::Channel> CreateCustomBinderChannel(
return channel;
}
bool InitializeBinderChannelJavaClass(void* jni_env_void) {
return grpc_binder::FindNativeConnectionHelper(
static_cast<JNIEnv*>(jni_env_void)) != nullptr;
}
} // namespace experimental
} // namespace grpc
@ -149,6 +150,11 @@ std::shared_ptr<grpc::Channel> CreateCustomBinderChannel(
return {};
}
bool InitializeBinderChannelJavaClass(void* jni_env_void) {
GPR_ASSERT(0);
return {};
}
} // namespace experimental
} // namespace grpc

@ -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<jclass>(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

@ -21,24 +21,21 @@
#include <jni.h>
#include <string>
#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

@ -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

Loading…
Cancel
Save