安卓監(jiān)聽(tīng)截屏

安卓監(jiān)聽(tīng)截屏返奉,適用于安卓13已下粗合,安卓14以上系統(tǒng)提供了截屏的通知

在activity的onResume中調(diào)用 startListen

在activity的onPause中調(diào)用 startListen

public class ScreenShotHelper {
private static final String TAG = "ScreenShotHelper";

/**
 * 讀取媒體數(shù)據(jù)庫(kù)時(shí)需要讀取的列
 */
private static final String[] MEDIA_PROJECTIONS = {
        MediaStore.Images.ImageColumns.DATA,
        MediaStore.Images.ImageColumns.DATE_TAKEN,
};
/**
 * 讀取媒體數(shù)據(jù)庫(kù)時(shí)需要讀取的列, 其中 WIDTH 和 HEIGHT 字段在 API 16 以后才有
 */
private static final String[] MEDIA_PROJECTIONS_API_16 = {
        MediaStore.Images.ImageColumns.DATA,
        MediaStore.Images.ImageColumns.DATE_TAKEN,
        MediaStore.Images.ImageColumns.WIDTH,
        MediaStore.Images.ImageColumns.HEIGHT,
};

/**
 * 截屏依據(jù)中的路徑判斷關(guān)鍵字
 */
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","Screenshot"
};

private static Point sScreenRealSize;

/**
 * 已回調(diào)過(guò)的路徑
 */
private final static List<String> sHasCallbackPaths = new ArrayList<String>();

private Context mContext;

private OnScreenShotListener mListener;

private long mStartListenTime;

/**
 * 內(nèi)部存儲(chǔ)器內(nèi)容觀察者
 */
private MediaContentObserver mInternalObserver;

/**
 * 外部存儲(chǔ)器內(nèi)容觀察者
 */
private MediaContentObserver mExternalObserver;

/**
 * 運(yùn)行在 UI 線程的 Handler, 用于運(yùn)行監(jiān)聽(tīng)器回調(diào)
 */
private final Handler mUiHandler = new Handler(Looper.getMainLooper());

private ScreenShotHelper(Context context) {
    if (context == null) {
        throw new IllegalArgumentException("The context must not be null.");
    }
    mContext = context;

    // 獲取屏幕真實(shí)的分辨率
    if (sScreenRealSize == null) {
        sScreenRealSize = getRealScreenSize();
        if (sScreenRealSize != null) {
            Log.d(TAG, "Screen Real Size: " + sScreenRealSize.x + " * " + sScreenRealSize.y);
        } else {
            Log.w(TAG, "Get screen real size failed.");
        }
    }
}

public static ScreenShotHelper newInstance(Context context) {
    assertInMainThread();
    return new ScreenShotHelper(context);
}

/**
 * 啟動(dòng)監(jiān)聽(tīng)
 */
public void startListen() {
    assertInMainThread();

   //        sHasCallbackPaths.clear();

    // 記錄開(kāi)始監(jiān)聽(tīng)的時(shí)間戳
    mStartListenTime = System.currentTimeMillis();

    // 創(chuàng)建內(nèi)容觀察者
    mInternalObserver = new MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, mUiHandler);
    mExternalObserver = new MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mUiHandler);

    // 注冊(cè)內(nèi)容觀察者
    mContext.getApplicationContext().getContentResolver().registerContentObserver(
            MediaStore.Images.Media.INTERNAL_CONTENT_URI,
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q,
            mInternalObserver
    );
    mContext.getApplicationContext().getContentResolver().registerContentObserver(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q,
            mExternalObserver
    );
}

/**
 * 停止監(jiān)聽(tīng)
 */
public void stopListen() {
    assertInMainThread();

    // 注銷(xiāo)內(nèi)容觀察者
    if (mInternalObserver != null) {
        try {
            mContext.getApplicationContext().getContentResolver().unregisterContentObserver(mInternalObserver);
        } catch (Exception e) {
            e.printStackTrace();
        }
        mInternalObserver = null;
    }
    if (mExternalObserver != null) {
        try {
            mContext.getApplicationContext().getContentResolver().unregisterContentObserver(mExternalObserver);
        } catch (Exception e) {
            e.printStackTrace();
        }
        mExternalObserver = null;
    }

    // 清空數(shù)據(jù)
    mStartListenTime = 0;
//        sHasCallbackPaths.clear();

    //切記!7濉腰池!:必須設(shè)置為空 可能mListener 會(huì)隱式持有Activity導(dǎo)致釋放不掉
    mListener = null;
}

/**
 * 處理媒體數(shù)據(jù)庫(kù)的內(nèi)容改變
 */
