【原創(chuàng)博文,轉(zhuǎn)載請注明出處!】
前段時間棋牌游戲在弱網(wǎng)環(huán)境下奔潰率比較高,綜合各種測試環(huán)境和奔潰日志,比較多見的情況是:弱網(wǎng)下離開當(dāng)前的界面后,當(dāng)前界面的網(wǎng)絡(luò)請求才回調(diào)過來刷新UI。與iOS 原生開發(fā)UIViewController不同的是,Cocos2d-x 引擎中c++環(huán)境下創(chuàng)建的類的回收是在析構(gòu)函數(shù)執(zhí)行完之后的某個合理時間才會進(jìn)行。所以,離開當(dāng)前的Scene或Layer等經(jīng)常發(fā)起網(wǎng)絡(luò)請求的地方,回調(diào)回來后,本質(zhì)上Scene已經(jīng)被銷毀了,但是內(nèi)存還在,因此在回調(diào)刷新UI的時候,因為內(nèi)部控件被銷毀造成各種野指針奔潰。
解決辦法:構(gòu)造了一個網(wǎng)絡(luò)工具安全助手。
工具類的3個重量級方法:
/**
將發(fā)起請求的類名加入注冊池
node : 當(dāng)前調(diào)用的類對象(取對象的地址作為唯一標(biāo)識符)
*/
static void registerClassName(Node *node);
/**
將發(fā)起請求的類名移出注冊池
node : 當(dāng)前調(diào)用的類對象(取對象的地址作為唯一標(biāo)識符)
*/
static void unregisterClassName(Node *node);
/**
是否需要進(jìn)行網(wǎng)絡(luò)請求回調(diào)
node : 當(dāng)前調(diào)用的類對象(取對象的地址作為唯一標(biāo)識符)
*/
static bool needCallback(Node *node);
思路:
Step 1:在類的 onEnter();方法里面調(diào)用 static void registerClassName(Node *node); 注冊。
Step 2:在類的 onExit();方法里面調(diào)用static void unregisterClassName(Node *node); 解注冊。
Step 3:在網(wǎng)絡(luò)請求回調(diào)處調(diào)用static bool needCallback(Node *node); 攔截,如果那個類不存在了,回調(diào)就直接pass掉,否則就放過它。
涉及到如何選擇唯一標(biāo)識符的問題,也就是上述方法中的node。
參考cocos2d引擎,發(fā)現(xiàn)官方有使用其類的內(nèi)存地址作為唯一ID,覺得很棒。
string className = StringUtils::format("%p",node);
之前我們選擇類名作為ID,這樣在弱網(wǎng)下還會存在一個bug:如果用戶手速快,前后兩次進(jìn)入同一個layer,由于標(biāo)識符取了類名,所以這兩次肯定相同,所以不會攔截前一次的回調(diào),然后再刷新UI,(前一次的子控件實際上已經(jīng)銷毀了)然后就creash.
唯一標(biāo)識符選擇當(dāng)前類在內(nèi)存中的地址,很安全!想一下系統(tǒng)先創(chuàng)建類A,然后創(chuàng)建類B,再銷毀類A,這個時候B和A的內(nèi)存地址相同的概率有多大?
(先創(chuàng)建類A,然后創(chuàng)建類B,再銷毀類A)。
(先創(chuàng)建類A,然后創(chuàng)建類B,再銷毀類A)。
(先創(chuàng)建類A,然后創(chuàng)建類B,再銷毀類A)。
B和A的內(nèi)存地址還可以相同???

下面讓我們一起來看看這個網(wǎng)絡(luò)安全助手的實現(xiàn)細(xì)節(jié)吧:
//
// SafeNetworkHandler.cpp
// HelloCpp-mobile
//
// Created by RephontilZhou on 2018/3/20.
//
#include "SafeNetworkHandler.hpp"
static SafeNetworkHandler *singleInstance = nullptr;
void SafeNetworkHandler::init()
{
if (!singleInstance) {
singleInstance = new SafeNetworkHandler();
}
}
/**
將發(fā)起請求的類名加入注冊池
node : 當(dāng)前調(diào)用的類對象(取對象的地址作為唯一標(biāo)識符)
*/
void SafeNetworkHandler::registerClassName(Node *node)
{
init();
string className = StringUtils::format("%p",node);
singleInstance->classNameList.push_back(className);
}
/**
將發(fā)起請求的類名移出注冊池
node : 當(dāng)前調(diào)用的類對象(取對象的地址作為唯一標(biāo)識符)
*/
void SafeNetworkHandler::unregisterClassName(Node *node)
{
string className = StringUtils::format("%p",node);
if (singleInstance->classNameList.size() > 0)
{
for (vector<string>::iterator it = singleInstance->classNameList.begin(); it != singleInstance->classNameList.end();)
{
if ((*it) == className) {
it = singleInstance->classNameList.erase(it);
}
else {
it++;
}
}
}
}
/**
是否需要進(jìn)行請求回調(diào)
node : 當(dāng)前調(diào)用的類對象(取對象的地址作為唯一標(biāo)識符)
*/
bool SafeNetworkHandler::needCallback(Node *node)
{
string className = StringUtils::format("%p",node);
bool enable = false;
if (singleInstance->classNameList.size() > 0)
{
for (vector<string>::iterator it = singleInstance->classNameList.begin(); it != singleInstance->classNameList.end(); ++it) {
if ((*it) == className) {
enable = true;
}
}
}
return enable;
}
好啦,簡書上面的第一篇技術(shù)分享到此結(jié)束啦,如果此時您正好也使用cocos2d-X的引擎開發(fā)游戲,碰巧在網(wǎng)絡(luò)請求中遇到同樣棘手的問題,那么上面的解決方案肯定能讓你笑得像菊花一樣地燦爛。
