Android App内截屏监控及涂鸦功能实现
Android截屏功能是一个常用的功能,可以方便的用来分享或者发送给好友,本文介绍了如何实现app内截屏监控功能,当发现用户在我们的app内进行了截屏操作时,将自动展示该截屏,并提供用户随意圈点涂鸦,添加马赛克,撤销,分享等功能。
本文GitHub源码地址
实现效果如下:
实现该功能有以下技术需求:
1. 当app在前台展示的时候能够自动监听用户在app内的截屏,当app进入后台,停止监听
2. 监听到截屏后展示该截屏,并提供涂鸦(包括随意圈点和敏感信息马赛克)和上传分享功能
3. 涂鸦的每一步都可以撤销
涉及如下知识点:
1. App内截屏监听
2. 大图压缩
3. ImageView尺寸自适应
4. 自定义View实现涂鸦功能
5. 涂鸦撤销操作
对于截图监听有两种常用方案,方案一是通过FileObserver监听截屏文件夹,当有新的截屏文件产生时,调用设定的回调函数执行相关操作。该方案优缺点如下:
优点:
1. 实现简单
缺点:
1. 不同手机默认的截屏路径可能不同,需要做适配处理
2. 不同手机截屏触发的事件名称可能不同,需要测试适配
3. 监听到截屏事件后马上获取图片获取不到,需要延迟一段时间
方案二是通过ContentObserver监听多媒体图片库资源的变化。当手机上有新的图片文件产生时都会通过MediaProvider类向图片数据库插入一条记录,以方便系统的图片库进行图片查询,可以通过ContentObserver接收图片插入事件,并获取插入图片的URI。
优点:
1. 不同手机触发的事件是一样的
缺点:
1. 不同手机截屏文件的前缀可能不同,需要做适配
2. 监听到截屏事件后马上获取图片获取不到,需要延迟一段时间
这两种方式都需要根据手机做适配,第一种方式可以控制截屏监控只在App前台展示的时候进行,操作简单,我们使用这种方式做截屏监控。
接下来通过代码介绍具体实现。
FileObserver通过startWatching/stopWatching方法进行启动/停止文件监控,我们在BaseActivity的onResume和onPause方法中分别调用两个方法,其他Activity继承BaseActivity,实现App进入前台开始监控,转入后台停止监控的效果。
BaseActivity.java
public class BaseActivity extends AppCompatActivity {
@Override
protected void onResume() {
super.onResume();
// 设置回调函数
FileObserverUtils.setSnapShotCallBack(new SnapShotTakeCallBack(this));
FileObserverUtils.startSnapshotWatching();
}
@Override
protected void onPause() {
super.onPause();
FileObserverUtils.stopSnapshotWatching();
}
}
通过setSnapShotCallBack设置回调函数,并进行FileObserver初始化:
public class FileObserverUtils {
...
public static void setSnapShotCallBack(ISnapShotCallBack callBack) {
snapShotCallBack = callBack;
initFileObserver();
}
private static void initFileObserver() {
SNAP_SHOT_FOLDER_PATH = Environment.getExternalStorageDirectory()
+ File.separator + Environment.DIRECTORY_PICTURES
+ File.separator + "Screenshots" + File.separator;
fileObserver = new FileObserver(SNAP_SHOT_FOLDER_PATH, FileObserver.CREATE) {
@Override
public void onEvent(int event, String path) {
if (null != path && event == FileObserver.CREATE && (!path.equals(lastShownSnapshot))){
lastShownSnapshot = path; // 有些手机同一张截图会触发多个CREATE事件,避免重复展示
String snapShotFilePath = SNAP_SHOT_FOLDER_PATH + path;
int tryTimes = 0;
while (true) {
try {
// 收到CREATE事件后马上获取并不能获取到,需要延迟一段时间
Thread.sleep(600);
} catch (Exception e) {
e.printStackTrace();
}
try {
BitmapFactory.decodeFile(snapShotFilePath);
break;
} catch (Exception e) {
e.printStackTrace();
tryTimes++;
if (tryTimes >= MAX_TRYS) {
// 尝试MAX_TRYS次失败后,放弃
return;
}
}
}
snapShotCallBack.snapShotTaken(path);
}
}
};
}
...
}
FileObserver初始化传入要监控的截屏图片文件夹路径,当该文件夹下面的文件发生变化,包括截图生成新的图片时,调用onEvent函数,传入event和文件的path。我们根据event过滤出截屏事件,在这里是FileObserver.CREATE事件。收到事件后马上获取截图是获取不到的,需要过几百毫秒才能获取到,这里会让线程sleep一段时间再尝试获取,重试两次如果还获取失败就放弃。获取成功的话调用设置好的回调函数进行下一步操作。
我们的回调函数很简单,就是打开一个用于展示截屏的新的Activity叫SnapShotEditActivity,并传入截屏路径: