【Cocos2d-x源码分析】 UserDefault如何保存本地数据

原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/50580793

Cocos2d-x提供了UserDefault类来在本地保存简单的游戏数据。今天我们的目标就是分析UserDefault是如何工作的。

本文的分析的是Cocosd2-x 3.8版本的源码,使用Vistual Studio2013。


1、初探UserDefualt

熟悉Coco2d-x的童鞋应该都知道,UserDefault类主要提供了以下接口来保存数据。

代码1:

    // 获取bool类型数据
    bool    getBoolForKey(const char* key);
    virtual bool getBoolForKey(const char* key, bool defaultValue);
    // 获取int类型数据
    int     getIntegerForKey(const char* key);
    virtual int getIntegerForKey(const char* key, int defaultValue);
    // 获取float类型数据
    float    getFloatForKey(const char* key);
    virtual float getFloatForKey(const char* key, float defaultValue);
    // 获取double类型数据
    double  getDoubleForKey(const char* key);
    virtual double getDoubleForKey(const char* key, double defaultValue);
    // 获取string类型数据
    std::string getStringForKey(const char* key);
    virtual std::string getStringForKey(const char* key, const std::string & defaultValue);
    // 获取Data类型数据,从CCData.h中我们可以看到Data其实保存的是
    // unsigned char* _bytes类型数据
    Data getDataForKey(const char* key);
    virtual Data getDataForKey(const char* key, const Data& defaultValue);

    // 获取bool类型数据
    virtual void setBoolForKey(const char* key, bool value)
    // 获取int类型数据
    virtual void setIntegerForKey(const char* key, int value);
    // 获取float类型数据
    virtual void setFloatForKey(const char* key, float value);
    // 获取double类型数据
    virtual void setDoubleForKey(const char* key, double value);
    // 获取string类型数据
    virtual void setStringForKey(const char* key, const std::string & value);
    // 获取Data类型数据
    virtual void setDataForKey(const char* key, const Data& value);

    static UserDefault* getInstance();

其中,UserDefault在实现上使用了单例模式,getInstance方法返回唯一的实例。setXXXForKey用来设置指定类型的数据,getXXXForKey用来获取指定类型的数据。这几个接口简单易懂,那接下来,我们就到源码里面看看UserDefault是如何保存本地数据的。

2、UserDefault::getInstance()实现

首先,我们肯定要先看看UserDefault是如何初始化的,我们找到UserDefault::getInstance()函数。

代码2:

UserDefault* UserDefault::getInstance()
{
    if (!_userDefault)
    {
        initXMLFilePath();

        // only create xml file one time
        // the file exists after the program exit
        if ((!isXMLFileExist()) && (!createXMLFile()))
        {
            return nullptr;
        }

        _userDefault = new (std::nothrow) UserDefault();
    }

    return _userDefault;
}

代码2是getInstance的实现代码,里面出现了“XMLFilePath”和“XMLFile”字样,我们是不是可以大胆地猜测:UserDefault会不会将数据保存在XML文件中?带着这个猜测,我们继续往下分析。在代码2中,_userDefault的定义如下:

UserDefault* UserDefault::_userDefault = nullptr;

当用户第一次调用getInstance函数时候,! _userDefault判断必然为真,所以执行了if语句里面的代码。其实这就是单例模式的典型实现方式。Cocos2d-x采用了“懒汉式”的单例模式实现,当用户真正需要使用时再进行初始化。该初始化过程主要做了下面三件事:

  • initXMLFilePath()
  • isXMLFileExist()
  • createXMLFile()

我们先来看看initXMLFilePath函数的实现:

代码3:

void UserDefault::initXMLFilePath()
{
    if (! _isFilePathInitialized)
    {
        _filePath += FileUtils::getInstance()->getWritablePath() + XML_FILE_NAME;
        _isFilePathInitialized = true;
    }    
}

