您好,登錄后才能下訂單哦!
本文章的目的是通過分析monkeyrunner是如何實(shí)現(xiàn)截屏來作為一個(gè)例子嘗試投石問路為下一篇文章做準(zhǔn)備,往下一篇文章本人有意分析下monkeyrunner究竟是如何和目標(biāo)測試機(jī)器通信的,所以最好的辦法本人認(rèn)為是先跟蹤一個(gè)調(diào)用示例從高層到底層進(jìn)行分析,本人以前分析操作系統(tǒng)源代碼的時(shí)候就是先從用戶層的write這個(gè)api入手,然后一路打通到vfs文件系統(tǒng)層,到設(shè)備驅(qū)動(dòng)層的,其效果比單純的理論描述更容易理解和接受。
在整個(gè)代碼分析過程中會(huì)設(shè)計(jì)到以下的庫,希望想動(dòng)手分析的同學(xué)們準(zhǔn)備好源碼:
首先我們先看takeSnapshot的入口函數(shù)是在MonkeyDevice這個(gè)class里面的(因?yàn)樗械拇a都是反編譯的,所以代碼排版方便可能有點(diǎn)別扭).
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測試腳本嘗試去截屏的入口函數(shù),所做的事情大概如下
public MonkeyDevice(IChimpDevice impl) /* */ { /* 75 */ this.impl = impl; /* */ }其中IChimpDevice是一個(gè)接口,里面定義好了MonkeyDevice需要和目標(biāo)測試機(jī)器通訊的規(guī)范:
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的構(gòu)造函數(shù)運(yùn)用了面向?qū)ο蟮亩鄳B(tài)技術(shù)把某一個(gè)實(shí)現(xiàn)了IChimpDevice接口的對象賦予給成員函數(shù)IChimpDevice類型的impl成員變量,那么“某一個(gè)設(shè)備對象”又是在哪里傳進(jìn)來的呢?
/* */ @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; /* */ }該函數(shù)所做的事情就是根據(jù)用戶輸入的函數(shù)等待連接上一個(gè)測試設(shè)備然后返回設(shè)備并賦值給上面的MonkeyDevice中的impl成員變量。返回的device是通過chimpchat.jar這個(gè)庫里面的com.android.chimpchat.ChimpChat模塊中的waitForConnection方法實(shí)現(xiàn)的:
/* */ public IChimpDevice waitForConnection(long timeoutMs, String deviceId) /* */ { /* 91 */ return this.mBackend.waitForConnection(timeoutMs, deviceId); /* */ }這里面又調(diào)用了ChimpChat這個(gè)類的成員變量mBackend的waitForConnection方法來獲得設(shè)備,這個(gè)變量是在ChimpChat的構(gòu)造函數(shù)初始化的:
/* */ private ChimpChat(IChimpBackend backend) /* */ { /* 39 */ this.mBackend = backend; /* */ }那么這個(gè)backend參數(shù)又是從哪里傳進(jìn)來的呢?也就是說ChimpChat的構(gòu)造函數(shù)是在哪里被調(diào)用的呢?其實(shí)就是在ChimpChat里面的getInstance的兩個(gè)重載方法里面:
/* */ 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這個(gè)方法進(jìn)行初始化的,那么我們看下該方法做了什么事情:
/* */ private static IChimpBackend createBackendByName(String backendName) /* */ { /* 77 */ if ("adb".equals(backendName)) { /* 78 */ return new AdbBackend(sAdbLocation, sNoInitAdb); /* */ } /* 80 */ return null; /* */ }其實(shí)它最終實(shí)例化的就是ChimpChat.jar庫里面的AdbBackend這個(gè)Class。其實(shí)這個(gè)類就是封裝了adb的一個(gè)wrapper類。
/* */ 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方法獲得目標(biāo)設(shè)備(其實(shí)該方法里面所做的事情可以類比直接執(zhí)行命令"adb devices",下文有更詳細(xì)的描述), 如果該設(shè)備存在且是ONLINE狀態(tài)(關(guān)于各總狀態(tài)的描述請查看上一篇文章《adb概覽及協(xié)議參考》)的話就去實(shí)例化一個(gè)AdbChimpDevice設(shè)備對象并返回。
IChimpImage image = this.impl.takeSnapshot();
/* */ 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; /* */ }方法代碼很少,一眼就可以看到它是調(diào)用了自己的成員變量device的getScreenshot這個(gè)方法獲得截圖然后轉(zhuǎn)換成AdbChimpImage,至于怎么轉(zhuǎn)換的我們不需要去管它,無非就是不同的類如何一層層繼承,最終如何通過多態(tài)繼承機(jī)制進(jìn)行轉(zhuǎn)換而已。
/* */ public AdbChimpDevice(IDevice device) /* */ { /* 70 */ this.device = device; /* 71 */ this.manager = createManager("127.0.0.1", 12345); /* */ /* 73 */ Preconditions.checkNotNull(this.manager); /* */ }那么我們一如既往的需要找到該參數(shù)的device是在哪里傳進(jìn)來的。相信大家還記得上一章節(jié)描述的AdbBackend是如何實(shí)例化AdbChimpDevice的,在實(shí)例化之前會(huì)調(diào)用一個(gè)findAttachedDevice的方法的先獲得一個(gè)實(shí)現(xiàn)了IDevice接口的對象,然后傳給這里的AdbChimpDevice構(gòu)造函數(shù)進(jìn)行實(shí)例化的。
/* */ 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這個(gè)方法究竟找到的是怎么樣的一個(gè)IDevice對象了,在分析之前先要注意這里的IDevice接口定義的都是一些底層的操作目標(biāo)設(shè)備的接口方法,由此可知我們已經(jīng)慢慢接近真相了。以下是其代碼片段:
/* */ 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; /* */ }我們繼續(xù)看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; /* */ }簡單明了,一個(gè)循環(huán)所有列出來的(好比"adb devices -l"命令)所有設(shè)備,找到想要的那個(gè)。這里的AdbChimDevice里面的this.bridge成員變量其實(shí)代表的就是一個(gè)通過socket連接到adb服務(wù)器的一個(gè)adb客戶端,這就是為什么我之前說chimpchat的AdbBackend事實(shí)上就是adb的一個(gè)wrapper。
/* */ public IDevice[] getDevices() /* */ { /* 484 */ synchronized (sLock) { /* 485 */ if (this.mDeviceMonitor != null) { /* 486 */ return this.mDeviceMonitor.getDevices(); /* */ } /* */ }里面調(diào)用了AndroidDebugBridge的成員變量mDeviceMonitor的getDevices函數(shù),那么我們看下這個(gè)成員變量究竟是定義成什么類型的:
/* */ private DeviceMonitor mDeviceMonitor;然后我們再跑到該DeviceMonitor類中去查看getDevices這個(gè)方法的代碼:
/* */ Device[] getDevices() /* */ { /* 131 */ synchronized (this.mDevices) { /* 132 */ return (Device[])this.mDevices.toArray(new Device[this.mDevices.size()]); /* */ } /* */ }代碼是取得成員函數(shù)mDevices的所有device列表然后返回,那么我們看下mDevices究竟是什么類型的列表:
/* 60 */ private final ArrayList<Device> mDevices = new ArrayList();最終mDevices實(shí)際上是Device類型的一個(gè)列表,那么找到Device這個(gè)類就達(dá)到我們的目標(biāo)了,就知道本章節(jié)開始的“return new AdbChimpImage(this.device.getScreenshot());”中的那個(gè)device究竟是什么device,也就是說知道去哪里去查找getScreenshot這個(gè)方法是在什么地方實(shí)現(xiàn)的了。
/* */ public RawImage getScreenshot() /* */ throws TimeoutException, AdbCommandRejectedException, IOException /* */ { /* 558 */ return AdbHelper.getFrameBuffer(AndroidDebugBridge.getSocketAddress(), this); /* */ }里面只有一行代碼,通過調(diào)用工具類AdbHelper的getFramebBuffer方法來獲得截圖,其實(shí)這里單看名字getFrameBuffer就應(yīng)該猜到MonkeyRunner究竟是怎么截圖的了,不就是讀取目標(biāo)機(jī)器的FrameBuffer 設(shè)備的緩存嘛,至于FrameBuffer設(shè)備的詳細(xì)解析請大家自行g(shù)oogle了,這里你只需要知道它是一個(gè)可以映射到用戶空間的代表顯卡內(nèi)容的一個(gè)設(shè)備,獲得它就相當(dāng)于獲得當(dāng)前的屏幕截圖就足夠了。
/* */ 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; /* */ }其實(shí)過程就是根據(jù)adb協(xié)議整合命令請求字串"framebuffer:",然后連接到adb服務(wù)器把請求發(fā)送到adb服務(wù)器,最終發(fā)送到設(shè)備上的adb守護(hù)進(jìn)程通過讀取framebuffer設(shè)備獲得當(dāng)前截圖。
作者 | 自主博客 | 微信 | CSDN |
天地會(huì)珠海分舵 | http://techgogogo.com | 服務(wù)號:TechGoGoGo 掃描碼:
| 向AI問一下細(xì)節(jié) 免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。 猜你喜歡最新資訊相關(guān)推薦
相關(guān)標(biāo)簽AI
助 手 |