ReactNative4 Android源码分析二:《JNI智能指针之实现篇》

2016年11月30日 218点热度 0人点赞 0条评论

又到了周三 到定时推文的时候,支付宝风波虽刚过,但留下的却是值得我们思考的,为何这么产品的微小举动就能带动两三亿人的关注?说明产品拿捏客户需求的重要性,平台的重要性,也说明以信用分750+的饥饿营销并没有让大家觉得厌恶,反而更加去追捧,验证了陈奕迅的歌词,”得不得到的永远在骚动“!



图片



回顾



上一篇介绍了《ReactNative4Android源码分析2: JNI智能指针之介绍篇》JNI智能指针与wrapper class的作用,下面将对它们的具体实现进行分析,并解答上篇提出的几个问题

前篇回顾了java object在JNI中的引用对象jobject的3种类型。智能指针自然也有相应的如下类型:

global_ref

全局指针与jobject全局引用相对应,使用场景包括全局变量、成员变量等。这些场景中的jobject,不应该从native返回至JVM时释放,故使用global_ref进行包裹。

local_ref

局部指针与jobject局部引用相对应,使用场景包括局部变量、函数返回值等。当local_ref离开所在作用域时,会释放自身对jobject的引用,即在析构函数中调用DeleteLocalRef。

weak_ref

弱指针与jobject弱全局引用相对应,在目前版本的RN代码中未实际使用。

alias_ref

别名指针,不对持有的jobject进行生命周期管理。即在构造与析构别名智能指针对象时,不会对持有的jobject进行创建与销毁的JNI操作。该指针的目的只是为了提供调用wrapper对象方法的能力,jobject的生命周期由另外的智能指针或直接由JVM进行管理和保证有效性,指针自身不对其额外进行管理。

以上智能指针均未提供引用计数功能,而是通过在智能指针间交换被管理的对象来进行指针转换。智能指针的类图如下,其代码位于ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/References.h:

图片

智能指针


从上图可以看出,由于功能区别,alias_ref别名指针是独立的一个类,其余的智能指针有共同的父类base_owned_ref。最需要关注智能指针的存储,base_owned_ref与alias_ref均有同样的成员变量:

detail::ReprStorage<Repr> storage_;

storage_用来存储创建出来的wrapper对象。这边的设计比较巧妙,使用C++中的类型萃取技术(type traits)把wrapper对象和jobject关联,并将jobject(JNI层),javaobject(RN层),wrapper对象(RN层)三者在内存空间上统一了。先看ReprStorage的实现:

template <typename Repr>struct ReprStorage {  explicit ReprStorage(JniType<Repr> obj) noexcept;  void set(JniType<Repr> obj) noexcept;  Repr& get() noexcept;  const Repr& get() const noexcept;
  JniType<Repr> jobj() const noexcept; private:  using Storage = typename std::aligned_storage<sizeof(JObjectBase), alignof(JObjectBase)>::type;
  Storage storage_;
};template <typename Repr>void ReprStorage<Repr>::set(JniType<Repr> obj) noexcept {  new (&storage_) Repr;
  ReprAccess<Repr>::set(get(), obj);
}template <typename Repr>
Repr& ReprStorage<Repr>::get() noexcept {  return *reinterpret_cast<Repr*>(&storage_);
}

无关的代码已被略去。ReprStorage使用私有变量storage_做为存储空间,尺寸为JObjectBase类的size。从set和get函数可以看出,storage_内存空间的分配是delay到设值的时候,并将storage_内存空间的指针通过reinterpret_cast类型转换为Repr类型。ReprStorage的模板参数Repr是存储的wrapper class的类型,在上章的使用范例中,也就是MyClass:

struct MyClass : public JavaClass<MyClass>

wrapper class之间的继承关系如下

wrapper class继承关系

图片

JObjectBase


JObjectBase是wrapper class的根父类,这里显然存在一个问题:为何能用父类size的内存空间去存放任意子类对象?完成继承关系的讨论后,再回顾这个问题。图中的CustomJavaClass是一个自定义wrapper class,它继承于JavaClass的一个模板实例。所有Java类(除去Object类)的native镜像wrapper class,均需要继承于JavaClass的某个模板实例。JavaClass起到两个桥梁作用:当前定义的wrapper class与对应Java类父类的wrapper class之间继承关系的桥梁;当前定义的wrapper class与对应Java对象的JNI jobject的桥梁。它有三个模板参数,下面是它的类声明,其代码位于ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses.h:

template <typename T, typename Base = JObject, typename JType = void>class FBEXPORT JavaClass : public Base {public:  static alias_ref<JClass> javaClassStatic();  static local_ref<JClass> javaClassLocal();protected:  /// Allocates a new object and invokes the specified constructor
  /// Like JClass's getConstructor, this function can only check at runtime if
  /// the class actually has a constructor that accepts the corresponding types.
  /// While a JavaClass-type can expose this function directly, it is recommended
  /// to instead to use this to explicitly only expose those constructors that
  /// the Java class actually has (i.e. with static create() functions).
  template<typename... Args>  static local_ref<T> newInstance(Args... args) {return detail::newInstance<T>(args...);
  }  javaobject self() const noexcept;
}

