本文來自網易雲社區
作者:孫聖翔
在一張Android手機上截圖有好多辦法,為了能夠高效率的截圖,我幾乎把所有的方法都嘗試了一般。走了好多路,也遇到了好多的問題。
只是想記錄下這其中的不容易。
下面所有的測試都是用的我的三星 S4.
屏幕分辨率 1080x1920
androidviewclient
截圖速度: 4.5s
最開始截圖用的是 google官方提供的純python庫androidviewclient,代碼的地址在 https://github.com/dtmilano/AndroidViewClient
基於adb協議,只能在電腦上用。最初被我用在的一個手游自動化測試工具airtest上面。使用它很簡單,我寫個簡單的例子
from com.dtmilano.android.viewclient import ViewClient c, _ = ViewClient.connectToDeviceOrExit(verbose=False, serialno='10.242.74.241:5555') s = c.takeSnapshot() s.save('snapshot.png', 'PNG')
不過這個python庫也有坑人的地方。它更新到pypi的時候,所有的歷史版本都找不到。使得可以更新過去,但是更新不回來。
最新的版本在有些機器上還跑不了。截圖的時候某些手機還會出現圖片缺少顏色的問題。
screencap
截圖速度: 2.0s
Android手機上自帶有一個截圖工具,一般都是被放在了/system/bin/screencap下。
使用的時候需要在電腦上安裝adb,然后adb shell進入shell環境,使用的時候,需要生成一個臨時圖片在手機上,然后把照片從手機上傳輸回來。
可以寫成一個批處理腳本
@echo off adb shell screencap -p /sdcard/snapshot.png adb pull /sdcard/snapshot.png adb shell rm /sdcard/snapshot.png
這個樣子截圖,要比androidviewclient的穩定性好很多。只是需要生成一個臨時文件,感覺好別扭。
APK程序直接截圖
截圖速度: < 1s
stackoverflow上也有不少代碼例子。apk必須用java寫,意味着我必須學一下java了,買了一本書《Android第一行代碼》。
學習了2個多星期,總算入門了。然后寫了一個手機app截圖。截圖代碼我就不貼了,這個比較長一點,網上也有很多例子。
這種方法截圖效率在1s以內。不過只有在當前App在前台運行的時候才可以截圖。就算寫成Service也不行。
后來想想,截取不到圖也算合理。假如一個App可以截取到其他App運行時的圖片,豈不是越權了,用戶的隱私還怎么保證。
既然這樣,只能放棄了。
ASL
之后有幸看到了google出的一個android-screenshot-library的東西,簡稱ASL。代碼在http://code.google.com/p/android-screenshot-library/
看到這個東西真是讓人欣喜若狂。立馬下載下來試了試,心情立馬就不好了,截圖來的圖,竟然是黑屏。接着又借了4個手機試驗。
結果截圖只有一台手機截出來的圖能看(還是缺少一個顏色通道的那種)。 看看了代碼實現的原理,是直接讀取framebuffer。
這個地方我解釋下:
在linux中,所有的東西通通都可以映射成文件,連屏幕映射成了文件。android的在/dev/graphics/fb0。
通過讀取fb0中的數據,然后在根據一些算法就可以還原出屏幕的圖像了。
還有一個庫, http://code.google.com/p/android-fb2png/ 看代碼原理應該和ASL差不多,不過實現了PC端的一個adb_screenshot的程序。
沒法截圖怎么用啊,放棄吧。 哎
重回screencap, Golang重寫截圖程序。
截圖速度: 1s
好在android是開源的,直接可以翻到screencap實現的源碼。意外的發現他有兩種輸出格式。
一種是png格式 (耗時1.5s)
還有一種是原始的圖片格式(這種原始的格式,跟bmp差不多)。 試驗了下,好使400ms
之前看過一個韓國人寫的remotedroid <https://code.google.com/p/remoteroid/> 截圖速度快的讓人震驚。
所以我在想是不是screencap中png的壓縮算法有問題。參考下代碼中,他輸出的格式。用Go語言寫了一個轉化的程序。
// TakeSnapshot by cmd: /system/bin/screencap func TakeSnapshot() (img *image.RGBA, err error) { scrbf = bytes.NewBuffer(nil) cmd := exec.Command("screencap") cmd.Stdout = scrbf if err = cmd.Run(); err != nil { return } var width, height, format int32 binary.Read(scrbf, binary.LittleEndian, &width) binary.Read(scrbf, binary.LittleEndian, &height) err = binary.Read(scrbf, binary.LittleEndian, &format) if err != nil { return } img = image.NewRGBA(image.Rectangle{image.ZP, image.Point{int(width), int(height)}}) return } func main(){ s, _ := TakeSnapshot() out, _ := os.Create("snapshot.png") defer out.Close() png.Encode(out, s) }
利用總共用時1.2s的樣子。比之前用screencap 2s快了不少哎。感覺似乎還可以更快點。把png改成jpeg試試。
func main(){ s, _ := TakeSnapshot() out, _ := os.Create("snapshot.png") defer out.Close() jpeg.Encode(out, s, jpeg.Options{60}) }
這種方法變成了1.1s, 感覺似乎還可以更快點。 需要稍微復雜點,需要減少內存申請和拷貝的次數。
// TakeSnapshot by cmd: /system/bin/screencap var SCRBUFLEN int func TakeSnapshot() (img *image.RGBA, err error) { var scrbf *bytes.Buffer if SCRBUFLEN == 0 { scrbf = bytes.NewBuffer(nil) } else { scrbf = bytes.NewBuffer(make([]byte, 0, SCRBUFLEN)) } cmd := exec.Command("screencap") cmd.Stdout = scrbf if err = cmd.Run(); err != nil { return } var width, height, format int32 binary.Read(scrbf, binary.LittleEndian, &width) binary.Read(scrbf, binary.LittleEndian, &height) SCRBUFLEN = int(width * height * 4 err = binary.Read(scrbf, binary.LittleEndian, &format) if err != nil { return } w, h := int(width), int(height) img = &image.RGBA{scrbf.Bytes(), 4 * w, image.Rect(0, 0, w, h)} return }
改完后,變成1.0s了。 終於到了還算可以接受的程度。 整理下代碼終於可以讓他拋頭露面了。
https://github.com/netease/airinput
這就是我在做Android截圖的時候,所遇到的大部分問題。要知道截個圖是多么的不容易。 另外想說,請一定不要放棄,總會有辦法的。
網易雲免費體驗館,0成本體驗20+款雲產品!
更多網易研發、產品、運營經驗分享請訪問網易雲社區。
相關文章:
【推薦】 如何實現最佳的跨平台游戲體驗?Unity成亮解密實時渲染
【推薦】 全局腳手架了解一下【fle-cli】
【推薦】 一文了解安卓APP逆向分析與保護機制