Android 截屏檢測


最近項目中新接到一個需求,對手機截屏進行檢測並進行后續操作,類似於Snapchat,iOS具有先天優勢,因iOS系統提供了相關API!Google無果之后原作者決定再次造輪子,為了持續表達對Rx的敬意,命名為RxScreenshotDetector, github 源碼地址 。

效果有圖有真相

原理

安卓系統並沒有提供任何截屏檢測相關的API,網上針對Snapchat的這項功能進行了分析,大致猜測可能有以下幾種途徑:

  • 使用FileObserver,監聽Screenshots目錄下的文件變化;

  • 使用ContentObserver,監聽MediaStore.Images.Media.EXTERNAL_CONTENT_URI資源的變化;

  • 重載(hook)截屏組合鍵(不靠譜),有的機型使用的是特殊手勢進行截屏;

主要參考了 StackOverflow上面的這個回答 。

核心代碼如下:

private static final String TAG = "RxScreenshotDetector";
private static final String EXTERNAL_CONTENT_URI_MATCHER =
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString();
private static final String[] PROJECTION = new String[] {
        MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.DATA,
        MediaStore.Images.Media.DATE_ADDED
};
private static final String SORT_ORDER = MediaStore.Images.Media.DATE_ADDED + " DESC";
private static final long DEFAULT_DETECT_WINDOW_SECONDS = 10;

final ContentResolver contentResolver = context.getContentResolver();
final ContentObserver contentObserver = new ContentObserver(null) {
    @Override
    public void onChange(boolean selfChange, Uri uri) {
        Log.d(TAG, "onChange: " + selfChange + ", " + uri.toString());
        if (uri.toString().matches(EXTERNAL_CONTENT_URI_MATCHER)) {
            Cursor cursor = null;
            try {
                cursor = contentResolver.query(uri, PROJECTION, null, null,
                        SORT_ORDER);
                if (cursor != null && cursor.moveToFirst()) {
                    String path = cursor.getString(
                            cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                    long dateAdded = cursor.getLong(cursor.getColumnIndex(
                            MediaStore.Images.Media.DATE_ADDED));
                    long currentTime = System.currentTimeMillis() / 1000;
                    Log.d(TAG, "path: " + path + ", dateAdded: " + dateAdded +
                            ", currentTime: " + currentTime);
                    if (path.toLowerCase().contains("screenshot") &&
                            Math.abs(currentTime - dateAdded) <=
                                    DEFAULT_DETECT_WINDOW_SECONDS) {
                        // screenshot added!
                    }
                }
            } catch (Exception e) {
                Log.d(TAG, "open cursor fail");
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
        }
        super.onChange(selfChange, uri);
    }
};
contentResolver.registerContentObserver(
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, contentObserver);

RxScreenshotDetector.java hosted with ❤ by  GitHub

主要有以下幾點需要注意:

  • 權限,讀取資源的時候需要READ_EXTERNAL_STORAGE權限,這里我使用了 RxPermissions 來以reactive的方式請求權限;

  • 從ContentResolver查詢資源的時候,需要按照資源創建時間降序排列,針對最新的一個資源判斷是否為截屏的圖片,為contentResolver.query的最后一個參數傳遞MediaStore.Images.Media.DATE_ADDED + " DESC"即可,而判斷圖片是否為截圖則比較簡單,路徑包含screenshot關鍵字,且添加時間在10s之內;

使用示例

RxScreenshotDetector完整使用代碼如下:

RxScreenshotDetector.start(getApplicationContext())
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .compose(this.<String>bindUntilEvent(ActivityEvent.PAUSE))
        .subscribe(new Subscriber<String>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {
                e.printStackTrace();
            }

            @Override
            public void onNext(String path) {
                mTextView.setText(mTextView.getText() + "\nScreenshot: " + path);
            }
        });

這里使用了 RxLifecycle ,在Activity onPause之后unsubscribe,以保證不會發生內存泄漏。此外subscribe傳入的是完整的Subscriber,是為了防止授權失敗時沒有onError處理器,導致crash。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM