截屏

import android.content.Context;

import android.database.ContentObserver;

import android.database.Cursor;

import android.graphics.BitmapFactory;

import android.graphics.Point;

import android.net.Uri;

import android.os.Build;

import android.os.Handler;

import android.os.Looper;

import android.provider.MediaStore;

import android.support.annotation.RequiresApi;

import android.text.TextUtils;

import android.util.Log;

import android.view.Display;

import android.view.WindowManager;

import java.lang.reflect.Method;

import java.util.ArrayList;

import java.util.List;

public class ScreenShotListenManager {

private static final StringTAG ="ScreenShotListenManager";

? ? /**

* 讀取媒體數(shù)據(jù)庫(kù)時(shí)需要讀取的列

*/

? ? private static final String[]MEDIA_PROJECTIONS = {

MediaStore.Images.ImageColumns.DATA,

? ? ? ? ? ? MediaStore.Images.ImageColumns.DATE_TAKEN,

? ? ? ? ? ? MediaStore.Images.ImageColumns.DATE_ADDED,

? ? };

? ? /**

* 讀取媒體數(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","Screenshots","Screenshot", "screen_cap", "screen-cap", "screen cap","截屏","截屏錄屏","屏"

? ? };

? ? private static PointsScreenRealSize;

? ? /**

* 已回調(diào)過(guò)的路徑

*/

? ? private final static ListsHasCallbackPaths =new ArrayList();

? ? private ContextmContext;

? ? private OnScreenShotListenermListener;

? ? private long mStartListenTime;

? ? /**

* 內(nèi)部存儲(chǔ)器內(nèi)容觀察者

*/

? ? private MediaContentObservermInternalObserver;

? ? /**

* 外部存儲(chǔ)器內(nèi)容觀察者

*/

? ? private MediaContentObservermExternalObserver;

? ? /**

* 運(yùn)行在 UI 線程的 Handler, 用于運(yùn)行監(jiān)聽(tīng)器回調(diào)

*/

? ? private final HandlermUiHandler =new Handler(Looper.getMainLooper());

? ? private ScreenShotListenManager(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 ScreenShotListenManagernewInstance(Context context) {

assertInMainThread();

? ? ? ? return new ScreenShotListenManager(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);

? ? ? ? boolean androidTen = Build.VERSION.SDK_INT >=29; // 重點(diǎn)是這里得判斷图甜,要注冊(cè)不同的版本

? ? ? ? // 注冊(cè)內(nèi)容觀察者

? ? ? ? mContext.getContentResolver().registerContentObserver(

MediaStore.Images.Media.INTERNAL_CONTENT_URI,

? ? ? ? ? ? ? ? androidTen,

? ? ? ? ? ? ? ? mInternalObserver

? ? ? ? );

? ? ? ? mContext.getContentResolver().registerContentObserver(

MediaStore.Images.Media.EXTERNAL_CONTENT_URI,

? ? ? ? ? ? ? ? androidTen,

? ? ? ? ? ? ? ? mExternalObserver

? ? ? ? );

? ? }

/**

* 停止監(jiān)聽(tīng)

*/

? ? public void stopListen() {

assertInMainThread();

? ? ? ? // 注銷(xiāo)內(nèi)容觀察者

? ? ? ? if (mInternalObserver !=null) {

try {

mContext.getContentResolver().unregisterContentObserver(mInternalObserver);

? ? ? ? ? ? }catch (Exception e) {

e.printStackTrace();

? ? ? ? ? ? }

mInternalObserver =null;

? ? ? ? }

if (mExternalObserver !=null) {

try {

mContext.getContentResolver().unregisterContentObserver(mExternalObserver);

? ? ? ? ? ? }catch (Exception e) {

e.printStackTrace();

? ? ? ? ? ? }

mExternalObserver =null;

? ? ? ? }

// 清空數(shù)據(jù)

? ? ? ? mStartListenTime =0;

//? ? ? ? sHasCallbackPaths.clear();

//切記4卧薄!!:必須設(shè)置為空 可能mListener 會(huì)隱式持有Activity導(dǎo)致釋放不掉

? ? ? ? mListener =null;

? ? }

/**

* 處理媒體數(shù)據(jù)庫(kù)的內(nèi)容改變

*/

? ? private void handleMediaContentChange(Uri contentUri) {

Cursor cursor =null;

? ? ? ? try {

// 數(shù)據(jù)改變時(shí)查詢(xún)數(shù)據(jù)庫(kù)中最后加入的一條數(shù)據(jù)

? ? ? ? ? ? cursor =mContext.getContentResolver().query(

contentUri,

? ? ? ? ? ? ? ? ? ? Build.VERSION.SDK_INT <16 ?MEDIA_PROJECTIONS :MEDIA_PROJECTIONS_API_16,

null,

null,

? ? ? ? ? ? ? ? ? ? MediaStore.Images.ImageColumns.DATE_ADDED? +" desc limit 1"

? ? ? ? ? ? );

? ? ? ? ? ? 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();

//? ? ? ? ? ? MToast.showShort(MainApplication.getInstances(),"截圖異常: " + e.getMessage());

? ? ? ? }finally {

if (cursor !=null && !cursor.isClosed()) {

cursor.close();

? ? ? ? ? ? }

}

}

private PointgetImageSize(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) {

if (checkScreenShot(data, dateTaken, width, height)) {

if(BuildConfig.DEBUG) {

Log.d(TAG, "ScreenShot: path = " + data +"; size = " + width +" * " + height

+"; date = " + dateTaken);

? ? ? ? ? ? }

//? ? ? ? ? ? MToast.showShort(MainApplication.getInstances(),"ScreenShot: path = " + data);

? ? ? ? ? ? if (mListener !=null && !checkCallback(data)) {

mListener.onShot(data);

? ? ? ? ? ? }

}else {

// 如果在觀察區(qū)間媒體數(shù)據(jù)庫(kù)有數(shù)據(jù)改變吱晒,又不符合截屏規(guī)則跺撼,則輸出到 log 待分析

? ? ? ? ? ? if(BuildConfig.DEBUG) {

Log.w(TAG, "Media content changed, but not screenshot: path = " + data

+"; size = " + width +" * " + height +"; date = " + dateTaken);

? ? ? ? ? ? }

//? ? ? ? ? ? MToast.showShort(MainApplication.getInstances(),"Media content changed, but not screenshot: path = " + data);

? ? ? ? }

}

/**

* 判斷指定的數(shù)據(jù)行是否符合截屏條件

*/

? ? private boolean checkScreenShot(String data, long dateTaken, int width, int height) {

/*

* 判斷依據(jù)一: 時(shí)間判斷

*/

// 如果加入數(shù)據(jù)庫(kù)的時(shí)間在開(kāi)始監(jiān)聽(tīng)之前, 或者與當(dāng)前時(shí)間相差大于10秒, 則認(rèn)為當(dāng)前沒(méi)有截屏

? ? ? ? if (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)容改變的通知;

? ? * 刪除一個(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 PointgetRealScreenSize() {

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) {

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;

? ? }

//? ? public Bitmap createScreenShotBitmap(Context context, String screenFilePath) {

//

//? ? ? ? View v = LayoutInflater.from(context).inflate(R.layout.share_screenshot_layout, null);

//? ? ? ? ImageView iv = (ImageView) v.findViewById(R.id.iv);

//? ? ? ? Bitmap bitmap = BitmapFactory.decodeFile(screenFilePath);

//? ? ? ? iv.setImageBitmap(bitmap);

//

//? ? ? ? //整體布局

//? ? ? ? Point point = getRealScreenSize();

//? ? ? ? v.measure(View.MeasureSpec.makeMeasureSpec(point.x, View.MeasureSpec.EXACTLY),

//? ? ? ? ? ? ? ? View.MeasureSpec.makeMeasureSpec(point.y, View.MeasureSpec.EXACTLY));

//

//? ? ? ? v.layout(0, 0, point.x, point.y);

//

////? ? ? ? Bitmap result = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.RGB_565);

//? ? ? ? Bitmap result = Bitmap.createBitmap(v.getWidth(), v.getHeight() + dp2px(context, 140), Bitmap.Config.ARGB_8888);

//? ? ? ? Canvas c = new Canvas(result);

//? ? ? ? c.drawColor(Color.WHITE);

//? ? ? ? // Draw view to canvas

//? ? ? ? v.draw(c);

//

//? ? ? ? return result;

//? ? }

? ? 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 MediaContentObserverextends ContentObserver {

private UrimContentUri;

? ? ? ? public MediaContentObserver(Uri contentUri, Handler handler) {

super(handler);

? ? ? ? ? ? mContentUri = contentUri;

? ? ? ? }

@Override

? ? ? ? public void onChange(boolean selfChange) {

super.onChange(selfChange);

? ? ? ? ? ? handleMediaContentChange(mContentUri);

? ? ? ? }

}

}

調(diào)用頁(yè)灭贷,oncreate調(diào)用init和start

onpause調(diào)用stop

/**

* 開(kāi)始監(jiān)聽(tīng)

*/

public void initScreenShotListen() {

screenShotListenManager = ScreenShotListenManager.newInstance(getActivity());

? ? initScreenShotListener =true;

}

/**

* 開(kāi)始監(jiān)聽(tīng)

*/

private void startScreenShotListen() {

if (initScreenShotListener && !isHasScreenShotListener &&screenShotListenManager !=null) {

screenShotListenManager.setListener(new ScreenShotListenManager.OnScreenShotListener() {

@Override

? ? ? ? ? ? public void onShot(String imagePath) {

if (BuildConfig.DEBUG) {

? ? ? ? ? ? ? ? ? ? Log.d("===========", "onShot: " +"獲得截圖路徑:" + imagePath +"===" + webUrl)

}

}

});

? ? ? ? screenShotListenManager.startListen();

? ? ? ? isHasScreenShotListener =true;

? ? }

}

/**

* 停止監(jiān)聽(tīng)

*/

private void stopScreenShotListen() {

if (initScreenShotListener &&isHasScreenShotListener &&screenShotListenManager !=null) {

screenShotListenManager.stopListen();

? ? ? ? isHasScreenShotListener =false;

? ? }

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末削茁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子粟害,更是在濱河造成了極大的恐慌蕴忆,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悲幅,死亡現(xiàn)場(chǎng)離奇詭異套鹅,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)汰具,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)则涯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)袜硫,“玉大人现斋,你說(shuō)我怎么就攤上這事讨阻。” “怎么了存谎?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵拔疚,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我既荚,道長(zhǎng),這世上最難降的妖魔是什么栋艳? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任恰聘,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晴叨。我一直安慰自己凿宾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布兼蕊。 她就那樣靜靜地躺著初厚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪孙技。 梳的紋絲不亂的頭發(fā)上产禾,一...
    開(kāi)封第一講書(shū)人閱讀 51,598評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音牵啦,去河邊找鬼亚情。 笑死,一個(gè)胖子當(dāng)著我的面吹牛哈雏,可吹牛的內(nèi)容都是我干的楞件。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼裳瘪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼土浸!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起彭羹,我...
    開(kāi)封第一講書(shū)人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤黄伊,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后皆怕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體毅舆,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年愈腾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了憋活。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡虱黄,死狀恐怖悦即,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情橱乱,我是刑警寧澤辜梳,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站泳叠,受9級(jí)特大地震影響作瞄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜危纫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一宗挥、第九天 我趴在偏房一處隱蔽的房頂上張望乌庶。 院中可真熱鬧,春花似錦契耿、人聲如沸瞒大。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)透敌。三九已至,卻和暖如春踢械,著一層夾襖步出監(jiān)牢的瞬間酗电,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工裸燎, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留顾瞻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓德绿,卻偏偏與公主長(zhǎng)得像荷荤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子移稳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

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