private void handleMediaContentChange(Uri contentUri) {
    Log.d(TAG,"handleMediaContentChange"+contentUri);
    Cursor cursor = null;
    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            String order = MediaStore.Images.ImageColumns.DATE_ADDED + " desc ";
            Bundle queryArgs = createSqlQueryBundle(null, null, order, 1, 0);
            cursor = mContext.getApplicationContext().getContentResolver().query(
                    contentUri,
                    Build.VERSION.SDK_INT < 16 ? MEDIA_PROJECTIONS : MEDIA_PROJECTIONS_API_16,
                    queryArgs,
                    null
            );
        }else {
            String order = MediaStore.Images.ImageColumns.DATE_ADDED + " desc ";
            cursor = mContext.getApplicationContext().getContentResolver().query(
                    contentUri,
                    Build.VERSION.SDK_INT < 16 ? MEDIA_PROJECTIONS : MEDIA_PROJECTIONS_API_16,
                    null,
                    null,
                    MediaStore.Images.ImageColumns.DATE_ADDED + " desc limit 1"
            );
        }
        // 數(shù)據(jù)改變時(shí)查詢(xún)數(shù)據(jù)庫(kù)中最后加入的一條數(shù)據(jù)


        if (cursor == null) {
            Log.e(TAG, "Deviant logic.");
            return;
        }
        if (!cursor.moveToFirst()) {
            Log.d(TAG, "Cursor no data.");
            return;
        }

        // 獲取各列的索引
        int dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
        int dateTakenIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN);
        int widthIndex = -1;
        int heightIndex = -1;
        if (Build.VERSION.SDK_INT >= 16) {
            widthIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.WIDTH);
            heightIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.HEIGHT);
        }

        // 獲取行數(shù)據(jù)
        String data = cursor.getString(dataIndex);
        long dateTaken = cursor.getLong(dateTakenIndex);
        int width = 0;
        int height = 0;
        if (widthIndex >= 0 && heightIndex >= 0) {
            width = cursor.getInt(widthIndex);
            height = cursor.getInt(heightIndex);
        } else {
            // API 16 之前, 寬高要手動(dòng)獲取
            Point size = getImageSize(data);
            width = size.x;
            height = size.y;
        }

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

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

    } finally {
        if (cursor != null && !cursor.isClosed()) {
            cursor.close();
        }
    }
}
public static Bundle createSqlQueryBundle(String selection, String[] selectionArgs, String sortOrder, int limitCount, int offset) {
    if (selection == null && selectionArgs == null && sortOrder == null) {
        return null;
    } else {
        Bundle queryArgs = new Bundle();
        if (selection != null) {
            queryArgs.putString("android:query-arg-sql-selection", selection);
        }

        if (selectionArgs != null) {
            queryArgs.putStringArray("android:query-arg-sql-selection-args", selectionArgs);
        }

        if (sortOrder != null) {
            queryArgs.putString("android:query-arg-sql-sort-order", sortOrder);
        }

        if (Build.VERSION.SDK_INT >= 30) {
            queryArgs.putInt("android:query-arg-limit", limitCount);
            queryArgs.putInt("android:query-arg-offset", offset);
        }

        return queryArgs;
    }
}
private Point getImageSize(String imagePath) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(imagePath, options);
    return new Point(options.outWidth, options.outHeight);
}

/**
 * 處理獲取到的一行數(shù)據(jù)
 */
private void handleMediaRowData(String data, long dateTaken, int width, int height) {
    Log.d(TAG,"handleMediaRowData"+data);
    if (checkScreenShot(data, dateTaken, width, height)) {
        Log.d(TAG, "ScreenShot: path = " + data + "; size = " + width + " * " + height
                + "; date = " + dateTaken);
        if (mListener != null && !checkCallback(data)) {
            mListener.onShot(data);
        }
    } else {
        // 如果在觀察區(qū)間媒體數(shù)據(jù)庫(kù)有數(shù)據(jù)改變,又不符合截屏規(guī)則忙芒,則輸出到 log 待分析
        Log.w(TAG, "Media content changed, but not screenshot: path = " + data
                + "; size = " + width + " * " + height + "; date = " + dateTaken);
    }
}

/**
 * 判斷指定的數(shù)據(jù)行是否符合截屏條件
 */
private boolean checkScreenShot(String data, long dateTaken, int width, int height) {
    Log.d(TAG,"checkScreenShot"+data);
    /*
     * 判斷依據(jù)一: 時(shí)間判斷
     */
    // 如果加入數(shù)據(jù)庫(kù)的時(shí)間在開(kāi)始監(jiān)聽(tīng)之前, 或者與當(dāng)前時(shí)間相差大于10秒, 則認(rèn)為當(dāng)前沒(méi)有截屏
    if (dateTaken < mStartListenTime || (System.currentTimeMillis() - dateTaken) > 10 * 1000) {
        return false;
    }

    /*
     * 判斷依據(jù)二: 尺寸判斷
     */
    if (sScreenRealSize != null) {
        // 如果圖片尺寸超出屏幕, 則認(rèn)為當(dāng)前沒(méi)有截屏
        if (!((width <= sScreenRealSize.x && height <= sScreenRealSize.y)
                || (height <= sScreenRealSize.x && width <= sScreenRealSize.y))) {
            return false;
        }
    }

    /*
     * 判斷依據(jù)三: 路徑判斷
     */
    if (TextUtils.isEmpty(data)) {
        return false;
    }
    data = data.toLowerCase();
    // 判斷圖片路徑是否含有指定的關(guān)鍵字之一, 如果有, 則認(rèn)為當(dāng)前截屏了
    for (String keyWork : KEYWORDS) {
        if (data.contains(keyWork)) {
            return true;
        }
    }

    return false;
}

