android | 限制emoji末尾乱码的字节长度限制过滤器实现

本文介绍了在处理UTF-8编码限制下,如何实现输入框支持中文、emoji且无乱码,通过InputFilter实现字节长度限制,特别关注了如何处理emoji的高位代理和低位代理,以及截断策略来确保输入的正确性。

当输入框的长度限制是以UTF-8字节为单位,但又需要支持输入中文、emoji,并且还不能出现emoji乱码?

实现以上需求,你需要知道的事:

  1. 中文字符转UTF-8为3个字节,emoji为4个字节。

  2. emoji由高位代理和低位代理组成,少其中一个都会导致乱码。

  3. InputFilter中的source、start、end、dest、dstart、dend都代表了什么?

理解以上要点之后,就着手代码的编写,方法的核心是:

  1. 先将所有的字符(英文、中文、emoji、其他字符)都转成UTF-8,判断新输入的字符是否满足长度需求。
  2. 若不满足,则需要按照最大允许的长度,截断输入的字符。
  3. 判断截断后的字符是否存在emoji末位乱码情况,有则回退,不允许输入半个emoji
/**
 * 限制emoji末尾乱码的字节长度限制过滤器
 *
 * @param source 输入的文字
 * @param start 输入-0,删除-0
 * @param end 输入-文字的长度,删除-0
 * @param dest 原先显示的内容
 * @param dstart 输入-原光标位置,删除-光标删除结束位置
 * @param dend  输入-原光标位置,删除-光标删除开始位置
 * @return
*/
class LimitCharLengthFilter(private var max: Int) : InputFilter {
    override fun filter(
        source: CharSequence,
        start: Int,
        end: Int,
        dest: Spanned,
        dstart: Int,
        dend: Int
    ): CharSequence {
        GlobalTouchUtil.onGlobalTouch()

        val bytes = dest.toString().toByteArray(StandardCharsets.UTF_8).size
        val sourceBytes = source.toString().toByteArray(StandardCharsets.UTF_8).size

        var keep = 0
        if (bytes + sourceBytes <= max) {
            //输入source之后整体长度不会超过max限制
            keep = source.length
        } else {
            for (i in source.indices) {
                //转成UTF-8
                val currentSourceBytes = source.subSequence(0, i + 1)
                    .toString().toByteArray(StandardCharsets.UTF_8).size

                if (bytes + currentSourceBytes > max) {
                    //超长则截断,记录当前标记,为了不显示后面的字符
                    keep = i
                    //若截断后是半个emoji结尾
                    if (Character.isHighSurrogate(source[ keep ])  ||   Character.isLowSurrogate(source[ keep ])) {
                        //回退这半个emoji,不展示这个emoji,防止乱码
                        --keep
                        if (keep == start) {
                            return ""
                        }
                    }
                    break
                }
            }
        }

        return if (keep <= 0) "" else source.subSequence(start, keep + start)
    }
}

方法调用举例如下:

const val USER_NAME_MAX_LENGTH = 63

