游戏中有时需要下载网络图片

比如公告/广告的显示

做法一般是按照以下步骤

1.检查本地是否已下载此图片,若有则直接显示(FileUtils::isFileExist)

2.发起网络请求,并下载保存到本地(curl+FILE写入操作)

3.根据保存路径和文件名,显示已下载的图片

这样如需下载图片,可以仅下载一遍

为了更好的体验,使用多线程防止下载过程中的卡顿

转载请注明出处http://www.cnblogs.com/billyrun/articles/5881551.html

 

线程和文件读写部分可以使用C++库函数

libcurl已经在cocos引擎中包含了

准备工作是了解FILE和CURL的相关接口

FILE相关:http://www.cnblogs.com/whiteyun/archive/2009/08/08/1541822.html

LIBCURL:https://curl.haxx.se/libcurl/c/libcurl-tutorial.html

 

实现过程中遇到了几个常见问题

首先是CURL的一些参数和顺序不能搞错(大体上可以分四步)

第一步,初始化

void* _curl = curl_easy_init();

第二步,设置各种参数

curl_easy_setopt(...);

第三步,执行请求然后释放

CURLcode res = curl_easy_perform(_curl);
curl_easy_cleanup(_curl);

第四步,处理请求结果

if(res == 0)...//0表示成功

 

另外还有容易出错的就是多线程的部分

在工作线程中处理界面显示更新

比较安全的做法是调用performFunctionInCocosThread

因为渲染部分都是在cocos主线程中去做的

这里传递的function常常是一个lambda表达式

注意一些局部变量不能传递引用

比如一个局部变量int t = 100

而lambda写成[&]

这样编译不会报错,但在函数中可能取不到t(局部变量释放)

在安卓中出现了这样的问题,windows上貌似没事儿

可能与performFunctionInCocosThread的机制也有关

 

代码如下

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__

#include "cocos2d.h"

class HelloWorld : public cocos2d::Layer
{
public:
    // there's no 'id' in cpp, so we recommend returning the class instance pointer
    static cocos2d::Scene* createScene();

    // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
    virtual bool init();
    
    // a selector callback
    void clickCallback(cocos2d::Ref* pSender);
    // implement the "static create()" method manually
    CREATE_FUNC(HelloWorld);

    HelloWorld();
    virtual ~HelloWorld();

private:
    std::thread    *m_pWorkThread;
    void download();
};

#endif // __HELLOWORLD_SCENE_H__
View Code
#include "HelloWorldScene.h"
#include "../../../curl/include/win32/curl/curl.h"
#include "../../../curl/include/win32/curl/easy.h"
USING_NS_CC;

#define IMG_NAME "curl_test3.jpg"
#define    TAG_LABEL 99

HelloWorld::HelloWorld()
    : m_pWorkThread(nullptr)
{}
HelloWorld::~HelloWorld()
{
    CC_SAFE_DELETE(m_pWorkThread);
}