在代码3中,我们可以看到,initXMLFilePath函数主要功能就是初始化文件的存放路径。文件的名字XML_FILE_NAME被定义为:

#define XML_FILE_NAME "UserDefault.xml"

到这里我们是不是几乎可以确定,UserDefault就是利用xml文件来保存本地数据,而且这个文件的名称就叫“UserDefault.xml”!那这个文件又被存放在哪里呢?这又依赖于FileUtils类来根据不同的平台来确定不同的目录。关于这一点,大家可以看看我的另一篇博客【Cocos2d-x源码分析】 FileUtils如何跨平台查找文件,在这里就不再一一分析了。

对于_filePath 的值,我们可以将其输出,看看它具体的值。我在win32中调用UserDefault::getInstance()->getXMLFilePath()函数输出如下:

C:/Users/fred/AppData/Local/CocosTest/UserDefault.xml 

接下来isXMLFileExist方法判断_filePath 路径上的xml文件是否存在,如果不存在则调用createXMLFile方法创建一个新的xml文件。

代码4:

// create new xml file
bool UserDefault::createXMLFile()
{
    bool bRet = false;  
    tinyxml2::XMLDocument *pDoc = new tinyxml2::XMLDocument(); 
    if (nullptr==pDoc)  
    {  
        return false;  
    }  
    tinyxml2::XMLDeclaration *pDeclaration = pDoc->NewDeclaration(nullptr);  
    if (nullptr==pDeclaration)  
    {  
        return false;  
    }  
    pDoc->LinkEndChild(pDeclaration); 
    tinyxml2::XMLElement *pRootEle = pDoc->NewElement(USERDEFAULT_ROOT_NAME);  
    if (nullptr==pRootEle)  
    {  
        return false;  
    }  
    pDoc->LinkEndChild(pRootEle);  
    bRet = tinyxml2::XML_SUCCESS == pDoc->SaveFile(FileUtils::getInstance()->getSuitableFOpen(_filePath).c_str());

    if(pDoc)
    {
        delete pDoc;
    }

    return bRet;
}

在代码4中,我们可以捕捉到两个重要信息:

一是Cocos2d-x使用tinyxml2来操作xml文件。由于本文只是分析UserDefault的实现机制,对于tinyxml2就不展开介绍,需要进一步了解的童鞋可以移步官网或者GitHub

二是createXMLFile函数创建了一个xml文件并设置了头节点,然后保存在_filePath指定的路径上。我们找到该xml文件,可以看到初始化后的xml文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<userDefaultRoot/>

3、setXXXForKey和getXXXForKey的实现

通过前面的分析,我们知道UserDefault通过xml文件来保存本地数据。如果你在平时编程时有使用过xml文件,是不是很容易猜到setXXXForKey和getXXXForKey是如何实现的?没错,其实就是创建 or 查找结点,然后读写该结点。由于不同类型的setXXXForKey和getXXXForKey方法有很大的相似性,这里我们就挑比较典型的setStringForKey和getStringForKey方法来讲解一下。

getStringForKey的实现如下:

代码5:

std::string UserDefault::getStringForKey(const char* pKey)
{
    return getStringForKey(pKey, "");
}

string UserDefault::getStringForKey(const char* pKey, const std::string & defaultValue)
{
    const char* value = nullptr;
    tinyxml2::XMLElement* rootNode;
    tinyxml2::XMLDocument* doc;
    tinyxml2::XMLElement* node;
    node =  getXMLNodeForKey(pKey, &rootNode, &doc);
    // find the node
    if (node && node->FirstChild())
    {
        value = (const char*)(node->FirstChild()->Value());
    }

    string ret = defaultValue;

    if (value)
    {
        ret = string(value);
    }

    if (doc) delete doc;

    return ret;
}

