亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

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

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

MonkeyRunner源碼分析之-誰動了我的截圖?

發布時間:2020-08-02 11:21:42 來源:網絡 閱讀:268 作者:zhukev 欄目:移動開發

本文章的目的是通過分析monkeyrunner是如何實現截屏來作為一個例子嘗試投石問路為下一篇文章做準備,往下一篇文章本人有意分析下monkeyrunner究竟是如何和目標測試機器通信的,所以最好的辦法本人認為是先跟蹤一個調用示例從高層到底層進行分析,本人以前分析操作系統源代碼的時候就是先從用戶層的write這個api入手,然后一路打通到vfs文件系統層,到設備驅動層的,其效果比單純的理論描述更容易理解和接受。

在整個代碼分析過程中會設計到以下的庫,希望想動手分析的同學們準備好源碼:

  • monkeyrunner
  • chimpchat
  • ddmlib
想來google對自動化測試框架的命名很有趣,有叫猴子(Monkey)的,也有叫大猩猩(Chimp)的。

1. 究竟是哪個禽獸動了我的截圖?

首先我們先看takeSnapshot的入口函數是在MonkeyDevice這個class里面的(因為所有的代碼都是反編譯的,所以代碼排版方便可能有點別扭).

MonkeyDevice.class takeSnapshot():

/*     */   @MonkeyRunnerExported(doc="Gets the device's screen buffer, yielding a screen capture of the entire display.", returns="A MonkeyImage object (a bitmap wrapper)") /*     */   public MonkeyImage takeSnapshot() /*     */   { /*  92 */     IChimpImage image = this.impl.takeSnapshot(); /*  93 */     return new MonkeyImage(image); /*     */   }
這是我們的monkeyrunner測試腳本嘗試去截屏的入口函數,所做的事情大概如下

  • 調用MonkeyDevice的成員變量impl的takeSnapshot()函數(往下我們會看impl是怎么傳進來的)去獲得截圖并賦予給IChimpImage的變量
  • 把截圖轉換成MonkeyImage并返回給用戶
