最近有做在监听系统截屏操作,然后对截屏的图片获取到,再在其底部加入二维码或者其他信息生成分享海报。这里面最不好做的就是监听系统截屏的操作了,系统没有提供相关api,所以得靠“骚操作”,目前业内主流的操作,
通过 自定义媒体内容观察者内部类,去观察媒体数据库的改变,当改变的时候发送通知。然而存在一定的问题:部分机型(或者说一些奇怪的操作会导致读出来的照片并不是最新的一张照片,而是一张很老的照片,从而导致了监听截屏操作失败),接下来我们一起来看看这个问题。
其中ContentObserver基本代码如下:
/**
* 媒体内容观察者(观察媒体数据库的改变)
*/
private class MediaContentObserver extends ContentObserver {
private Uri mContentUri;
public MediaContentObserver(Uri contentUri, Handler handler) {
super(handler);
mContentUri = contentUri;
}
public void onChange(boolean selfChange) {
super.onChange(selfChange);
handleMediaContentChange(mContentUri);
}
}
其中获取最后一次更新的媒体文件时的代码(为便于查看 删除了判空处理代码):
/**
* 处理媒体数据库的内容改变
*/
private void handleMediaContentChange(Uri contentUri) {
Cursor cursor = null;
/** 读取媒体数据库时需要读取的列 */
private static final String[] MEDIA_PROJECTIONS = {
MediaStore.Images.ImageColumns.DATA,
MediaStore.Images.ImageColumns.DATE_TAKEN };
try {
// 数据改变时查询数据库中最后加入的一条数据
cursor = mContext.getContentResolver().query(
contentUri,
MEDIA_PROJECTIONS,
null,
null,
MediaStore.Images.ImageColumns.DATE_ADDED + " desc limit 1"
);
// 获取各列的索引
int dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
int dateTakenIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN);
// 获取行数据
String data = cursor.getString(dataIndex);
long dateTaken = cursor.getLong(dateTakenIndex);
// 处理获取到的第一行数据
handleMediaRowData(data, dateTaken);
}
这里似乎看不出来问题,流程很清晰:
1、监听媒体库的数据变化,变化后回调通知
2、通过查询语句:
mContext.getContentResolver().query(
contentUri,
MEDIA_PROJECTIONS,
null, null,
MediaStore.Images.ImageColumns.DATE_ADDED + " desc limit 1" )
按DATE_ADDED(上文Android监听截屏事件之媒体读取的探索有讲过,DATE_ADDED的意思是“添加的到数据的时间,单位秒),那么这里的意图就是–>“获取到最新添加到数据库的文件”
然而事实呢?
出现的问题就是,有一部分手机监听获取图片的时候,会有拿到一张明明是很久之前的图片,而我们这里的代码认为那是最新的照片,于是打个断点去看了看为什么。
我们对代码进行改造,去打印contentUri里面所有的数据
cursor = mContext.getContentResolver().query(
contentUri,
null,
null,
null,
MediaStore.Images.ImageColumns.DATE_ADDED + " desc"
);
while (cursor.moveToNext()) {
// 获取各列的索引
int dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
int dateTakenIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN);
int dateAddedIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_ADDED);
int dateModifiedIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_MODIFIED);
// 获取行数据
String data = cursor.getString(dataIndex);
String dateTaken = TimeUtils.getFormateTime(cursor.getLong(dateTakenIndex));
String dateAdded = TimeUtils.getFormateTime(cursor.getLong(dateAddedIndex));
String dateModified = TimeUtils.getFormateTime(cursor.getLong(dateModifiedIndex));
Log.e("julis_data","文件名:"+data+" 添加时间:"+" "+dateAdded+" 修改时间:"+dateModified+" dateTaken时间:"+dateTaken);
}
其中TimeUtils.getFormateTime
的作用是将时间戳转换为“yyyy-MM-dd HH:mm”的形式
打印出来所有图片的信息如下图所示:
这是排在最前面的图片的信息
找到关于截屏图片的信息
看到打印出来的数据后一上来就傻眼了,dateAdded的时间居然是在 51436年…这明显有问题嘛。不急,我们断点去看看各个点的原有数据。这是获取到第一张图片时候的断点信息:
这是一直向下打断点直到获取到的图片为截屏图片信息
从两个断点信息可以看到 截图图片的dataAdded的时间是正常的,而我们看到我们默认代码按date_added
排序获取到最新一张照片的,时间戳为1561025865348,而截屏图片的时间戳为1562747517,明显前者更大,所以导致我们做监听截屏的时候获取到“最新”的照片并不是我们想要的那张截图照片。其中 1561025865348 很明显是一个以ms毫秒为单位的一个时间戳,所以使用date_added
排序的话有一些问题,而我们打印date_modified
获取到的时间都是正确的,而我们也可以看到获取的 date_taken
都是比较正确的数据,都是以秒为单位,所以这里我对查询代码做了下修改:
将MediaStore.Images.ImageColumns.DATE_ADDED
改为MediaStore.Images.ImageColumns.DATE_MODIFIED
亲测原来有问题的手机都没有这个问题了。
mContext.getContentResolver().query(
contentUri,
MEDIA_PROJECTIONS,
null, null,
MediaStore.Images.ImageColumns.DATE_MODIFIED + " desc limit 1" )
总结:
1、将DATE_ADDED
改为DATE_MODIFIED
这样改动的方式也许不能保证全部手机能监听到截屏,但是自己测出来的原先有问题的手机都没有问题了。
2、通过打印数据发现,DATE_TAKEN
也都是统一保持的ms毫秒为单位,讲道理DATE_ADDED
改为DATE_TAKEN
也应该能解决部分手机无法监听到的问题。
3、个人猜测 部分手机厂商在对图片存储的时候对于添加的时间戳可能存在相关的差异导致了这样的威问题。
4、在58同城同样有类似的问题:在进入聊天界面后,截个屏(相当于当前相册最新图片为那张截屏的图),可是点“+”操作弹出来推荐的照片的确是 MediaStore.Images.ImageColumns.DATE_ADDED desc limit 1
最新的那张图片。