(Idle to have nothing to do, do the test.) Recently got to get appium, feel very interesting, in-depth study of the next.
Look at the younger brother before this article, first understand the architecture of appium, for your understanding is good, recommend this article: Testerhome
Appium is open source project, can obtain source code: Appium-master
Importing with Maven in Eclipse will find 2 items: Bootstrap and Sauce_appium_junit.
Sauce_appium_junit is a collection of test cases that help you learn. Bootstrap is a server in the Appium architecture that is placed on the mobile phone side. Let's start with it.
Bootstrap Structure
Project structure for the bootstrap
Bootstrap effect
Bootstrap in Appium is in the form of a jar package, it is actually a uiautomator write case package, through the PC-side command can be executed on the mobile side.
Bootstrap Source Code Analysis
The first entry for the program is the Bootstrap class. So starting with this class, step-by-step explanation of the project
Bootstrap.java
Package Io.appium.android.bootstrap;import Io.appium.android.bootstrap.exceptions.socketserverexception;import com.android.uiautomator.testrunner.uiautomatortestcase;/** * The Bootstrap class runs the socket server. Uiautomator developed scripts can be launched directly on the PC side */public class Bootstrap extends Uiautomatortestcase {public void Testrunserver () { socketserver server; try { //start the socket server and listen on port 4724. Server = new Socketserver (4724); Server.listenforever (); } catch (Final socketserverexception e) { logger.error (E.geterror ()); System.exit (1);}}
This class inherits from Uiautomatortestcase. So it can be executed via adb shell uiautomator runtest appiumbootstrap.jar-c io.appium.android.bootstrap.Bootstrap.
The class is simple, which is to start the thread and listen on port 4724, which communicates with Appium.
Then go to the Server.listenforever () method.
Socketserver.java
/** * listens on the socket for data, and calls {@link #handleClientData ()} when * it ' s available. * * @throws socketserverexception */public void Listenforever () throws Socketserverexception {Logger.debug ("App Ium Socket Server ready "); Read Strings.json file Data Updatestrings.loadstringsjson (); Register two types of listeners: and and Crash Dismisscrashalerts (); Final TimerTask updatewatchers = new TimerTask () {@Override public void run () {try {//check whether the system There is abnormal watchers.check (); } catch (Final Exception e) {}}}; Timer, starts 0.1 seconds, executes every 0.1 seconds. Timer.scheduleatfixedrate (updatewatchers, 100, 100); 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) {//Get client data HandlecLientdata (); } in.close (); Out.close (); Client.close (); Logger.debug ("Closed Client Connection"); } catch (Final IOException e) {throw new Socketserverexception ("Error when client is trying to connect"); } }
Updatestrings.loadstringsjson () is called first in the method, and the method is as follows:
Updatestrings
/** * Strings.json files are saved in the strings.xml of the APK, parsed and push to the device side by the Appium server before bootstrap boot * * @return */public static Boo Lean Loadstringsjson () {logger.debug ("Loading json ..."); try {final String FilePath = "/data/local/tmp/strings.json"; Final file Jsonfile = new file (FilePath); JSON will isn't exist for APKs that is only on device//Your case must specify the path of the APK, if you start an application that is already on the device and there is no app path in cases, the JSON file does not exist Because the node server can ' t extract the JSON from the APK. if (!jsonfile.exists ()) {return false; } final DataInputStream datainput = new DataInputStream (new FileInputStream (Jsonfile)); Final byte[] Jsonbytes = new byte[(int) jsonfile.length ()]; Datainput.readfully (jsonbytes); This closes FileInputStream datainput.close (); Final String jsonstring = new String (jsonbytes, "UTF-8"); Assign the read information to a property in the Find class to do a post-use find.apkstrings = new Jsonobject (jsonstring); Logger.debug ("JSON loading compLete. "); catch (Final Exception e) {logger.error ("Error loading JSON:" + e.getmessage ()); return false; } return true; }
It then goes back to the Listenforever () of the ServerSocket class, where it executes to dismisscrashalerts (); The method is to register some listeners to see if there are any pop-up boxes or exceptions for and and crash.
public void Dismisscrashalerts () { try { new uiwatchers (). Registeranrandcrashwatchers (); Logger.debug ("Registered crash watchers."); catch (Final Exception e) { logger.debug ("Unable to register crash watchers."); } }
At this point in the Listenforever () method to register the heartbeat program, every 0.1 seconds start to execute the above registered listener to check the system for any exceptions.
Final TimerTask updatewatchers = new TimerTask () { @Override public void Run () { try { //Check System for exception W Atchers.check (); } catch (Final Exception e) { }}} ; Timer, starts 0.1 seconds, executes every 0.1 seconds. timer.scheduleatfixedrate (updatewatchers, 100, 100);
The data channel is then started, accepting the data sent by the client and returning the results to the client.
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"));
Next is the most important method Handleclientdata (), and the main function of the Listenforever () method is completed. Now see what the Handleclientdata () method did.
/** * When data was available on the socket, this method is called to run the * command or throw an error if it can ' t. * * @throws socketserverexception */private void Handleclientdata () throws Socketserverexception {try { Input.setlength (0); Clear String Res; int A; (char)-1 is not equal to-1. Checked to ensure the read call doesn ' t block. while ((A = In.read ())! =-1 && in.ready ()) {input.append ((char) a); } final String inputstring = input.tostring (); Logger.debug ("Got Data from client:" + inputstring); try {final 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 () + ")"); } }
The method reads the data from the client, obtains the Androidcommand object using the GetCommand () method, and then executes the RunCommand () method to obtain a direct result. The effect of the method is then shifted to RunCommand (). So now let's see what the RunCommand () method means.
/** * When {@link #handleClientData ()} have valid data, this method delegates the * command. * * @param cmd * androidcommand * @return Result * /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); } catch ( Final Exception e) { res = new Androidcommandresult (Wdstatus.unknown_error, E.getmessage ()); } } else { c20/>//This code should never is executed, here for future-proofing res = new Androidcommandresult (wdstatus.unknown_ ERROR, "Unknown command type, could not execute!"); } return res.tostring (); }}
The method first makes the judgment, judging the command data which type, main related machine command and action command, we mainly focus on the action command, because there are many kinds of action. So take a look at the Androidcommandexecutor.execute () method in the first else if. The main line has shifted to the method, and cut to a glance.
Androidcommandexecutor.java
/** * Gets The handler out of the map, and executes the command. * * @param command * The {@link Androidcommand} * @return {@link Androidcommandresult} */ Public Androidcommandresult Execute (final androidcommand command) { try { logger.debug ("Got Command action:" + C Ommand.action ()); if (Map.containskey (Command.action ())) { return Map.get (Command.action ()). Execute (command); } else { return new Androidcommandresult (Wdstatus.unknown_command, "UNKNOWN COMMAND:" + command.action ()); } } catch (Final Jsonexception e) { logger.error ("Could not decode Action/params of command"); return new Androidcommandresult (Wdstatus.json_decoder_error, "Could not decode action/params of command, please Check format! "); } }
The entity that is finally going to execute the command in this method
if (Map.containskey (Command.action ())) { return Map.get (Command.action ()). Execute (command); } else { return new Androidcommandresult (Wdstatus.unknown_command, "UNKNOWN COMMAND:" + command.action ()); }
The key is the above lines of code, called Map.get (Command.action ()). Execute (command). It seems that to understand the meaning of this command, you must know what is stored in the map of the object, then in the class to find the initialization code of the map:
static {Map.put ("Waitforidle", New Waitforidle ()); Map.put ("Clear", new Clear ()); Map.put ("orientation", new orientation ()); Map.put ("Swipe", new swipe ()); Map.put ("Flick", New Flick ()); Map.put ("Drag", new drag ()); Map.put ("Pinch", New Pinch ()); Map.put ("Click", New Click ()); Map.put ("Touchlongclick", New Touchlongclick ()); Map.put ("TouchDown", New TouchDown ()); Map.put ("TouchUp", New TouchUp ()); Map.put ("TouchMove", New TouchMove ()); Map.put ("GetText", New GetText ()); Map.put ("SetText", New SetText ()); Map.put ("GetName", New GetName ()); Map.put ("GetAttribute", New GetAttribute ()); Map.put ("Getdevicesize", New Getdevicesize ()); Map.put ("ScrollTo", New ScrollTo ()); Map.put ("Find", new Find ()); Map.put ("GetLocation", New GetLocation ()); Map.put ("GetSize", New GetSize ()); Map.put ("Wake", New Wake ()); Map.put ("Pressback", New Pressback ()); Map.put ("Dumpwindowhierarchy", New Dumpwindowhierarchy ()); Map.put ("PresskeYcode ", New Presskeycode ()); Map.put ("Longpresskeycode", New Longpresskeycode ()); Map.put ("Takescreenshot", New Takescreenshot ()); Map.put ("Updatestrings", New Updatestrings ()); Map.put ("Getdatadir", New Getdatadir ()); Map.put ("Performmultipointergesture", New Multipointergesture ()); Map.put ("Opennotification", New Opennotification ()); }
Enlightened, the map is a map of the <String,CommandHandler> form. The value values correspond to objects, which are inherited and CommandHandler, which have the Execute method, which is to invoke different objects depending on the command to get the result of the relevant code. From the definition of map can be seen, Appium can operate mobile phone command also a lot, I used to have scrollto,updatestrings,getdatadir, and so on, above also, open notice bar, press inferior still not used, But with these commands you can also see what appium can do.
Inherit CommandHandler the object has a lot of, I choose one to say what it is exactly what, the other I will talk about in the future, pick click.
Adding the command suffix that is now passed is the click, then it invokes the Execute method of the Click Object.
Click.java
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.arraylist;import java.util.hashtable;/** * This handler are used to click Elements in the Android UI. * * Based on the element Id, click the element. * */public class Click extends CommandHandler {/* * @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 Jsonexcepti on {if (Command.iselementcommand ()) {try {final androidelement el = command.getelement (); El.click (); 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"); }} else {final hashtable<string, object> params = Command.params (); Final double[] coords = {double.parsedouble (Params.get ("X"). ToString ()), Double.parsedouble (Params.get ("Y"). toSt Ring ())}; Final arraylist<integer> posvals = Absposfromcoords (coords); Final Boolean res = Uidevice.getinstance (). Click (Posvals.get (0), Posvals.get (1)); Return Getsuccessresult (RES); } }}
This class is the only one of an Execute method, the Execute method will first determine whether the passed parameter object is a coordinate value or an element value, if it is the value of the element then directly call the click Method in Androidelement, we will see this method. If it is a coordinate, what will it do? It calls Uidevice's click Method, and anyone who has used Uiautomator knows it is a class in the Uiautomator package. So the uiautomator mechanism that Appium uses on machines above api16. Someone seems to think it's easy. Well, let's analyze a touchdown command, and if the command suffix passed is touchdown, it calls the Execute method of the touchdown object.
Map.put ("TouchDown", New TouchDown ());
The Execute method inside this class is a bit of a point.
Touchdown.java
Package Io.appium.android.bootstrap.handler;import Com.android.uiautomator.common.reflectionutils;import Com.android.uiautomator.core.uiobjectnotfoundexception;import Io.appium.android.bootstrap.logger;import java.lang.reflect.method;/** * This handler was used to perform a touchDown event on an element in the * Android UI. * */public class TouchDown extends TouchEvent { @Override protected Boolean executetouchevent () throws uiobjectnotfoundexception { printeventdebugline ("TouchDown"); try { final reflectionutils utils = new Reflectionutils (); Final Method TouchDown = Utils.getcontrollermethod ("TouchDown", Int.class, int.class); Return (Boolean) Touchdown.invoke (Utils.getcontroller (), Clickx, clicky); } catch (Final Exception e) { logger.debug ("Problem invoking TouchDown:" + e); return false;}} }
The method uses reflection to invoke the hidden API in Uiautomator to perform the down operation. Not specifically, the latter will be said again and again.
Summary
Say so much nonsense, try to use a flowchart to describe it again.