static size_t downLoadMethod(void *ptr, size_t size, size_t nmemb, void *userdata)
{
    FILE *fp = (FILE*)userdata;
    size_t written = fwrite(ptr, size, nmemb, fp);
    return written;
}
int progressMethod(void *ptr, double totalToDownload, double nowDownloaded, double totalToUpLoad, double nowUpLoaded)
{
    static int percent = 0;
    int tmp = (int)(nowDownloaded / totalToDownload * 100);

    if (percent != tmp)
    {
        percent = tmp;

        HelloWorld* layer = (HelloWorld*)ptr;
        cocos2d::Label* _label = (cocos2d::Label*)layer->getChildByTag(TAG_LABEL);

        Director::getInstance()->getScheduler()->performFunctionInCocosThread([=]{
            std::string text = "percent is ";// +percent;
            std::stringstream ss;
            ss << percent;
            text = text + ss.str();
            _label->setString(text.c_str());
            CCLOG("downloading... %d%%", percent);
        });
    }
    return 0;
}
Scene* HelloWorld::createScene()
{
    // 'scene' is an autorelease object
    auto scene = Scene::create();
    
    // 'layer' is an autorelease object
    auto layer = HelloWorld::create();

    // add layer as a child to scene
    scene->addChild(layer);

    // return the scene
    return scene;
}
// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Layer::init() )
    {
        return false;
    }
    
    Size visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();

    // 2. add a "click" icon to download
    auto clickItem = MenuItemImage::create(
                                           "CloseNormal.png",
                                           "CloseSelected.png",
                                           CC_CALLBACK_1(HelloWorld::clickCallback, this));
    clickItem->setPosition(Vec2(origin.x + visibleSize.width - clickItem->getContentSize().width * 2 ,
        origin.y + clickItem->getContentSize().height * 2));
    clickItem->setScale(2);

    // create menu, it's an autorelease object
    auto menu = Menu::create(clickItem, NULL);
    menu->setPosition(Vec2::ZERO);
    this->addChild(menu, 1);

    /////////////////////////////
    // 3. add your codes below...
    auto label = Label::createWithTTF("C-url TEST", "fonts/Marker Felt.ttf", 24);
    
    // position the label on the center of the screen
    label->setPosition(Vec2(origin.x + visibleSize.width/2,
                            origin.y + visibleSize.height - label->getContentSize().height));

    // add the label as a child to this layer
    this->addChild(label, 1);
    label->setTag(TAG_LABEL);
    label->runAction(CCRepeatForever::create(CCSequence::createWithTwoActions(MoveBy::create(0.5, Vec3(100, 0, 0)), MoveBy::create(0.5, Vec3(-100, 0, 0)))));
    
    return true;
}
void HelloWorld::clickCallback(Ref* pSender)
{
    CCLOG("HelloWorld::clickCallback begin ");
    const std::string path = FileUtils::getInstance()->getWritablePath() + IMG_NAME;
    if (FileUtils::getInstance()->isFileExist(path))
    {
        CCLOG("HelloWorld::clickCallback %s", path.c_str());
        cocos2d::Label* _label = (cocos2d::Label*)getChildByTag(TAG_LABEL);
        _label->setString("Already Exist!!");

        Sprite* img = Sprite::create(path.c_str());
        Size visibleSize = Director::getInstance()->getVisibleSize();
        img->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2));
        this->addChild(img);
    }
    else
    {
        // make a new thread to do download
        m_pWorkThread = new std::thread(&HelloWorld::download, this);
        m_pWorkThread->detach();
    }
}
void HelloWorld::download()
{
    // Create a file to save package.
    std::string path = FileUtils::getInstance()->getWritablePath();
    const std::string outFileName = path + IMG_NAME;
    FILE *fp = fopen(outFileName.c_str(), "wb");
    if (!fp)
    {
        CCLOG("can not create file");
        return;
    }


    // Download pacakge
    // 注意curl库函数调用顺序!和参数设置
    void* _curl = curl_easy_init();
    std::string url = "http://img.25pp.com/uploadfile/soft/images/2013/1020/20131020020135604.jpg";
    //std::string url = "http://sw.bos.baidu.com/sw-search-sp/software/f11c9bcd24cfd/BaiduYunGuanjia_5.4.10.1.exe";

    curl_easy_setopt(_curl, CURLOPT_URL, url.c_str());
    // 保存写入相关参数
    curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, downLoadMethod);        //回调函数
    curl_easy_setopt(_curl, CURLOPT_WRITEDATA, fp);                        //回调参数
    // 下载进度进度相关参数
    curl_easy_setopt(_curl, CURLOPT_NOPROGRESS, false);                    //是否"不显示进度"
    curl_easy_setopt(_curl, CURLOPT_PROGRESSFUNCTION, progressMethod);    //回调函数
    curl_easy_setopt(_curl, CURLOPT_PROGRESSDATA, this);                //回调参数
    
    curl_easy_setopt(_curl, CURLOPT_NOSIGNAL, 1L);
    curl_easy_setopt(_curl, CURLOPT_FOLLOWLOCATION, 1);

    // 执行请求,后释放
    CURLcode res = curl_easy_perform(_curl);
    curl_easy_cleanup(_curl);

    if (res != 0)
    {        
        CCLOG("error when download package %d", res);
    }
    else
    {
        Director::getInstance()->getScheduler()->performFunctionInCocosThread([outFileName, this]{
            // outFileName 必须传拷贝值不能传引用
            // 函数结束时thread关闭 释放资源,源引用不复存在!
            Sprite* img = Sprite::create(outFileName.c_str());

            Size visibleSize = Director::getInstance()->getVisibleSize();
            img->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2));
            this->addChild(img);
        });
        CCLOG("succeed downloading package %s", outFileName.c_str());
    }

    // 最后关闭文件流
    fclose(fp);
}