這里重點是impl這個變量是怎么回事,它是在MonkeyDevice的構造函數中被賦值的:
public MonkeyDevice(IChimpDevice impl) /*     */   { /*  75 */     this.impl = impl; /*     */   }
其中IChimpDevice是一個接口,里面定義好了MonkeyDevice需要和目標測試機器通訊的規范:
public abstract interface IChimpDevice {   public abstract ChimpManager getManager();      public abstract void dispose();      public abstract HierarchyViewer getHierarchyViewer();      public abstract IChimpImage takeSnapshot();      public abstract void reboot(@Nullable String paramString);      public abstract Collection<String> getPropertyList();      public abstract String getProperty(String paramString);      public abstract String getSystemProperty(String paramString);      public abstract void touch(int paramInt1, int paramInt2, TouchPressType paramTouchPressType);      public abstract void press(String paramString, TouchPressType paramTouchPressType);      public abstract void press(PhysicalButton paramPhysicalButton, TouchPressType paramTouchPressType);      public abstract void drag(int paramInt1, int paramInt2, int paramInt3, int paramInt4, int paramInt5, long paramLong);      public abstract void type(String paramString);      public abstract String shell(String paramString);      public abstract String shell(String paramString, int paramInt);      public abstract boolean installPackage(String paramString);      public abstract boolean removePackage(String paramString);      public abstract void startActivity(@Nullable String paramString1, @Nullable String paramString2, @Nullable String paramString3, @Nullable String paramString4, Collection<String> paramCollection, Map<String, Object> paramMap, @Nullable String paramString5, int paramInt);      public abstract void broadcastIntent(@Nullable String paramString1, @Nullable String paramString2, @Nullable String paramString3, @Nullable String paramString4, Collection<String> paramCollection, Map<String, Object> paramMap, @Nullable String paramString5, int paramInt);      public abstract Map<String, Object> instrument(String paramString, Map<String, Object> paramMap);      public abstract void wake();      public abstract Collection<String> getViewIdList();      public abstract IChimpView getView(ISelector paramISelector);      public abstract IChimpView getRootView();      public abstract Collection<IChimpView> getViews(IMultiSelector paramIMultiSelector); }
MonkeyDevice的構造函數運用了面向對象的多態技術把某一個實現了IChimpDevice接口的對象賦予給成員函數IChimpDevice類型的impl成員變量,那么“某一個設備對象”又是在哪里傳進來的呢?
在我們的測試代碼中我們很清楚一個MonkeyDevice對象的初始化都不是直接調用構造函數實現的,而是通過調用MonkeyRunner實例的waitForConnection實現的,代碼如下:
/*     */   @MonkeyRunnerExported(doc="Waits for the workstation to connect to the device.", args={"timeout", "deviceId"}, argDocs={"The timeout in seconds to wait. The default is to wait indefinitely.", "A regular expression that specifies the device name. See the documentation for 'adb' in the Developer Guide to learn more about device names."}, returns="A ChimpDevice object representing the connected device.") /*     */   public static MonkeyDevice waitForConnection(PyObject[] args, String[] kws) /*     */   { /*  64 */     ArgParser ap = JythonUtils.createArgParser(args, kws); /*  65 */     Preconditions.checkNotNull(ap); /*     */     long timeoutMs; /*     */     try /*     */     { /*  69 */       double timeoutInSecs = JythonUtils.getFloat(ap, 0); /*  70 */       timeoutMs = (timeoutInSecs * 1000.0D); /*     */     } catch (PyException e) { /*  72 */       timeoutMs = Long.MAX_VALUE; /*     */     } /*     */      /*  75 */     IChimpDevice device = chimpchat.waitForConnection(timeoutMs, ap.getString(1, ".*")); /*     */      /*  77 */     MonkeyDevice chimpDevice = new MonkeyDevice(device); /*  78 */     return chimpDevice; /*     */   }
該函數所做的事情就是根據用戶輸入的函數等待連接上一個測試設備然后返回設備并賦值給上面的MonkeyDevice中的impl成員變量。返回的device是通過chimpchat.jar這個庫里面的com.android.chimpchat.ChimpChat模塊中的waitForConnection方法實現的:
/*     */   public IChimpDevice waitForConnection(long timeoutMs, String deviceId) /*     */   { /*  91 */     return this.mBackend.waitForConnection(timeoutMs, deviceId); /*     */   }
這里面又調用了ChimpChat這個類的成員變量mBackend的waitForConnection方法來獲得設備,這個變量是在ChimpChat的構造函數初始化的:
/*     */   private ChimpChat(IChimpBackend backend) /*     */   { /*  39 */     this.mBackend = backend; /*     */   }
那么這個backend參數又是從哪里傳進來的呢?也就是說ChimpChat的構造函數是在哪里被調用的呢?其實就是在ChimpChat里面的getInstance的兩個重載方法里面:
/*     */   public static ChimpChat getInstance(Map<String, String> options) /*     */   { /*  48 */     sAdbLocation = (String)options.get("adbLocation"); /*  49 */     sNoInitAdb = Boolean.valueOf((String)options.get("noInitAdb")).booleanValue(); /*     */      /*  51 */     IChimpBackend backend = createBackendByName((String)options.get("backend")); /*  52 */     if (backend == null) { /*  53 */       return null; /*     */     } /*  55 */     ChimpChat chimpchat = new ChimpChat(backend); /*  56 */     return chimpchat; /*     */   } /*     */    /*     */  /*     */  /*     */   public static ChimpChat getInstance() /*     */   { /*  63 */     Map<String, String> options = new TreeMap(); /*  64 */     options.put("backend", "adb"); /*  65 */     return getInstance(options); /*     */   }
從代碼可以看到backend最終是通過createBackendByName這個方法進行初始化的,那么我們看下該方法做了什么事情:
/*     */   private static IChimpBackend createBackendByName(String backendName) /*     */   { /*  77 */     if ("adb".equals(backendName)) { /*  78 */       return new AdbBackend(sAdbLocation, sNoInitAdb); /*     */     } /*  80 */     return null; /*     */   }
其實它最終實例化的就是ChimpChat.jar庫里面的AdbBackend這個Class。其實這個類就是封裝了adb的一個wrapper類。
MonkeyRunner源碼分析之-誰動了我的截圖?

