【Cocos2d-x】遮蔽Emoji並解決由於Emoji導致的崩潰問題

高廟小僧發表於2021-05-25

IOS的Emoji表情因為編碼問題,在Android手機上無法正常顯示,如果當前的cc.Label節點使用的是系統字,在系統字型檔中找不到對應編碼的字元,會導致崩潰。
為了解決這個問題,又要兼顧新老版本,要在3個地方做調整:

  1. Android虛擬鍵盤輸入時,遮蔽Emoji;
  2. IOS虛擬鍵盤輸入時,遮蔽Emoji;
  3. Lua端遮蔽Emoji;

一、Android端遮蔽Emoji

1、在Cocos2dxHelper.java類中新增過濾Emoji方法

private static boolean containsEmoji(String source) {
    if (null == source || 0 == source.length()) {
        return false;
    }
    int len = source.length();
    for (int i = 0; i < len; i++) {
        char c = source.charAt(i);
        if (isEmojiCharacter(c)) {
            return true;
        }
    }
    return false;
}

private static boolean isEmojiCharacter(char c) {
    return !((c == 0x0) || (c == 0x9) || (c == 0xA)
            || (c == 0xD)
            || ((c >= 0x20) && (c <= 0xD7FF))
            || ((c >= 0xE000) && (c <= 0xFFFD))
            || ((c >= 0x10000) && (c <= 0x10FFFF)));
}

private static String filterEmoji(String source) {
    if (!containsEmoji(source)) {
        return source;// don't contain, just return
    }
    StringBuilder buf = null;
    int len = source.length();
    for (int i = 0; i < len; i++) {
        char c = source.charAt(i);
        if (!isEmojiCharacter(c)) {
            if (buf == null) {
                buf = new StringBuilder(source.length());
            }
            buf.append(c);
        }
    }
    if (buf == null) {
        return null;
    } else {
        if (buf.length() == len) {
            buf = null;
            return source;
        } else {
            return buf.toString();
        }
    }
}

2、修改Cocos2dxHelper.java類的setEditTextDialogResult方法

把程式碼

final byte[] bytesUTF8 = pResult.getBytes("UTF8");

修改為

String text = filterEmoji(pResult);
if (null == text || 0 == text.length()) {
        text = "";
}
final byte[] bytesUTF8 = text.getBytes("UTF8");

二、IOS遮蔽Emoji表情

IOS的Emoji表情的輸入有兩種方式:一種是Emoji表情頁,另一種是輸入法打字聯想出來的。

1、遮蔽Emoji表情頁中的表情輸入

- (BOOL)textField:(UITextField *) textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    if ([textField isFirstResponder]) {
        if([[[textField textInputMode] primaryLanguage] isEqualToString:@"emoji"] || ![[textField textInputMode] primaryLanguage]) {
            return NO;
        }
    }
    ...
    ...
}

2、過濾輸入法聯想出來的Emoji:

1)檢測並過濾Emoji表情

// 過濾所有表情。containEmoji為NO表示不含有表情,YES表示含有表情
- (BOOL)stringContainsEmoji:(UITextField *)textField
{
    __block BOOL containEmoji = NO;
    self.stringAfterFilterEmoji = @"";
    NSString* string = textField.text;
    [string enumerateSubstringsInRange:NSMakeRange(0, [string length]) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
        const unichar hs = [substring characterAtIndex:0];
        BOOL is_emoji = NO;
        // surrogate pair
        if (0xd800 <= hs && hs <= 0xdbff) {
            if (substring.length > 1) {
                const unichar ls = [substring characterAtIndex:1];
                const int uc = ((hs - 0xd800) * 0x400) + (ls - 0xdc00) + 0x10000;
                if (0x1d000 <= uc && uc <= 0x1f77f) {
                    is_emoji = YES;
                }
            }
        } else if (substring.length > 1) {
            const unichar ls = [substring characterAtIndex:1];
            if (ls == 0x20e3) {
                is_emoji = YES;
            }
        } else {
            // non surrogate
            if (0x2100 <= hs && hs <= 0x27ff) {
                is_emoji = YES;
            } else if (0x2B05 <= hs && hs <= 0x2b07) {
                is_emoji = YES;
            } else if (0x2934 <= hs && hs <= 0x2935) {
                is_emoji = YES;
            } else if (0x3297 <= hs && hs <= 0x3299) {
                is_emoji = YES;
            } else if (hs == 0xa9 || hs == 0xae || hs == 0x303d || hs == 0x3030 || hs == 0x2b55 || hs == 0x2b1c || hs == 0x2b1b || hs == 0x2b50) {
                is_emoji = YES;
            }
        }
        if (!is_emoji) {
            self.stringAfterFilterEmoji = [self.stringAfterFilterEmoji stringByAppendingString:substring];
        }
        containEmoji = is_emoji ? is_emoji : containEmoji;
    }];
    return containEmoji;
}

2)應用過濾過的輸入

- (BOOL)textFieldShouldEndEditing:(UITextField *)sender {
    if ([self stringContainsEmoji:sender]) {
        sender.text = self.stringAfterFilterEmoji;
    }
    ...
    ...
}

三、Lua遮蔽Emoji

由於要兼顧老版本,不僅僅要在輸入端對Emoji進行過濾,如果老版本使用Emoji,同樣要新增過濾;但Lua的編碼跟Java和OC有很大不同,Java可以通過char、OC通過unichar可以獲取Unicode字元,但Lua不行,Lua是通過string的位元組連結的形式來儲存Unicode的字元;
通過總結發現,一個Emoji表情的第1個位元組(string.byte(s, 1))等於240,第2個位元組(string.byte(s,2))等於159的不能被Android識別,從而崩潰,因此可以過濾此種Emoji,從而解決崩潰問題。

function M:getByteCount( byte )
    local ret = 0
    if byte > 0 and byte <= 127 then
        ret = 1
    elseif byte >= 192 and byte < 223 then
        ret = 2
    elseif byte >= 224 and byte < 239 then
        ret = 3
    elseif byte >= 240 and byte <= 247 then
        ret = 4
    end
    return ret
end

function M:filterEmoji( source )
    local len = string.len(source)
    if len < 2 then return source end
    local ret_str = ""
    local i = 1
    while i <= len do
        local is_emoji = false
        local byte_1 = string.byte(source, i)
        if byte_1 == 240 then
            local byte_2 = string.byte(source, i + 1)
            if byte_2 == 159 then
                is_emoji = true
            end
        end
        local byte_count = self:getByteCount(byte_1)
        byte_count = byte_count < 1 and 1 or byte_count
        if not is_emoji then
            ret_str = ret_str..string.sub(source, i, i + byte_count - 1)
        end
        i = i + byte_count
    end
    return ret_str
end

相關文章