IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android进阶之路 - 花样百出的截屏需求 -> 正文阅读

[移动开发]Android进阶之路 - 花样百出的截屏需求

最近又遇到了截图相关的需求,联合之前的截图需求后,抽时间整理了一下截屏的常见需求,特此记录 ~

关联篇

针对每个人不同的需求,可采用不同的方法 - - ~

截取当前屏幕界面(activity)

一般调用对应方法时出现调用不到的场景时,我们需要根据提示进行修改,这里应该需要通过activity进行方法调用 ~

别人的方法

    public Bitmap screen() {
        View dView = getWindow().getDecorView();
        dView.setDrawingCacheEnabled(true);
        dView.buildDrawingCache();
        return Bitmap.createBitmap(dView.getDrawingCache());
    }

我的方法 - activity截图

    //缺点:无法截取WebView页面,截屏后是白屏!
    public static Bitmap capture(Activity activity) {
        activity.getWindow().getDecorView().setDrawingCacheEnabled(true);
        Bitmap bmp = activity.getWindow().getDecorView().getDrawingCache();
        return bmp;
    }

截图并保存

    public void screen() {
        View dView = getWindow().getDecorView();
        dView.setDrawingCacheEnabled(true);
        dView.buildDrawingCache();
        Bitmap bitmap = Bitmap.createBitmap(dView.getDrawingCache());
        if(bitmap !=null ) {
            try {
                // 获取内置SD卡路径
                String sdCardPath = Environment.getExternalStorageDirectory().getPath();
                // 图片文件路径(自行定义)
                String filePath = sdCardPath + File.separator +"screenshot.png";
                File file =new File(filePath);
                FileOutputStream os = new FileOutputStream(file);
                bitmap.compress(Bitmap.CompressFormat.PNG,100, os);
                os.flush();
                os.close();
            } catch(Exception e) {
            }
        }
    }

截取某个控件或区域(view)

可作用于根view或某个控件view

  • 简洁版
	/**
     * 截取指定View为图片
     */
    public static Bitmap captureView(View view) throws Throwable {
        Bitmap bm = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
        view.draw(new Canvas(bm));
        return bm;
    }

