博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
支付宝截图反馈功能实现
阅读量:5909 次
发布时间:2019-06-19

本文共 7443 字,大约阅读时间需要 24 分钟。

最近项目中有个截图反馈的功能要做成sdk供业务方使用,类似支付宝中的功能,但是功能更复杂

实现思路:

  • 监听截图
  • 显示监听结果加跳转交互

对于实现监听截图的功能,前辈们已经做了很多,这里采用MediaContentObserver的解决方案,详情可查看 。

坑点梳理

  • 部分机型一次截图,会有多次回调(vivo x9 2次)
  • vivo Y51A 截图关键字为汉字截图
  • 截图加载在部分机型出现 decoder->decode returned false 加载失败
  • 悬浮窗权限兼容问题(WindowManager.LayoutParams.TYPE_PHONE 在O版本中废弃,实际运行结果为,赋予了悬浮窗权限仍报没有权限error)
  • 大部分机型的截图是png格式,有些为jpg
  • 安卓7.1.1 miui 9.1 小米 mix2 由于全面屏的缘故,截图高度为2160大于屏幕高度1980

实现

/**    * 媒体内容观察者(观察媒体数据库的改变)    */   private class MediaContentObserver extends ContentObserver {       private Uri mContentUri;       //MediaStore.Images.Media.INTERNAL_CONTENT_URI       //MediaStore.Images.Media.EXTERNAL_CONTENT_URI       public MediaContentObserver(Uri contentUri, Handler handler) {           super(handler);           mContentUri = contentUri;       }       @Override       public void onChange(boolean selfChange) {           super.onChange(selfChange);           processMediaContentChange(mContentUri);       }   }复制代码

在监听到有内容改变时,去查询图片内容

/**    * 读取媒体数据库时需要读取的列    */   private static final String[] MEDIA_PROJECTIONS = {           MediaStore.Images.ImageColumns.DATA,           MediaStore.Images.ImageColumns.DATE_TAKEN,   };   /**    * 读取媒体数据库时需要读取的列, 其中 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,   };      Cursor 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"           );复制代码

留意这里的 MediaStore.Images.ImageColumns.DATE_ADDED ,实测发现在vivo x9上无效,需要查询 MediaStore.Images.ImageColumns.DATE_MODIFIED 才能获得

之后根据图片大小和图片所在文件夹或文件名检查是否符合为一张截图,部分截图关键字如下:

private static final String[] KEYWORDS = {            "screenshot", "screen_shot", "screen-shot", "screen shot",            "screencapture", "screen_capture", "screen-capture", "screen capture",            "screencap", "screen_cap", "screen-cap", "screen cap", "截屏"    };复制代码

在此,顺带说下系统的截图流程

如果留意下,会发现每次截图,系统都有相应的日志输出

04-27 16:51:14.291 9240-9240/? D/TakeScreenshotService: send Broadcast, URI:file:///storage/emulated/0/截屏/截屏_20180427_165114.jpg04-27 16:51:14.301 1939-1949/? D/Parcel: acquire_object  ret:0 size:2097152    release_object  ret:0  size:209715204-27 16:51:14.301 1939-1939/? D/MediaScannerReceiver: action: android.intent.action.MEDIA_SCANNER_SCAN_FILE path: /storage/emulated/0/截屏/截屏_20180427_165114.jpg复制代码
com.android.systemui.screenshot.TakeScreenshotService.java...省略部分代码// If the storage for this user is locked, we have no place to store            // the screenshot, so skip taking it instead of showing a misleading            // animation and error notification.            if (!getSystemService(UserManager.class).isUserUnlocked()) {                Log.w(TAG, "Skipping screenshot because storage is locked!");                post(finisher);                return;            }            if (mScreenshot == null) {                mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);//1            }            switch (msg.what) {                case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:                    mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);//2                    break;                case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:                    mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0);//3                    break;            }复制代码

从片段1,2,3清楚第看到实际进行截图的是GlobalScreenshot

void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible,            int x, int y, int width, int height) {            //实际截屏        mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);        //...        if (requiresRotation) {            // Rotate the screenshot to the current orientation            //... 旋转截图        }        if (width != mDisplayMetrics.widthPixels || height != mDisplayMetrics.heightPixels) {            // Crop the screenshot to selected region            //...        }        // Optimizations        mScreenBitmap.setHasAlpha(false);        mScreenBitmap.prepareToDraw();        // 开始截屏动画展示        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,                statusBarVisible, navBarVisible);            }                /**     * Starts the animation after taking the screenshot     */    private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible,            boolean navBarVisible) {            //省略部分代码                    mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {            @Override            public void onAnimationEnd(Animator animation) {                // Save the screenshot once we have a bit of time now |保存截图                saveScreenshotInWorkerThread(finisher);                mWindowManager.removeView(mScreenshotLayout);                // Clear any references to the bitmap                mScreenBitmap = null;                mScreenshotView.setImageBitmap(null);            }        });        //省略部分代码            }                    /**     * Creates a new worker thread and saves the screenshot to the media store.     */    private void saveScreenshotInWorkerThread(Runnable finisher) {        SaveImageInBackgroundData data = new SaveImageInBackgroundData();        data.context = mContext;        data.image = mScreenBitmap;        data.iconSize = mNotificationIconSize;        data.finisher = finisher;        data.previewWidth = mPreviewWidth;        data.previewheight = mPreviewHeight;        if (mSaveInBgTask != null) {            mSaveInBgTask.cancel(false);        }        mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager)                .execute();//开启后台任务,保存截图    }            这里我们关注下保存的内容,在SaveImageInBackgroundTask#doInBackground()中            // Save            OutputStream out = new FileOutputStream(mImageFilePath);            image.compress(Bitmap.CompressFormat.PNG, 100, out);            out.flush();            out.close();            //将图片写到存储空间,注意,这里部分机型存储的为jpg图片            // Save the screenshot to the MediaStore            ContentValues values = new ContentValues();            ContentResolver resolver = context.getContentResolver();            values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath);            values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName);            values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName);            values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime);            values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds);            values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds);            values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");            values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth);            values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight);            values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());            Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);//更新媒体信息复制代码

转载地址:http://klvpx.baihongyu.com/

你可能感兴趣的文章
Javascript自定义事件
查看>>
10-13C#语句(1)
查看>>
11-02SQLserver基础--字符串函数
查看>>
jQuery Wookmark
查看>>
Java学习笔记二:数据类型II
查看>>
Tracking your habits in Org-mode
查看>>
jquery图片轮播插件
查看>>
为elasticSearch开发c++接口
查看>>
同一件事(watch+scss) gulp和grunt速度的对比
查看>>
zabbix的配置使用
查看>>
csu2161: 漫漫上学路(Hash+最短路)
查看>>
jQuery监听键盘事件及相关操作使用教程
查看>>
使用js修改url地址参数
查看>>
在Notepad++中为Python配置编译环境
查看>>
操作系统知识点-3.进程原理(下):进程通信
查看>>
重复引用错误:duplicate symbols for architecture x86_64
查看>>
restricted 模式及其 使用
查看>>
MFC小程序
查看>>
转:Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB
查看>>
计算机图形学 课设
查看>>