无关的代码已被略去。

第一个模板参数是子wrapper class的类型
JavaClass的这个模板实例作为这个wrapper class的父类,提供了创建wrapper class对象的工厂方法和与对应jobject关联的能力,故需要获得子类的类型。

第二个模板参数是该JavaClass模板实例的父类。
它的默认类型是JObject,代表java.lang.Object类的wrapper class,是唯一不需要继承于JavaClass的wrapper对象。JObject提供了对java对象的Class、Field、Method等的访问封装方法,wrapper class通过对它的继承关系,获得了去调用Java method的能力。若wrapper class无需提供Java类父类方法的调用能力,则第二个模板参数保持默认值JObject即可,否则,第二个模板参数就为Java类父类的wrapper class,例子在上章中已提供。由于JavaClass帮助构建了继承链,wrapper class具备了提供父java类的native镜像方法的能力。

第三个模板参数是定义的wrapper class对应Java对象的JNI jobject的类型

JavaClass会将wrapper class与jobject建立起绑定关系。根据jobject的具体类型,会分两种情况,如果为JNI预定义的jobject类型,例如jclass、jthrowable、jarray、jstring等,第三个模板参数就是它们,RN中已经预定义了它们的wrapper class。例如:

class FBEXPORT JString : public JavaClass<JString, JObject, jstring> {}

另外一种情况就是非预定义类型,也就是jobject这个通用类型(jclass、jstring等预定义类型也是它的子类),这时第三个参数就应为默认值void。即不由模板参数指定jobject的具体子类,而是使用wrapper class内部嵌套定义的扩展子类。

图片

浏览jobject内部定义前,先回顾刚才的存储问题。既然sizeof(JObjectBase)的内存空间能够正确放置任意wrapper class子类的实例,就说明子类所占的内存空间与根类JObjectBase一样。而对于一个C++类而言,对象的size就是所有非static成员变量的size之和(需考虑内存对齐),这就约束了wrapper class作为子类不能额外声明任何非static成员变量,才能与根父类JObjectBase保持size的一致。从wrapper class的设计目的考虑,它只是Java类在native空间的镜像类和接口包装类,业务逻辑应由调用者实现,wrapper class自身应该是无状态的,所以不允许wrapper class定义非static成员变量是合理的。

智能指针存储的是wrapper class的实例,wrapper class中存储的是jobject,从以上分析可以知道,存储的jobject成员变量只能由根父类JObjectBase去承载。下面是JObjectBase的类定义,代码位于ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/References-forward.h:

struct JObjectBase {  jobject get() const noexcept;  void set(jobject reference) noexcept;
  jobject this_;
};

JObjectBase是一个简单的bean类,唯一的成员变量就是jobject,这也是所有wrapper class唯一的成员变量。在JavaClass模板类中,为了实现jobject与wrapper class两者的关联,对jobject做了内部扩展定义。为了理解它,先回顾下jobject在jni.h中的原始定义:

class _jobject {};
typedef _jobject*   jobject;class _jstring : public _jobject {};

所以,jobject就是个指针,指向java object在JVM中的内存对象,对于像dexposed这样的热修复框架,就是利用这些指针去修改java对象模型来改变java method的属性以实现hook。这里的定义_jobject是空类,只是为了定义指针语法以在JNI中去引用内存对象,并不意味Java内存对象真的是个空对象,真正定义是JVM内部的、平台相关的,而不需要将实现细节暴露给JNI。在JavaClass中,对jobject的扩展定义javaobject类型如下:

template <typename T, typename Base = JObject, typename JType = void>class FBEXPORT JavaClass : public Base {  using JObjType = typename detail::JTypeFor<T, Base, JType>;public:  using _javaobject = typename JObjType::_javaobject;  using javaobject = typename JObjType::javaobject;
};namespace detail {template <typename, typename Base, typename JType>struct JTypeFor {  static_assert(  std::is_base_of<std::remove_pointer<jobject>::type,typename std::remove_pointer<JType>::type
  >::value, "");  using _javaobject = typename std::remove_pointer<JType>::type;  using javaobject = JType;
};template <typename T, typename Base>struct JTypeFor<T, Base, void> {  // JNI pattern for jobject assignable pointer
  struct _javaobject :  Base::_javaobject {// This allows us to map back to the defining type (in ReprType, for// example).typedef T JniRefRepr;
  };  using javaobject = _javaobject*;
};
}

