Android系统截屏监听的实现.docx
- 文档编号:25710908
- 上传时间:2023-06-11
- 格式:DOCX
- 页数:17
- 大小:19.32KB
Android系统截屏监听的实现.docx
《Android系统截屏监听的实现.docx》由会员分享,可在线阅读,更多相关《Android系统截屏监听的实现.docx(17页珍藏版)》请在冰豆网上搜索。
Android系统截屏监听的实现
Android系统截屏监听的实现
1.原理
Android系统并没有提供截屏通知相关的API,需要我们自己利用系统能提供的相关特性变通实现。
Android系统有一个媒体数据库,每拍一张照片,或使用系统截屏截取一张图片,都会把这张图片的详细信息加入到这个媒体数据库,并发出内容改变通知,我们可以利用内容观察者(ContentObserver)监听媒体数据库的变化,当数据库有变化时,获取最后插入的一条图片数据,如果该图片符合特定的规则,则认为被截屏了。
需要ContentObserver监听的资源URI:
MediaStore.Images.Media.INTERNAL_CONTENT_URI
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
读取外部存储器资源,需要添加权限:
android.permission.READ_EXTERNAL_STORAGE
2.截屏判断依据
当ContentObserver监听到媒体数据库的数据改变,在有数据改变时获取最后插入数据库的一条图片数据,如果符合以下规则,则认为截屏了:
1、时间判断,图片的生成时间在开始监听之后,并与当前时间相隔10秒内:
开始监听后生成的图片才有意义,相隔10秒内说明是刚刚生成的
2、尺寸判断,图片的尺寸没有超过屏幕的尺寸:
图片尺寸超过屏幕尺寸,不可能是截屏图片
3、路径判断,图片路径符合包含特定的关键词:
这一点是关键,截屏图片的保存路径通常包含“screenshot”
PS:
这些判断是为了增加截屏检测结果的可靠性,防止误报,防止遗漏。
其中截屏图片的路径正常Android系统保存的路径格式为:
“外部存储器/Pictures/Screenshots/Screenshot_20161001-164643.png”,但Android系统碎片化严重,加上其他第三方截屏APP等,所以路径关键字除了检查是否包含“screenshot”外,还可以适当增加其他关键字,详见最后的监听器完整代码。
这种监听截屏的方法也不是100%准确,例如某些被root的机器使用第三方截屏APP自定义保存路径,还比如通过ADB命令在电脑上获取手机屏幕快照均不能监听到,但这也是目前可行性最高的方法,对于绝大多数用户都比较靠谱。
3.监听器的封装与使用
我把ContentObserver对媒体数据库的的监听,以及对结果的判断全都封装在一个独立的类中(ScreenShotListenManager),使用步骤相当简单:
1、在AndroidManifest.xml中添加权限:
name="android.permission.READ_EXTERNAL_STORAGE"/> 1 1 2、监听器的调用: ScreenShotListenManagermanager=ScreenShotListenManager.newInstance(context); manager.setListener( newOnScreenShotListener(){ publicvoidonShot(StringimagePath){ //dosomething } } ); manager.startListen(); ... manager.stopListen(); 监听器类(ScreenShotListenManager)完整代码实现: packagecom.xiets.screenshotlistener; importandroid.content.Context; importandroid.database.ContentObserver; importandroid.database.Cursor; importandroid.graphics.BitmapFactory; importandroid.graphics.Point; import.Uri; importandroid.os.Build; importandroid.os.Handler; importandroid.os.Looper; importandroid.provider.MediaStore; importandroid.text.TextUtils; importandroid.util.Log; importandroid.view.Display; importandroid.view.WindowManager; importjava.lang.reflect.Method; importjava.util.ArrayList; importjava.util.List; /** *截屏监听管理器 * * *截屏判断依据: 监听媒体数据库的数据改变,在有数据改变时获取最后 *插入数据库的一条图片数据,如果符合以下规则,则认为截屏了: * *1.时间判断,图片的生成时间在开始监听之后,并与当前时间相隔10秒内; *2.尺寸判断,图片的尺寸没有超过屏幕的尺寸; *3.路径判断,图片路径符合包含特定的关键词。 * * *Demo: *{@code
*
*//RequiresPermission:
android.permission.READ_EXTERNAL_STORAGE
*
*ScreenShotListenManagermanager=ScreenShotListenManager.newInstance(context);
*
*manager.setListener(
*newOnScreenShotListener(){
*publicvoidonShot(StringimagePath){
*//dosomething
*}
*}
*);
*
*manager.startListen();
*...
*manager.stopListen();
*
*}
*
*@authorxietansheng
*/
publicclassScreenShotListenManager{
privatestaticfinalStringTAG="ScreenShotListenManager";
/**读取媒体数据库时需要读取的列*/
privatestaticfinalString[]MEDIA_PROJECTIONS={
MediaStore.Images.ImageColumns.DATA,
MediaStore.Images.ImageColumns.DATE_TAKEN,
};
/**读取媒体数据库时需要读取的列,其中WIDTH和HEIGHT字段在API16以后才有*/
privatestaticfinalString[]MEDIA_PROJECTIONS_API_16={
MediaStore.Images.ImageColumns.DATA,
MediaStore.Images.ImageColumns.DATE_TAKEN,
MediaStore.Images.ImageColumns.WIDTH,
MediaStore.Images.ImageColumns.HEIGHT,
};
/**截屏依据中的路径判断关键字*/
privatestaticfinalString[]KEYWORDS={
"screenshot","screen_shot","screen-shot","screenshot",
"screencapture","screen_capture","screen-capture","screencapture",
"screencap","screen_cap","screen-cap","screencap"
};
privatestaticPointsScreenRealSize;
/**已回调过的路径*/
privatefinalList
privateContextmContext;
privateOnScreenShotListenermListener;
privatelongmStartListenTime;
/**内部存储器内容观察者*/
privateMediaContentObservermInternalObserver;
/**外部存储器内容观察者*/
privateMediaContentObservermExternalObserver;
/**运行在UI线程的Handler,用于运行监听器回调*/
privatefinalHandlermUiHandler=newHandler(Looper.getMainLooper());
privateScreenShotListenManager(Contextcontext){
if(context==null){
thrownewIllegalArgumentException("Thecontextmustnotbenull.");
}
mContext=context;
//获取屏幕真实的分辨率
if(sScreenRealSize==null){
sScreenRealSize=getRealScreenSize();
if(sScreenRealSize!
=null){
Log.d(TAG,"ScreenRealSize:
"+sScreenRealSize.x+"*"+sScreenRealSize.y);
}else{
Log.w(TAG,"Getscreenrealsizefailed.");
}
}
}
publicstaticScreenShotListenManagernewInstance(Contextcontext){
assertInMnThread();
returnnewScreenShotListenManager(context);
}
/**
*启动监听
*/
publicvoidstartListen(){
assertInMainThread();
sHasCallbackPaths.clear();
//记录开始监听的时间戳
mStartListenTime=System.currentTimeMillis();
//创建内容观察者
mInternalObserver=newMediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI,mUiHandler);
mExternalObserver=newMediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,mUiHandler);
//注册内容观察者
mContext.getContentResolver().registerContentObserver(
MediaStore.Images.Media.INTERNAL_CONTENT_URI,
false,
mInternalObserver
);
mContext.getContentResolver().registerContentObserver(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
false,
mExternalObserver
);
}
/**
*停止监听
*/
publicvoidstopListen(){
assertInMainThread();
//注销内容观察者
if(mInternalObserver!
=null){
try{
mContext.getContentResolver().unregisterContentObserver(mInternalObserver);
}catch(Exceptione){
e.printStackTrace();
}
mInternalObserver=null;
}
if(mExternalObserver!
=null){
try{
mContext.getContentResolver().unregisterContentObserver(mExternalObserver);
}catch(Exceptione){
e.printStackTrace();
}
mExternalObserver=null;
}
//清空数据
mStartListenTime=0;
sHasCallbackPaths.clear();
}
/**
*处理媒体数据库的内容改变
*/
privatevoidhandleMediaContentChange(UricontentUri){
Cursorcursor=null;
try{
//数据改变时查询数据库中最后加入的一条数据
cursor=mContext.getContentResolver().query(
contentUri,
Build.VERSION.SDK_INT<16?
MEDIA_PROJECTIONS:
MEDIA_PROJECTIONS_API_16,
null,
null,
MediaStore.Images.ImageColumns.DATE_ADDED+"desclimit1"
);
if(cursor==null){
Log.e(TAG,"Deviantlogic.");
return;
}
if(!
cursor.moveToFirst()){
Log.d(TAG,"Cursornodata.");
return;
}
//获取各列的索引
intdataIndex=cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
intdateTakenIndex=cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN);
intwidthIndex=-1;
intheightIndex=-1;
if(Build.VERSION.SDK_INT>=16){
widthIndex=cursor.getColumnIndex(MediaStore.Images.ImageColumns.WIDTH);
heightIndex=cursor.getColumnIndex(MediaStore.Images.ImageColumns.HEIGHT);
}
//获取行数据
Stringdata=cursor.getString(dataIndex);
longdateTaken=cursor.getLong(dateTakenIndex);
intwidth=0;
intheight=0;
if(widthIndex>=0&&heightIndex>=0){
width=cursor.getInt(widthIndex);
height=cursor.getInt(heightIndex);
}else{
//API16之前,宽高要手动获取
Pointsize=getImageSize(data);
width=size.x;
height=size.y;
}
//处理获取到的第一行数据
handleMediaRowData(data,dateTaken,width,height);
}catch(Exceptione){
e.printStackTrace();
}finally{
if(cursor!
=null&&!
cursor.isClosed()){
cursor.close();
}
}
}
privatePointgetImageSize(StringimagePath){
BitmapFactory.Optionsoptions=newBitmapFactory.Options();
options.inJustDBounds=true;
BitmapFactory.decodeFile(imagePath,options);
returnnewPoint(options.outWidth,options.outHeight);
}
/**
*处理获取到的一行数据
*/
privatevoidhandleMediaRowData(Stringdata,longdateTaken,intwidth,intheight){
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{
//如果在观察区间媒体数据库有数据改变,又不符合截屏规则,则输出到log待分析
Log.w(TAG,"Mediacontentchanged,butnotscreenshot:
path="+data
+";size="+width+"*"+height+";date="+dateTaken);
}
}
/**
*判断指定的数据行是否符合截屏条件
*/
privatebooleancheckScreenShot(Stringdata,longdateTaken,intwidth,intheight){
/*
*判断依据一:
时间判断
*/
//如果加入数据库的时间在开始监听之前,或者与当前时间相差大于10秒,则认为当前没有截屏
if(dateTaken
returnfalse;
}
/*
*判断依据二:
尺寸判断
*/
if(sScreenRealSize!
=null){
//如果图片尺寸超出屏幕,则认为当前没有截屏
if(
!
(
(width<=sScreenRealSize.x&&height<=sScreenRealSize.y)
||
(height<=sScreenRealSize.x&&width<=sScreenRealSize.y)
)){
returnfalse;
}
}
/*
*判断依据三:
路径判断
*/
if(TextUtils.isEmpty(data)){
returnfalse;
}
data=data.toLowerCase();
//判断图片路径是否含有指定的关键字之一,如果有,则认为当前截屏了
for(StringkeyWork:
KEYWORDS){
if(data.contains(keyWork)){
returntrue;
}
}
returnfalse;
}
/**
*判断是否已回调过,某些手机ROM截屏一次会发出多次内容改变的通知;
*删除一个图片也会发通知,同时防止删除图片时误将上一张符合截屏规则的图片当做是当前截屏.
*/
privatebooleancheckCallback(StringimagePath){
if(sHasCallbackPaths.contains(imagePath)){
returntrue;
}
//大概缓存15~20条记录便可
if(sHasCallbackPaths.size()>=20){
for(inti=0;i<5;i++){
sHasCallbackPaths.remove(0);
}
}
sHasCallbackPaths.add(imagePath);
returnfalse;
}
/**
*获取屏幕分辨率
*/
privatePointgetRealScreenSize(){
PointscreenSize=null;
try{
screenSize=newPoint();
WindowManagerwindowManager=(WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
DisplaydefaultDisplay=windowManager.getDefaultDisplay();
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.JELLY_BEAN_MR1){
defaultDisplay.getRealSize(screenSize);
}else{
try{
MethodmGetRawW=Disp
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Android 系统 监听 实现