有的时候,有些业务逻辑我们不希望别人能看懂,而是尽可能让别人看不懂。对于一个职场老司机来说,能写只有自己能看懂的代码对于维护自己在公司的核心开发者地位,是有一定作用的。在当今互联网裁员如家常便饭的年代,我想,很多人都有这么想过。我今天讲的内容,不但是使你的代码变得让你的同事看不懂,而是没法看。那么来了,只要你们公司没有代码评审,那我会让你卡别人脖子到你自己都讨厌你自己。做人怎么可以这么狠?公司把所有资源都给你好吗?
NDK的so文件有哪几种引入方式?
没错,有2种。
android {
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
}
以上为第一种,这种是要将源代码写到项目中,将源代码一起参与编译构建的。
android {
sourceSets {
getByName("main") {
jniLibs.srcDir("src/main/jniLibs")
}
}
}
以上为第二种,这种只需要将编译好的so库文件放到jniLibs目录下,即可。这种方式,是你成为公司核心开发者的第一步,大量核心的代码被你隐藏,你只要保证功能能用。甚至你只需要保证测试测到的地方的功能可用。怎么说呢?比如你在代码里面集成cURL+jsoncpp+openssl,偷偷加密请求你的服务器,弄一个开关,这样公司的项目的生死就掌握在你手里了,哈哈!你想什么时候崩溃就什么时候崩溃,甚至可以把你公司的客户引流到自己的项目。以上操作纯属玩笑,千万不要说是我教你的,不要这样操作,你会进去的!
复习JNI层的反射
Java/Kotlin代码,本身也是带有反射机制的。本篇我来讲一下JNI层的反射。以下为常用的方法:
-
env->FindClass:用于查找 Java 类。 -
env->GetMethodID和env->GetStaticMethodID:用于获取方法的引用,分别用于实例方法和静态方法。 -
env->GetFieldID和env->GetStaticFieldID:用于获取属性的引用,分别用于实例属性和静态属性。 -
env->CallVoidMethod和env->CallStaticVoidMethod:分别用于调用非静态方法和静态方法。 -
env->SetObjectField和env->SetStaticObjectField:分别用于修改非静态属性和静态属性,其它数据类型类似。 -
env->NewStringUTF:创建 Java 字符串对象,即jstring,这样才能返回出去。 -
env->NewObject:调用构造函数创建 Java 对象。 -
env->DeleteLocalRef:删除局部引用,防止内存泄漏。 -
env->GetStringUTFChars:将 Java 字符串转换为 C++ 字符串。 -
env->ReleaseStringUTFChars:删除 Java 字符串和 C++ 字符串,释放内存。
转入正题,有哪些常见的坑?
主要的坑在于Kotlin不完全是Java,和纯Java代码的反射还是有一些差别的。
数据类型对应表
| Java类型 | Native类型 | C类型 | 有无符号,长度 | 签名 |
|---|---|---|---|---|
| boolean | jboolean | unsigned char | uint8_t | Z |
| byte | jbyte | signed char | int8_t | B |
| char | jchar | unsigned short | uint16_t | C |
| short | jshort | signed short | int16_t | S |
| int | jint | int | int32_t | I |
| long | jlong | long | int64_t | J |
| float | jfloat | float | int32_t | F |
| double | jdouble | double | int64_t | D |
| int[] | jintArray | int[] | / | [I |
| double[] | jdoubleArray | double[] | / | [D |
| void | void | void | / | V |
| String | jstring | char* | / | Ljava/lang/String; |
| Object | jobject | / | / | Ljava/lang/Object; |
| Class | jclass | / | / | Ljava/lang/Class; |
| List | jobject | / | / | Ljava/util/List; |
方法签名的坑
基本规则
需要注意的是,如果以L加全类名表示,不要忘记把.换成/,并在结尾加;。万物皆对象,任何实例数据类型都可以用jobject接,包括jclass。布尔型Z和长整型J在这里跟其它的长的有点不一样。方法签名的格式为(参数1参数2参数n)返回值,例如参数没有就为()返回值,返回值没有就为(参数1参数2参数n)V。
Kotlin中的可空方法参数
比如在data class中,有些参数是可空的类型,比如Boolean?。重点来了,如果是可空的,则用Ljava/lang/Boolean;,不能为空的,则用Z,代表的是Java的boolean类型。Kotlin中的方法,一律要把所有参数的签名都写上,包括可为空的,传参的时候传空给它就可以了。
Kotlin中的object类
我们知道object class是可以给方法加上@JvmStatic注解的,表示它翻译成Java的一个静态方法,在Java中可以直接用.调用。如果没有加,则要用.INSTANCE.调用。举个例子:
// 没有加@JvmStatic的yy方法的调用方式
jclass xxClass = env->FindClass(XX_CLASS_PATH);
if (xxClass == nullptr) {
LOGE("Failed to find class: %s", XX_CLASS_PATH);
return;
}
jfieldID instanceField = env->GetStaticFieldID(xxClass, "INSTANCE", "Lcom/xx/XX;");
jobject instance = env->GetStaticObjectField(xxClass, instanceField)
if (instance == nullptr) {
LOGE("Failed to get XX.INSTANCE");
return;
}
jmethodID yyMethod = env->GetMethodID(xxClass, "yy", "此处省略方法签名...");
env->CallVoidMethod(instance, yyMethod, 参数若干);
注意这里没有使用CallStaticVoidMethod,且用实例调用的。
Kotlin中的高阶函数
Kotlin中的高阶函数,有的人也叫lambda表达式,如()->Unit。没有参数的和带有1个参数的分别用Lkotlin/jvm/functions/Function0;和Lkotlin/jvm/functions/Function1;。而不是Ljava/lang/Object。
写到最后
如果你不想把业务逻辑暴露出去,比如SDK的开发者,则可以使用JNI去调用Java的方法,这样只开源Java层的源码就可以了。最后重申一遍,惹出事来,不要提我的名字,好自为之。