在JavaClass中,将jobject拓展定义为javaobject。javaobject是typename JObjType::javaobject,也就是JTypeFor的一个模板实例类型的成员指针类型。以例子代码中的MyClass为例,父类JavaClass接收的三个模板参数分别为MyClass,JObject,void,JTypeFor的三个模板参数也依次是它们,由于第三个参数是void,故会使用上面代码中的JTypeFor

struct JTypeFor<MyClass, JObject, void> {  struct _javaobject :  JObject::_javaobject { typedef MyClass JniRefRepr;
  };  using javaobject = _javaobject*;
};
}

可看到_javaobject继承于JObject:: _javaobject,它的定义如下:

 typedef _jobject _javaobject; typedef _javaobject* javaobject;

JObject::_javaobject就是jni.h中的_jobject类型,故MyClass中的_javaobject对_jobject的继承扩展,只是添加了一个嵌套类成员类型JniRefRepr,来指向当前_javaobject所对应的wrapper class类型,这就是所谓的C++类型萃取技术。因为_jobject是用来指向Java内存对象,所以不能用继承后添加成员变量的方式来扩展,否则会破坏内存对象,而成员类型是属于类定义,不会占用对象的空间。另,javaobject是指向_javaobject的指针,jobject是指向_jobject的指针。

问题解答


现在来解答上章的三个问题:

javaobject与jobject的关系是什么?

两者本质是一样的,都是指向java内存对象的JNI引用;区别是javaobject是jobject继承扩展,继承后的javaobject拥有一个类成员类型变量,指向对应的wrapper class,使得两者彼此关联。从内存上看,sizeof(JObjectBase)==sizeof(任意wrapper class)==sizeof(jobject)==sizeof(javaobject),达到了有机的统一。

为什么智能指针的模板参数能够接受多种类型?

在上章例子中,local_ref\与local_ref\传递了不同模板参数,从语法上看区别很大,但在内部实现时,都会进行类型萃取。即无论传递的何种类型,都会萃取出对应的ReprType(wrapper class类型)、JniType(javaobject类型)。javaobject类是wrapper class的成员类型,故从wrapper class可以获得对应javaobject引用的类型;javaobject类的成员类型是wrapper class,故从javaobject业可以获得对应wrapper class的类型。在框架中提供了两个工具来进行类型萃取和转换:

// Given T, either a jobject-like type or a JavaClass-derived type, ReprType<T>// is the corresponding JavaClass-derived type and JniType<T> is the// jobject-like type.template <typename T>using ReprType = typename detail::RefReprType<T>::type;template <typename T>using JniType = typename detail::JavaObjectType<T>::type;

ReprType用来从模板参数中获得wrapper class类型,JniType用来从模板参数中获得javaobject类型。通过这样的机制,两个类型彼此打通,故无论传递何种模板参数,智能指针都能正确存储对应类型的wrapper class

模板参数起到的作用是什么?

从上可以了解到,智能指针的模板参数用来获取存储的wrapper class的类型。对于local_ref和global_ref,它们由于都是强引用,可以用来直接调用存储的wrapper class提供的方法,所以它们的实现模板类basic_strong_ref在base_owned_ref提供的存储功能的基础上,继承扩展提供了指针操作符的重载,以将对智能指针的访问转发到wrapper对象上,代码如下:

template<typename T, typename Alloc>inline auto basic_strong_ref<T, Alloc>::operator->() noexcept -> Repr* {return &storage_.get();
}
template<typename T, typename Alloc>inline auto basic_strong_ref<T, Alloc>::operator->() const noexcept -> const Repr* {return &storage_.get(); }

重载实现中,从storage_中获得存储的wrapper class实例返回即可。对于模板参数Alloc,则是分配器的类型,封装了创建、销毁jobject的操作,由智能指针在构造和析构时调用,实现jobject生命周期的管理。

总结


总结一下,本篇简述了wrapper class与智能指针的主干实现。Wrapper class扩展jobject至javaobject,使类型彼此关联;构建了继承链,将对java method的反射调用和java类自身的继承关系分解在链路的不同节点。智能指针通过类型萃取负责将jobject存储至正确的wrapper实例,以对外提供镜像方法,结合构造与析构函数,自动进行jobject的生命周期管理。

React Native for Android源码分析 一《JNI智能指针之介绍篇》:http://mp.weixin.qq.com/s?__biz=MzIyMjQ0MTU0NA==&mid=2247483765&idx=1&sn=405df16a4e2aea5a113fed54837b0d83&chksm=e82c3852df5bb144c079526ddc4d376b4ba0e583d106e46e07b2a77c3c9775f915dc2e991ea3#rd

本文出自于系曾任职于百度,现平安金科高级架构师手笔,更多干货请关注本公众号, 详细文章请点击原文查看。

图片

Tamic开发社区

专业高水准的移动社区

Android &  iOS

图片

长按二维码关注

35370ReactNative4 Android源码分析二:《JNI智能指针之实现篇》

这个人很懒,什么都没留下

文章评论