Startup of Appium Android Bootstrap source code analysis, appiumandroid

Source: Internet
Author: User

Startup of Appium Android Bootstrap source code analysis, appiumandroid

Through the previous two articles, "Control of Appium Android Bootstrap source code analysis AndroidElement" and "Command Parsing and execution of Appium Android Bootstrap source code analysis", we learned that the commands sent by Appium from the pc are how to locate command-related controls and parse and execute the command. The problem we have left is how bootstrap starts and runs. We will discuss this issue through the analysis in this article, and concatenate the classes learned before to see how they interact.


1. the Startup Mode of Bootstrap is controlled by Appium from the pc side by sending commands through adb:
From the preceding debugging information, we can see that AppiumBootstrap. jar uses the uiautomator command as a test package. It specifies the test class I/O. appium. android. bootstrap. Bootstrap. bootstrap. If you read the previous article "start and run of uiautomation or source code analysis", you should be familiar with the startup principle of uiautomation or.
  • Startup command: uiautomator runtest AppiumBootstrap. jar-c io. appium. android. bootstrap. Bootstrap
Let's go to the Bootstrap class and see how it is implemented:
public class Bootstrap extends UiAutomatorTestCase {  public void testRunServer() {    SocketServer server;    try {      server = new SocketServer(4724);      server.listenForever();    } catch (final SocketServerException e) {      Logger.error(e.getError());      System.exit(1);    }  }}
From the code, we can see that this class inherits from UiAutomatorTestCase, so that it can be executed by uiautomator as a test case class. This class has only one test method, testRunServer. The source of all things is here:
  • Create a socket server and listen to port 4724. Appium sends the command over this port on the pc end.
  • The loop listener obtains the command data sent by Appium from the pc and then processes the data accordingly.

2. Create a socket server and initialize the commanding from Action to CommandHandler. Let's take a look at the etserver constructor:
  public SocketServer(final int port) throws SocketServerException {    keepListening = true;    executor = new AndroidCommandExecutor();    try {      server = new ServerSocket(port);      Logger.debug("Socket opened on port " + port);    } catch (final IOException e) {      throw new SocketServerException(          "Could not start socket server listening on " + port);    }  }
The first thing it does is to create an AndroidCommandExecutor instance first, you should remember that the class mentioned in the previous article saved a ing table of static and important actions to the CommandHandler instance of the command processing class? If you have not read it, check it first. After this static ing table is created, the next step of the constructor seems to be to create a ServerSocket to connect and communicate Appium from the PC.
3. Get and execute the Appium command data Bootstrap after creating the socket server, the next step is to call the listenForever method of SocketServer to read and process the command data sent by appium cyclically:
  public void listenForever() throws SocketServerException {    Logger.debug("Appium Socket Server Ready");    ...    try {      client = server.accept();      Logger.debug("Client connected");      in = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));      out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream(), "UTF-8"));      while (keepListening) {        handleClientData();      }      in.close();      out.close();      client.close();      Logger.debug("Closed client connection");    } catch (final IOException e) {      throw new SocketServerException("Error when client was trying to connect");    }    ...}
