最近打算抽空学习一下张绍文老师的《Android开发高手课》。
想要理解本地监控APP内存的框架的实现原理。
发现理解起来都没有那么容易,在阅读代码的过程中,发现C++、linux、hook、framework等方面的功底均有所不足,果然一个人要认识自己,就需要跟外界某种东西发生碰撞和摩擦,才能够真正认识自己。
在此打算创作一系列相关的技术博客,记录所学所想。如果你也有跟我一样的需求和想法。可以持续关注我的这个账号。一起突破。
张绍文老师说过:“看再多的文章,不去思考文章所讲的内容和意图也是没用的;思考再多,不去动手真正实践也是没用的。”
“专栏把进阶的各个主题由点到线串联起来,但这背后必然少不了一些基础的、底层的知识进行支撑”。
而我想要把自己缺失的基础、底层的知识补充起来,记录于此。最终能够实现本地内存监控框架。
使用到的开源库介绍
先来了解使用到的jni相关开源库:fbjni
fbjni
JNI官方文档
fbjni 是从 Facebook 开源的一款jni工具类库,主要提供了工具类,ref utils ,Global JniEnv。
为什么使用JNI wrapper?
标准 JNI 非常冗长且容易出错。fbjni 的目标是使其使用简单、健壮和可扩展。
标准版本的jni代码:
// JNI Example with error handling: toString()
std::string jniToString(JNIEnv* env, jobject self) {
static auto cls = env->FindClass("java/lang/Object");
assert(cls != nullptr);
static auto id = env->GetMethodID(cls, "toString", "()Ljava/lang/String;");
assert(id != nullptr);
auto jstr = static_cast<jstring>(env->CallObjectMethod(self, toStringId));
throwIfJavaExceptionRaised(env);
auto chbuf = env->GetStringUTFChars(jstr, nullptr);
throwIfJavaExceptionRaised(env);
auto stdstr = std::string(chbuf);
env->ReleaseStringUTFChars(jstr, chbuf);
return stdstr;
}
使用fbjni工具:
在这个例子中
为了可读性,我们跳过了throwIfJavaExceptionRaised()的实现。使用fbjni,我们可以通过稳健的错误处理大大减少代码。
// fbjni Example: toString()
std::string helperToString(alias_ref<jobject> obj) {
static auto toStringMethod =
findClass("java/lang/Object")->getMethod<jstring()>("toString");
return toStringMethod(obj)->toStdString();
}
实际上,toString()通常很有用,因此我们把它封装的很容易使用。
// fbjni Example (alternative): toString()
std::string helperToString(alias_ref<jobject> obj) {
return obj->toString();
}
这些实现与普通的JNI示例基本上做相同的事情。主要区别在于,所有错误都是通过抛出C++异常来处理的。在普通的JNI代码中,使用了一些断言来简化代码。
JNI is a C API
API中几乎没有语法糖来帮助您管理资源。由于它只是一个API规范,实现会因JVM而异——web上的大多数文章都假设HotSpot而不是Dalvik。文档严重偏向于Java调用单个独立的本地函数的用例(这通常是一个相当容易处理的用例)。然而,我们的代码通常是异步的,需要Java和本地代码之间的回调或其他有状态的交互。
Memory Lifecycle
Java和native代码有单独的内存堆,因此管理双方使用的对象的生命周期需要格外小心。JNI提供了引用的概念,它允许native代码告诉JVM它仍在使用一个对象,并防止该对象被垃圾收集。从Java调用时,native函数的所有参数都会自动转换为native引用,在调用完成时会删除这些引用。要在native端维护状态或启动对Java的调用,您需要自己管理这些引用。引用来自固定大小的池,因此除了泄漏它们所指向的Java对象之外,泄漏或双重释放引用本身就是一个问题。fbjni提供RAII智能参考,极大地简化了这些引用的管理。
相反,C++智能指针只有在C++代码持有它们的情况下才能生存。通过interpret_cast,指向堆内存的指针可以存储在Java对象中,但您需要确保在Java对象未使用时删除C++分配。fbjni的Hybrid类提供了一种方便的方式来封装这种行为。
Exceptions
C++和Java的大多数用法都使用异常。这两个异常框架是不兼容的,因此碰到JNI边界的异常会成为一个问题:C++异常会导致程序中止,而Java异常则需要C风格检查是否存在错误条件。未处理的Java异常将导致下一个JNI调用中止,这可能与最初导致异常的代码完全无关。fbjni为异常提供了一个翻译工具,使它们能够以合理的方式跨边界传递,并提供了将C++函数封装在try/catch块中的helpers。
Java Type Signatures
native函数需要指定它们所对应的Java函数签名的字符串化版本(称为描述符)。这是一个机制;javac可以为您做到这一点。然而,这种操作流是不方便的;例如,您不能按原样使用javah生成的函数原型,因为它们假定是隐式函数注册,这在旧版本的Android上是不可靠的。如果描述符出错(或者在重构过程中忘记更新它),通常会导致很快但很难调试的崩溃。fbjni包括包装器,这些包装器基于C++函数原型为您推导函数签名。
Type Safety
JNI提供了一个单一的基本对象类型的jobject,并对jstring、jclass和jexception进行了标记专门化。fbjni使得扩展它相对容易,这样任何任意Java类都可以在C++中以类型安全的方式处理。