Android截屏事件監(jiān)聽總結(jié)

轉(zhuǎn)載注明出處:http://www.lxweimin.com/p/7ead15eeaaef

前言

Android系統(tǒng)沒有直接對(duì)截屏事件監(jiān)聽的接口,只能自己動(dòng)手來豐衣足食,一般有三種方法。

  • 利用FileObserver監(jiān)聽某個(gè)目錄中資源變化情況
  • 利用ContentObserver監(jiān)聽全部資源的變化
  • 監(jiān)聽截屏快捷按鍵

由于廠商自定義Android系統(tǒng)的多樣性,再加上快捷鍵的不同以及第三方應(yīng)用,監(jiān)聽截屏快捷鍵這事基本不靠譜,可以直接忽略。

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

FileObserver

添加權(quán)限:

<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();
    }
    
    /**
     * 目錄監(jiān)聽器
     */
    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);
            // 監(jiān)聽到事件,做一些過濾去重處理操作
        }
    }
}

打印的日志:

一加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

可以通過指定構(gòu)造函數(shù)中的mask,監(jiān)聽某一個(gè)事件。

類型 int值 說明
FileObserver.ACCESS 1 讀取某個(gè)文件
FileObserver.MODIFY 2 向某個(gè)文件寫入數(shù)據(jù)
FileObserver.ATTRIB 4 文件的屬性被修改(權(quán)限/日期/擁有者)
FileObserver.CLOSE_WRITE 8 寫入數(shù)據(jù)后關(guān)閉
FileObserver.CLOSE_NOWRITE 16 只讀模式打開文件后關(guān)閉
FileObserver.OPEN 32 打開某個(gè)文件
FileObserver.MOVED_FROM 64 有文件或者文件夾從被監(jiān)聽的文件夾中移走
FileObserver.MOVED_TO 128 有文件或者文件夾移動(dòng)到被監(jiān)聽的文件夾
FileObserver.CREATE 256 文件或者文件夾被創(chuàng)建
FileObserver.DELETE 512 文件被刪除
FileObserver.DELETE_SELF 1024 被監(jiān)聽的文件或者目錄被刪除
FileObserver.MOVE_SELF 2048 被監(jiān)聽的文件或者目錄被移走

幾點(diǎn)注意事項(xiàng):

  • 每一次截屏,有多個(gè)事件回調(diào)
  • 每一次截屏,不同的手機(jī),事件回調(diào)可能有些不同,參考上述日志
  • 不同的手機(jī),默認(rèn)截屏圖片儲(chǔ)存的文件夾可能不同
  • FileObserver只能監(jiān)聽文件夾中子文件和子文件夾的變化情況,不能監(jiān)聽子文件夾內(nèi)部的資源變化
  • 需要<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>權(quán)限,否則可能收不到事件

基于第三點(diǎn)和第四點(diǎn),這種方法并不能適用于所有的機(jī)型。

注意:如果自己寫Demo沒有收到事件,檢查一下權(quán)限和監(jiān)聽的目錄

ContentObserver

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

添加權(quán)限

<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"
    };

    /** 讀取媒體數(shù)據(jù)庫時(shí)需要讀取的列 */
    private static final String[] MEDIA_PROJECTIONS =  {
            MediaStore.Images.ImageColumns.DATA,
            MediaStore.Images.ImageColumns.DATE_TAKEN,
    };
    
        /** 內(nèi)部存儲(chǔ)器內(nèi)容觀察者 */
    private ContentObserver mInternalObserver;

    /** 外部存儲(chǔ)器內(nèi)容觀察者 */
    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);
    
        // 添加監(jiān)聽
        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();
        
        // 注銷監(jiān)聽
        this.getContentResolver().unregisterContentObserver(mInternalObserver);
        this.getContentResolver().unregisterContentObserver(mExternalObserver);
    }
    
    private void handleMediaContentChange(Uri contentUri) {
        Cursor cursor = null;
        try {
            // 數(shù)據(jù)改變時(shí)查詢數(shù)據(jù)庫中最后加入的一條數(shù)據(jù)
            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);

            // 獲取行數(shù)據(jù)
            String data = cursor.getString(dataIndex);
            long dateTaken = cursor.getLong(dateTakenIndex);

            // 處理獲取到的第一行數(shù)據(jù)
            handleMediaRowData(data, dateTaken);

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

        } finally {
            if (cursor != null && !cursor.isClosed()) {
                cursor.close();
            }
        }
    }
    
    /**
     * 處理監(jiān)聽到的資源
     */
    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();
        // 判斷圖片路徑是否含有指定的關(guān)鍵字之一, 如果有, 則認(rèn)為當(dāng)前截屏了
        for (String keyWork : KEYWORDS) {
            if (data.contains(keyWork)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 媒體內(nèi)容觀察者(觀察媒體數(shù)據(jù)庫的改變)
     */
    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

注意事項(xiàng):

  • ContentObserver會(huì)監(jiān)聽到所有圖片資源的變化情況,要做好去重過濾工作
  • 根據(jù)uri去讀取ContentProvider內(nèi)容時(shí)候,記得關(guān)閉cursor,防止內(nèi)存泄漏
  • 關(guān)鍵字可擴(kuò)展,大大增加的監(jiān)聽的范圍,比FileObserver好用多了,但是去重過濾會(huì)比FileObserver復(fù)雜一些。

總結(jié)

目前這是在網(wǎng)上搜索到的關(guān)于截屏監(jiān)聽方法的總結(jié),如果大家還有什么比較好的監(jiān)聽方法,歡迎分享。

參考:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,196評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,688評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,456評(píng)論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,955評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,195評(píng)論 0 287
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,725評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,608評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,802評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,048評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評(píng)論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,424評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,762評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,605評(píng)論 25 708
  • (一) 朋友在計(jì)劃離開深圳,準(zhǔn)備把這邊的房子賣了,回老家過抬頭悠然見南山的生活去。去追尋她想要的遠(yuǎn)方和詩。 每年都...
    伍瑤瑤閱讀 201評(píng)論 0 0
  • 日子里應(yīng)當(dāng)少些跌宕的水花,多些實(shí)用的積累 -----from曉娜 ...
    劉曉娜的原創(chuàng)閱讀 507評(píng)論 2 7
  • 烈日炎炎。陽光照耀著一條古老的街,也照耀著老街上的一個(gè)少年。古玩街,在這座有著近兩千年歷史的古城里是極為有名的。老...
    小小坤_閱讀 555評(píng)論 0 0