使用jni时,因为java层和native层的接口需要对应,如果因为误操作导致不对应,则运行时调用不正确的接口可能会出错抛异常,进而影响在线服务稳定。这里添加一种jni natie接口检查的流程,以保证当java和native接口不对应时,在服务启动阶段就能检测出来,避免错误发生在后面出现更大影响。
1. java添加一个回调函数,返回当前java层声明的native方法接口名,及参数标签
public static String[] getNativeMethodsText() {
Map<String, String> methods = new HashMap<>();
methods.put("native_test", "(Ljava/lang/String;)I");
String[] retMethods = new String[methods.size() * 2];
int index = 0;
for (Map.Entry<String, String> entry : methods.entrySet()) {
retMethods[index++] = entry.getKey();
retMethods[index++] = entry.getValue();
}
return retMethods;
}
2. native层,初始化JNI_OnLoad阶段,回调java层接口取得其声明的接口名,与当前native实现的接口做映射,然后利用注册本地方法做检验,如果不成功即可报错
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv *env = NULL;
g_jvm = vm;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK)
{
return -1;
}
const char *clasPathName = "test";
jclass myClass = env->FindClass(clasPathName);
int ret = JniRegisterNatives(env, myClass);
if (ret != JNI_OK)
{
return ret;
}
return JNI_VERSION_1_6;
}
int JniRegisterNatives(JNIEnv *env, jclass cls)
{
using MethodsKey = std::pair<std::string, std::string>;
using MethodsValue = void *;
struct MethodsHash
{
inline std::size_t operator()(const MethodsKey &k) const
{
return std::hash<std::string>()(k.first) + std::hash<std::string>()(k.second);
}
inline bool operator()(const MethodsKey &k1, const MethodsKey &k2) const
{
return std::equal_to<std::string>()(k1.first, k2.first) &&
std::equal_to<std::string>()(k1.second, k2.second);
}
};
static const std::unordered_map<MethodsKey, MethodsValue, MethodsHash, MethodsHash> native_methods_hash{
{{"native_test", "(Ljava/lang/String;)I"}, reinterpret_cast<void *>(native_test)}
};
auto methods_content = JniGetJavaNativeMethods(env, cls);
std::vector<JNINativeMethod> valid_native_methods;
for (const auto &method : methods_content)
{
auto it = native_methods_hash.find(method);
if (it == native_methods_hash.end())
{
std::cout << "Failed to find method (\"" << method.first << "\" \"" << method.second
<< "\") in native functions" << std::endl;
return -1;
}
JNINativeMethod native_method{(char *)it->first.first.data(),
(char *)it->first.second.data(), it->second};
valid_native_methods.emplace_back(std::move(native_method));
}
return env->RegisterNatives(cls, valid_native_methods.data(), valid_native_methods.size());
}
std::vector<std::pair<std::string, std::string>> JniGetJavaNativeMethods(JNIEnv *env, jclass cls)
{
std::vector<std::pair<std::string, std::string>> results;
jmethodID method_id =
env->GetStaticMethodID(cls, "getNativeMethodsText", "()[Ljava/lang/String;");
if (method_id == nullptr)
{
std::cout << "Failed to get method getNativeMethodsText()" << std::endl;
return results;
}
jobject j_ret = env->CallStaticObjectMethod(cls, method_id);
if (j_ret == nullptr)
{
std::cout << "Failed to call method getNativeMethodsText()" << std::endl;
return results;
}
jobjectArray j_ret_array = (jobjectArray)j_ret;
int32_t string_count = env->GetArrayLength(j_ret_array);
if (string_count > 0)
{
int32_t method_count = string_count / 2;
results.reserve(method_count);
for (int32_t i{}; i < method_count; ++i)
{
jstring j_method_name = (jstring)env->GetObjectArrayElement(j_ret_array, 2 * i);
const char *str_method_name = env->GetStringUTFChars(j_method_name, nullptr);
jstring j_method_descriptor =
(jstring)env->GetObjectArrayElement(j_ret_array, 2 * i + 1);
const char *str_method_descriptor =
env->GetStringUTFChars(j_method_descriptor, nullptr);
results.emplace_back(str_method_name, str_method_descriptor);
}
}
return results;
}
jint native_test(JNIEnv * env, jclass cls, jstring str)
{
return 0;
}
这样,即可检测java层声明的接口,是否在native层都有实现。
ps:
1. 查签名的方法
javap -s -p MainActivity.class
局限:
1. java层和native层,增删改接口,都必须得在java回调函数或native检测函数里加一遍
2. 如果接口名和参数签名都没改,只改了native实现,该方法无法检测。
本文介绍了一种JNI接口检查流程,旨在确保Java和native层的接口对应,避免因误操作导致运行时错误。通过在Java层添加回调函数获取接口信息,并在JNI_OnLoad阶段进行映射和验证,可以在服务启动阶段发现不匹配的问题,提高服务稳定性。
2万+

被折叠的 条评论
为什么被折叠?



