1. 程式人生 > >Android截圖事件監聽

Android截圖事件監聽

1. 前言

Android系統沒有直接對截圖事件監聽的介面,也沒有廣播,只能自己動手來豐衣足食,一般有三種方法。

  • 利用FileObserver監聽某個目錄中資源變化情況
  • 利用ContentObserver監聽全部資源的變化
  • 監聽截圖快捷按鍵

由於廠商自定義Android系統的多樣性,再加上快捷鍵的不同以及第三方應用,監聽截圖快捷鍵這事基本不靠譜,可以直接忽略。

本文使用的測試手機,一加2(One Plus 2)。

2. FileObserver

新增許可權:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
/>

程式碼示例:

public class ScreenshotActivity extends AppCompatActivity {

    private final String TAG = "Screenshot";

    private static final String PATH = Environment.getExternalStorageDirectory() + File.separator 
            + Environment.DIRECTORY_PICTURES + File.separator + "Screenshots" + File.separator;

    protected
void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_screenshot); mFileObserver = new CustomFileObserver(PATH); } @Override protected void onResume() { super.onResume(); mFileObserver.startWatching(); Log.d(TAG, PATH); } @Override
protected void onStop() { super.onStop(); mFileObserver.stopWatching(); } /** * 目錄監聽器 */ private class CustomFileObserver extends FileObserver { private String mPath; public CustomFileObserver(String path) { super(path); this.mPath = path; } public CustomFileObserver(String path, int mask) { super(path, mask); this.mPath = path; } @Override public void onEvent(int event, String path) { Log.d(TAG, path + " " + event); // 監聽到事件,做一些過濾去重處理操作 } } }

列印的日誌:

一加2

D/Screenshot: Screenshot_2016-12-16-17-49-18.png 256
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 32
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 2
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 2
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 2
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 2
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 2
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 2
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 2
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 2
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 2
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 2
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 2
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 2
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 2
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 2
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 2
D/Screenshot: Screenshot_2016-12-16-17-49-18.png 8

三星 S4

D/Screenshot: Screenshot_2016-12-16-19-01-08.png 256
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 32
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 2
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 8
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 32
D/Screenshot: Screenshot_2016-12-16-19-01-08.png 16

可以通過指定建構函式中的mask,監聽某一個事件。

型別 int值 說明
FileObserver.ACCESS 1 讀取某個檔案
FileObserver.MODIFY 2 向某個檔案寫入資料
FileObserver.ATTRIB 4 檔案的屬性被修改(許可權/日期/擁有者)
FileObserver.CLOSE_WRITE 8 寫入資料後關閉
FileObserver.CLOSE_NOWRITE 16 只讀模式開啟檔案後關閉
FileObserver.OPEN 32 開啟某個檔案
FileObserver.MOVED_FROM 64 有檔案或者資料夾從被監聽的資料夾中移走
FileObserver.MOVED_TO 128 有檔案或者資料夾移動到被監聽的資料夾
FileObserver.CREATE 256 檔案或者資料夾被建立
FileObserver.DELETE 512 檔案被刪除
FileObserver.DELETE_SELF 1024 被監聽的檔案或者目錄被刪除
FileObserver.MOVE_SELF 2048 被監聽的檔案或者目錄被移走

幾點注意事項:

  • 每一次截圖,有多個事件回撥
  • 每一次截圖,不同的手機,事件回撥可能有些不同,參考上述日誌
  • 不同的手機,預設截圖圖片儲存的資料夾可能不同
  • FileObserver只能監聽資料夾中子檔案和子資料夾的變化情況,不能監聽子資料夾內部的資源變化
  • 需要<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>許可權,否則可能收不到事件

基於第三點和第四點,這種方法並不能適用於所有的機型。

注意:如果自己寫Demo沒有收到事件,檢查一下許可權和監聽的目錄

3. ContentObserver

ContentObserver用來監聽指定uri的所有資源變化,我們可以用它來監聽圖片資源變化情況,然後做過濾。

新增許可權

<uses-permission android:name="MediaStore.Images.Media.INTERNAL_CONTENT_URI"/>
<uses-permission android:name="MediaStore.Images.Media.EXTERNAL_CONTENT_URI"/>

程式碼示例:

public class ScreenshotActivity extends AppCompatActivity {
    private static final String[] KEYWORDS = {
            "screenshot", "screen_shot", "screen-shot", "screen shot",
            "screencapture", "screen_capture", "screen-capture", "screen capture",
            "screencap", "screen_cap", "screen-cap", "screen cap"
    };

    /** 讀取媒體資料庫時需要讀取的列 */
    private static final String[] MEDIA_PROJECTIONS =  {
            MediaStore.Images.ImageColumns.DATA,
            MediaStore.Images.ImageColumns.DATE_TAKEN,
    };

    /** 內部儲存器內容觀察者 */
    private ContentObserver mInternalObserver;

    /** 外部儲存器內容觀察者 */
    private ContentObserver mExternalObserver;

    private HandlerThread mHandlerThread;
    private Handler mHandler;

    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_screenshot);

        mHandlerThread = new HandlerThread("Screenshot_Observer");
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());

        // 初始化
        mInternalObserver = new MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, mHandler);
        mExternalObserver = new MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mHandler);

        // 新增監聽
        this.getContentResolver().registerContentObserver(
            MediaStore.Images.Media.INTERNAL_CONTENT_URI,
            false,
            mInternalObserver
        );
        this.getContentResolver().registerContentObserver(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            false,
            mExternalObserver
        );
    }

    protected void onDestroy() {
        super.onDestroy();
        // 登出監聽
        this.getContentResolver().unregisterContentObserver(mInternalObserver);
        this.getContentResolver().unregisterContentObserver(mExternalObserver);
    }

    private void handleMediaContentChange(Uri contentUri) {
        Cursor cursor = null;
        try {
            // 資料改變時查詢資料庫中最後加入的一條資料
            cursor = this.getContentResolver().query(
                    contentUri,
                    MEDIA_PROJECTIONS,
                    null,
                    null,
                    MediaStore.Images.ImageColumns.DATE_ADDED + " desc limit 1"
            );

            if (cursor == null) {
                return;
            }
            if (!cursor.moveToFirst()) {
                return;
            }

            // 獲取各列的索引
            int dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
            int dateTakenIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN);

            // 獲取行資料
            String data = cursor.getString(dataIndex);
            long dateTaken = cursor.getLong(dateTakenIndex);

            // 處理獲取到的第一行資料
            handleMediaRowData(data, dateTaken);

        } catch (Exception e) {
            e.printStackTrace();

        } finally {
            if (cursor != null && !cursor.isClosed()) {
                cursor.close();
            }
        }
    }

    /**
     * 處理監聽到的資源
     */
    private void handleMediaRowData(String data, long dateTaken) {
        if (checkScreenShot(data, dateTaken)) {
            Log.d(TAG, data + " " + dateTaken);
        } else {
            Log.d(TAG, "Not screenshot event");
        }
    }

    /**
     * 判斷是否是截圖
     */
    private boolean checkScreenShot(String data, long dateTaken) {

        data = data.toLowerCase();
        // 判斷圖片路徑是否含有指定的關鍵字之一, 如果有, 則認為當前截圖了
        for (String keyWork : KEYWORDS) {
            if (data.contains(keyWork)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 媒體內容觀察者(觀察媒體資料庫的改變)
     */
    private class MediaContentObserver extends ContentObserver {

        private Uri mContentUri;

        public MediaContentObserver(Uri contentUri, Handler handler) {
            super(handler);
            mContentUri = contentUri;
        }

        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);
            Log.d(TAG, mContentUri.toString());
            handleMediaContentChange(mContentUri);
        }
    }
}

日誌:

D/Screenshot: content://media/external/images/media
D/Screenshot: /storage/emulated/0/Pictures/Screenshots/Screenshot_2016-12-19-11-24-02.png 1482117842287

注意事項:

  • ContentObserver會監聽到所有圖片資源的變化情況,要做好去重過濾工作
  • 根據uri去讀取ContentProvider內容時候,記得關閉cursor,防止記憶體洩漏
  • 關鍵字可擴充套件,大大增加的監聽的範圍,比FileObserver好用多了,但是去重過濾會比FileObserver複雜一些。

4. 總結

目前這是在網上搜索到的關於截圖監聽方法的總結,如果大家還有什麼比較好的監聽方法,歡迎分享。

參考: