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

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

前言

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

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

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

本文使用的測(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)聽(tīng)器
     */
    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)聽(tīng)到事件费奸,做一些過(guò)濾去重處理操作
        }
    }
}

打印的日志:

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

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

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

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

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

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

注意:如果自己寫(xiě)Demo沒(méi)有收到事件魄缚,檢查一下權(quán)限和監(jiān)聽(tīng)的目錄

ContentObserver

ContentObserver用來(lái)監(jiān)聽(tīng)指定uri的所有資源變化,我們可以用它來(lái)監(jiān)聽(tīng)圖片資源變化情況焚廊,然后做過(guò)濾冶匹。

添加權(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ù)庫(kù)時(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)聽(tīng)
        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();
        
        // 注銷(xiāo)監(jiān)聽(tīng)
        this.getContentResolver().unregisterContentObserver(mInternalObserver);
        this.getContentResolver().unregisterContentObserver(mExternalObserver);
    }
    
    private void handleMediaContentChange(Uri contentUri) {
        Cursor cursor = null;
        try {
            // 數(shù)據(jù)改變時(shí)查詢數(shù)據(jù)庫(kù)中最后加入的一條數(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)聽(tīng)到的資源
     */
    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ù)庫(kù)的改變)
     */
    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)聽(tīng)到所有圖片資源的變化情況,要做好去重過(guò)濾工作
  • 根據(jù)uri去讀取ContentProvider內(nèi)容時(shí)候咆瘟,記得關(guān)閉cursor嚼隘,防止內(nèi)存泄漏
  • 關(guān)鍵字可擴(kuò)展,大大增加的監(jiān)聽(tīng)的范圍袒餐,比FileObserver好用多了嗓蘑,但是去重過(guò)濾會(huì)比FileObserver復(fù)雜一些。

總結(jié)

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

參考:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末幢炸,一起剝皮案震驚了整個(gè)濱河市泄隔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宛徊,老刑警劉巖佛嬉,帶你破解...
    沈念sama閱讀 221,331評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逻澳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡暖呕,警方通過(guò)查閱死者的電腦和手機(jī)斜做,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)湾揽,“玉大人瓤逼,你說(shuō)我怎么就攤上這事】馕铮” “怎么了霸旗?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,755評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)戚揭。 經(jīng)常有香客問(wèn)我诱告,道長(zhǎng),這世上最難降的妖魔是什么民晒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,528評(píng)論 1 296
  • 正文 為了忘掉前任精居,我火速辦了婚禮,結(jié)果婚禮上潜必,老公的妹妹穿的比我還像新娘靴姿。我一直安慰自己,他們只是感情好刮便,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,526評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布空猜。 她就那樣靜靜地躺著,像睡著了一般恨旱。 火紅的嫁衣襯著肌膚如雪辈毯。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,166評(píng)論 1 308
  • 那天搜贤,我揣著相機(jī)與錄音谆沃,去河邊找鬼。 笑死仪芒,一個(gè)胖子當(dāng)著我的面吹牛唁影,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掂名,決...
    沈念sama閱讀 40,768評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼据沈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了饺蔑?” 一聲冷哼從身側(cè)響起锌介,我...
    開(kāi)封第一講書(shū)人閱讀 39,664評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后孔祸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體隆敢,經(jīng)...
    沈念sama閱讀 46,205評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,290評(píng)論 3 340
  • 正文 我和宋清朗相戀三年崔慧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拂蝎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,435評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡惶室,死狀恐怖温自,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拇涤,我是刑警寧澤捣作,帶...
    沈念sama閱讀 36,126評(píng)論 5 349
  • 正文 年R本政府宣布誉结,位于F島的核電站鹅士,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏惩坑。R本人自食惡果不足惜掉盅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,804評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望以舒。 院中可真熱鬧趾痘,春花似錦、人聲如沸蔓钟。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,276評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)滥沫。三九已至侣集,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間兰绣,已是汗流浹背世分。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缀辩,地道東北人臭埋。 一個(gè)月前我還...
    沈念sama閱讀 48,818評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像臀玄,于是被迫代替她去往敵國(guó)和親瓢阴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,442評(píng)論 2 359

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,265評(píng)論 25 707
  • 作者:東城小蘇 “好無(wú)聊啊!好無(wú)聊啊!好無(wú)聊啊!好無(wú)聊健无、荣恐、、睬涧、”少女坐在房間的地板上嘴里不停的念叨著募胃,此時(shí)她正坐在...
    東城小蘇閱讀 528評(píng)論 0 4
  • (一) 朋友在計(jì)劃離開(kāi)深圳旗唁,準(zhǔn)備把這邊的房子賣(mài)了,回老家過(guò)抬頭悠然見(jiàn)南山的生活去痹束。去追尋她想要的遠(yuǎn)方和詩(shī)检疫。 每年都...
    伍瑤瑤閱讀 197評(píng)論 0 0
  • 日子里應(yīng)當(dāng)少些跌宕的水花屎媳,多些實(shí)用的積累 -----from曉娜 ...
    劉曉娜的原創(chuàng)閱讀 498評(píng)論 2 7
  • 烈日炎炎。陽(yáng)光照耀著一條古老的街论巍,也照耀著老街上的一個(gè)少年洼哎。古玩街,在這座有著近兩千年歷史的古城里是極為有名的船逮。老...
    小小坤_閱讀 552評(píng)論 0 0