Android截屏与WebView长图分享经验总结

最近在做新业务需求的同时,我们在 Android 上遇到了一些之前没有碰到过的问题,截屏分享、 WebView 生成长图以及长图在各个分享渠道分享时图片模糊甚至分享失败等问题,在这过程中踩了很多坑,到目前为止绝大部分的问题都还算是有了比较满意的解决方案。以下就从三个方面来总结一下过程中遇到的挑战和***的解决方案。

一、概述

最近在做新业务需求的同时,我们在 Android 上遇到了一些之前没有碰到过的问题,截屏分享、 WebView 生成长图以及长图在各个分享渠道分享时图片模糊甚至分享失败等问题,在这过程中踩了很多坑,到目前为止绝大部分的问题都还算是有了比较满意的解决方案。以下就从三个方面来总结一下过程中遇到的挑战和***的解决方案。

二、截图分享

在 Android 原生系统中是没有提供截图的广播或者监听事件的,也就是说代码层面无法获知用户的截屏操作,这样就无法满足用户截屏后跳出分享提示的需求。既然无法从根本上解决截屏监听的问题,那么就要考虑通过其他方式间接实现,目前比较成熟稳定的方案是监听系统媒体数据库资源的变化,具体方案原理如下:

Android 系统有一个媒体数据库,每拍一张照片,或使用系统截屏截取一张图片,都会把这张图片的详细信息加入到这个媒体数据库,并发出内容改变通知,我们可以利用内容观察者(ContentObserver)监听媒体数据库的变化,当数据库有变化时,获取***插入的一条图片数据,如果该图片符合特定的规则,则认为被截屏了。

考虑到手机存储包括内部存储器和外部存储器,为了增强兼容性,***同时监听两种储存空间的变化,以下是需要 ContentObserver 监听的资源 URI :

 
 
 
 
  1. MediaStore.Images.Media.INTERNAL_CONTENT_URI 
  2. MediaStore.Images.Media.EXTERNAL_CONTENT_URI

读取外部存储器资源,需要添加权限:

 
 
 
 
  1. android.permission.READ_EXTERNAL_STORAGE

注:在 Android 6.0 及以上版本需要动态申请权限

1. 截屏判断规则

当 ContentObserver 监听到媒体数据库的数据改变, 在有数据改变时获取***插入数据库的一条图片数据, 如果符合以下规则, 则认为截屏了:

  1. 时间判断:通常截屏生成后会立马存入系统多媒体数据库,也就是说监听到数据库变化的时间与截图生成的时间不会相差太多,这里推荐以10秒作为阈值,当然这个也是经验值。
  2. 尺寸判断:截屏顾名思义取得是当前手机屏幕尺寸大小的图片,所以图片宽高大于屏幕宽高的肯定都不是截图产生的。
  3. 路径判断:由于各手机厂家存放截图的文件路径都不太一样,国内情况可能会更严重,但是通常图片保存路径都会包含一些常见的关键词,比如 “screenshot”、 “screencapture” 、 “screencap” 、 “截图”、 “截屏”等,每次都检查图片路径信息是否包含这些关键词。

关于第3点需要补充说明一下,由于要判断图片文件路径是否包含关键字,所以目前仅支持中英文环境,如果需要支持其他语言,需要手动添加一些该语言的关键词,否则有可能获取不到图片。

以上3点基本上可以保证截图的正常监听,当然在实际测试过程中,还会发现有些机型存在多报的情况,所以还需要做一些去重等工作,关于去重下面还会再提及。

2. 关键代码

原理都了解清楚了,那么接下来就是如何实现的问题了。这里最关键是媒体内容观察者的设置,从数据库中取出***条数据并解析图片信息,然后再检验图片信息是否符合以上3条规则。

为了说清楚如何监听媒体数据库改变,先要稍微讲一下 ContentObserver 的原理。 ContentObserver ——内容观察者,目的是观察(捕捉)特定 Uri 引起的数据库的变化,继而做一些相应的处理,它类似于数据库技术中的触发器(Trigger),当 ContentObserver 所观察的 Uri 发生变化时,便会触发它。当然想要观察就必须先要注册, Android 系统提供了 ContentResolver#registerContentObserver 方法用来注册观察器。此部分不熟悉的同学可以温习一下 Android 的 ContentProvider 相关知识。

接下来直接用代码说明整个注册和触发流程,代码如下:

 
 
 
 
  1. private void initMediaContentObserver() {
  2.     // 运行在 UI 线程的 Handler, 用于运行监听器回调 
  3.     private final Handler mUiHandler = new Handler(Looper.getMainLooper());
  4.     // 创建内容观察者,包括内部存储和外部存储
  5.     mInternalObserver = new MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, mUiHandler);
  6.     mExternalObserver = new MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mUiHandler);
  7.     // 注册内容观察者
  8.     mContext.getContentResolver().registerContentObserver(
  9.             MediaStore.Images.Media.INTERNAL_CONTENT_URI, false, mInternalObserver);
  10.     mContext.getContentResolver().registerContentObserver(
  11.             MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, mExternalObserver);
  12. }
  13. /**
  14.  * 自定义媒体内容观察者类(观察媒体数据库的改变)
  15.  */
  16. private class MediaContentObserver extends ContentObserver {
  17.     private Uri mediaContentUri;       // 需要观察的Uri
  18.     public MediaContentObserver(Uri contentUri, Handler handler) {
  19.         super(handler);
  20.         mediaContentUri = contentUri;
  21.     }
  22.     @Override
  23.     public void onChange(boolean selfChange) {
  24.         super.onChange(selfChange);
  25.         // 处理媒体数据库反馈的数据变化
  26.         handleMediaContentChange(mediaContentUri);
  27.     }
  28. }

有注册就需要在 Activity 销毁时取消注册,所以还需要封装一个解除注册的方法供外部调用, Android 系统提供 ContentResolver#unregisterContentObserver 方法来取消注册,代码比较简单,这里就不再展示了。

监听器设置和注册完成后,一旦用户操作了截屏动作,系统就会执行 ContentObserver#onChange 回调方法,在这个方法中我们可以根据 Uri 获取并解析数据。这里展示一下具体的数据解析过程,上述提到的规则判断比较简单,就不再展示了。

  1. private void handleMediaContentChange(Uri contentUri) {
  2.     Cursor cursor = null;
  3.         try {
  4.             // 数据改变时查询数据库中***加入的一条数据
  5.             cursor = mContext.getContentResolver().query(contentUri,
  6.                     Build.VERSION.SDK_INT < 16 ? MEDIA_PROJECTIONS : MEDIA_PROJECTIONS_API_16,
  7.                     null, null, MediaStore.Images.ImageColumns.DATE_ADDED + " desc limit 1");
  8.             if (cursor == null)  return;
  9.             if (!cursor.moveToFirst()) return;       
  10.             // cursor.getColumnIndex获取数据库列索引
  11.             int dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
  12.             String data = cursor.getString(dataIndex);        // 图片存储地址
  13.             int dateTakenIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN);
  14.             long dateTaken = cursor.getLong(dateTakenIndex);  // 图片生成时间
  15.             int width = 0;
  16.             int height = 0;
  17.             if (Build.VERSION.SDK_INT >= 16) {
  18.                 int widthIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.WIDTH);
  19.                 int heightIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.HEIGHT);
  20.                 width = cursor.getInt(widthIndex);    // 获取图片高度
  21.                 height = cursor.getInt(heightIndex);  // 获取图片宽度
  22.             } else {
  23.                 Point size = getImageSize(data);     // 根据路径获取图片宽和高
  24.                 width = size.x;
  25.                 height = size.y;
  26.             }
  27.             // 处理获取到的***行数据,分别判断路径是否包含关键词、时间差以及图片宽高和屏幕宽高的大小关系
  28.             handleMediaRowData(data, dateTaken, width, height);
  29.         } catch (Exception e) {
  30.             e.printStackTrace();
  31.         } finally {
  32.             if (cursor != null && !cursor.isClosed())

    文章标题:Android截屏与WebView长图分享经验总结
    当前链接:http://www.mswzjz.cn/qtweb/news20/347720.html

    攀枝花网站建设、攀枝花网站运维推广公司-贝锐智能,是专注品牌与效果的网络营销公司;服务项目有等

    广告

    声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 贝锐智能