中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

appium框架之bootstrap

發布時間:2020-07-23 22:12:12 來源:網絡 閱讀:474 作者:zhukev 欄目:移動開發

原文地址:http://www.it165.net/pro/html/201407/17696.html

bootstrap結構

如圖所示為bootstrap的項目結構

appium框架之bootstrap

view sourceprint?
01.package io.appium.android.bootstrap;
02. 
03.import io.appium.android.bootstrap.exceptions.SocketServerException;
04. 
05.import com.android.uiautomator.testrunner.UiAutomatorTestCase;
06. 
07./**
08.* The Bootstrap class runs the socket server. uiautomator開發的腳本,可以直接在pc端啟動
09.*/
10.public class Bootstrap extends UiAutomatorTestCase {
11. 
12.public void testRunServer() {
13.SocketServer server;
14.try {
15.// 啟動socket服務器,監聽4724端口。
16.server = new SocketServer(4724);
17.server.listenForever();
18.catch (final SocketServerException e) {
19.Logger.error(e.getError());
20.System.exit(1);
21.}
22. 
23.}
24.}

該類繼承自UiAutomatorTestCase。所以它才能通過adb shell uiautomator runtest AppiumBootstrap.jar -c io.appium.android.bootstrap.Bootstrap被執行。

該類很簡單,就是啟動線程,監聽4724端口,該端口與appium通信。

然后走server.listenForever()方法。

SocketServer.java

view sourceprint?
01./**
02.* Listens on the socket for data, and calls {@link #handleClientData()} when
03.* it's available.
04.*
05.* @throws SocketServerException
06.*/
07.public void listenForever() throws SocketServerException {
08.Logger.debug("Appium Socket Server Ready");
09.//讀取strings.json文件的數據
10.UpdateStrings.loadStringsJson();
11.// 注冊兩種監聽器:AND和Crash
12.dismissCrashAlerts();
13.final TimerTask updateWatchers = new TimerTask() {
14.@Override
15.public void run() {
16.try {
17.// 檢查系統是否有異常
18.watchers.check();
19.catch (final Exception e) {
20.}
21.}
22.};
23.// 計時器,0.1秒后開始,每隔0.1秒執行一次。
24.timer.scheduleAtFixedRate(updateWatchers, 100100);
25. 
26.try {
27.client = server.accept();
28.Logger.debug("Client connected");
29.in = new BufferedReader(new InputStreamReader(client.getInputStream(),
30."UTF-8"));
31.out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream(),
32."UTF-8"));
33.while (keepListening) {
34.// 獲取客戶端數據
35.handleClientData();
36.}
37.in.close();
38.out.close();
39.client.close();
40.Logger.debug("Closed client connection");
41.catch (final IOException e) {
42.throw new SocketServerException("Error when client was trying to connect");
43.}
44.}

該方法中首先調用UpdateStrings.loadStringsJson();該方法如下:

UpdateStrings

view sourceprint?
01./**
02.* strings.json文件保存的是apk的strings.xml里的內容,在Bootstrap啟動前由appium服務器解析并push到設備端的
03.*
04.* @return
05.*/
06.public static boolean loadStringsJson() {
07.Logger.debug("Loading json...");
08.try {
09.final String filePath = "/data/local/tmp/strings.json";
10.final File jsonFile = new File(filePath);
11.// json will not exist for apks that are only on device
12.// 你的case必須寫明apk的路徑,如果啟動設備上已有的應用而case中沒有app路徑,此時json文件是不存在的
13.// because the node server can't extract the json from the apk.
14.if (!jsonFile.exists()) {
15.return false;
16.}
17.final DataInputStream dataInput = new DataInputStream(
18.new FileInputStream(jsonFile));
19.final byte[] jsonBytes = new byte[(int) jsonFile.length()];
20.dataInput.readFully(jsonBytes);
21.// this closes FileInputStream
22.dataInput.close();
23.final String jsonString = new String(jsonBytes, "UTF-8");
24.// 將讀取出來的信息賦給Find類中的屬性,以做后用
25.Find.apkStrings = new JSONObject(jsonString);
26.Logger.debug("json loading complete.");
27.catch (final Exception e) {
28.Logger.error("Error loading json: " + e.getMessage());
29.return false;
30.}
31.return true;
32.}

然后回到ServerSocket類的listenForever(),此時執行到dismissCrashAlerts();該方法作用是注冊一些監聽器,觀察是否有糖出口或者AND和crash的異常。

view sourceprint?
1.public void dismissCrashAlerts() {
2.try {
3.new UiWatchers().registerAnrAndCrashWatchers();
4.Logger.debug("Registered crash watchers.");
5.catch (final Exception e) {
6.Logger.debug("Unable to register crash watchers.");
7.}
8.}

此時listenForever()方法里執行到注冊心跳程序,每隔0.1秒開始執行一遍上面注冊的監聽器來檢查系統是否存在異常。

view sourceprint?
01.final TimerTask updateWatchers = new TimerTask() {
02.@Override
03.public void run() {
04.try {
05.// 檢查系統是否有異常
06.watchers.check();
07.catch (final Exception e) {
08.}
09.}
10.};
11.// 計時器,0.1秒后開始,每隔0.1秒執行一次。
12.timer.scheduleAtFixedRate(updateWatchers, 100100);

然后啟動數據通道,接受客戶端發來的數據和返回結果給客戶端。

view sourceprint?
1.client = server.accept();
2.Logger.debug("Client connected");
3.in = new BufferedReader(new InputStreamReader(client.getInputStream(),
4."UTF-8"));
5.out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream(),
6."UTF-8"));

接下來就是最重要的方法handleClientData();到此listenForever()方法的主要作用就完成了。現在來看handleClientData()方法做了啥。

view sourceprint?
01./**
02.* When data is available on the socket, this method is called to run the
03.* command or throw an error if it can't.
04.*
05.* @throws SocketServerException
06.*/
07.private void handleClientData() throws SocketServerException {
08.try {
09.input.setLength(0); // clear
10. 
11.String res;
12.int a;
13.// (char) -1 is not equal to -1.
14.// ready is checked to ensure the read call doesn't block.
15.while ((a = in.read()) != -1 && in.ready()) {
16.input.append((char) a);
17.}
18.final String inputString = input.toString();
19.Logger.debug("Got data from client: " + inputString);
20.try {
21.final <a href="http://www.it165.net/pro/ydad/" target="_blank" class="keylink">Android</a>Command cmd = getCommand(inputString);
22.Logger.debug("Got command of type " + cmd.commandType().toString());
23.res = runCommand(cmd);
24.Logger.debug("Returning result: " + res);
25.catch (final CommandTypeException e) {
26.res = new <a href="http://www.it165.net/pro/ydad/" target="_blank"class="keylink">Android</a>CommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage())
27..toString();
28.catch (final JSONException e) {
29.res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR,
30."Error running and parsing command").toString();
31.}
32.out.write(res);
33.out.flush();
34.catch (final IOException e) {
35.throw new SocketServerException("Error processing data to/from socket ("
36.+ e.toString() + ")");
37.}
38.}

該方法中讀取客戶端發來的數據,利用getCommand()方法獲得AndroidCommand對象,然后執行runCommand()方法,獲取直接的結果。那么該方法的作用就轉移到了runCommand()。所以現在就來看runCommand()方法是啥意思啦。

view sourceprint?
01./**
02.* When {@link #handleClientData()} has valid data, this method delegates the
03.* command.
04.*
05.* @param cmd
06.*          AndroidCommand
07.* @return Result
08.*/
09.private String runCommand(final AndroidCommand cmd) {
10.AndroidCommandResult res;
11.if (cmd.commandType() == AndroidCommandType.SHUTDOWN) {
12.keepListening = false;
13.res = new AndroidCommandResult(WDStatus.SUCCESS, "OK, shutting down");
14.else if (cmd.commandType() == AndroidCommandType.ACTION) {
15.try {
16.res = executor.execute(cmd);
17.catch (final Exception e) {
18.res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage());
19.}
20.else {
21.// this code should never be executed, here for future-proofing
22.res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR,
23."Unknown command type, could not execute!");
24.}
25.return res.toString();
26.}
27.}

該方法首先做了判斷,判斷命令數據哪種類型,主要有關機命令和動作命令,我們主要關注動作命令,因為動作有很多種。所以來關注第一個else if中的AndroidCommandExecutor.execute()方法。主線又轉移到了該方法中了,切去瞅一眼。

AndroidCommandExecutor.java

view sourceprint?
01./**
02.* Gets the handler out of the map, and executes the command.
03.*
04.* @param command
05.*          The {@link AndroidCommand}
06.* @return {@link AndroidCommandResult}
07.*/
08.public AndroidCommandResult execute(final AndroidCommand command) {
09.try {
10.Logger.debug("Got command action: " + command.action());
11. 
12.if (map.containsKey(command.action())) {
13.return map.get(command.action()).execute(command);
14.else {
15.return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND,
16."Unknown command: " + command.action());
17.}
18.catch (final JSONException e) {
19.Logger.error("Could not decode action/params of command");
20.return new AndroidCommandResult(WDStatus.JSON_DECODER_ERROR,
21."Could not decode action/params of command, please check format!");
22.}
23.}

該方法中終于要執行命令的實體啦

view sourceprint?
1.if (map.containsKey(command.action())) {
2.return map.get(command.action()).execute(command);
3.else {
4.return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND,
5."Unknown command: " + command.action());
6.}

關鍵是上面這幾行代碼,調用了map.get(command.action()).execute(command).看來要想弄懂這個命令的意思,肯定得知道map里存放的對象是哪些,那么在該類中找到map的初始化代碼:

view sourceprint?
01.static {
02.map.put("waitForIdle"new WaitForIdle());
03.map.put("clear"new Clear());
04.map.put("orientation"new Orientation());
05.map.put("swipe"new Swipe());
06.map.put("flick"new Flick());
07.map.put("drag"new Drag());
08.map.put("pinch"new Pinch());
09.map.put("click"new Click());
10.map.put("touchLongClick"new TouchLongClick());
11.map.put("touchDown"new TouchDown());
12.map.put("touchUp"new TouchUp());
13.map.put("touchMove"new TouchMove());
14.map.put("getText"new GetText());
15.map.put("setText"new SetText());
16.map.put("getName"new GetName());
17.map.put("getAttribute"new GetAttribute());
18.map.put("getDeviceSize"new GetDeviceSize());
19.map.put("scrollTo"new ScrollTo());
20.map.put("find"new Find());
21.map.put("getLocation"new GetLocation());
22.map.put("getSize"new GetSize());
23.map.put("wake"new Wake());
24.map.put("pressBack"new PressBack());
25.map.put("dumpWindowHierarchy"new DumpWindowHierarchy());
26.map.put("pressKeyCode"new PressKeyCode());
27.map.put("longPressKeyCode"new LongPressKeyCode());
28.map.put("takeScreenshot"new TakeScreenshot());
29.map.put("updateStrings"new UpdateStrings());
30.map.put("getDataDir"new GetDataDir());
31.map.put("performMultiPointerGesture"new MultiPointerGesture());
32.map.put("openNotification"new OpenNotification());
33.}

豁然開朗,該map是<String,CommandHandler>形式的map。value值對應的都是一個個的對象,這些對象都繼承與CommandHandler,里面都有execute方法,該方法就是根據命令的不同調用不同的對象來執行相關代碼獲取結果。從map的定義可以看出,appium可以操作手機的命令還不少,我用過的有scrollTo,updateStrings,getDataDir等,上面還有截圖、打開通知欄、按下等還沒用過,但通過這些命令你也可以了解appium可以做哪些事。

繼承CommandHandler的對象有很多,我挑一個來講講它具體是干嘛的,其他的我以后會挨個講,就挑click吧。

加入現在傳過來的命令后綴是click的話,那么它會調用Click對象的execute方法。

Click.java

view sourceprint?
01.package io.appium.android.bootstrap.handler;
02. 
03.import com.android.uiautomator.core.UiDevice;
04.import com.android.uiautomator.core.UiObjectNotFoundException;
05.import io.appium.android.bootstrap.*;
06.import org.json.JSONException;
07. 
08.import java.util.ArrayList;
09.import java.util.Hashtable;
10. 
11./**
12.* This handler is used to click elements in the Android UI.
13.*
14.* Based on the element Id, click that element.
15.*
16.*/
17.public class Click extends CommandHandler {
18. 
19./*
20.* @param command The {@link AndroidCommand}
21.*
22.* @return {@link AndroidCommandResult}
23.*
24.* @throws JSONException
25.*
26.* @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android.
27.* bootstrap.AndroidCommand)
28.*/
29.@Override
30.public AndroidCommandResult execute(final AndroidCommand command)
31.throws JSONException {
32.if (command.isElementCommand()) {
33.try {
34.final AndroidElement el = command.getElement();
35.el.click();
36.return getSucce***esult(true);
37.catch (final UiObjectNotFoundException e) {
38.return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
39.e.getMessage());
40.catch (final Exception e) { // handle NullPointerException
41.return getErrorResult("Unknown error");
42.}
43.else {
44.final Hashtable<String, Object> params = command.params();
45.final Double[] coords = { Double.parseDouble(params.get("x").toString()),
46.Double.parseDouble(params.get("y").toString()) };
47.final ArrayList<Integer> posVals = absPosFromCoords(coords);
48.final boolean res = UiDevice.getInstance().click(posVals.get(0),
49.posVals.get(1));
50.return getSucce***esult(res);
51.}
52.}
53.}

該類就一個execute方法這根獨苗,execute方法中會先判斷傳入的參數對象是坐標值還是元素值,如果是元素值那么直接調用AndroidElement中的click方法,一會我們再去看這個方法。如果是坐標的話,它會干什么呢。它會調用UiDevice的click方法,用過UiAutomator的人都知道它是uiautomator包中的類。所以說appium在api16以上的機器上使用的uiautomator機制。貌似有人覺得這好像easy了點。那好吧,我們再分析一個touchDown命令,如果傳過來的命令后綴是touchDown,那么它會調用TouchDown對象的execute方法。

view sourceprint?
1.map.put("touchDown"new TouchDown());

這個類里面的execute方法就有點意思啦。

TouchDown.java

view sourceprint?
01.package io.appium.android.bootstrap.handler;
02. 
03.import com.android.uiautomator.common.ReflectionUtils;
04.import com.android.uiautomator.core.UiObjectNotFoundException;
05.import io.appium.android.bootstrap.Logger;
06. 
07.import java.lang.reflect.Method;
08. 
09./**
10.* This handler is used to perform a touchDown event on an element in the
11.* Android UI.
12.*
13.*/
14.public class TouchDown extends TouchEvent {
15. 
16.@Override
17.protected boolean executeTouchEvent() throws UiObjectNotFoundException {
18.printEventDebugLine("TouchDown");
19.try {
20.final ReflectionUtils utils = new ReflectionUtils();
21.final Method touchDown = utils.getControllerMethod("touchDown"int.class,
22.int.class);
23.return (Boolean) touchDown.invoke(utils.getController(), clickX, clickY);
24.catch (final Exception e) {
25.Logger.debug("Problem invoking touchDown: " + e);
26.return false;
27.}
28.}
29.}

該方法里用到了反射,調用uiautomator里的隱藏api來執行按下操作。就不具體講了,后面會挨個說一遍的。

總結

說了這么多廢話,嘗試著用流程圖描述一遍吧。

appium框架之bootstrap


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

彭山县| 尤溪县| 济阳县| 巩留县| 宝坻区| 惠安县| 金寨县| 武川县| 勐海县| 恩施市| 左权县| 仲巴县| 临桂县| 双柏县| 营山县| 商河县| 子洲县| 柳河县| 若尔盖县| 平阳县| 临夏市| 定襄县| 会东县| 三河市| 进贤县| 南安市| 河池市| 墨脱县| 通渭县| 普兰县| 山东省| 抚州市| 舞阳县| 新民市| 湾仔区| 浦县| 桐庐县| 磐安县| 威宁| 荆门市| 塘沽区|