[基于 Node.js 的自动化测试-Macaca] – Android 输入中文的实现

本帖已被设为精华帖!,

上一篇 – Macaca 获取 Android 应用的性能

[基于 Node.js 的自动化测试-Macaca] - Android 输入中文的实现

在测试 Android 设备时,经常会遇到输入中文的场景,切换键盘等操作繁琐易出问题 issue。

说说编码

说到中文,最常见的字符集就是 GB2312,为了兼容一些繁体字就需要扩展版的 GBK,Big5(大五)。为了容纳全世界所有语言文字的编码,Unicode 协会开发了 Unicode 项目,总所周知 Unicode 是兼容 ASCII 的,而且市面上的操作系统(桌面设备、手持便携设备)都是支持 Unicode 字符集的,这里当然包括 Android。

Android 键盘允许输入的字符在 ASCII 集范围内,所以只要找到可以转换的字符和转换方式就能够,将字符最终表达为 ASCII 传给操作系统就可以实现中文输入,前提是不要产生乱码。

public static void main(String[] agrs) {
System.out.println("-------- result --------");
SortedMap availableCharsets = Charset.availableCharsets();
System.out.println(availableCharsets);
Charset defaultCharset = Charset.defaultCharset();
System.out.println(defaultCharset);

Charset UTF8 = Charset.forName("UTF-8");
Charset ASCII = Charset.forName("US-ASCII");

if (Charset.isSupported("UTF-7")) {
System.out.println("UTF-7 is supported.");
Charset UTF7 = Charset.forName("UTF-7");
String text = "中";
byte[] encoded = text.getBytes(UTF8);
System.out.println(encoded.length);

System.out.println(encoded[0]);
System.out.println(encoded[1]);
System.out.println(encoded[2]);

String str1 = new String(encoded, ASCII);
System.out.println(str1);
} else {
System.out.println("UTF-7 is not supported.");
}
System.out.println("\n------------------------");
}

有兴趣可以自己试一下,查看下当前环境支持的编码,默认编码等,下载此仓库 java-charset-sample。

  • Unicode 汉字对应表

UTF 编码

UTF-8 和 UTF-16 等 UTF 标准定义了 Unicode 数值和字符的映射关系

ASCII 码一共规定了128个字符的编码,如大写的字母A是65(二进制01000001),只占用了一个字节的后面7位,最前面的1位统一规定为0。所以我们寻找到 7 位的字符表达就可以保证无乱码的方式与 ASCII 进行转换。

UTF-7

与其他 UTF 系列编码不同,满足我们的需要,UTF-7 转成 ASCII 刚好不会产生乱码,虽然 UTF-7 并不被 Unicode 标准正式认可。

UIAutomator 输入端:

Charset UTF7 = Charset.forName("UTF-7");
Charset ASCII = Charset.forName("US-ASCII");

byte[] encoded = text.getBytes(UTF7);
String str = new String(encoded, ASCII);

boolean result = element.setText(str);

此时我们的字符中文已经被转成 &Ti1lhw- 这样的形式,通过实验,UTF-7 或 UTF-7 变种都以 &+ 作为开始,- 作为结束,天然的隔断,使得我们可以轻松的断字,处理起来方便很多,当一个字符完整技术后,触发字符提交即实现了中文输入。

为什么不用 UTF8?

UTF-8 没有字节序和 BOM 问题,而且 UTF-8 在互联网世界里使用也是最广泛的,但是 UTF-8 是一种变长的编码方式。它可以使用1 ~ 4个字节表示一个符号,根据不同的符号而变化字节长度。

Unicode 符号范围    | UTF-8 编码方式
(十六进制) |(二进制)
--------------------+------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

如我们测试“中文”的“中”字,Unicode 是 4E2D,属于三个字节表示范围,即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然后,从“中”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。

当然,想利用 UTF 转换,也还是有办法的,比如我们可以利用 UTF-32 定长的特性,顺便说下,UTF-16 曾经是可以当定长编码使用,但是后期膨胀较快,Unicode 收录的字符很快就超过了 65536,所以如果还想用定长编码就只能采取 UTF-32,虽然浪费了空间和输入时间(我们的场景里可以忽略这部分消耗),但是定长处理起来就没有了断字的困难。

  • UTF-7 rfc2152 标准

另一种实现

我们可以将字逐一编码来实现,但是仍然会遇到上面出现的应该怎么隔断的问题,所以我们可以人工隔断。如下实现,将字符转成有 & 间隔的 16 进制 Unicode 字符串,避免了乱码。在 Android 输入端丢弃掉 & 结束前的按键响应,直到 & 将整个字符提交,实现中文的输入。

与 UTF-7 实现相比,键盘输入动作长度相等,耗时基本等价。

public static String string2Unicode(String string) {
StringBuffer unicode = new StringBuffer();

for (int i = 0; i < string.length(); i++) {
char c = string.charAt(i);
unicode.append(Integer.toHexString(c) + "&");
}
return unicode.toString();
}

boolean result = element.setText(string2Unicode(text));

StringBuffer string = new StringBuffer();
String str = "";

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
int c = getUnicodeChar(keyCode, event);

if (c == 0) {
return super.onKeyDown(keyCode, event);
}

if (c == '&') {
int data = Integer.parseInt(string.toString(), 16);
str = "" + (char) data;
InputConnection ic = getCurrentInputConnection();
ic.commitText(str, 1);
return true;
}

string.append((char) c);
return true;
}

Android输入法服务

Macaca 已经将如上的一种实现封装成了模块,集成到 Android 驱动中,我们直接启动输入法服务就可以了,用户无感知。

$ adb shell ime enable android.unicode.ime/.Utf7ImeService
$ adb shell ime set android.unicode.ime/.Utf7ImeService
  • android-unicode 源码
  • service 实现参考了 sumio 的 uiautomator-unicode-input-helper。

欢迎讨论,互相学习。

微博: http://weibo.com/xudafeng
Github: https://github.com/xudafeng

下一篇 – 原来程序员都是这么聊天的

* 注:本文来自网络投稿,不代表本站立场,如若侵犯版权,请及时知会删除