到了現在我們終于定位到ChimpChat這個類里面的成員變量mBackend實際上就是AdbBackend了。那么我們就要去看下它里面的waitForConnection方法究竟是如何獲得一個接口是IChimpDevice的device的(也就是我們文章開頭描述的impl這個MonkeyDevice的成員變量).
/*     */   public IChimpDevice waitForConnection(long timeoutMs, String deviceIdRegex) /*     */   { /*     */     do { /* 119 */       IDevice device = findAttachedDevice(deviceIdRegex); /*     */        /* 121 */       if ((device != null) && (device.getState() == IDevice.DeviceState.ONLINE)) { /* 122 */         IChimpDevice chimpDevice = new AdbChimpDevice(device); /* 123 */         this.devices.add(chimpDevice); /* 124 */         return chimpDevice; /*     */       } /*     */       try /*     */       { /* 128 */         Thread.sleep(200L); /*     */       } catch (InterruptedException e) { /* 130 */         LOG.log(Level.SEVERE, "Error sleeping", e); /*     */       } /* 132 */       timeoutMs -= 200L; /* 133 */     } while (timeoutMs > 0L); /*     */      /*     */  /* 136 */     return null; /*     */   }
方法首先通過findAttachedDevice方法獲得目標設備(其實該方法里面所做的事情可以類比直接執行命令"adb devices",下文有更詳細的描述), 如果該設備存在且是ONLINE狀態(關于各總狀態的描述請查看上一篇文章《adb概覽及協議參考》)的話就去實例化一個AdbChimpDevice設備對象并返回。
經過以上的一大堆描述,最終我們的目的就是確定文章開頭的takeSnapshot入口函數所用到的獲取截圖的device(impl)究竟是什么device,這里我們終于確定了就是ChimChat.jar這個庫里面的AdbChimpDevice這個設備。
IChimpImage image = this.impl.takeSnapshot();

2. 大猩猩是如何通過AdbChimpDevice進行怒吼傳遞信息的

