RxScreenshotDetector:Android 截屏檢測
原文出處: http://blog.piasy.com/Android-Screenshot-Detector/
應PM需求,YOLO可能會對直播過程中的截屏進行檢測并通知其他人,類似于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);</pre>
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); } });</pre>
這里使用了 RxLifecycle ,在Activity onPause之后unsubscribe,以保證不會發生內存泄漏。此外subscribe傳入的是完整的Subscriber,是為了防止授權失敗時沒有onError處理器,導致crash。
最后,安利一發 YOLO直播APP 。
Written on January 29, 2016
</div>