源碼:https://github.com/zengfw/LongScreenShot
支持截取微博浇衬、知乎叭披、今日頭條等第三方APP......
先瞅瞅效果圖:
再瞅瞅最終的長(zhǎng)截圖:
我是長(zhǎng)截圖一田轧,瞅瞅嘛...
我是長(zhǎng)截圖二辖源,再瞅一下嘛...
上一周腦子突然冒出長(zhǎng)截圖這個(gè)功能儒洛,想著如何截取如微博精耐,知乎,頭條等這些第三方APP的界面呢琅锻?出于好奇心卦停,花了一周業(yè)余時(shí)間,擼一個(gè)看看恼蓬。
不就是截屏+拼圖惊完,還能有什么難度么?這個(gè)处硬。小槐。。好像確實(shí)是郁油。
Question:
1.如何截屏本股?
Android 5.0 API 21之前,想要系統(tǒng)截屏桐腌,是需要root拄显,不過Android 5.0開始開放了響應(yīng)的截屏接口:
MediaProjection (added in API level 21.)
- A token granting applications the ability to capture screen contents and/or record system audio. The exact capabilities
granted depend on the type of MediaProjection.
2.如何優(yōu)雅的截圖?
懸浮窗那么小案站,難道每次我都得滑一定的距離躬审,然后點(diǎn)一次懸浮窗么,理論上可以蟆盐,但體驗(yàn)不好承边。估計(jì)更多人傾向只要觸摸屏幕就可以截圖,所以選擇監(jiān)聽?wèi)腋〈巴獾挠|屏事件石挂。
3.如何監(jiān)聽?wèi)腋〈翱谕獠康腡ouchEvent博助?
懸浮窗外的觸屏事件都已經(jīng)脫離整個(gè)應(yīng)用了,如何監(jiān)聽呢痹愚?這里確實(shí)卡了些時(shí)間富岳,因?yàn)榇_實(shí)找不到如何捕獲這個(gè)事件的好蛔糯,我感覺這個(gè)問題也是最煩的一個(gè),后來來了點(diǎn)靈感窖式,我設(shè)置一個(gè)全屏的透明背景蚁飒,然后給這個(gè)背景設(shè)置onTouch事件,哦B艽;绰摺!恍然大悟阁簸,以為這樣就可以了爬早?錯(cuò)!强窖!這樣會(huì)出現(xiàn)整個(gè)手機(jī)的事件都將被這個(gè)透明背景攔截凸椿,無法傳遞到手機(jī)桌面,如果非開發(fā)人員安裝了這個(gè)軟件翅溺。脑漫。,告訴他咙崎,重新開機(jī)吧优幸。。褪猛。所以翻了下WindowManager的源碼网杆,看到flag參數(shù),把各種flag參數(shù)的注釋看了遍伊滋,最后定位在如下幾個(gè)flag參數(shù)值上碳却。
/** Window flag: this window won't ever get key input focus, so the
* user can not send key or other button events to it. Those will
* instead go to whatever focusable window is behind it. This flag
* will also enable {@link #FLAG_NOT_TOUCH_MODAL} whether or not that
* is explicitly set.
*
* <p>Setting this flag also implies that the window will not need to
* interact with
* a soft input method, so it will be Z-ordered and positioned
* independently of any active input method (typically this means it
* gets Z-ordered on top of the input method, so it can use the full
* screen for its content and cover the input method if needed. You
* can use {@link #FLAG_ALT_FOCUSABLE_IM} to modify this behavior. */
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
/** Window flag: this window can never receive touch events. */
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;
/** Window flag: even when this window is focusable (its
* {@link #FLAG_NOT_FOCUSABLE} is not set), allow any pointer events
* outside of the window to be sent to the windows behind it. Otherwise
* it will consume all pointer events itself, regardless of whether they
* are inside of the window. */
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
/** Window flag: if you have set {@link #FLAG_NOT_TOUCH_MODAL}, you
* can set this flag to receive a single special MotionEvent with
* the action
* {@link MotionEvent#ACTION_OUTSIDE MotionEvent.ACTION_OUTSIDE} for
* touches that occur outside of your window. Note that you will not
* receive the full down/move/up gesture, only the location of the
* first down as an ACTION_OUTSIDE.
*/
public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
在全屏透明背景的環(huán)境下,本以為可以監(jiān)聽桌面的Down笑旺、Move昼浦、Up事件,但是出現(xiàn)事件全部被攔截死在這個(gè)透明背景上,無法傳到手機(jī)桌面,再發(fā)現(xiàn)組合這些參數(shù)削彬,總結(jié)這種思路不可取。
查看注釋可以知道設(shè)置FLAG_WATCH_OUTSIDE_TOUCH可以在窗口外部(即App外部)接收一個(gè)指定事件MotionEvent#ACTION_OUTSIDE使兔,但同時(shí),你將無法接收到任何的Down藤韵、Move虐沥、Up事件。所以泽艘,也只能這樣了置蜀。奈搜。有其它高招的兄弟指點(diǎn)下哈悉盆。
4.如何控制截屏頻次盯荤?
在步驟3的基礎(chǔ)上,基本可以做一個(gè)截圖策略了焕盟,比如秋秤,每接收一次ACTION_OUTSIDE就截一次圖,又或者脚翘,每次監(jiān)聽一次ACTION_OUTSIDE灼卢,就進(jìn)行一次計(jì)數(shù)器的累加,為了保證截圖能承上啟下連貫来农,可以設(shè)置閾值為2這樣鞋真。
5.如何拼圖?
這里因人而異了沃于,但目的都一樣涩咖,將上述步驟所截的圖對(duì)比出不同的地方,然后把不同的地方拼接起來繁莹。出于運(yùn)算效率考慮檩互,這里我是用JNI來實(shí)現(xiàn)的,主函數(shù):
JNIEXPORT void JNICALL Java_com_zfw_screenshot_utils_SewUtils_merge(
JNIEnv *env, jobject thiz, jobject bmp0, jobject bmp1, jobject bmp2, int h0, int h1, int h2, int samePart, int len) {
int *pixels_0 = lockPixel(env, bmp0);
int *pixels_1 = lockPixel(env, bmp1);
int *pixels_2 = lockPixel(env, bmp2);
/* -------------------- merge the difference ----------------------- */
int index = 0;
while(index < h0) {
if(index < h1) {
getRowPixels(pixels_0, index, pixels_1, index, len);
} else {
getRowPixels(pixels_0, index, pixels_2, index - h1 + samePart, len);
}
index++;
}
/* -------------------- merge the difference ----------------------- */
unlockPixel(env, bmp0);
unlockPixel(env, bmp1);
unlockPixel(env, bmp2);
}
功能實(shí)現(xiàn)上沒什么難度咨演,感覺更多的是得選好實(shí)現(xiàn)的策略闸昨,比如如何優(yōu)雅的監(jiān)聽?wèi)腋〈巴獾腡ouch事件,如何優(yōu)雅的實(shí)現(xiàn)一個(gè)“定點(diǎn)”截圖的策略薄风,如何優(yōu)雅的對(duì)比兩個(gè)Bitmap的不同地方饵较,進(jìn)行拼接。
源碼傳送門:https://github.com/zengfw/LongScreenShot
有什么好的策略以及問題遭赂,歡迎留言循诉,一起探討哈,看看有沒有更優(yōu)雅的實(shí)現(xiàn)方式嵌牺!