Appium输入慢的原因分析

来源:互联网 发布:北京java培训中心 编辑:程序博客网 时间:2024/06/05 17:05

使用appium输入中文,发现好慢!至少5秒以上,如果在这样的情况下做测试,这就好悲剧了。
从appium(1.6.3)代码上来看,没有什么问题,直接是通过boostrap的setText的方法。说是就下载了appium-bootstrap的代码看,从这里开发找到的代码,都是java的代码,找到 io.appium.android.bootstrap.handler.SetText
在new Clear().execute(command);时间长达5秒(打日志发现),不管文本框有没有内容,都会执行

/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * You may obtain a copy of the License at * *     http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package io.appium.android.bootstrap.handler;import com.android.uiautomator.core.UiDevice;import com.android.uiautomator.core.UiObjectNotFoundException;import com.android.uiautomator.core.UiSelector;import io.appium.android.bootstrap.*;import io.appium.android.bootstrap.exceptions.ElementNotFoundException;import io.appium.android.bootstrap.handler.Find;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 {    AndroidElement el = null;    if (command.isElementCommand()) {      el = command.getElement();      Logger.debug("Using element passed in: " + el.getId());    } else {      try {        AndroidElementsHash  elements = AndroidElementsHash.getInstance();        el = elements.getElement(new UiSelector().focused(true), "");        Logger.debug("Using currently-focused element: " + el.getId());      } catch (ElementNotFoundException e) {        Logger.debug("Error retrieving focused element: " + e);        return getErrorResult("Unable to set text without a focused element.");      }    }    try {      final Hashtable<String, Object> params = command.params();      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 (!result) {        return getErrorResult("el.setText() failed!");      }      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");    }  }}

然后,我们再看Clear的代码

/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * You may obtain a copy of the License at * *     http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package io.appium.android.bootstrap.handler;import android.graphics.Rect;import android.os.SystemClock;import android.view.InputDevice;import android.view.KeyCharacterMap;import android.view.KeyEvent;import com.android.uiautomator.core.UiObject;import com.android.uiautomator.core.UiObjectNotFoundException;import com.android.uiautomator.core.UiSelector;import io.appium.android.bootstrap.AndroidCommand;import io.appium.android.bootstrap.AndroidCommandResult;import io.appium.android.bootstrap.AndroidElement;import io.appium.android.bootstrap.CommandHandler;import io.appium.android.bootstrap.Logger;import io.appium.android.bootstrap.WDStatus;import io.appium.uiautomator.core.InteractionController;import io.appium.uiautomator.core.UiAutomatorBridge;import org.json.JSONException;import java.lang.reflect.InvocationTargetException;/** * This handler is used to clear elements in the Android UI. * * Based on the element Id, clear that element. * * UiAutomator method clearText is flaky hence overriding it with custom implementation. */public class Clear extends CommandHandler {  /*   * Trying to select entire text with correctLongClick and increasing time intervals.   * Checking if element still has text in them and and if true falling back on UiAutomator clearText   *   * @param command The {@link AndroidCommand}   *   * @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()) {      try {        final AndroidElement el = command.getElement();        // first, try to do native clearing        Logger.debug("Attempting to clear using UiObject.clearText().");        el.clearText();  //无条件都会执行这块。然后再分析clearText        if (el.getText().isEmpty()) {          return getSuccessResult(true);        }        // see if there is hint text        if (hasHintText(el)) {          Logger.debug("Text remains after clearing, "              + "but it appears to be hint text.");          return getSuccessResult(true);        }        // next try to select everything and delete        Logger.debug("Clearing text not successful. Attempting to clear " +                "by selecting all and deleting.");        if (selectAndDelete(el)) {          return getSuccessResult(true);        }        // see if there is hint text        if (hasHintText(el)) {          Logger.debug("Text remains after clearing, "              + "but it appears to be hint text.");          return getSuccessResult(true);        }        // finally try to send delete keys        Logger.debug("Clearing text not successful. Attempting to clear " +                "by sending delete keys.");        if (sendDeleteKeys(el)) {          return getSuccessResult(true);        }        if (!el.getText().isEmpty()) {          // either there was a failure, or there is hint text          if (hasHintText(el)) {            Logger.debug("Text remains after clearing, " +                    "but it appears to be hint text.");            return getSuccessResult(true);          } else if (!el.getText().isEmpty()) {            Logger.debug("Exhausted all means to clear text but '" +                    el.getText() + "' remains.");            return getErrorResult("Clear text not successful.");          }        }        return getSuccessResult(true);      } catch (final UiObjectNotFoundException e) {        return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,            e.getMessage());      } catch (final Exception e) { // handle NullPointerException        return getErrorResult("Unknown error clearing text");      }    }    return getErrorResult("Unknown error");  }  private boolean selectAndDelete(AndroidElement el)      throws UiObjectNotFoundException, IllegalAccessException,        InvocationTargetException, NoSuchMethodException {    Rect rect = el.getVisibleBounds();    // Trying to select entire text.    TouchLongClick.correctLongClick(rect.left + 20, rect.centerY(), 2000);    UiObject selectAll = new UiObject(new UiSelector().descriptionContains("Select all"));    if (selectAll.waitForExists(2000)) {      selectAll.click();    }    // wait for the selection    SystemClock.sleep(500);    // delete it    UiAutomatorBridge.getInstance().getInteractionController().sendKey(KeyEvent.KEYCODE_DEL, 0);    return el.getText().isEmpty();  }  private boolean sendDeleteKeys(AndroidElement el)      throws UiObjectNotFoundException, IllegalAccessException,        InvocationTargetException, NoSuchMethodException {    String tempTextHolder = "";    // Preventing infinite while loop.    while (!el.getText().isEmpty() && !tempTextHolder.equalsIgnoreCase(el.getText())) {      // Trying send delete keys after clicking in text box.      el.click();      // Sending delete keys asynchronously, both forward and backward      for (int key : new int[] { KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_FORWARD_DEL }) {        tempTextHolder = el.getText();        final int length = tempTextHolder.length();        final long eventTime = SystemClock.uptimeMillis();        KeyEvent deleteEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN,                key, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,                InputDevice.SOURCE_KEYBOARD);        for (int count = 0; count < length; count++) {          UiAutomatorBridge.getInstance().injectInputEvent(deleteEvent, false);        }      }    }    return el.getText().isEmpty();  }  private boolean hasHintText(AndroidElement el)      throws UiObjectNotFoundException, IllegalAccessException,        InvocationTargetException, NoSuchMethodException {    // to test if the remaining text is hint text, try sending a single    // delete key and testing if there is any change.    // ignore the off-chance that the delete silently fails and we get a false    // positive.    String currText = el.getText();    try {      if (!el.getBoolAttribute("focused")) {        Logger.debug("Could not check for hint text because the element is not focused!");        return false;      }    } catch (final Exception e) {      Logger.debug("Could not check for hint text: " + e.getMessage());      return false;    }    InteractionController interactionController = UiAutomatorBridge.getInstance().getInteractionController();    interactionController.sendKey(KeyEvent.KEYCODE_DEL, 0);    interactionController.sendKey(KeyEvent.KEYCODE_FORWARD_DEL, 0);    return currText.equals(el.getText());  }}

再看看AndroidElement.clearText是什么样的

  public void clearText() throws UiObjectNotFoundException {    el.clearTextField();  }

这个都就是com.android.uiautomator.core.UiObject.clearTextField
于是找再找到uiautomator的代码再来分析(这个代码需要下载andriod sdk,在对应android版本的目录下,会有源码,也有uiautomator的源代码),我这里的路径是:
Android\sdk\sources\android-19\com\android\uiautomator\core
在UiObject.java找到clearTextField实现

/**     * Clears the existing text contents in an editable field.     *     * The {@link UiSelector} of this object must reference a UI element that is editable.     *     * When you call this method, the method first sets focus at the start edge of the field.     * The method then simulates a long-press to select the existing text, and deletes the     * selected text.     *     * If a "Select-All" option is displayed, the method will automatically attempt to use it     * to ensure full text selection.     *     * Note that it is possible that not all the text in the field is selected; for example,     * if the text contains separators such as spaces, slashes, at symbol etc.     * Also, not all editable fields support the long-press functionality.     *     * @throws UiObjectNotFoundException     * @since API Level 16     */    public void clearTextField() throws UiObjectNotFoundException {        Tracer.trace();        // long click left + center        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());        if(node == null) {            throw new UiObjectNotFoundException(getSelector().toString());        }        Rect rect = getVisibleBounds(node);        getInteractionController().longTapNoSync(rect.left + 20, rect.centerY()); //长按        // check if the edit menu is open        UiObject selectAll = new UiObject(new UiSelector().descriptionContains("Select all"));        if(selectAll.waitForExists(50))            selectAll.click();        // wait for the selection        SystemClock.sleep(250); //这里等250ms        // delete it        getInteractionController().sendKey(KeyEvent.KEYCODE_DEL, 0);    }

相信大家,可以找到慢的原因了。这里做一次长按,然再再做全选,然后再sleep(250),还有一个selectAll.waitForExists(50), 这些都是耗费时间的。

再找一下UiObject.java中setText的实现

    public boolean setText(String text) throws UiObjectNotFoundException {        Tracer.trace(text);        clearTextField();        return getInteractionController().sendText(text);    }

发现这里又调用了一次clearTextField,这样算来,设一次文本,都会清理两次文本,于是,这时间就长了。
优化:只需要将io.appium.android.bootstrap.handler.SetText中的new Clear().execute(command)去掉就可以了。

原创粉丝点击