其實chimpchat這個大猩猩并不是最終處理我們的截圖的庫,細究下去會發現AdbChimpDevice其實只是相當于一個信息的傳遞著的角色,只是過程中加入了自己的一些特有信息而已。這就好比大猩猩在原始森林中沒有通訊設備,只能使用原始的怒吼來通知伙伴有危險等情況了。
既然我們已經定位到截圖設備是AdbChimpDevice,那么我們就去看看它里面的tapeSnapshot方法是怎么實現的:
/*     */   public IChimpImage takeSnapshot() /*     */   { /*     */     try { /* 209 */       return new AdbChimpImage(this.device.getScreenshot()); /*     */     } catch (TimeoutException e) { /* 211 */       LOG.log(Level.SEVERE, "Unable to take snapshot", e); /* 212 */       return null; /*     */     } catch (AdbCommandRejectedException e) { /* 214 */       LOG.log(Level.SEVERE, "Unable to take snapshot", e); /* 215 */       return null; /*     */     } catch (IOException e) { /* 217 */       LOG.log(Level.SEVERE, "Unable to take snapshot", e); } /* 218 */     return null; /*     */   }
方法代碼很少,一眼就可以看到它是調用了自己的成員變量device的getScreenshot這個方法獲得截圖然后轉換成AdbChimpImage,至于怎么轉換的我們不需要去管它,無非就是不同的類如何一層層繼承,最終如何通過多態繼承機制進行轉換而已。
這里我們關鍵是先去找到成員變量device又是什么設備,它里面的截圖又是怎么回事。
繼續分析代碼可以看到該device變量也是在AdbChimpDevice的構造函數中進行定義的:
/*     */   public AdbChimpDevice(IDevice device) /*     */   { /*  70 */     this.device = device; /*  71 */     this.manager = createManager("127.0.0.1", 12345); /*     */      /*  73 */     Preconditions.checkNotNull(this.manager); /*     */   }
那么我們一如既往的需要找到該參數的device是在哪里傳進來的。相信大家還記得上一章節描述的AdbBackend是如何實例化AdbChimpDevice的,在實例化之前會調用一個findAttachedDevice的方法的先獲得一個實現了IDevice接口的對象,然后傳給這里的AdbChimpDevice構造函數進行實例化的。
/*     */   public IChimpDevice waitForConnection(long timeoutMs, String deviceIdRegex) /*     */   { /*     */     do { /* 119 */       IDevice device = findAttachedDevice(deviceIdRegex); /*     */        /* 121 */       if ((device != null) && (device.getState() == IDevice.DeviceState.ONLINE)) { /* 122 */         IChimpDevice chimpDevice = new AdbChimpDevice(device); /* 123 */         this.devices.add(chimpDevice); /* 124 */         return chimpDevice; /*     */       } /*     */       try /*     */       { /* 128 */         Thread.sleep(200L); /*     */       } catch (InterruptedException e) { /* 130 */         LOG.log(Level.SEVERE, "Error sleeping", e); /*     */       } /* 132 */       timeoutMs -= 200L; /* 133 */     } while (timeoutMs > 0L); /*     */      /*     */  /* 136 */     return null; /*     */   }
那么我們就需要分析下findAttachedDevice這個方法究竟找到的是怎么樣的一個IDevice對象了,在分析之前先要注意這里的IDevice接口定義的都是一些底層的操作目標設備的接口方法,由此可知我們已經慢慢接近真相了。以下是其代碼片段:
/*     */ public abstract interface IDevice extends IShellEnabledDevice /*     */ { /*     */   public static final String PROP_BUILD_VERSION = "ro.build.version.release"; /*     */   public static final String PROP_BUILD_API_LEVEL = "ro.build.version.sdk"; /*     */   public static final String PROP_BUILD_CODENAME = "ro.build.version.codename"; /*     */   public static final String PROP_DEVICE_MODEL = "ro.product.model"; /*     */   public static final String PROP_DEVICE_MANUFACTURER = "ro.product.manufacturer"; /*     */   public static final String PROP_DEVICE_CPU_ABI = "ro.product.cpu.abi"; /*     */   public static final String PROP_DEVICE_CPU_ABI2 = "ro.product.cpu.abi2"; /*     */   public static final String PROP_BUILD_CHARACTERISTICS = "ro.build.characteristics"; /*     */   public static final String PROP_DEBUGGABLE = "ro.debuggable"; /*     */   public static final String FIRST_EMULATOR_SN = "emulator-5554"; /*     */   public static final int CHANGE_STATE = 1; /*     */   public static final int CHANGE_CLIENT_LIST = 2; /*     */   public static final int CHANGE_BUILD_INFO = 4; /*     */   @Deprecated /*     */   public static final String PROP_BUILD_VERSION_NUMBER = "ro.build.version.sdk"; /*     */   public static final String MNT_EXTERNAL_STORAGE = "EXTERNAL_STORAGE"; /*     */   public static final String MNT_ROOT = "ANDROID_ROOT"; /*     */   public static final String MNT_DATA = "ANDROID_DATA"; /*     */    /*     */   @NonNull /*     */   public abstract String getSerialNumber(); /*     */    /*     */   @Nullable /*     */   public abstract String getAvdName(); /*     */    /*     */   public abstract DeviceState getState(); /*     */    /*     */   public abstract java.util.Map<String, String> getProperties(); /*     */    /*     */   public abstract int getPropertyCount(); /*     */    /*     */   public abstract String getProperty(String paramString); /*     */    /*     */   public abstract boolean arePropertiesSet(); /*     */    /*     */   public abstract String getPropertySync(String paramString) throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException; /*     */    /*     */   public abstract String getPropertyCacheOrSync(String paramString) throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException; /*     */    /*     */   public abstract boolean supportsFeature(@NonNull Feature paramFeature); /*     */    /*     */   public static enum Feature /*     */   { /*  53 */     SCREEN_RECORD,  /*  54 */     PROCSTATS; /*     */      /*     */     private Feature() {} /*     */   } /*     */    /*  59 */   public static enum HardwareFeature { WATCH("watch"); /*     */      /*     */     private final String mCharacteristic; /*     */      /*     */     private HardwareFeature(String characteristic) { /*  64 */       this.mCharacteristic = characteristic; /*     */     }
我們繼續看findAttachedDevice的源碼:
/*     */   private IDevice findAttachedDevice(String deviceIdRegex) /*     */   { /* 101 */     Pattern pattern = Pattern.compile(deviceIdRegex); /* 102 */     for (IDevice device : this.bridge.getDevices()) { /* 103 */       String serialNumber = device.getSerialNumber(); /* 104 */       if (pattern.matcher(serialNumber).matches()) { /* 105 */         return device; /*     */       } /*     */     } /* 108 */     return null; /*     */   }
簡單明了,一個循環所有列出來的(好比"adb devices -l"命令)所有設備,找到想要的那個。這里的AdbChimDevice里面的this.bridge成員變量其實代表的就是一個通過socket連接到adb服務器的一個adb客戶端,這就是為什么我之前說chimpchat的AdbBackend事實上就是adb的一個wrapper。
往下我們繼續跟蹤看這個adb的wrapper是如何getDevices的,代碼跳轉到ddmlib這個庫里面的AndroidDebugBridge這個class:
/*      */   public IDevice[] getDevices() /*      */   { /*  484 */     synchronized (sLock) { /*  485 */       if (this.mDeviceMonitor != null) { /*  486 */         return this.mDeviceMonitor.getDevices(); /*      */       } /*      */     }
里面調用了AndroidDebugBridge的成員變量mDeviceMonitor的getDevices函數,那么我們看下這個成員變量究竟是定義成什么類型的:
/*      */   private DeviceMonitor mDeviceMonitor;
然后我們再跑到該DeviceMonitor類中去查看getDevices這個方法的代碼:
/*     */   Device[] getDevices() /*     */   { /* 131 */     synchronized (this.mDevices) { /* 132 */       return (Device[])this.mDevices.toArray(new Device[this.mDevices.size()]); /*     */     } /*     */   }
代碼是取得成員函數mDevices的所有device列表然后返回,那么我們看下mDevices究竟是什么類型的列表:
/*  60 */   private final ArrayList<Device> mDevices = new ArrayList();
最終mDevices實際上是Device類型的一個列表,那么找到Device這個類就達到我們的目標了,就知道本章節開始的“return new AdbChimpImage(this.device.getScreenshot());”中的那個device究竟是什么device,也就是說知道去哪里去查找getScreenshot這個方法是在什么地方實現的了。
最終定位到com.android.ddmlib.Device這個類。
通過這個章節的分析可以看出來在chimpchat里面的AdbChimpDevice其實不是最終負責去獲得截圖的類,它只是作為一個信息重新包裝的再分發的中轉站而已。ddmlib才是最終處理我們的截圖的庫。

3. ddmlib庫如何通過請求adb服務器讀取FrameBuffer設備進行截圖

好我們繼續看Device這個類究竟是如何獲得我們的截圖的:
/*      */   public RawImage getScreenshot() /*      */     throws TimeoutException, AdbCommandRejectedException, IOException /*      */   { /*  558 */     return AdbHelper.getFrameBuffer(AndroidDebugBridge.getSocketAddress(), this); /*      */   }
里面只有一行代碼,通過調用工具類AdbHelper的getFramebBuffer方法來獲得截圖,其實這里單看名字getFrameBuffer就應該猜到MonkeyRunner究竟是怎么截圖的了,不就是讀取目標機器的FrameBuffer 設備的緩存嘛,至于FrameBuffer設備的詳細解析請大家自行google了,這里你只需要知道它是一個可以映射到用戶空間的代表顯卡內容的一個設備,獲得它就相當于獲得當前的屏幕截圖就足夠了。
好,我們還是繼續往下分析,看getFrameBuffer是怎么實現截屏的,定位到ddmlib的AdbHelper類:
/*     */   static RawImage getFrameBuffer(InetSocketAddress adbSockAddr, Device device) /*     */     throws TimeoutException, AdbCommandRejectedException, IOException /*     */   { /* 272 */     RawImage imageParams = new RawImage(); /* 273 */     byte[] request = formAdbRequest("framebuffer:"); /* 274 */     byte[] nudge = { 0 }; /*     */      /*     */  /*     */  /*     */  /* 279 */     SocketChannel adbChan = null; /*     */     try { /* 281 */       adbChan = SocketChannel.open(adbSockAddr); /* 282 */       adbChan.configureBlocking(false); /*     */        /*     */  /*     */  /* 286 */       setDevice(adbChan, device); /*     */        /* 288 */       write(adbChan, request); /*     */        /* 290 */       AdbResponse resp = readAdbResponse(adbChan, false); /* 291 */       if (!resp.okay) { /* 292 */         throw new AdbCommandRejectedException(resp.message); /*     */       } /*     */        /*     */  /* 296 */       byte[] reply = new byte[4]; /* 297 */       read(adbChan, reply); /*     */        /* 299 */       ByteBuffer buf = ByteBuffer.wrap(reply); /* 300 */       buf.order(ByteOrder.LITTLE_ENDIAN); /*     */        /* 302 */       int version = buf.getInt(); /*     */        /*     */  /* 305 */       int headerSize = RawImage.getHeaderSize(version); /*     */        /*     */  /* 308 */       reply = new byte[headerSize * 4]; /* 309 */       read(adbChan, reply); /*     */        /* 311 */       buf = ByteBuffer.wrap(reply); /* 312 */       buf.order(ByteOrder.LITTLE_ENDIAN); /*     */        /*     */  /* 315 */       if (!imageParams.readHeader(version, buf)) { /* 316 */         Log.e("Screenshot", "Unsupported protocol: " + version); /* 317 */         return null; /*     */       } /*     */        /* 320 */       Log.d("ddms", "image params: bpp=" + imageParams.bpp + ", size=" + imageParams.size + ", width=" + imageParams.width + ", height=" + imageParams.height); /*     */        /*     */  /*     */  /* 324 */       write(adbChan, nudge); /*     */        /* 326 */       reply = new byte[imageParams.size]; /* 327 */       read(adbChan, reply); /*     */        /* 329 */       imageParams.data = reply; /*     */     } finally { /* 331 */       if (adbChan != null) { /* 332 */         adbChan.close(); /*     */       } /*     */     } /*     */      /* 336 */     return imageParams; /*     */   }
其實過程就是根據adb協議整合命令請求字串"framebuffer:",然后連接到adb服務器把請求發送到adb服務器,最終發送到設備上的adb守護進程通過讀取framebuffer設備獲得當前截圖。
具體流程和adb協議詳細解析請看本人上一篇文章:
  • adb概覽及協議參考


 

作者

自主博客

微信

CSDN

天地會珠海分舵

http://techgogogo.com


服務號:TechGoGoGo

掃描碼:

MonkeyRunner源碼分析之-誰動了我的截圖?

向AI問一下細節

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

AI

富宁县| 凤城市| 东乡县| 德阳市| 紫金县| 周宁县| 巍山| 磐石市| 新沂市| 平顺县| 长顺县| 林甸县| 普安县| 宣恩县| 普兰县| 杭锦旗| 宜兴市| 渑池县| 荆州市| 稻城县| 玛多县| 松原市| 萝北县| 新巴尔虎左旗| 湖南省| 桂林市| 双辽市| 洪泽县| 藁城市| 府谷县| 黔江区| 阜宁县| 康乐县| 宣恩县| 抚宁县| 阳西县| 天祝| 庆城县| 余庆县| 宜兰县| 霞浦县|