美文网首页
NDK-014: jni: Android共享内存的序列化过程

NDK-014: jni: Android共享内存的序列化过程

作者: xqiiitan | 来源:发表于2025-01-03 12:42 被阅读0次

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; // 指针逻动的位置加上当前数据所占大小

实现步骤:

  1. 构建Parcel.java对象。
  2. Android(run)创建Parcel.cpp 对象,会把首地址返回给Java层。
    同时开辟一块共享内存,共享内存中有几个值(mData, mDataPos, mDataCapacity)
  3. Java层通过首地址,拿到Parcel.cpp对象。
    指针偏移默认是0,去写入数据,然后指针偏移位置往后挪动。再次写入数据,指针再往后挪。
  4. 读取数据
    根据地址找到对象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++ (更加灵活,接近底层内存,操作的是一块内存地址)

  1. Parcel.kt 中定义Native方法;
  2. 生成.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++ 【更加灵活,更加接近底层内存,操作的是一块内存地址】。

坚持,并对自己有用。多花时间。
只要能力够强,就不要怕没工作。

做人无胜高远事业,摆脱的俗情便入名流。不为名利去做事情。
不为名利
为学不胜增益功夫,灭除得物类便超盛境
静得下心愿意花时间
记住别人的好。做学问没有快速的方法。

相关文章

网友评论

      本文标题:NDK-014: jni: Android共享内存的序列化过程

      本文链接:https://www.haomeiwen.com/subject/towfyjtx.html