cocos2d-x 多线程编程
今天我们将深入学习 cocos2d-x 多线程编程,对于这方面不太了解或感兴趣的同学可以参考本文。
基本线程 API 介绍
每个进程都有一个唯一的进程 ID,同样,每个线程也有一个线程 ID。不过,进程 ID 在整个系统中是唯一的,而线程 ID 仅在其所属的进程环境中有效。线程 ID 使用 pthread_t 数据类型来表示,在实现时,pthread_t 数据类型可以用一个结构来代表,因此可移植的操作系统不能将其作为整数处理。所以,必须使用特定函数来比较两个线程 ID。
1. pthread_equal
- 功能:比较两个线程 ID。
- 头文件:
#include <pthread.h> - 函数原形:
int pthread_equal(pthread_t tid1, pthread_t tid2); - 参数:
tid1为线程 1 的 ID,tid2为线程 2 的 ID。 - 返回值:若两个线程 ID 相等,返回非 0 值;否则返回 0。
2. pthread_self
- 功能:获取自身线程的 ID。
- 头文件:
#include <pthread.h> - 函数原形:
pthread_t pthread_self(void); - 参数:无。
- 返回值:调用该函数的线程的 ID。
3. pthread_create
- 功能:创建线程。
- 头文件:
#include <pthread.h> - 函数原形:
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg); - 参数:
tidp:指向线程 ID 的指针,用于存储新创建线程的 ID。attr:线程属性,可设置线程的各种特性,若为NULL则使用默认属性。start_rtn:线程启动时执行的函数指针,该函数返回void*类型,参数为void*类型。arg:传递给线程启动函数的参数。- 返回值:若成功创建线程,返回 0;否则返回错误编号。
线程创建示例:下载器实例
1. DownLoader.h 文件
#ifndef __MythLeague__DownLoader__
#define __MythLeague__DownLoader__
#include "Global/Constants.h"
// 下载状态枚举
typedef enum {
DownloadStatus_Init,
DownloadStatus_Loading,
DownloadStatus_Success,
DownloadStatus_FailedWhen_Init,
DownloadStatus_FailedWhen_GetHandle,
DownloadStatus_FailedWhen_GetInfo
} Enum_DownloadStatus;
// 下载任务结构体
typedef struct {
std::string resUrl;
std::string fileName;
} Struct_DownLoadTask;
class DownLoader : public cocos2d::CCObject {
public:
DownLoader();
virtual ~DownLoader();
CREATE_FUNC(DownLoader);
bool init();
// 下载资源
void downloadRes(Struct_DownLoadTask p_downloadTask);
// 开线程
static void* startNewThread(void* args);
bool startDownload();
// 处理下载数据
static size_t process_data(void *buffer, size_t size, size_t nmemb, std::string& user_p);
// 监测下载完毕
void checkDownloadStatus(float p_delay);
Struct_DownLoadTask m_downloadTask;
Enum_DownloadStatus m_iDownloadStatus;
};
#endif /* defined(__MythLeague__DownLoader__) */
2. DownLoader.cpp 文件
#include "DownLoader.h"
#include "DownloadMgr.h"
#include "Global/Tools.h"
#define FILE_EXIST (200)
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
#include "CURL/curl.h"
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
#include "platform/third_party/win32/curl/curl.h"
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#include "platform/third_party/android/modules/libcurl/include/curl/curl.h"
#endif
using namespace std;
using namespace cocos2d;
#define DownLoader_Check_Interval (0.5f)
DownLoader::DownLoader() {
m_iDownloadStatus = DownloadStatus_Init;
}
DownLoader::~DownLoader() {
}
bool DownLoader::init() {
return true;
}
// 下载资源
void DownLoader::downloadRes(Struct_DownLoadTask p_downloadTask) {
m_downloadTask = p_downloadTask;
pthread_t l_pid;
pthread_attr_t l_attr;
pthread_attr_init(&l_attr);
int l_iPriority = sched_get_priority_max(SCHED_OTHER);
pthread_attr_setschedpolicy(&l_attr, l_iPriority);
pthread_create(&l_pid, &l_attr, DownLoader::startNewThread, this);
CCDirector::sharedDirector()->getScheduler()->scheduleSelector(schedule_selector(DownLoader::checkDownloadStatus), this, DownLoader_Check_Interval, false);
}
// 开线程(静态方法)
void* DownLoader::startNewThread(void* p_args) {
// 线程独立
pthread_detach(pthread_self());
// 在新线程里下载
DownLoader* l_thisDownloader = (DownLoader*)p_args;
l_thisDownloader->startDownload();
return NULL;
}
bool DownLoader::startDownload() {
m_iDownloadStatus = DownloadStatus_Loading;
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
// 初始化 libcurl
CURLcode return_code;
return_code = curl_global_init(CURL_GLOBAL_ALL);
if (CURLE_OK != return_code) {
CCLog("init libcurl failed.");
curl_global_cleanup();
m_iDownloadStatus = DownloadStatus_FailedWhen_Init;
return false;
}
// 获取 easy handle
CURL *easy_handle = curl_easy_init();
if (NULL == easy_handle) {
CCLog("get a easy handle failed.");
curl_easy_cleanup(easy_handle);
curl_global_cleanup();
m_iDownloadStatus = DownloadStatus_FailedWhen_GetHandle;
return false;
}
// 设置 easy handle 属性
return_code = curl_easy_setopt(easy_handle, CURLOPT_URL, m_downloadTask.resUrl.c_str());
// 设置回调函数
curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, process_data);
// 回调函数的额外参数
std::string connectx;
return_code = curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, &connectx);
// 执行数据请求
return_code = curl_easy_perform(easy_handle);
// 判断获取响应的 http 地址是否存在,若存在则返回 200,400 以上则为不存在,一般不存在为 404 错误
int retcode = 0;
return_code = curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &retcode);
if (CURLE_OK == return_code && FILE_EXIST == retcode) {
double length = 0;
return_code = curl_easy_getinfo(easy_handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
string l_fullPath = Tools::getWritableFilePath(m_downloadTask.fileName.c_str());
FILE *fp = fopen(l_fullPath.c_str(), "wb+");
if (fp != NULL) {
fwrite(connectx.c_str(), 1, length, fp);
// 返回实际写入文本的长度,若不等于 length 则写文件发生错误
fclose(fp);
} else {
CCLog("error here file: %s line: %d", __FILE__, __LINE__);
}
} else {
CCLog("......url 文件不存在!");
curl_easy_cleanup(easy_handle);
curl_global_cleanup();
m_iDownloadStatus = DownloadStatus_FailedWhen_GetInfo;
return false;
}
// 释放资源
curl_easy_cleanup(easy_handle);
curl_global_cleanup();
#endif
m_iDownloadStatus = DownloadStatus_Success;
return true;
}
// 处理下载数据
size_t DownLoader::process_data(void *buffer, size_t size, size_t nmemb, std::string& user_p) {
user_p.append((char*)buffer, size * nmemb);
return size * nmemb;
}
// 监测下载完毕
void DownLoader::checkDownloadStatus(float p_delay) {
if (m_iDownloadStatus >= DownloadStatus_Success) {
CCDirector::sharedDirector()->getScheduler()->unscheduleSelector(schedule_selector(DownLoader::checkDownloadStatus), this);
// 成功回调
if (m_iDownloadStatus == DownloadStatus_Success) {
DownloadMgr::sharedMgr()->callWhenDownloaderFinish_success(this);
}
// 失败回调
else {
DownloadMgr::sharedMgr()->callWhenDownloaderFinish_failed(this);
}
}
}
通过上述代码,我们实现了一个下载器的基本功能。不过,该下载器在处理异常方面还有待完善。
后续工作
下载管理器
还需要编写一个下载管理器,其基本功能应包括:
- 管理一个或多个下载器。
- 显示下载状态及进度,并屏蔽相关事件。
- 提供与外部交互的接口。
显示层
需要创建一个显示层,用于显示当前下载内容和总的下载进度。