解决格式化字符输入的困扰--Android
来源:互联网 发布:微服务架构 数据库 编辑:程序博客网 时间:2024/05/15 23:52
项目需求:
我们有一个输入框,为了提高用户体验,会自动格式化输入的字符串,其实这些东西在各种电商APP上很常见,举个例子,
例如输入手机号:13323450000
输入过程中,APP会自动做判断,根据输入的长度做判断进而格式化例如变为:133 2345 0000
这期间的实现其实很简单,就是每当我们输入字符时,都会触发一个Event事件,如果当我们输入3个字符后,在输入第四个字符时,程序就会自动在其前面增加一个空格。
如果你们试验一下的话就是会发现,如果我们用sendkeys来输入的话,基本很难成功,输入的字符不是多几位就是少几位,反正就是各种错误,后来我想到是否可以分开输入,3,4,4节奏的输入,但是还是会失败,为什么呢?
下面贴一段代码,大家一看便知:
package io.appium.android.bootstrap.handler;import com.android.uiautomator.core.UiDevice;import com.android.uiautomator.core.UiObjectNotFoundException;import io.appium.android.bootstrap.*;import org.json.JSONException;import java.util.Hashtable;/** * This handler is used to set text in elements that support it. * */public class SetText extends CommandHandler { /* * @param command The {@link AndroidCommand} used for this handler. * * @return {@link AndroidCommandResult} * * @throws JSONException * * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. * bootstrap.AndroidCommand) */ @Override public AndroidCommandResult execute(final AndroidCommand command) throws JSONException { if (command.isElementCommand()) { // Only makes sense on an element try { final Hashtable<String, Object> params = command.params(); final AndroidElement el = command.getElement(); boolean replace = Boolean.parseBoolean(params.get("replace").toString()); String text = params.get("text").toString(); boolean pressEnter = false; if (text.endsWith("\\n")) { pressEnter = true; text = text.replace("\\n", ""); Logger.debug("Will press enter after setting text"); } boolean unicodeKeyboard = false; if (params.get("unicodeKeyboard") != null) { unicodeKeyboard = Boolean.parseBoolean(params.get("unicodeKeyboard").toString()); } String currText = el.getText(); new Clear().execute(command); if (!el.getText().isEmpty()) { // clear could have failed, or we could have a hint in the field // we'll assume it is the latter Logger.debug("Text not cleared. Assuming remainder is hint text."); currText = ""; } if (!replace) { text = currText + text; } final boolean result = el.setText(text, unicodeKeyboard); if (pressEnter) { final UiDevice d = UiDevice.getInstance(); d.pressEnter(); } return getSuccessResult(result); } catch (final UiObjectNotFoundException e) { return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage()); } catch (final Exception e) { // handle NullPointerException return getErrorResult("Unknown error"); } } else { return getErrorResult("Unable to set text without an element."); } }}
这就是bootstrap中的setText类,中间有一段代码“new Clear().execute(command);” 清除命令
Appium的实现都是通过bootstrap来和uiautomator做交互的,So 刚刚那个想法有点儿障碍,但是我没有屈服,我们是开源的所以我修改了这个类,但是实际操作后我发现貌似没有什么作用,在赋值时依然会先clear,这是为什么呢,没办法,再次向下追踪,刚刚已经说过,bootstrap是跟uiautomator做交互,实际上也就是说是最终通过uiautomator来操作我们的手机,所以我找到了uiautomator的源码,发现了一个我不愿意相信的事实,大家看下面的源码,是UiObject类的内容,截图了:
这个里面也会clear,so,我彻底死心了这个办法是无解了。
不过我们不会屈服的,对吧,哈哈,换个角度,如果我能够控制输入的速度,是不是就可以了呢,可是,Appium暴露给我们的只有一个sendKeys的API,怎么办呢?
继续解读bootstrap的源码,通过UiObject类中的那段代码我们可以看到,最终调用的是一个sendText的API,继续往下挖,我们会发现这个API无法再往下点了(通过ctrl + 单击),说明是隐藏的类,从Android sdk的source包中,我找到了这个方法的源码:
public boolean sendText(String text) { if (DEBUG) { Log.d(LOG_TAG, "sendText (" + text + ")"); } KeyEvent[] events = mKeyCharacterMap.getEvents(text.toCharArray()); if (events != null) { long keyDelay = Configurator.getInstance().getKeyInjectionDelay(); for (KeyEvent event2 : events) { // We have to change the time of an event before injecting it because // all KeyEvents returned by KeyCharacterMap.getEvents() have the same // time stamp and the system rejects too old events. Hence, it is // possible for an event to become stale before it is injected if it // takes too long to inject the preceding ones. KeyEvent event = KeyEvent.changeTimeRepeat(event2, SystemClock.uptimeMillis(), 0); if (!injectEventSync(event)) { return false; } SystemClock.sleep(keyDelay); } } return true; }
这里面我们发现了什么?对,有sleep,哈哈,好像看到希望了,先简单说一下它的工作原理,我们把一个string给这个API,它先把String分割为单个的KeyEvent,然后有一个for循环来依次执行,每次执行都会判断一下是否成功,并且会sleep一下。所以,我们控制一下这个keyDelay就能实现我们的愿望了,好激动,不过这个keyDelay是传人的,而是
“long keyDelay = Configurator.getInstance().getKeyInjectionDelay();”
那么我们需要继续深挖了,Configurator类源码不少,我贴几个重要的如下:
getKeyInjectionDelay API:
/** * Gets the current delay between key presses when injecting text input. * See {@link UiObject#setText(String)} * * @return current delay in milliseconds * @since API Level 18 */ public long getKeyInjectionDelay() { return mKeyInjectionDelay; }
getKeyInjectionDelay API:
/** * Sets a delay between key presses when injecting text input. * See {@link UiObject#setText(String)} * * @param delay Delay value in milliseconds * @return self * @since API Level 18 */ public Configurator setKeyInjectionDelay(long delay) { mKeyInjectionDelay = delay; return this; }
mKeyInjectionDelay 成员变量
// Default is inject as fast as we can private long mKeyInjectionDelay = 0; // ms
getInstance()
/** * Retrieves a singleton instance of Configurator. * * @return Configurator instance * @since API Level 18 */ public static Configurator getInstance() { if (sConfigurator == null) { sConfigurator = new Configurator(); } return sConfigurator; }
行了,这样大家应该可以看出来了,getInstance()可以获取一个实例,所以,我们可以在bootstrap做一些修改,找到我最上面贴的那段代码中加一行这样的代码:
Configurator.getInstance().setKeyInjectionDelay(500);
位置是在这行代码前面:
final boolean result = el.setText(text, unicodeKeyboard);
新增代码的意思就是将mKeyInjectionDelay设置为500毫秒,即:在每次执行KeyEvent时先等待500毫秒。
结果:我发现这个做法成功的几率也很低,因为它会拖慢整个Event执行的速度,只有在机器变的很慢时才会有效,当时我用的是红米手机,刷了4.4的原生Android系统的,so,这个办法也行不通,不过确让我了解了uiautomator的运行原理及部分源码的运行逻辑,虽然时间浪费了,不过还是值得的。
但是,问题没有解决,怎么办?
后来我再次去研究Appium提供的API,发现了一个sendKeyEvent的API,这次好像就没有那么大的激情了,不过只能死马当活马医了,这个方法是直接发送key值,关于key值,key值的Map网上有很多,在这里我给大家直接发一个源码文件吧,就不贴了,而且咱们论坛里已经有人发出来了,大家也可以去参考一下。
实现方法我大体说一下,其实很简单,先把String分割为单个的char,然后通过key的Map映射关系一一转换为key值,然后使用一个for循环,一一直接调用sendKeyEvent方法把key值传进去即可,中间设置一个sleep,我设置的是500毫秒,失败的几率很小,目前我的公司项目中是这么用的,没什么大问题,我把它封装成了一个方法,在某种意义上可以代替sendKeys方法了。
最后,关于这个问题,我首先是从github上咨询的官方,但是官方一直没有什么具体的解决办法!
https://github.com/appium/appium/issues/3812#issuecomment-60433195
关于keyEvent 映射,贴一个本站的帖子:
http://www.testerhome.com/topics/1386
当然如果有AndroidSDK源码的童鞋也可以去找一下这个类:KeyEvent.java(这是最权威的!)
- 解决格式化字符输入的困扰--Android
- 解决格式化字符输入的困扰--Android
- 解决flexbuilder中的一个困扰:字符串的隐含字符
- 字符数据的非格式化输入及输出
- 利用格式化输入输出打印出输入的字符
- Android特殊字符的输入
- Android特殊字符的输入
- Android 中 EditText 的十六进制格式化输入
- 困扰解决
- android 格式化输入控制
- 解决TextView显示格式化后的字符仍然未对齐
- jsp在mysql下实现中文的输入(困扰了很久的问题,终于解决了)
- 困扰的ISA问题宣告解决
- 解决了困扰多日的Memory Leak
- 困扰几天的webservices异常.终于解决!
- 解决ov5640困扰好久的问题
- 解决Myeclipse ctrl+h带来的困扰
- jbpm一直困扰的一个问题(解决)
- windows下cocos2dx 2.2.x编译安卓工程
- 为mysql数据库建立索引
- oracle数据库体系架构详解
- ZOJ Problem Set - 3019 Puzzle
- swap过高的问题解决
- 解决格式化字符输入的困扰--Android
- web 开发
- 菜鸟学JS(五)——window.onload与$(document).ready()
- Lua 学习笔记:沙盒
- 浅谈erlang游戏服务器项目--英雄远征服务启动流程
- 如何配置eclipse/zend studio的代码自动提示功能
- Ubuntu 修改时区和时间
- C语言中的内联函数(inline)与宏定义(#define)详细解析
- ASCII,Unicode和UTF-8与java