溫馨提示×

溫馨提示×

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

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

MonkeyRunner源碼分析之-誰動(dòng)了我的截圖?

發(fā)布時(shí)間:2020-08-02 11:21:42 來源:網(wǎng)絡(luò) 閱讀:267 作者:zhukev 欄目:移動(dòng)開發(fā)

本文章的目的是通過分析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)備好源碼:

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

1. 究竟是哪個(gè)禽獸動(dòng)了我的截圖?

首先我們先看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ù),所做的事情大概如下

  • 調(diào)用MonkeyDevice的成員變量impl的takeSnapshot()函數(shù)(往下我們會(huì)看impl是怎么傳進(jìn)來的)去獲得截圖并賦予給IChimpImage的變量
  • 把截圖轉(zhuǎn)換成MonkeyImage并返回給用戶
這里重點(diǎn)是impl這個(gè)變量是怎么回事,它是在MonkeyDevice的構(gòu)造函數(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)來的呢?
在我們的測試代碼中我們很清楚一個(gè)MonkeyDevice對象的初始化都不是直接調(diào)用構(gòu)造函數(shù)實(shí)現(xiàn)的,而是通過調(diào)用MonkeyRunner實(shí)例的waitForConnection實(shí)現(xià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類。
MonkeyRunner源碼分析之-誰動(dòng)了我的截圖?

到了現(xiàn)在我們終于定位到ChimpChat這個(gè)類里面的成員變量mBackend實(shí)際上就是AdbBackend了。那么我們就要去看下它里面的waitForConnection方法究竟是如何獲得一個(gè)接口是IChimpDevice的device的(也就是我們文章開頭描述的impl這個(gè)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方法獲得目標(biāo)設(shè)備(其實(shí)該方法里面所做的事情可以類比直接執(zhí)行命令"adb devices",下文有更詳細(xì)的描述), 如果該設(shè)備存在且是ONLINE狀態(tài)(關(guān)于各總狀態(tài)的描述請查看上一篇文章《adb概覽及協(xié)議參考》)的話就去實(shí)例化一個(gè)AdbChimpDevice設(shè)備對象并返回。
經(jīng)過以上的一大堆描述,最終我們的目的就是確定文章開頭的takeSnapshot入口函數(shù)所用到的獲取截圖的device(impl)究竟是什么device,這里我們終于確定了就是ChimChat.jar這個(gè)庫里面的AdbChimpDevice這個(gè)設(shè)備。
IChimpImage image = this.impl.takeSnapshot();

2. 大猩猩是如何通過AdbChimpDevice進(jìn)行怒吼傳遞信息的

其實(shí)chimpchat這個(gè)大猩猩并不是最終處理我們的截圖的庫,細(xì)究下去會(huì)發(fā)現(xiàn)AdbChimpDevice其實(shí)只是相當(dāng)于一個(gè)信息的傳遞著的角色,只是過程中加入了自己的一些特有信息而已。這就好比大猩猩在原始森林中沒有通訊設(shè)備,只能使用原始的怒吼來通知伙伴有危險(xiǎn)等情況了。
既然我們已經(jīng)定位到截圖設(shè)備是AdbChimpDevice,那么我們就去看看它里面的tapeSnapshot方法是怎么實(shí)現(xiàn)的:
/*     */   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)換而已。
這里我們關(guān)鍵是先去找到成員變量device又是什么設(shè)備,它里面的截圖又是怎么回事。
繼續(xù)分析代碼可以看到該device變量也是在AdbChimpDevice的構(gòu)造函數(shù)中進(jì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。
往下我們繼續(xù)跟蹤看這個(gè)adb的wrapper是如何getDevices的,代碼跳轉(zhuǎn)到ddmlib這個(gè)庫里面的AndroidDebugBridge這個(gè)class:
/*      */   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)的了。
最終定位到com.android.ddmlib.Device這個(gè)類。
通過這個(gè)章節(jié)的分析可以看出來在chimpchat里面的AdbChimpDevice其實(shí)不是最終負(fù)責(zé)去獲得截圖的類,它只是作為一個(gè)信息重新包裝的再分發(fā)的中轉(zhuǎn)站而已。ddmlib才是最終處理我們的截圖的庫。

3. ddmlib庫如何通過請求adb服務(wù)器讀取FrameBuffer設(shè)備進(jìn)行截圖

好我們繼續(xù)看Device這個(gè)類究竟是如何獲得我們的截圖的:
/*      */   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)前的屏幕截圖就足夠了。
好,我們還是繼續(xù)往下分析,看getFrameBuffer是怎么實(shí)現(xiàn)截屏的,定位到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; /*     */   }
其實(shí)過程就是根據(jù)adb協(xié)議整合命令請求字串"framebuffer:",然后連接到adb服務(wù)器把請求發(fā)送到adb服務(wù)器,最終發(fā)送到設(shè)備上的adb守護(hù)進(jìn)程通過讀取framebuffer設(shè)備獲得當(dāng)前截圖。
具體流程和adb協(xié)議詳細(xì)解析請看本人上一篇文章:
  • adb概覽及協(xié)議參考


  1.  

    作者

    自主博客

    微信

    CSDN

    天地會(huì)珠海分舵

    http://techgogogo.com


    服務(wù)號:TechGoGoGo

    掃描碼:

    MonkeyRunner源碼分析之-誰動(dòng)了我的截圖?

    向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)容。

    AI