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