[Android]高性能MMKV数据交互分析-MMKV初始化
16lz
2021-01-26
大家好,我系苍王。 以下是我这个系列的相关文章,有兴趣可以参考一下,可以给个喜欢或者关注我的文章。
[Android]如何做一个崩溃率少于千分之三噶应用app--章节列表
组件化群1已经满员,进来的可以加群2 763094035
MMKV框架初始化
MMKV.initialize(this); public static String initialize(Context context) { //创建存储目录 rootDir = context.getFilesDir().getAbsolutePath() + "/mmkv"; initialize(rootDir); return rootDir; } private static native void initialize(String var0);复制代码
之后是调用jni中的操作了
extern "C" JNIEXPORT JNICALL voidJava_com_tencent_mmkv_MMKV_initialize(JNIEnv *env, jobject obj, jstring rootDir) { //c++中大于0和非空都是为真 if (!rootDir) { return; } //string转为char* const char *kstr = env->GetStringUTFChars(rootDir, nullptr); if (kstr) { MMKV::initializeMMKV(kstr); //初始化完后释放kstr env->ReleaseStringUTFChars(rootDir, kstr); }}复制代码
MMKV初始化, pthread_once()函数详解
在多线程环境中,有些事仅需要执行一次。通常当初始化应用程序时,可以比较容易地将其放在main函数中。但当你写一个库时,就不能在main里面初始化了,你可以用静态初始化,但使用一次初始化(pthread_once)会比较容易些。
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void));
功能:本函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。
在多线程编程环境下,尽管pthread_once()调用会出现在多个线程中,init_routine()函数仅执行一次,究竟在哪个线程中执行是不定的,是由内核调度来决定。 那么initialize方法只会执行一次。
void MMKV::initializeMMKV(const std::string &rootDir) { //进程控制 static pthread_once_t once_control = PTHREAD_ONCE_INIT; //进程中只执行一次initialize pthread_once(&once_control, initialize); //保存文件地址 g_rootDir = rootDir; //strdup字符串拷贝 //c_str()函数返回一个指向正规C字符串的指针, 内容与本string串相同.这是为了与c语言兼容,在c语言中没有string类型,故必须通过string类对象的成员函数c_str()把string 对象转换成c中的字符串样式。注意:一定要使用strcpy()函数 等来操作方法c_str()返回的指针 //需要分配空间的 char *path = strdup(g_rootDir.c_str()); //创建MmapedFile mkPath(path); //清除地址空间 free(path); MMKVInfo("root dir: %s", g_rootDir.c_str());}void initialize() { //建立哈希表 // 使用unordered_map优点因为内部实现了哈希表,因此其查找速度非常的快 ,缺点哈希表的建立比较耗费时间 g_instanceDic = new unordered_map; //初始化线程锁 g_instanceLock = ThreadLock(); //testAESCrypt(); MMKVInfo("page size:%d", DEFAULT_MMAP_SIZE);}//使用getpagesize函数获得一页内存大小//系统给我们提供真正的内存时,用页为单位提供,一次最少提供一页的真实内存空间 //分配内存空间:你真实的分配了多少内存,就使用多少内存,不要越界使用 //但是系统提供的真实内存空间是以页来提供的。const int DEFAULT_MMAP_SIZE = getpagesize();复制代码
创建保存的文件夹,遍历地址名,然后循环创建
bool mkPath(char *path) { //文件(夹)信息结构体 struct stat sb = {}; bool done = false; char *slash = path; while (!done) { //拿出文件夹名 slash += strspn(slash, "/"); slash += strcspn(slash, "/"); //遍历到最尾部 done = (*slash == '\0'); *slash = '\0'; //如果文件夹不为空 if (stat(path, &sb) != 0) { //创建文件夹 if (errno != ENOENT || mkdir(path, 0777) != 0) { MMKVWarning("%s", path); return false; } } else if (!S_ISDIR(sb.st_mode)) { //如果不是文件夹,抛错误 MMKVWarning("%s: %s", path, strerror(ENOTDIR)); return false; } *slash = '/'; } return true;}复制代码
保存文件初始化
//需要填写mmapId,线程(单线程,多线程),加密密钥 MMKV.mmkvWithID(mmapID, MMKV.SINGLE_PROCESS_MODE, cryptKey); public static MMKV mmkvWithID(String mmapID, int mode, String cryptKey) { if(rootDir == null) { //如果主目录未初始化抛出异常 throw new IllegalStateException("You should Call MMKV.initialize() first."); } else { //id 不能超过46个字节 verifyMMID(mmapID); 创立出jni中分配的ID long handle = getMMKVWithID(mmapID, mode, cryptKey); return new MMKV(handle); } } //mmkv的id是long类型 private static native long getMMKVWithID(String var0, int var1, String var2);复制代码
extern "C" JNIEXPORT JNICALL jlong Java_com_tencent_mmkv_MMKV_getMMKVWithID( JNIEnv *env, jobject obj, jstring mmapID, jint mode, jstring cryptKey) { MMKV *kv = nullptr; //初始化为空指针 if (!mmapID) { //如果为空,直接返回空指针 return (jlong) kv; } string str = jstring2string(env, mmapID); //格式化mmapId if (cryptKey != nullptr) { string crypt = jstring2string(env, cryptKey); //格式化密钥 if (crypt.length() > 0) { //创建文件,大小为一页,和加密密钥 kv = MMKV::mmkvWithID(str, DEFAULT_MMAP_SIZE, (MMKVMode) mode, &crypt); } } if (!kv) { //如果创建失败,重新创建一个不加密的文件 kv = MMKV::mmkvWithID(str, DEFAULT_MMAP_SIZE, (MMKVMode) mode, nullptr); } return (jlong) kv;}MMKV *MMKV::mmkvWithID(const std::string &mmapID, int size, MMKVMode mode, string *cryptKey) { //如果mmapId为空范围空指针 if (mmapID.empty()) { return nullptr; } //设定单例锁 SCOPEDLOCK(g_instanceLock); //在记录文件的map中找mampID,对应的mapFile文件 auto itr = g_instanceDic->find(mmapID); //返回结束位置胡迭代器 //如果已经存在 if (itr != g_instanceDic->end()) { //返回现在对应mmkv MMKV *kv = itr->second; return kv; } //新建一个mmkv auto kv = new MMKV(mmapID, size, mode, cryptKey); //保存到map中 (*g_instanceDic)[mmapID] = kv; return kv;}复制代码
创建MMKV 初始化
MMKV::MMKV(const std::string &mmapID, int size, MMKVMode mode, string *cryptKey) : m_mmapID(mmapID) , m_path(mappedKVPathWithID(m_mmapID, mode)) //构建mmkv文件地址 , m_crcPath(crcPathWithID(m_mmapID, mode)) //crc文件 , m_metaFile(m_crcPath, DEFAULT_MMAP_SIZE, (mode & MMKV_ASHMEM) ? MMAP_ASHMEM : MMAP_FILE) //初始化MmapedFile , m_crypter(nullptr) //加密器 , m_fileLock(m_metaFile.getFd()) //文件锁 , m_sharedProcessLock(&m_fileLock, SharedLockType) //进程锁 , m_exclusiveProcessLock(&m_fileLock, ExclusiveLockType) //专用进程锁 , m_isInterProcess((mode & MMKV_MULTI_PROCESS) != 0) //是否多进程 , m_isAshmem((mode & MMKV_ASHMEM) != 0) { //是否开启共享内存 m_fd = -1; m_ptr = nullptr; m_size = 0; m_actualSize = 0; m_output = nullptr; if (m_isAshmem) { //如果需要共享内存,需要使用创建共享共享内存的MmapedFile m_ashmemFile = new MmapedFile(m_mmapID, static_cast(size), MMAP_ASHMEM); //读取匿名内存的文件fd地址 m_fd = m_ashmemFile->getFd(); } else { m_ashmemFile = nullptr; } if (cryptKey && cryptKey->length() > 0) { //是否存在加密密钥,存在则创建AES加密器 m_crypter = new AESCrypt((const unsigned char *) cryptKey->data(), cryptKey->length()); } //是否直接冲文件加载 m_needLoadFromFile = true; m_crcDigest = 0; //是否开启进程锁 m_sharedProcessLock.m_enable = m_isInterProcess; //是否开启专用进程锁 m_exclusiveProcessLock.m_enable = m_isInterProcess; // sensitive zone { //单例锁 SCOPEDLOCK(m_sharedProcessLock); loadFromFile(); }}复制代码
读取文件
void MMKV::loadFromFile() { if (m_isAshmem) { //是否使用匿名内存,是则初始化匿名内存 loadFromAshmem(); return; } m_metaInfo.read(m_metaFile.getMemory()); //打开文件,m_fd文件描述符 m_fd = open(m_path.c_str(), O_RDWR | O_CREAT, S_IRWXU); if (m_fd < 0) { //文件不存在 MMKVError("fail to open:%s, %s", m_path.c_str(), strerror(errno)); } else { m_size = 0; struct stat st = {0}; if (fstat(m_fd, &st) != -1) { //文件状态是否可读 m_size = static_cast(st.st_size); } // round up to (n * pagesize) if (m_size < DEFAULT_MMAP_SIZE || (m_size % DEFAULT_MMAP_SIZE != 0)) { //文件大小有内容,且小于1页 size_t oldSize = m_size; m_size = ((m_size / DEFAULT_MMAP_SIZE) + 1) * DEFAULT_MMAP_SIZE; if (ftruncate(m_fd, m_size) != 0) { //指定文件大小输出是否可行 MMKVError("fail to truncate [%s] to size %zu, %s", m_mmapID.c_str(), m_size, strerror(errno)); m_size = static_cast(st.st_size); } zeroFillFile(m_fd, oldSize, m_size - oldSize); //清空文件 } //将文件映射到内存 m_ptr = (char *) mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0); //之后操作和匿名内存中操作相同 if (m_ptr == MAP_FAILED) { MMKVError("fail to mmap [%s], %s", m_mmapID.c_str(), strerror(errno)); } else { memcpy(&m_actualSize, m_ptr, Fixed32Size); MMKVInfo("loading [%s] with %zu size in total, file size is %zu", m_mmapID.c_str(), m_actualSize, m_size); bool loaded = false; if (m_actualSize > 0) { if (m_actualSize < m_size && m_actualSize + Fixed32Size <= m_size) { if (checkFileCRCValid()) { MMKVInfo("loading [%s] with crc %u sequence %u", m_mmapID.c_str(), m_metaInfo.m_crcDigest, m_metaInfo.m_sequence); MMBuffer inputBuffer(m_ptr + Fixed32Size, m_actualSize, MMBufferNoCopy); if (m_crypter) { decryptBuffer(*m_crypter, inputBuffer); } //初始化Photobuf存储结构orderedMap m_dic = MiniPBCoder::decodeMap(inputBuffer); m_output = new CodedOutputData(m_ptr + Fixed32Size + m_actualSize, m_size - Fixed32Size - m_actualSize); loaded = true; } } } if (!loaded) { SCOPEDLOCK(m_exclusiveProcessLock); if (m_actualSize > 0) { writeAcutalSize(0); } m_output = new CodedOutputData(m_ptr + Fixed32Size, m_size - Fixed32Size); recaculateCRCDigest(); } MMKVInfo("loaded [%s] with %zu values", m_mmapID.c_str(), m_dic.size()); } } if (!isFileValid()) { MMKVWarning("[%s] file not valid", m_mmapID.c_str()); } m_needLoadFromFile = false;}复制代码
读取匿名内存
void MMKV::loadFromAshmem() { //复制MmapedFile的段地址 m_metaInfo.read(m_metaFile.getMemory()); //如果匿名内存存在且匿名内存文件为真 if (m_fd < 0 || !m_ashmemFile) { MMKVError("ashmem file invalid %s, fd:%d", m_path.c_str(), m_fd); } else { //读取匿名内存文件大小 m_size = m_ashmemFile->getFileSize(); //读取匿名内存起始地址 m_ptr = (char *) m_ashmemFile->getMemory(); if (m_ptr != MAP_FAILED) { //如果地址存在 //复制匿名内存出来 memcpy(&m_actualSize, m_ptr, Fixed32Size); MMKVInfo("loading [%s] with %zu size in total, file size is %zu", m_mmapID.c_str(), m_actualSize, m_size); bool loaded = false; if (m_actualSize > 0) { if (m_actualSize < m_size && m_actualSize + Fixed32Size <= m_size) { if (checkFileCRCValid()) {//检查m_ptr和m_size都设置了 MMKVInfo("loading [%s] with crc %u sequence %u", m_mmapID.c_str(), m_metaInfo.m_crcDigest, m_metaInfo.m_sequence); //创造MMBuffer数据 MMBuffer inputBuffer(m_ptr + Fixed32Size, m_actualSize, MMBufferNoCopy); if (m_crypter) { //如果加密,解密MMBuffer数据 decryptBuffer(*m_crypter, inputBuffer); } // m_dic = MiniPBCoder::decodeMap(inputBuffer); m_output = new CodedOutputData(m_ptr + Fixed32Size + m_actualSize, m_size - Fixed32Size - m_actualSize); //标志已经读取成功 loaded = true; } } } if (!loaded) { //使用范围锁 SCOPEDLOCK(m_exclusiveProcessLock); if (m_actualSize > 0) { writeAcutalSize(0); } //输出流 m_output = new CodedOutputData(m_ptr + Fixed32Size, m_size - Fixed32Size); //重新计算crc校验 recaculateCRCDigest(); } MMKVInfo("loaded [%s] with %zu values", m_mmapID.c_str(), m_dic.size()); } } if (!isFileValid()) { MMKVWarning("[%s] ashmem not valid", m_mmapID.c_str()); } m_needLoadFromFile = false;}复制代码
说明一下存储文件的创建(分为文件和共享内存)
MmapedFile::MmapedFile(const std::string &path, size_t size, bool fileType) : m_name(path), m_fd(-1), m_segmentPtr(nullptr), m_segmentSize(0), m_fileType(fileType) { if (m_fileType == MMAP_FILE) { //文件创建 //创建一个文件 m_fd = open(m_name.c_str(), O_RDWR | O_CREAT, S_IRWXU); if (m_fd < 0) { MMKVError("fail to open:%s, %s", m_name.c_str(), strerror(errno)); } else { //文件属性的访问 struct stat st = {}; if (fstat(m_fd, &st) != -1) { m_segmentSize = static_cast(st.st_size); } if (m_segmentSize < DEFAULT_MMAP_SIZE) { m_segmentSize = static_cast(DEFAULT_MMAP_SIZE); if (ftruncate(m_fd, m_segmentSize) != 0 || !zeroFillFile(m_fd, 0, m_segmentSize)) { MMKVError("fail to truncate [%s] to size %zu, %s", m_name.c_str(), m_segmentSize, strerror(errno)); close(m_fd); m_fd = -1; removeFile(m_name); return; } } m_segmentPtr = (char *) mmap(nullptr, m_segmentSize, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0); if (m_segmentPtr == MAP_FAILED) { MMKVError("fail to mmap [%s], %s", m_name.c_str(), strerror(errno)); close(m_fd); m_fd = -1; m_segmentPtr = nullptr; } } } else { //共享内存创建 #define ASHMEM_NAME_DEF "/dev/ashmem" m_fd = open(ASHMEM_NAME_DEF, O_RDWR); if (m_fd < 0) { //创建失败 MMKVError("fail to open ashmem:%s, %s", m_name.c_str(), strerror(errno)); } else { if (ioctl(m_fd, ASHMEM_SET_NAME, m_name.c_str()) != 0) { //内存访问io控制 MMKVError("fail to set ashmem name:%s, %s", m_name.c_str(), strerror(errno)); } else if (ioctl(m_fd, ASHMEM_SET_SIZE, size) != 0) { MMKVError("fail to set ashmem:%s, size %d, %s", m_name.c_str(), size, strerror(errno)); } else { //访问成功 //获取页长度 m_segmentSize = static_cast(size); //获取页指针 m_segmentPtr = (char *) mmap(nullptr, m_segmentSize, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0); if (m_segmentPtr == MAP_FAILED) { MMKVError("fail to mmap [%s], %s", m_name.c_str(), strerror(errno)); m_segmentPtr = nullptr; } else { return; } } close(m_fd); m_fd = -1; } }}复制代码
本节是第1篇,第2~4篇的分析,已经在小专栏里刊登,请浏览https://xiaozhuanlan.com/cangwang_process查看更详细的分析.
更多相关文章
- android 模拟器手机如何添加文件到sd卡?
- error: Error parsing XML: unbound prefix 与 error: Invalid s
- Android(安卓)ListView动画(逐行显示动画效果)
- Base64方式上传文件
- Android(安卓)studio的那些坑- so文件添加的正确位置
- Android(安卓)ADB详细介绍及用法
- Android应用程序四大组件之使用AIDL如何实现跨进程调用Service
- 从assets文件夹中读取文件
- Android(安卓)OpenCV_face使用OpenCV3.20的一个免安装OpenCV Man