在代码5中,我们可以看到getStringForKey(const char* pKey)实际上调用了getStringForKey(const char* pKey, const std::string & defaultValue)来实现数据保存,这对于其他类型的getter方法也差不多如此。getStringForKey方法中最重要的是getXMLNodeForKey函数。从它的命名我们可以看出,该函数在xml文件中查找指定key的xml结点然后返回,这样getStringForKey方法就直接从目标结点中读取保存的数据然后返回。我们进一步跟踪,看看getXMLNodeForKey函数的实现。

代码6:

static tinyxml2::XMLElement* getXMLNodeForKey(const char* pKey, tinyxml2::XMLElement** rootNode, tinyxml2::XMLDocument **doc)
{
    tinyxml2::XMLElement* curNode = nullptr;

    // check the key value
    if (! pKey)
    {
        return nullptr;
    }

    do 
    {
         tinyxml2::XMLDocument* xmlDoc = new tinyxml2::XMLDocument();
        *doc = xmlDoc;

        std::string xmlBuffer = FileUtils::getInstance()->getStringFromFile(UserDefault::getInstance()->getXMLFilePath());

        if (xmlBuffer.empty())
        {
            CCLOG("can not read xml file");
            break;
        }
        xmlDoc->Parse(xmlBuffer.c_str(), xmlBuffer.size());

        // get root node
        *rootNode = xmlDoc->RootElement();
        if (nullptr == *rootNode)
        {
            CCLOG("read root node error");
            break;
        }
        // find the node
        curNode = (*rootNode)->FirstChildElement();
        while (nullptr != curNode)
        {
            const char* nodeName = curNode->Value();
            if (!strcmp(nodeName, pKey))
            {
                break;
            }

            curNode = curNode->NextSiblingElement();
        }
    } while (0);

    return curNode;
}

从代码5中,我们可以看到getXMLNodeForKey的工作就是将xml文件读进内存、解析、遍历节点直至找到参数key对应的目标结点。这里涉及tinyxml2较多的xml操作函数,感兴趣的童鞋可以自动gg一下。

不知道你有没有注意到getXMLNodeForKey并不是UserDefault的成员函数,而是被定义为static函数,这样它的可见性就被限制在仅该文件可见,作者给出了这样做的理由:

/**
 * define the functions here because we don't want to
 * export xmlNodePtr and other types in "CCUserDefault.h"
 */

接下来,再来看看setStringForKey函数的实现。

代码7:

void UserDefault::setStringForKey(const char* pKey, const std::string & value)
{
    // check key
    if (! pKey)
    {
        return;
    }

    setValueForKey(pKey, value.c_str());
}

不用解释,我们继续追踪setValueForKey

代码8:

static void setValueForKey(const char* pKey, const char* pValue)
{
     tinyxml2::XMLElement* rootNode;
    tinyxml2::XMLDocument* doc;
    tinyxml2::XMLElement* node;
    // check the params
    if (! pKey || ! pValue)
    {
        return;
    }
    // find the node
    node = getXMLNodeForKey(pKey, &rootNode, &doc);
    // if node exist, change the content
    if (node)
    {
        if (node->FirstChild())
        {
            node->FirstChild()->SetValue(pValue);
        }
        else
        {
            tinyxml2::XMLText* content = doc->NewText(pValue);
            node->LinkEndChild(content);
        }
    }
    else
    {
        if (rootNode)
        {
            tinyxml2::XMLElement* tmpNode = doc->NewElement(pKey);//new tinyxml2::XMLElement(pKey);
            rootNode->LinkEndChild(tmpNode);
            tinyxml2::XMLText* content = doc->NewText(pValue);//new tinyxml2::XMLText(pValue);
            tmpNode->LinkEndChild(content);
        }    
    }

    // save file and free doc
    if (doc)
    {
        doc->SaveFile(FileUtils::getInstance()->getSuitableFOpen(UserDefault::getInstance()->getXMLFilePath()).c_str());
        delete doc;
    }
}