First, call server. accept accepts appium connection requests. After the connection, it initializes the instances of the BufferedReader and BufferredWriter classes used to read the socket. Finally, it enters handleClicentData for Real Data Reading and processing.
 private void handleClientData() throws SocketServerException {    try {      input.setLength(0); // clear      String res;      int a;      // (char) -1 is not equal to -1.      // ready is checked to ensure the read call doesn't block.      while ((a = in.read()) != -1 && in.ready()) {        input.append((char) a);      }      String inputString = input.toString();      Logger.debug("Got data from client: " + inputString);      try {        AndroidCommand cmd = getCommand(inputString);        Logger.debug("Got command of type " + cmd.commandType().toString());        res = runCommand(cmd);        Logger.debug("Returning result: " + res);      } catch (final CommandTypeException e) {        res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage())            .toString();      } catch (final JSONException e) {        res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR,            "Error running and parsing command").toString();      }      out.write(res);      out.flush();    } catch (final IOException e) {      throw new SocketServerException("Error processing data to/from socket ("          + e.toString() + ")");    }  }
  • Read the data sent by appium through the socket just created
  • Send the obtained json command string to the getCommand method to instantiate our AndroidCommand class. Then we can use this parser to obtain the desired json command item.
  private AndroidCommand getCommand(final String data) throws JSONException,      CommandTypeException {    return new AndroidCommand(data);  }
  • Call the runCommand method to execute the execute method of the AndroidComandExecutor object that we instantiate when constructing the ServerSocket in section 2, this command will eventually get the action sent by appium through the above AndroidCommand command parser instance, and then call the corresponding CommandHandler Based on map to process the command. If the command is related to the control, for example, to get the text information of a control, GetText, the processing command class will continue to get the corresponding control from the control hash table maintained by AndroidElementHash, then, use UiObject to send out the command .. for more information, see the previous article.
      private String runCommand(final AndroidCommand cmd) {    AndroidCommandResult res;    if (cmd.commandType() == AndroidCommandType.SHUTDOWN) {      keepListening = false;      res = new AndroidCommandResult(WDStatus.SUCCESS, "OK, shutting down");    } else if (cmd.commandType() == AndroidCommandType.ACTION) {      try {        res = executor.execute(cmd);      } ...  }
  • Write the returned information to the socket through the socket write object established above and send it to appium

4. it may be strange to know how controls are added to the control hash table, how the entire running process is finished, and how to obtain a control from the control hash table, but why didn't I see a control added to the control hash table? In fact, when you write a script, you need to find the control before sending the click command to a control, such:
WebElement el = driver.findElement(By.name("Add note"));
The finElement here is actually a command. The CommandHandler implementation class of the control to get the control and put it in the control hash table.
We can see that the commands from appium contain several items, which we have encountered or encountered:
  • Cmd: Indicates an action.
  • Action: Specifies that this action is a find command.
  • Params
    • Strategy: The Policy for selecting sub-accounts is to search based on the space name.
    • Selector: Specify the sub-content to be selected as "Add note"
    • Context: Specifies the key value id of the target control in the space hash table. This parameter is null because this control has not been used before.
    • Multiple: Indicates whether your script code uses findElements or findElement and whether to obtain multiple controls.
Find rewrite the execute method of the parent class for a long time. Let's take a look at it one step by one.

  • Step 1: Obtain the Control Selection Sub-policy so that the UiSelector of uiautomator can be created through this policy.
  public AndroidCommandResult execute(final AndroidCommand command)      throws JSONException {    final Hashtable<String, Object> params = command.params();    // only makes sense on a device    final Strategy strategy;    try {      strategy = Strategy.fromString((String) params.get("strategy"));    } catch (final InvalidStrategyException e) {      return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND, e.getMessage());    }   ...}
Appium supports the following policies. In fact, we often specify findElement in writing scripts:
public enum Strategy {  CLASS_NAME("class name"),  CSS_SELECTOR("css selector"),  ID("id"),  NAME("name"),  LINK_TEXT("link text"),  PARTIAL_LINK_TEXT("partial link text"),  XPATH("xpath"),  ACCESSIBILITY_ID("accessibility id"),  ANDROID_UIAUTOMATOR("-android uiautomator");
  • Step 2: obtain other sub-selection information such as content, control hash table key value, and check whether the Sub-selection meets the requirements of appium.
  public AndroidCommandResult execute(final AndroidCommand command)      throws JSONException {    final Hashtable<String, Object> params = command.params();   ...    final String contextId = (String) params.get("context");    final String text = (String) params.get("selector");    final boolean multiple = (Boolean) params.get("multiple");   ...}
  • Step 3: after obtaining the same Selection Sub-information, you can create a real UiSelector Selection Sub-list based on the Selection Sub-information, here, the List should take into account the combination of sub-selection in the future. Currently, we do not use it. The entire list will only have one UiSelector sub-selection.
  public AndroidCommandResult execute(final AndroidCommand command)      throws JSONException {   ...    try {      Object result = null;      List<UiSelector> selectors = getSelectors(strategy, text, multiple);       ...      }   ...}
  • Step 4: After the sub-UiSelector list is set up, Find will Find the control based on whether you are findElement or findElement, that is to say, whether you are searching for a control or multiple controls, however, whether there are multiple or one, the fetchElement method is eventually called to retrieve
  public AndroidCommandResult execute(final AndroidCommand command)      throws JSONException {   ...    try {      Object result = null;      List<UiSelector> selectors = getSelectors(strategy, text, multiple);      if (!multiple) {        for (final UiSelector sel : selectors) {          try {            Logger.debug("Using: " + sel.toString());            result = fetchElement(sel, contextId);          } catch (final ElementNotFoundException ignored) {          }          if (result != null) {            break;          }        }      }else {        List<AndroidElement> foundElements = new ArrayList<AndroidElement>();        for (final UiSelector sel : selectors) {          // With multiple selectors, we expect that some elements may not          // exist.          try {            Logger.debug("Using: " + sel.toString());            List<AndroidElement> elementsFromSelector = fetchElements(sel, contextId);            foundElements.addAll(elementsFromSelector);          } catch (final UiObjectNotFoundException ignored) {          }        }        if (strategy == Strategy.ANDROID_UIAUTOMATOR) {          foundElements = ElementHelpers.dedupe(foundElements);        }        result = elementsToJSONArray(foundElements);      }   ...}
GetElements of the control hash table class finally called by fetchElement:
  private ArrayList<AndroidElement> fetchElements(final UiSelector sel, final String contextId)      throws UiObjectNotFoundException {    return elements.getElements(sel, contextId);  }
This method of AndroidElementHash has been analyzed in the previous article "Appium Android Bootstrap source code analysis control AndroidElement". Let's take a look at it today. the control query commands sent from Appium are generally divided into two categories:
  • 1. Search directly based on Appium Driver. In this case, the json Command sent by appium does not contain the key value information of the control hash table.
WebElement addNote = driver.findElement(By.name("Add note"));
  • 2. search based on the parent control:
WebElement el = driver.findElement(By.className("android.widget.ListView")).findElement(By.name("Note1"));
The above script first finds the parent control ListView of the Note1 diary and saves the control to the control hash table, then, find the expected Note1 Based on the hash table key value of the parent control and the selection child of the Child control:
The getElement command of AndroidElementHash is used to obtain the target control based on different situations. [Java]View plaincopy
  1. /**
  2. * Return an elements child given the key (context id), or uses the selector
  3. * To get the element.
  4. *
  5. * @ Param sel
  6. * @ Param key
  7. * Element id.
  8. * @ Return {@ link AndroidElement}
  9. * @ Throws ElementNotFoundException
  10. */
  11. Public AndroidElement getElement (final UiSelector sel, final String key)
  12. Throws ElementNotFoundException {
  13. AndroidElement baseEl;
  14. BaseEl = elements. get (key );
  15. UiObject el;
  16. If (baseEl = null ){
  17. El = new UiObject (sel );
  18. } Else {
  19. Try {
  20. El = baseEl. getChild (sel );
  21. } Catch (final UiObjectNotFoundException e ){
  22. Throw new ElementNotFoundException ();
  23. }
  24. }
  25. If (el. exists ()){
  26. Return addElement (el );
  27. } Else {
  28. Throw new ElementNotFoundException ();
  29. }
  30. }
  • In case of 1st, you can directly create a UiObject object by selecting a sub-object, and then convert the UiObject to an AndroidElement object through addElement and save it to the control hash table.
  • In case of 2nd cases, obtain the parent Control Based on the hash table key value of the control passed by appium, and then find the target UiObject Control Based on the parent control using the Selection Sub-control, finally, the control converts the UiObject control into an AndroidElement control object through addElement and saves it to the control hash table.
The following is the addElement method to add the control to the control hash table.
  public AndroidElement addElement(final UiObject element) {    counter++;    final String key = counter.toString();    final AndroidElement el = new AndroidElement(key, element);    elements.put(key, el);    return el;  }

5. Summary
  • The jar package of bootstrap of Appium and the o. appium. android. bootstrap. Bootstrap class in it are started through uiautomator as a test package and test method class of uiautomator.
  • The Bootstrap test class inherits from the UiAutomatorTestCase that can be used by uiautomator.
  • Bootstrap starts a socket server and listens to the appium connection from Port 4724.
  • Once the appium connection comes up, bootstrap will constantly retrieve the command data sent from appium on the port for parsing and execution, and then write the result to the port and return it to appium.
  • After bootstrap obtains the json string command from appium, it parses the command action through the "AndroidCommand" command parser, then, map the action to the map of CommandHandler through the action of AndroidCommandExecutor to the real command processing class. These classes are the implementation classes inherited from CommandHandler, they all need to override the execute method of the parent class to get the QueryController/InteractionController that is not exposed by UiObject, UiDevice, or reflection to execute the command in the Android system.
  • There are two types of controls for obtaining appium. One is directly obtained through Appium/Android Driver, in this case, the appium query json command string does not contain the control key value of the control hash table; the other is obtained based on the key value of the control's parent control in the control hash table and the Selection Sub-control of the Child control, in this case, the appium query json command string provides both the key value of the parent control in the control hash table and the selection of sub-controls.
  • Once the obtained control does not exist in the control hash table, you need to add the AndroidElement control to the hash table.

An error occurred while starting the tomcat service when running the Android program in eclispse.

The reason is that a null pointer exception occurs, so the program crashes and cannot be started.
Check whether your code calls an empty object or the object is not assigned a value.


Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.