大家好茫舶,我系蒼王铭污。
以下是我這個(gè)系列的相關(guān)文章冀偶,有興趣可以參考一下舆声,可以給個(gè)喜歡或者關(guān)注我的文章花沉。
[Android]如何做一個(gè)崩潰率少于千分之三噶應(yīng)用app--章節(jié)列表
組件化群1已經(jīng)滿員柳爽,進(jìn)來(lái)的可以加群2 763094035
MMKV框架初始化
MMKV.initialize(this);
public static String initialize(Context context) {
//創(chuàng)建存儲(chǔ)目錄
rootDir = context.getFilesDir().getAbsolutePath() + "/mmkv";
initialize(rootDir);
return rootDir;
}
private static native void initialize(String var0);
之后是調(diào)用jni中的操作了
extern "C" JNIEXPORT JNICALL void
Java_com_tencent_mmkv_MMKV_initialize(JNIEnv *env, jobject obj, jstring rootDir) {
//c++中大于0和非空都是為真
if (!rootDir) {
return;
}
//string轉(zhuǎn)為char*
const char *kstr = env->GetStringUTFChars(rootDir, nullptr);
if (kstr) {
MMKV::initializeMMKV(kstr);
//初始化完后釋放kstr
env->ReleaseStringUTFChars(rootDir, kstr);
}
}
MMKV初始化媳握,
pthread_once()函數(shù)詳解
在多線程環(huán)境中,有些事僅需要執(zhí)行一次磷脯。通常當(dāng)初始化應(yīng)用程序時(shí)蛾找,可以比較容易地將其放在main函數(shù)中。但當(dāng)你寫一個(gè)庫(kù)時(shí)赵誓,就不能在main里面初始化了打毛,你可以用靜態(tài)初始化,但使用一次初始化(pthread_once)會(huì)比較容易些俩功。
int pthread_once(pthread_once_t once_control, void (init_routine) (void))幻枉;
功能:本函數(shù)使用初值為PTHREAD_ONCE_INIT的once_control變量保證init_routine()函數(shù)在本進(jìn)程執(zhí)行序列中僅執(zhí)行一次。
在多線程編程環(huán)境下诡蜓,盡管pthread_once()調(diào)用會(huì)出現(xiàn)在多個(gè)線程中熬甫,init_routine()函數(shù)僅執(zhí)行一次,究竟在哪個(gè)線程中執(zhí)行是不定的蔓罚,是由內(nèi)核調(diào)度來(lái)決定椿肩。
那么initialize方法只會(huì)執(zhí)行一次瞻颂。
void MMKV::initializeMMKV(const std::string &rootDir) {
//進(jìn)程控制
static pthread_once_t once_control = PTHREAD_ONCE_INIT;
//進(jìn)程中只執(zhí)行一次initialize
pthread_once(&once_control, initialize);
//保存文件地址
g_rootDir = rootDir;
//strdup字符串拷貝
//c_str()函數(shù)返回一個(gè)指向正規(guī)C字符串的指針, 內(nèi)容與本string串相同.這是為了與c語(yǔ)言兼容,在c語(yǔ)言中沒有string類型郑象,故必須通過(guò)string類對(duì)象的成員函數(shù)c_str()把string 對(duì)象轉(zhuǎn)換成c中的字符串樣式贡这。注意:一定要使用strcpy()函數(shù) 等來(lái)操作方法c_str()返回的指針
//需要分配空間的
char *path = strdup(g_rootDir.c_str());
//創(chuàng)建MmapedFile
mkPath(path);
//清除地址空間
free(path);
MMKVInfo("root dir: %s", g_rootDir.c_str());
}
void initialize() {
//建立哈希表<id,mmkv>
// 使用unordered_map優(yōu)點(diǎn)因?yàn)閮?nèi)部實(shí)現(xiàn)了哈希表,因此其查找速度非常的快 厂榛,缺點(diǎn)哈希表的建立比較耗費(fèi)時(shí)間
g_instanceDic = new unordered_map<std::string, MMKV *>;
//初始化線程鎖
g_instanceLock = ThreadLock();
//testAESCrypt();
MMKVInfo("page size:%d", DEFAULT_MMAP_SIZE);
}
//使用getpagesize函數(shù)獲得一頁(yè)內(nèi)存大小
//系統(tǒng)給我們提供真正的內(nèi)存時(shí)盖矫,用頁(yè)為單位提供,一次最少提供一頁(yè)的真實(shí)內(nèi)存空間
//分配內(nèi)存空間:你真實(shí)的分配了多少內(nèi)存击奶,就使用多少內(nèi)存炼彪,不要越界使用
//但是系統(tǒng)提供的真實(shí)內(nèi)存空間是以頁(yè)來(lái)提供的。
const int DEFAULT_MMAP_SIZE = getpagesize();
創(chuàng)建保存的文件夾正歼,遍歷地址名辐马,然后循環(huán)創(chuàng)建
bool mkPath(char *path) {
//文件(夾)信息結(jié)構(gòu)體
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) {
//創(chuàng)建文件夾
if (errno != ENOENT || mkdir(path, 0777) != 0) {
MMKVWarning("%s", path);
return false;
}
} else if (!S_ISDIR(sb.st_mode)) { //如果不是文件夾,拋錯(cuò)誤
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 不能超過(guò)46個(gè)字節(jié)
verifyMMID(mmapID);
創(chuàng)立出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) {
//創(chuàng)建文件檩帐,大小為一頁(yè),和加密密鑰
kv = MMKV::mmkvWithID(str, DEFAULT_MMAP_SIZE, (MMKVMode) mode, &crypt);
}
}
if (!kv) { //如果創(chuàng)建失敗另萤,重新創(chuàng)建一個(gè)不加密的文件
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;
}
//設(shè)定單例鎖
SCOPEDLOCK(g_instanceLock);
//在記錄文件的map中找mampID湃密,對(duì)應(yīng)的mapFile文件
auto itr = g_instanceDic->find(mmapID);
//返回結(jié)束位置胡迭代器
//如果已經(jīng)存在
if (itr != g_instanceDic->end()) {
//返回現(xiàn)在對(duì)應(yīng)mmkv
MMKV *kv = itr->second;
return kv;
}
//新建一個(gè)mmkv
auto kv = new MMKV(mmapID, size, mode, cryptKey);
//保存到map中
(*g_instanceDic)[mmapID] = kv;
return kv;
}
創(chuàng)建MMKV 初始化
MMKV::MMKV(const std::string &mmapID, int size, MMKVMode mode, string *cryptKey)
: m_mmapID(mmapID)
, m_path(mappedKVPathWithID(m_mmapID, mode)) //構(gòu)建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) //進(jìn)程鎖
, m_exclusiveProcessLock(&m_fileLock, ExclusiveLockType) //專用進(jìn)程鎖
, m_isInterProcess((mode & MMKV_MULTI_PROCESS) != 0) //是否多進(jìn)程
, m_isAshmem((mode & MMKV_ASHMEM) != 0) { //是否開啟共享內(nèi)存
m_fd = -1;
m_ptr = nullptr;
m_size = 0;
m_actualSize = 0;
m_output = nullptr;
if (m_isAshmem) { //如果需要共享內(nèi)存,需要使用創(chuàng)建共享共享內(nèi)存的MmapedFile
m_ashmemFile = new MmapedFile(m_mmapID, static_cast<size_t>(size), MMAP_ASHMEM);
//讀取匿名內(nèi)存的文件fd地址
m_fd = m_ashmemFile->getFd();
} else {
m_ashmemFile = nullptr;
}
if (cryptKey && cryptKey->length() > 0) { //是否存在加密密鑰四敞,存在則創(chuàng)建AES加密器
m_crypter = new AESCrypt((const unsigned char *) cryptKey->data(), cryptKey->length());
}
//是否直接沖文件加載
m_needLoadFromFile = true;
m_crcDigest = 0;
//是否開啟進(jìn)程鎖
m_sharedProcessLock.m_enable = m_isInterProcess;
//是否開啟專用進(jìn)程鎖
m_exclusiveProcessLock.m_enable = m_isInterProcess;
// sensitive zone
{
//單例鎖
SCOPEDLOCK(m_sharedProcessLock);
loadFromFile();
}
}
讀取文件
void MMKV::loadFromFile() {
if (m_isAshmem) { //是否使用匿名內(nèi)存泛源,是則初始化匿名內(nèi)存
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) { //文件狀態(tài)是否可讀
m_size = static_cast<size_t>(st.st_size);
}
// round up to (n * pagesize)
if (m_size < DEFAULT_MMAP_SIZE || (m_size % DEFAULT_MMAP_SIZE != 0)) { //文件大小有內(nèi)容忿危,且小于1頁(yè)
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<size_t>(st.st_size);
}
zeroFillFile(m_fd, oldSize, m_size - oldSize); //清空文件
}
//將文件映射到內(nèi)存
m_ptr = (char *) mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);
//之后操作和匿名內(nèi)存中操作相同
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存儲(chǔ)結(jié)構(gòu)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;
}
讀取匿名內(nèi)存
void MMKV::loadFromAshmem() {
//復(fù)制MmapedFile的段地址
m_metaInfo.read(m_metaFile.getMemory());
//如果匿名內(nèi)存存在且匿名內(nèi)存文件為真
if (m_fd < 0 || !m_ashmemFile) {
MMKVError("ashmem file invalid %s, fd:%d", m_path.c_str(), m_fd);
} else {
//讀取匿名內(nèi)存文件大小
m_size = m_ashmemFile->getFileSize();
//讀取匿名內(nèi)存起始地址
m_ptr = (char *) m_ashmemFile->getMemory();
if (m_ptr != MAP_FAILED) { //如果地址存在
//復(fù)制匿名內(nèi)存出來(lái)
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都設(shè)置了
MMKVInfo("loading [%s] with crc %u sequence %u", m_mmapID.c_str(),
m_metaInfo.m_crcDigest, m_metaInfo.m_sequence);
//創(chuàng)造MMBuffer數(shù)據(jù)
MMBuffer inputBuffer(m_ptr + Fixed32Size, m_actualSize, MMBufferNoCopy);
if (m_crypter) { //如果加密达箍,解密MMBuffer數(shù)據(jù)
decryptBuffer(*m_crypter, inputBuffer);
}
//
m_dic = MiniPBCoder::decodeMap(inputBuffer);
m_output = new CodedOutputData(m_ptr + Fixed32Size + m_actualSize,
m_size - Fixed32Size - m_actualSize);
//標(biāo)志已經(jīng)讀取成功
loaded = true;
}
}
}
if (!loaded) {
//使用范圍鎖
SCOPEDLOCK(m_exclusiveProcessLock);
if (m_actualSize > 0) {
writeAcutalSize(0);
}
//輸出流
m_output = new CodedOutputData(m_ptr + Fixed32Size, m_size - Fixed32Size);
//重新計(jì)算crc校驗(yàn)
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;
}
說(shuō)明一下存儲(chǔ)文件的創(chuàng)建(分為文件和共享內(nèi)存)
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) { //文件創(chuàng)建
//創(chuàng)建一個(gè)文件
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<size_t>(st.st_size);
}
if (m_segmentSize < DEFAULT_MMAP_SIZE) {
m_segmentSize = static_cast<size_t>(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 {
//共享內(nèi)存創(chuàng)建 #define ASHMEM_NAME_DEF "/dev/ashmem"
m_fd = open(ASHMEM_NAME_DEF, O_RDWR);
if (m_fd < 0) { //創(chuàng)建失敗
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) { //內(nèi)存訪問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 { //訪問成功
//獲取頁(yè)長(zhǎng)度
m_segmentSize = static_cast<size_t>(size);
//獲取頁(yè)指針
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;
}
}
}
第二和第三篇的分析,已經(jīng)在小專欄里刊登铺厨,請(qǐng)瀏覽https://xiaozhuanlan.com/cangwang_process查看更詳細(xì)的分析