Android長截圖的實現

Ricky_發表於2018-04-15

原始碼:github.com/zengfw/Long…

支援擷取微博、知乎、今日頭條等第三方APP......

先瞅瞅效果圖:

效果圖

再瞅瞅最終的長截圖:

我是長截圖一,瞅瞅嘛...

我是長截圖二,再瞅一下嘛...

上一週腦子突然冒出長截圖這個功能,想著如何擷取如微博,知乎,頭條等這些第三方APP的介面呢?出於好奇心,花了一週業餘時間,擼一個看看。

不就是截圖+拼圖,還能有什麼難度麼?這個。。。好像確實是。

Question:

1.如何截圖?

Android 5.0 API 21之前,想要系統截圖,是需要root,不過Android 5.0開始開放了響應的截圖介面: 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.如何優雅的截圖?

懸浮窗那麼小,難道每次我都得滑一定的距離,然後點一次懸浮窗麼,理論上可以,但體驗不好。估計更多人傾向只要觸控螢幕就可以截圖,所以選擇監聽懸浮窗外的觸屏事件。

3.如何監聽懸浮視窗外部的TouchEvent?

懸浮窗外的觸屏事件都已經脫離整個應用了,如何監聽呢?這裡確實卡了些時間,因為確實找不到如何捕獲這個事件的好,我感覺這個問題也是最煩的一個,後來來了點靈感,我設定一個全屏的透明背景,然後給這個背景設定onTouch事件,哦!!!恍然大悟,以為這樣就可以了?錯!!這樣會出現整個手機的事件都將被這個透明背景攔截,無法傳遞到手機桌面,如果非開發人員安裝了這個軟體。。,告訴他,重新開機吧。。。所以翻了下WindowManager的原始碼,看到flag引數,把各種flag引數的註釋看了遍,最後定位在如下幾個flag引數值上。


        /** 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;

複製程式碼

在全屏透明背景的環境下,本以為可以監聽桌面的Down、Move、Up事件,但是出現事件全部被攔截死在這個透明背景上,無法傳到手機桌面,再發現組合這些引數,總結這種思路不可取。

檢視註釋可以知道設定FLAG_WATCH_OUTSIDE_TOUCH可以在視窗外部(即App外部)接收一個指定事件MotionEvent#ACTION_OUTSIDE,但同時,你將無法接收到任何的Down、Move、Up事件。所以,也只能這樣了。。有其它高招的兄弟指點下哈。

4.如何控制截圖頻次?

在步驟3的基礎上,基本可以做一個截圖策略了,比如,每接收一次ACTION_OUTSIDE就截一次圖,又或者,每次監聽一次ACTION_OUTSIDE,就進行一次計數器的累加,為了保證截圖能承上啟下連貫,可以設定閾值為2這樣。

5.如何拼圖?

這裡因人而異了,但目的都一樣,將上述步驟所截的圖對比出不同的地方,然後把不同的地方拼接起來。出於運算效率考慮,這裡我是用JNI來實現的,主函式:

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);
}
複製程式碼

功能實現上沒什麼難度,感覺更多的是得選好實現的策略,比如如何優雅的監聽懸浮窗外的Touch事件,如何優雅的實現一個“定點”截圖的策略,如何優雅的對比兩個Bitmap的不同地方,進行拼接。

原始碼傳送門:github.com/zengfw/Long…

有什麼好的策略以及問題,歡迎留言,一起探討哈,看看有沒有更優雅的實現方式!

相關文章