JNI基础 - Android共享内存的序列化过程
重点:Native层构建 如何与Java层对应。代码见d14.
工作多年,要会一些其他东西。
- 1.进程间的通信方式有那些
- 2.binder和socket通信的区别有那些.(binder 共享内存,Soket需要copy内存) Socket 远程,本地低速(zygote)
- 3.Android为什么大部分场景下用Binder进行进程间通信。
- 4.Serializable 和 Parcelable 之前的区别。 (io流,共享内存)
Serializable 开销大。 - 5.Parcelable 序列化和反序列化 的具体过程。
对象就在内存里面,快一些。
Binder驱动为什么比Socket快?Socket需要拷贝两次内存。
共享内存
1.opencv Mat源码阅读
Java-> Mat.java = C++ Mat.cpp对象。矩阵操作。
重点:Native层构建的对象 如何与Java层对应。
将对象首地址 long nativeObj 返回回来。
public class Mat{ //Java对象
public final long nativeObj;
public Mat(){
//C++创建对象,返回long类型的指针(C++对象的指针地址)给Java。
nativeObj = n_Mat(rows, cols, type);
return;
}
public static native long n_Mat(int rows, int cols, int type);
}
// 下次操作的时候,通过这个首地址,能找到C++对象,然后就能调用对象的宽高通道等方法。
// native层实现(modules/java/generator/src/cpp/Mat.cpp)
JNIEXPORT jlong JNICALL Java_org_opencv_core_Mat_n_1Mat__DDI
(INIEnv*env,jclass, jdouble size_width, jdouble size_height, jint type);
JNIEXPORT jlong JNICALL Java_org_opencv_core_Mat_n_1Mat__DDI
(INIEnv*env,jclass, jdouble size_width, jdouble size_height, jint type)
{
static const char method_name[] = "Mat::n_1Mat__DDI()";
try {
LOGD("%s", method_name);
Size size((int)size_width,(int)size_height);
return (jlong)new Mat(size, type); // 返回c++对象的 指针
} catch(const std::exception &e){
throwJavaException(env, &e, method_name);
} catch(...){
throwJavaException(env,0, method_name);
}
return 0;
}
2.Parcel 源码解析
Student implements Parcelable ,实现接口。
为什么高效?
Parcel.obtain() // 拿到Parcel对象
-- new Parcel(0)
-- init(nativePtr)
-- nativePtr = nativeCreate() // native方法,jni实现。[androidxref.com]
// 源码: frameworks/base/core/jni/android_os_Parcel.cpp
// 关键字: nativeCreate --> android_os_Parcel_create 匹配jni实现方法。
static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz){
Parcel* parcel = new Parcel();// 构建了一个对象
return reinterpret_cast<jlong>(parcel); // reinterpret_cast 转换 返回指针地址 (long)
// 指针对象,转成jlang类型的首地址。返回
}
android_os_Parcel_writeInt() 将指针地址 nativePtr转换回Parcel* 对象。
// nativePtr 指针地址private static native void nativeWriteInt(long nativePtr, int val);
Parcel parcel = Parcel.obtain()
parcel.writeInt(12); // nativeWriteInt(long nativePtr, int value)
parcel.writeInt(22);
parcel.setDataPosition(0); // 数据指针重置
int n1 = parcel.readInt();
int n2 = parcel.readInt();
代码流程:
parcel.writeInt(12)
-- nativeWriteInt(long nativePtr, int value)
-- android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jlong nativePtr, jint val)
-- const status_t_err = parcel->writeInt32(val)
-- 源码:frameworks/native/libs/binder/Parcel.cpp
-- writeInt32(int32_t val)
-- writeAligned(val)
mDataPos:内存首地址的当前挪动位置,偏移量,最开始是0.记录数据写到哪里了。
mDataCapacity:共享内存的总大小
mData : 共享内存的首地址(头指针)
// * 取值 = val
*reinterpret_cast<T*>(mData+mDataPos) = val;
-- finishWrite(len)
mDataPos += len; // 指针逻动的位置加上当前数据所占大小
实现步骤:
- 构建Parcel.java对象。
- Android(run)创建Parcel.cpp 对象,会把首地址返回给Java层。
同时开辟一块共享内存,共享内存中有几个值(mData, mDataPos, mDataCapacity) - Java层通过首地址,拿到Parcel.cpp对象。
指针偏移默认是0,去写入数据,然后指针偏移位置往后挪动。再次写入数据,指针再往后挪。 - 读取数据
根据地址找到对象Parcel,然后 parcel->readInt32()
readAligned()
readAligned<int32_t>()
template<class T>
status_t Parcel::writeAligned(T val) {
COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));
if ((mDataPos+sizeof(val)) <= mDataCapacity) {
restart_write:
*reinterpret_cast<T*>(mData+mDataPos) = val;
return finishWrite(sizeof(val));
}
status_t err = growData(sizeof(val));
if (err == NO_ERROR) goto restart_write;
return err;
}
template<class T>
status_t Parcel::readAlianed(T *pArg) const {
COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T))== sizeof(T));
if((mDataPos+sizeof(T))<=mDatasize){
const void* data=mData+mDataPos; // 首地址+偏移量,得到当前待读取数据的指针。
mDataPos+= sizeof(T); // 偏移量偏移
*pArg = reinterpret cast<const T*>(data); // 根据指针,读取到的数据
return NO_ERROR;
} else {
return NOT_ENOUGH_DATA;
}
}
从共享内存中读取数据,数据指针偏移量先设置为0;
读取一个数据后,指针往后挪动;在读取数据,再往后挪动。
3.自己动手实现内存共享,代码在d14文件夹中
思考:写 String
java 能不能做到,内存共享实现 Parcel? C 和 C++ (更加灵活,接近底层内存,操作的是一块内存地址)
- Parcel.kt 中定义Native方法;
- 生成.h头文件:
String 怎么写进去,怎么读?jstring转char* ,
字符串前面写占据字节数5,后面写内容Darren。因为字符串不像int,double有标准的占据字节数。
参考源码的实现。
4.共享内存面试题讲解
- 1.进程间的通信方式有那些
- 2.binder和socket通信的区别有那些.
(binder 共享内存,Socket需要copy内存) Socket 远程通信(不能使用binder),如本地的低速通信(zygote孵化进程使用的socket)。 - 3.Android为什么大部分场景下用Binder进行进程间通信。
共享内存,更高效。 - 4.Serializable 和 Parcelable 之前的区别。 (io流,共享内存)
Serializable 操作的是IO流, 开销大。
Parcelable 操作的是共享内存,速度更快。 - 5.Parcelable 序列化和反序列化 的具体过程。
对象就在内存里面,快一些。
C++ 创建了一个共享内存。从共享内存中读取数据。注意:读取时读取的顺序要保持一致。
为什么不用java写 Parcel 序列化内存共享?
C/C++ 【更加灵活,更加接近底层内存,操作的是一块内存地址】。
坚持,并对自己有用。多花时间。
只要能力够强,就不要怕没工作。
做人无胜高远事业,摆脱的俗情便入名流。不为名利去做事情。
不为名利
为学不胜增益功夫,灭除得物类便超盛境
静得下心愿意花时间
记住别人的好。做学问没有快速的方法。











网友评论