//ChineseAndEnglishInputFilter是自定义的限制输入字符范围的filter
//LimitCharLengthFilter(USER_NAME_MAX_LENGTH )是本文介绍的限制emoji末尾乱码的字节长度限制过滤器
etInput.getEditText()?.filters =  listOf(ChineseAndEnglishInputFilter,LimitCharLengthFilter(USER_NAME_MAX_LENGTH ))
#!/usr/bin/env python3 import os import sys import tty import termios import requests import json # 配置参数(建议通过环境变量设置 API_KEY) API_KEY = os.environ.get("DEEPSEEK_API_KEY", "sk-f4e5e260d48f4d22b71dd991de75d31b") MODEL = "deepseek-chat" API_URL = "https://api.deepseek.com/v1/chat/completions" HISTORY_FILE = os.path.expanduser("~/.deepseek_history.json") END_SEQUENCE = ";;" # 自定义结束序列 class TerminalInput: """处理终端多行输入和特殊快捷键的类""" def __init__(self): self.fd = sys.stdin.fileno() self.old_settings = termios.tcgetattr(self.fd) def __enter__(self): """进入原始输入模式""" tty.setraw(self.fd) return self def __exit__(self, exc_type, exc_value, traceback): """恢复终端原始设置""" termios.tcsetattr(self.fd, termios.TCSADRAIN, self.old_settings) def get_multiline_input(self, prompt): """获取多行用户输入,支持自定义结束序列""" sys.stdout.write(prompt) sys.stdout.flush() input_buffer = [] last_char = None line_count = 0 while True: ch = sys.stdin.read(1) # 检测结束序列 if ch == END_SEQUENCE[0] and last_char == END_SEQUENCE[1]: # 删除结束序列的第一个字符 if input_buffer and input_buffer[-1] == END_SEQUENCE[1]: input_buffer.pop() sys.stdout.write("\b \b") # 清除上一个字符 return ''.join(input_buffer).rstrip(END_SEQUENCE) # 处理回车键 - 添加换行符 if ch == '\r' or ch == '\n': input_buffer.append('\n') sys.stdout.write('\n') line_count += 1 # 显示继续输入提示 if line_count > 0: sys.stdout.write("... ") # 多行输入提示符 sys.stdout.flush() last_char = ch continue # 处理退格键 if ch == '\x7f': # Backspace if input_buffer: # 特殊处理:删除换行符 if input_buffer[-1] == '\n': # 删除换行符并移动到上一行 input_buffer.pop() sys.stdout.write('\033[1A') # 移动到上一行 sys.stdout.write('\033[K') # 清除行 line_count -= 1 else: # 删除普通字符 input_buffer.pop() sys.stdout.write('\b \b') # 清除上一个字符 sys.stdout.flush() last_char = ch continue # 处理普通字符 input_buffer.append(ch) sys.stdout.write(ch) sys.stdout.flush() last_char = ch def main(): # 检查API密钥 if API_KEY == "your_api_key_here": print("请先设置API密钥:") print("方法1: 在脚本中修改API_KEY") print("方法2: 执行命令: export DEEPSEEK_API_KEY='your_key'") return # 加载对话历史 messages = load_history() print(f"\n DeepSeek 终端助手已启动 (输入 '{END_SEQUENCE}' 结束多行输入并发送,输入 'exit' 退出)") with TerminalInput() as term_input: while True: try: # 获取用户输入 user_input = term_input.get_multiline_input("\n 你: ") # 退出处理 if user_input.strip().lower() == "exit": save_history(messages) print("\n对话历史已保存至", HISTORY_FILE) break # 空输入处理 if not user_input.strip(): continue # 添加用户消息 messages.append({"role": "user", "content": user_input}) # 打印AI响应提示 print("\n AI: ", end="", flush=True) # 获取并显示AI响应 full_response = "" for chunk in stream_response(messages): if chunk: print(chunk, end="", flush=True) full_response += chunk print() # 确保换行 # 保存上下文 messages.append({"role": "assistant", "content": full_response}) if len(messages) > 6: # 保留最近3轮对话 messages = messages[-6:] except KeyboardInterrupt: print("\n已中断") save_history(messages) break except Exception as e: print(f"\n错误: {str(e)}") def stream_response(messages): """流式请求核心逻辑""" headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } data = { "model": MODEL, "messages": messages, "stream": True, "temperature": 0.7 } try: with requests.post(API_URL, headers=headers, json=data, stream=True, timeout=30) as res: res.raise_for_status() for line in res.iter_lines(): if line: decoded = line.decode('utf-8') if decoded.startswith("data:"): try: chunk = json.loads(decoded[5:]) if content := chunk.get("choices", [{}])[0].get("delta", {}).get("content"): yield content except json.JSONDecodeError: continue except requests.exceptions.RequestException as e: raise ConnectionError(f"API请求失败: {str(e)}") def load_history(): """加载历史对话""" if os.path.exists(HISTORY_FILE): try: with open(HISTORY_FILE, 'r') as f: return json.load(f) except: return [] return [] def save_history(messages): """保存对话历史""" with open(HISTORY_FILE, 'w') as f: json.dump(messages, f, ensure_ascii=False, indent=2) if __name__ == "__main__": main() 这个换行很乱导致返回格式看不懂 帮忙解决一下
最新发布
08-29
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值