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

达峰的夏天 · 2016年07月05日 · 最后由 思寒_seveniruby 回复于 2017年01月20日 · 2397 次阅读
本帖已被设为精华帖!

上一篇 - 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

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,虽然浪费了空间和输入时间 (我们的场景里可以忽略这部分消耗),但是定长处理起来就没有了断字的困难。

另一种实现

我们可以将字逐一编码来实现,但是仍然会遇到上面出现的应该怎么隔断的问题,所以我们可以人工隔断。如下实现,将字符转成有 & 间隔的 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

欢迎讨论,互相学习。

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

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

共收到 12 条回复 时间 点赞
达峰的夏天 [该话题已被删除] 中提及了此贴 07月05日 11:49

自己编写输入法时要考虑输入结束后弹下输入框这个动作,我记得之前碰到过输入时屏幕往上弹 输入后没有做往下弹的动作,然后界面就出问题了

#2 楼 @dongdong 输入法服务就没键盘 UI 界面了,没有这个问题

@xdf 这个输入法该怎么用呢?可以命令行用吗?比如用 adb shell 命令之类的

#4 楼 @codeskyblue 可以,看 android-unicode 源码。install apk 进去,启动就可以了

$ adb shell ime enable android.unicode.ime/.Utf7ImeService
$ adb shell ime set android.unicode.ime/.Utf7ImeService

比如想输入 中文 这两个字该怎么输入呢?

#3 楼 @xdf 具体什么原因我忘记了 之前为什么自己弄一套输入法好像就是因为 appium 输入法有这个问题 和有没有键盘没啥关系

@xdf 我其实比较想知道下是怎么实现的,看看能不能集成到 AutomatorX 这个项目中,应该输入法最近我也很头疼。

啊,我知道了,原来是这个样子。 比如 中middle 转化成 utf7 编码,就是 +Ti0-middle, 所以安装完 android-unicode.ime 那个输入法,只要 adb shell input text +Ti0-middle 可以输入成 中middle

发现个问题,输入 中文cn没问题,但是输入不了带空格的 中文 cn

达峰的夏天 [该话题已被删除] 中提及了此贴 07月08日 19:26
达峰的夏天 [该话题已被删除] 中提及了此贴 09月05日 22:49
达峰的夏天 UiAutomator 2.0 中文输入问题 中提及了此贴 01月20日 18:27
思寒_seveniruby 将本帖设为了精华贴 01月20日 20:23

加精理由: 迟来的加精. 对中文输入的解释很透彻.

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册