在代码8中,我们可以根据注释来阅读这段代码。该函数主要做了以下事情:

  • 在xml文件中查找参数key指定的结点
  • 如果找到目标结点,直接修改对应的值;如果没有找到目标结点,则创建一个新结点并链接到xml字符串中。
  • 保存修改后的文件,释放资源

总结:

  • UserDefault类通过XML文件来将游戏数据保存本地,该文件名称为UserDefault.xml。
  • 每次调用setXXXForKey和getXXXForKey函数时,UserDefault总是需要经历读入解析UserDefault.xml文件,查找参数key指定的结点,进行读/写操作,保存文件(如果前面是写操作) 等步骤。
  • UserDefault虽然提供了flush函数,但是该函数并未进行任何操作。UserDefault在每次的setXXXForKey的最后写回文件
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: cocos2d-x是一款流行的开游戏引擎,它可以用作开发本地移动游戏和桌面游戏。利用cocos2d-x游戏码,开发人员可以快速构建流畅、高效、具有吸引力的游戏。该引擎使用C ++语言和脚本语言Lua来实现游戏开发,用户可以根据自己的需要进行选择。 cocos2d-x游戏码包含许多强大的功能和工具,例如精灵、动画、场景管理、碰撞检测和音频控制等。通过这些功能,开发人员可以方便地创建各种类型的游戏,例如动作游戏、射击游戏、冒险游戏和益智游戏等。 此外,cocos2d-x游戏码还具有高度定制化的特性,允许开发人员根据他们的需求自定义游戏元素。这种定制化可以通过改进游戏画面,添加新的模式和关卡,甚至整个新的游戏玩法来完成。 总之,cocos2d-x游戏码是一款功能强大、易于使用和高度定制的游戏引擎,它可以帮助开发人员快速开发各种类型的游戏。无论你是专业开发人员还是无经验的游戏制作者,cocos2d-x都是一个极好的选择。 ### 回答2: Cocos2d-x游戏码是使用Cocos2d-x引擎进行开发的游戏程序代码。Cocos2d-x引擎是一个开的跨平台游戏引擎,可以支持iOS、Android、Windows Phone、Windows、MacOS和Linux等多个平台,并且提供了丰富的游戏开发API和工具集。Cocos2d-x游戏码包含了游戏的核心逻辑、UI设计、动画效果、音频效果等各方面的代码,可以作为开发者学习和借鉴的重要资料。 由于Cocos2d-x引擎支持多种编程语言(如C++、Lua等),因此Cocos2d-x游戏码也会有对应的编程语言版本。开发者可以通过阅读Cocos2d-x游戏码,了解游戏开发过程中的技术细节,学习如何使用Cocos2d-x引擎进行游戏开发。同时,开发者也可以通过对Cocos2d-x游戏码进行修改和优化,来满足游戏的特定需求,提升游戏的性能和用户体验。 总之,Cocos2d-x游戏码是游戏开发者必备的重要资,可以帮助开发者更好地了解游戏开发技术,提升游戏开发水平。 ### 回答3: cocos2d-x是一款优秀的游戏开发引擎,其码包含了许多功能强大的游戏开发工具和API。使用cocos2d-x可以帮助开发者更快速更高效地开发出优秀的游戏作品。 cocos2d-x的游戏码非常丰富,其包含了许多不同类型的游戏示例和模板,如平面射击、塔防、解谜等,这些示例可以帮助开发者更好地了解cocos2d-x的使用方法和原理。 cocos2d-x码还包含了许多强大的API和工具,例如场景管理、动画控制、音频引擎等,这些工具和API能够帮助开发者更好地完成游戏的开发和调试。 此外,cocos2d-x码还提供了完整的游戏开发框架,包括资管理、内存管理、事件机制等,这些框架可以帮助开发者更好地组织代码和提高代码的可维护性。 总的来说,cocos2d-x游戏码提供了丰富的工具和API,可以帮助开发者更高效地进行游戏开发,大大降低了开发难度和成本,是一款非常适合游戏开发者使用的引擎。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值