您好,登錄后才能下訂單哦!
本文章的目的是通過分析monkeyrunner是如何實現截屏來作為一個例子嘗試投石問路為下一篇文章做準備,往下一篇文章本人有意分析下monkeyrunner究竟是如何和目標測試機器通信的,所以最好的辦法本人認為是先跟蹤一個調用示例從高層到底層進行分析,本人以前分析操作系統源代碼的時候就是先從用戶層的write這個api入手,然后一路打通到vfs文件系統層,到設備驅動層的,其效果比單純的理論描述更容易理解和接受。
在整個代碼分析過程中會設計到以下的庫,希望想動手分析的同學們準備好源碼:
首先我們先看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測試腳本嘗試去截屏的入口函數,所做的事情大概如下
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成員變量,那么“某一個設備對象”又是在哪里傳進來的呢?
/* */ @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類。
/* */ 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設備對象并返回。
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; /* */ }方法代碼很少,一眼就可以看到它是調用了自己的成員變量device的getScreenshot這個方法獲得截圖然后轉換成AdbChimpImage,至于怎么轉換的我們不需要去管它,無非就是不同的類如何一層層繼承,最終如何通過多態繼承機制進行轉換而已。
/* */ 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。
/* */ 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這個方法是在什么地方實現的了。
/* */ public RawImage getScreenshot() /* */ throws TimeoutException, AdbCommandRejectedException, IOException /* */ { /* 558 */ return AdbHelper.getFrameBuffer(AndroidDebugBridge.getSocketAddress(), this); /* */ }里面只有一行代碼,通過調用工具類AdbHelper的getFramebBuffer方法來獲得截圖,其實這里單看名字getFrameBuffer就應該猜到MonkeyRunner究竟是怎么截圖的了,不就是讀取目標機器的FrameBuffer 設備的緩存嘛,至于FrameBuffer設備的詳細解析請大家自行google了,這里你只需要知道它是一個可以映射到用戶空間的代表顯卡內容的一個設備,獲得它就相當于獲得當前的屏幕截圖就足夠了。
/* */ 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設備獲得當前截圖。
作者 | 自主博客 | 微信 | CSDN |
天地會珠海分舵 | http://techgogogo.com | 服務號:TechGoGoGo 掃描碼:
| 向AI問一下細節 免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。 猜你喜歡最新資訊相關推薦相關標簽AI
助 手
黄浦区|
建阳市|
丰城市|
商河县|
荥经县|
景德镇市|
平阳县|
衡阳县|
汕头市|
河西区|
阿图什市|
鲁山县|
九龙城区|
文昌市|
中超|
沧州市|
桦川县|
九龙县|
富阳市|
江达县|
大埔区|
都匀市|
班戈县|
道孚县|
工布江达县|
若尔盖县|
平定县|
新平|
华亭县|
利川市|
平阴县|
寻甸|
洪雅县|
如皋市|
米林县|
菏泽市|
河津市|
岑溪市|
石柱|
蓝山县|
元江|
|