/**
 * 判斷是否已回調(diào)過(guò), 某些手機(jī)ROM截屏一次會(huì)發(fā)出多次內(nèi)容改變的通知; <br/>
 * 刪除一個(gè)圖片也會(huì)發(fā)通知, 同時(shí)防止刪除圖片時(shí)誤將上一張符合截屏規(guī)則的圖片當(dāng)做是當(dāng)前截屏.
 */
private boolean checkCallback(String imagePath) {
    if (sHasCallbackPaths.contains(imagePath)) {
        Log.d(TAG, "ScreenShot: imgPath has done"
                + "; imagePath = " + imagePath);
        return true;
    }
    // 大概緩存15~20條記錄便可
    if (sHasCallbackPaths.size() >= 20) {
        for (int i = 0; i < 5; i++) {
            sHasCallbackPaths.remove(0);
        }
    }
    sHasCallbackPaths.add(imagePath);
    return false;
}

/**
 * 獲取屏幕分辨率
 */
private Point getRealScreenSize() {
    Point screenSize = null;
    try {
        screenSize = new Point();
        WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        Display defaultDisplay = windowManager.getDefaultDisplay();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            ((Display) defaultDisplay).getRealSize(screenSize);
        } else {
            try {
                Method mGetRawW = Display.class.getMethod("getRawWidth");
                Method mGetRawH = Display.class.getMethod("getRawHeight");
                screenSize.set(
                        (Integer) mGetRawW.invoke(defaultDisplay),
                        (Integer) mGetRawH.invoke(defaultDisplay)
                );
            } catch (Exception e) {
                screenSize.set(defaultDisplay.getWidth(), defaultDisplay.getHeight());
                e.printStackTrace();
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return screenSize;
}


private int dp2px(Context ctx, float dp) {
    float scale = ctx.getResources().getDisplayMetrics().density;
    return (int) (dp * scale + 0.5f);
}

/**
 * 設(shè)置截屏監(jiān)聽(tīng)器
 */
public void setListener(OnScreenShotListener listener) {
    mListener = listener;
}

public interface OnScreenShotListener {
    void onShot(String imagePath);
}

private static void assertInMainThread() {
    if (Looper.myLooper() != Looper.getMainLooper()) {
        StackTraceElement[] elements = Thread.currentThread().getStackTrace();
        String methodMsg = null;
        if (elements != null && elements.length >= 4) {
            methodMsg = elements[3].toString();
        }
        throw new IllegalStateException("Call the method must be in main thread: " + methodMsg);
    }
}

/**
 * 媒體內(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);
        handleMediaContentChange(mContentUri);
    }
}


}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末示弓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子呵萨,更是在濱河造成了極大的恐慌奏属,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甘桑,死亡現(xiàn)場(chǎng)離奇詭異拍皮,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)跑杭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)铆帽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人德谅,你說(shuō)我怎么就攤上這事爹橱。” “怎么了窄做?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵愧驱,是天一觀的道長(zhǎng)慰技。 經(jīng)常有香客問(wèn)我,道長(zhǎng)组砚,這世上最難降的妖魔是什么吻商? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮糟红,結(jié)果婚禮上艾帐,老公的妹妹穿的比我還像新娘。我一直安慰自己盆偿,他們只是感情好柒爸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著事扭,像睡著了一般捎稚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上求橄,一...
    開(kāi)封第一講書(shū)人閱讀 51,541評(píng)論 1 305
  • 那天今野,我揣著相機(jī)與錄音,去河邊找鬼谈撒。 笑死腥泥,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的啃匿。 我是一名探鬼主播蛔外,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼溯乒!你這毒婦竟也來(lái)了夹厌?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤裆悄,失蹤者是張志新(化名)和其女友劉穎矛纹,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體光稼,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡或南,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了艾君。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片采够。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖冰垄,靈堂內(nèi)的尸體忽然破棺而出蹬癌,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布逝薪,位于F島的核電站隅要,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏董济。R本人自食惡果不足惜步清,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望感局。 院中可真熱鬧尼啡,春花似錦暂衡、人聲如沸询微。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)撑毛。三九已至,卻和暖如春唧领,著一層夾襖步出監(jiān)牢的瞬間藻雌,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工斩个, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胯杭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓受啥,卻偏偏與公主長(zhǎng)得像做个,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子滚局,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

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