截图并保存

  public void captureView(View view) {
  		//图片存储路径,可以不关注
        String imgPath = "/sdcard/test.png";
        view.setDrawingCacheEnabled(true);
        view.buildDrawingCache();
        Bitmap bitmap = view.getDrawingCache();
        //图片存储部分可以不关注
        if (bitmap != null) {
            try {
                FileOutputStream out = new FileOutputStream(imgPath);
                bitmap.compress(Bitmap.CompressFormat.PNG, 100,
                        out);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
  • 繁琐版
   public void screen(View view){
        View dView = view;
        dView.setDrawingCacheEnabled(true);
        dView.buildDrawingCache();
        //以下俩种方式,一种是取图片缓存,一种是重新绘制,可自行调节尝试
        //Bitmap bitmap = Bitmap.createBitmap(dView.getDrawingCache());
        Bitmap bitmap = Bitmap.createBitmap(dView.getWidth(), dView.getHeight(), Bitmap.Config.ARGB_8888);
        //使用Canvas,调用自定义view控件的onDraw方法,绘制图片
        Canvas canvas =new Canvas(bitmap);
        dView.draw(canvas);
    }
  • view截图(有人说截取view转bitmap后为null,可用这种方式清理缓存,未尝试
    public static Bitmap getViewBp(View v) {
        if (null == v) {
            return null;
        }
        v.setDrawingCacheEnabled(true);
        v.buildDrawingCache();
        if (Build.VERSION.SDK_INT >= 11) {
            v.measure(View.MeasureSpec.makeMeasureSpec(v.getWidth(),
                    View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(
                    v.getHeight(), View.MeasureSpec.EXACTLY));
            v.layout((int) v.getX(), (int) v.getY(),
                    (int) v.getX() + v.getMeasuredWidth(),
                    (int) v.getY() + v.getMeasuredHeight());
        } else {
            v.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
            v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
        }
        Bitmap b = Bitmap.createBitmap(v.getDrawingCache(), 0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());

        v.setDrawingCacheEnabled(false);
        v.destroyDrawingCache();
        return b;
    }
  • view截图(有人说截取view转bitmap后为null,可用这种方式清理缓存,未尝试
	public static Bitmap convertViewToBitmap(View view) {
        view.setDrawingCacheEnabled(true);
        view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
        view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
        view.buildDrawingCache();
        Bitmap bitmap = view.getDrawingCache();
        view.setDrawingCacheEnabled(false);
        return bitmap;
 	}

截取长屏

? 截取长屏其实原理就是截取整个ScrollView或者ListView的视图,因此实现原理跟上面中提到的截取某个控件的View基本一致。

ScrollView 实现截屏
		/*
         * 截取scrollview的屏幕
         **/
     public static Bitmap getScrollViewBitmap(ScrollView scrollView) {
         int h = 0;
         Bitmap bitmap;
         for  ( int i = 0 ; i < scrollView.getChildCount(); i++) {
         		h += scrollView.getChildAt(i).getHeight();
   		 }
 
         // 创建对应大小的bitmap
         bitmap = Bitmap.createBitmap(scrollView.getWidth(), h,Bitmap.Config.ARGB_8888);
         final Canvas canvas = new Canvas(bitmap);
         scrollView.draw(canvas);  
         return bitmap;
      }
ListView实现截屏
   /**
     * 截图listview
     **/
     public static Bitmap getListViewBitmap(ListView listView) {
         int h = 0;
         Bitmap bitmap;
         // 获取listView实际高度
         for  (int  i = 0 ; i < listView.getChildCount(); i++) {
        	 h += listView.getChildAt(i).getHeight();
       	 }
         Log.d(TAG,   "实际高度:"  + h);  
         Log.d(TAG,   "list 高度:"  + listView.getHeight());
         
         // 创建对应大小的bitmap
         bitmap = Bitmap.createBitmap(listView.getWidth(), h,       
         Bitmap.Config.ARGB_8888); 
         final  Canvas canvas =  new Canvas(bitmap);
         listView.draw(canvas);    
         return bitmap;
    }

WebView实现截屏

    //这是webview的,利用了webview的api
    private static Bitmap captureWebView(WebView webView) {
         Picture snapShot = webView.capturePicture();
         Bitmap bmp = Bitmap.createBitmap(snapShot.getWidth(),     
         snapShot.getHeight(), Bitmap.Config.ARGB_8888);   
         Canvas canvas = new Canvas(bmp);
         snapShot.draw(canvas);    
         return bmp;  
    }

局部、全局实时截屏

关于应用内全局截屏的需求,我在 如何优雅的实时获取用户操作界面 中详细的解释了使用方式


在日常开发中,一般截屏后的图片,我们都会进行压缩、保存,这里一并进行记录

图片压缩

 /**
     * 压缩图片
     *
     * @param bgimage
     * @param newWidth
     * @param newHeight
     * @return
     */
    public static Bitmap zoomImage(Bitmap bgimage, double newWidth, double newHeight) {
        // 获取这个图片的宽和高
        float width = bgimage.getWidth();
        float height = bgimage.getHeight();
        // 创建操作图片用的matrix对象
        Matrix matrix = new Matrix();
        // 计算宽高缩放率
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;
        // 缩放图片动作
        //matrix.postScale(scaleWidth, scaleHeight);//TODO 因为宽高不确定的因素,所以不缩放
        Bitmap bitmap = Bitmap.createBitmap(bgimage, 0, 0, (int) width,
                (int) height, matrix, true);
        return bitmap;
    }

截图、压缩

	 	Bitmap bitmap = null;
          try {
                 bitmap = captureView(mShareBackgroundSign);
              } catch (Throwable throwable) {
                    throwable.printStackTrace();
              }
           //图片压缩,加快使用速度~
           zoomImage(bitmap, 720, 1280);

保存图片到本地

注意:切记适配6.0、7.0

public static void savePhotoToSDCard(Bitmap photoBitmap, String path, String photoName) {
        if (checkSDCardAvailable()) {
            File dir = new File(path);
            if (!dir.exists()) {
                dir.mkdirs();
            }
 
            File photoFile = new File(path, photoName + ".png");
            FileOutputStream fileOutputStream = null;
            try {
                fileOutputStream = new FileOutputStream(photoFile);
                if (photoBitmap != null) {
                    if (photoBitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)) {
                        fileOutputStream.flush();
                    }
                }
            } catch (FileNotFoundException e) {
                photoFile.delete();
                e.printStackTrace();
            } catch (IOException e) {
                photoFile.delete();
                e.printStackTrace();
            } finally {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

截屏扩展

以下部分其实我并未在有限的开发中使用到,仅当做个人兴趣的知识延伸

截取带导航栏的整个屏幕

采用adb命令方式截屏的操作,屈指可数,可当做知识扩展,没有必要深读

  • 优点:只需在代码中执行截屏的命令即可,可根据需求封装一个方法,传入保存的路径和文件名即可;
  • 缺点:手机需要获取ROOT权限,调用命令之前需要先请求su获取ROOT权限;

adb 命令:这里指的不是连接电脑进行adb操控,而是在App内部实现adb命令的操控

在APK中调用 "adb shell screencap -p filepath" 命令

注意:该命令读取系统的framebuffer,需要获得系统权限

  1. 在AndroidManifest.xml文件中添加
 <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
  1. 修改APK为系统权限,将APK放到源码中编译, 修改Android.mk
 LOCAL_CERTIFICATE:= platform
        public void takeScreenShot () {
            String mSavedPath = Environment.getExternalStorageDirectory() + File.separator + "screenshot.png";
            try {
                Runtime.getRuntime().exec("screencap -p " + mSavedPath);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

利用系统的隐藏API,实现Screenshot,这部分代码是系统隐藏的,需要在源码下编译。

  1. 修改Android.mk, 添加系统权限
 LOCAL_CERTIFICATE := platform
  1. 修改AndroidManifest.xml 文件,添加权限
 <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />

Android本地编程(Native Programming)读取framebuffer命令行,框架的截屏功能是通过framebuffer来实现的

framebuffer介绍

帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,他允许上层应用程序在图形模式下直接对显示缓冲区进行 读写操作。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。
linux FrameBuffer 本质上只是提供了对图形设备的硬件抽象,在开发者看来,FrameBuffer 是一块显示缓存,往显示缓存中写入特定格式的数据就意味着向屏幕输出内容。所以说FrameBuffer就是一块白板。例如对于初始化为16 位色的FrameBuffer 来说, FrameBuffer中的两个字节代表屏幕上一个点,从上到下,从左至右,屏幕位置与内存地址是顺序的线性关系。
帧缓存有个地址,是在内存里。我们通过不停的向frame buffer中写入数据, 显示控制器就自动的从frame buffer中取数据并显示出来。全部的图形都共享内存中同一个帧缓存。

android截屏实现思路

Android系统是基于Linux内核的,所以也存在framebuffer这个设备,我们要实现截屏的话只要能获取到framebuffer中的数据,然后把数据转换成图片就可以了,android中的framebuffer数据是存放在 /dev/graphics/fb0 文件中的,所以我们只需要来获取这个文件的数据就可以得到当前屏幕的内容。
现在我们的测试代码运行时候是通过RC(remote controller)方式来运行被测应用的,那就需要在PC机上来访问模拟器或者真机上的framebuffer数据,这个的话可以通过android的ADB命令来实现。
各大手机自带的按键组合进行截屏,Android源码中对按键的捕获位于文件PhoneWindowManager.java(alps\frameworks\base\policy\src\com\android\internal\policy\impl)中,这个类处理所有的键盘输入事件,其中函数interceptKeyBeforeQueueing()会对常用的按键做特殊处理。

截取非含当前应用的屏幕部分(最佳官方方案)

Android 在5.0 之后支持了实时录屏的功能;通过实时录屏我们可以拿到截屏的图像。同时可以通过在Service中处理实现后台的录屏(具体的类讲解大家自行网上查阅)

类似使用手机的系统截屏(音量下键+电源键),针对的并非一个view,而是整个屏幕,在调用这个服务的时候,会弹出一个权限确认的弹框;同时需注意,这一方法只能在Android 5.0的系统设备上使用。

详细步骤

  1. 初始化一个MediaProjectionManager
 MediaProjectionManager mMediaProjectionManager = (MediaProjectionManager)getApplication().getSystemService(Context.MEDIA_PROJECTION_SERVICE); 
  1. 创建intent,并启动Intent(注意:这里是startActivityForResult)
 startActivityForResult(mMediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION); 
  1. 在onActivityResult中拿到Mediaprojection
 mResultCode = resultCode;
 mResultData = data;
 mMediaProjection = mMediaProjectionManager.getMediaProjection(mResultCode, mResultData);
  1. 设置VirtualDisplay 将图像和展示的View关联起来;一般来说我们会将图像展示到SurfaceView,这里为了为了便于拿到截图,我们使用ImageReader,他内置有SurfaceView
   mImageReader = ImageReader.newInstance(windowWidth, windowHeight, 0x1 ,2); 
   //ImageFormat.RGB_565
   mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror" ,windowWidth, windowHeight, mScreenDensity,DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,      
   mImageReader.getSurface(), null ,  null);
  1. 通过ImageReader拿到截图
   strDate = dateFormat.format(new  java.util.Date());
   nameImage = pathImage+strDate+ ".png";
   Image image = mImageReader.acquireLatestImage();
   int  width = image.getWidth();
   int  height = image.getHeight();
   final Image.Plane[] planes = image.getPlanes();
   final ByteBuffer buffer = planes[0].getBuffer();
   int pixelStride = planes[0].getPixelStride();
   int  rowStride = planes[0].getRowStride();
   int rowPadding = rowStride - pixelStride * width;
   Bitmap bitmap = Bitmap.createBitmap(width+rowPadding/pixelStride, height, Bitmap.Config.ARGB_8888);
   bitmap.copyPixelsFromBuffer(buffer);
   bitmap = Bitmap.createBitmap(bitmap, 0, 0 ,width, height);
   image.close();
  1. 注意截屏之后要及时关闭VirtualDisplay ,因为VirtualDisplay 是十分消耗内存和电量的
  if(mVirtualDisplay == null) {
		return ;
  }	
  mVirtualDisplay.release();
  mVirtualDisplay = null;

常用步骤

  1. 在Activity中开启截屏服务
  • 链式
        if (Build.VERSION.SDK_INT >= 21) {
            startActivityForResult(((MediaProjectionManager) getSystemService("media_projection")).createScreenCaptureIntent(), 1);
        } else {
            Log.e("TAG", "版本过低,无法截屏");
        }
  • 平常
 final MediaProjectionManager projectionManager = (MediaProjectionManager)
                getSystemService(Context.MEDIA_PROJECTION_SERVICE);
 Intent intent = projectionManager.createScreenCaptureIntent();
 startActivityForResult(intent, REQUEST_CODE);
  1. 重写onActivityResult方法
  • 链式 - 返回结果
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 1 && data != null) {
            parseData(data);
        }
    }
    private void parseData(Intent data){
        MediaProjection mMediaProjection = (MediaProjectionManager).getSystemService(
                Context.MEDIA_PROJECTION_SERVICE).getMediaProjection(Activity.RESULT_OK,data);
        ImageReader mImageReader = ImageReader.newInstance(
                getScreenWidth(),
                getScreenHeight(),
                PixelFormat.RGBA_8888,1);
        VirtualDisplay mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",
                getScreenWidth(),
                getScreenHeight(),
                Resources.getSystem().getDisplayMetrics().densityDpi,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                mImageReader.getSurface(), null, null);
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Image image = mImageReader.acquireLatestImage();
                // TODO 将image保存到本地即可
            }
        }, 300);
        mVirtualDisplay.release();
        mVirtualDisplay = null;
    }
  • 平常 - 返回结果
 	@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        handleScreenShotIntent(resultCode, data);
    }
    
    private void handleScreenShotIntent(int resultCode, Intent data) {
        onScreenshotTaskBegan();
        final MediaProjectionManager projectionManager = (MediaProjectionManager)
                getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        final MediaProjection mProjection = projectionManager.getMediaProjection(resultCode, data);
        Point size = Utils.getScreenSize(this);
        final int mWidth = size.x;
        final int mHeight = size.y;
        final ImageReader mImageReader = ImageReader.newInstance(mWidth, mHeight, PixelFormat
                .RGBA_8888, 2);
        final VirtualDisplay display = mProjection.createVirtualDisplay("screen-mirror", mWidth,
                mHeight, DisplayMetrics.DENSITY_MEDIUM,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION, mImageReader.getSurface(),
                null, null);

        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader mImageReader) {
                Image image = null;
                try {
                    image = mImageReader.acquireLatestImage();
                    if (image != null) {
                        final Image.Plane[] planes = image.getPlanes();
                        if (planes.length > 0) {
                            final ByteBuffer buffer = planes[0].getBuffer();
                            int pixelStride = planes[0].getPixelStride();
                            int rowStride = planes[0].getRowStride();
                            int rowPadding = rowStride - pixelStride * mWidth;
                            // create bitmap
                            Bitmap bmp = Bitmap.createBitmap(mWidth + rowPadding / pixelStride,
                                    mHeight, Bitmap.Config.ARGB_8888);
                            bmp.copyPixelsFromBuffer(buffer);
                            Bitmap croppedBitmap = Bitmap.createBitmap(bmp, 0, 0, mWidth, mHeight);
							//保存图片
                            saveBitmap(croppedBitmap);
                            if (croppedBitmap != null) {
                                croppedBitmap.recycle();
                            }
                            if (bmp != null) {
                                bmp.recycle();
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (image != null) {
                        image.close();
                    }
                    if (mImageReader != null) {
                        mImageReader.close();
                    }
                    if (display != null) {
                        display.release();
                    }
                    mImageReader.setOnImageAvailableListener(null, null);
                    mProjection.stop();
                    onScreenshotTaskOver();
                }
            }
        }, getBackgroundHandler());
    }
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-11 12:31:59  更:2021-08-11 12:34:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 6:40:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码