Android 截图


1. 概述

该方法是通过View的方式获取当前activity的屏幕截图,并不是frameBuffer的方式,所以有一定的局限性。但是这种方法相对简单,容易理解。

2. 使用方法

  1. 对activity进行截图

    /**     * Activity screenCap     *     * @param activity     * @return     */    public static Bitmap activityShot(Activity activity) {        /*获取windows中最顶层的view*/        View view = activity.getWindow().getDecorView();        //允许当前窗口保存缓存信息        view.setDrawingCacheEnabled(true);        view.buildDrawingCache();        //获取状态栏高度        Rect rect = new Rect();        view.getWindowVisibleDisplayFrame(rect);        int statusBarHeight = rect.top;        WindowManager windowManager = activity.getWindowManager();        //获取屏幕宽和高        DisplayMetrics outMetrics = new DisplayMetrics();        windowManager.getDefaultDisplay().getMetrics(outMetrics);        int width = outMetrics.widthPixels;        int height = outMetrics.heightPixels;        //去掉状态栏        Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache(), 0, statusBarHeight, width,                height-statusBarHeight);        //销毁缓存信息        view.destroyDrawingCache();        view.setDrawingCacheEnabled(false);        return bitmap;    }
  2. 可以将得到的bitmap格式图片保存到本地,也可以用于其他用途。下面是将bitmap保存到本地的方法。

    private static final String SCREENSHOTS_DIR_NAME = "screenShots";    private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot%s.jpg";    private static final String SCREENSHOT_FILE_PATH_TEMPLATE = "%s/%s/%s";    /**     * 存储到sdcard     *     * @param bmp     * @return     */    public static String saveToSD(Bitmap bmp) {        //判断sd卡是否存在        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {            //文件名            long systemTime = System.currentTimeMillis();            String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(systemTime));            String mFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);            File dir = new File(SCREENSHOTS_DIR_NAME);            //判断文件是否存在,不存在则创建            if (!dir.exists()) {                dir.mkdirs();            }            //文件全名            String mstrRootPath = Environment.getExternalStorageDirectory().toString();            String mFilePath = String.format(SCREENSHOT_FILE_PATH_TEMPLATE, mstrRootPath,                    SCREENSHOTS_DIR_NAME, mFileName);            Log.i(TAG, "file path:" + mFilePath);            File file = new File(mFilePath);            if (!file.exists()) {                try {                    file.createNewFile();                } catch (IOException e) {                    e.printStackTrace();                }            }            Log.i(TAG, "file path:" + file.getAbsolutePath());            FileOutputStream fos = null;            try {                fos = new FileOutputStream(file);                if (fos != null) {                    //第一参数是图片格式,第二参数是图片质量,第三参数是输出流                    bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);                    fos.flush();                }            } catch (FileNotFoundException e) {                e.printStackTrace();            } catch (IOException e) {                e.printStackTrace();            } finally {                if (fos != null) {                    try {                        fos.close();                    } catch (IOException e) {                        e.printStackTrace();                    }                }            }            return mFilePath;        }        return null;    }
  3. 注意在AndroidManifest.xml中注册写入的权限

3. 截图时遇到的坑

在程序中使用上面的方法进行应用内截图,结果出现了下面的错误提示:

java.lang.IllegalArgumentException: x + width must be <= bitmap.width()    at android.graphics.Bitmap.createBitmap(Bitmap.java:686)    at android.graphics.Bitmap.createBitmap(Bitmap.java:654)    java.lang.IllegalArgumentException: x + width must be <= bitmap.width()    at android.graphics.Bitmap.createBitmap(Bitmap.java:686)    at android.graphics.Bitmap.createBitmap(Bitmap.java:654)    at com.csmijo.practice.utils.ScreenCap.activityShot(ScreenCap.java:85)    at android.os.Handler.handleCallback(Handler.java:808)    at android.os.Handler.dispatchMessage(Handler.java:103)    at android.os.Looper.loop(Looper.java:193)    at android.app.ActivityThread.main(ActivityThread.java:5532)    at java.lang.reflect.Method.invokeNative(Native Method)    at java.lang.reflect.Method.invoke(Method.java:515)    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:891)    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:707)    at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:132)    at dalvik.system.NativeStart.main(Native Method)

马上要提交了出现这种坑,内心马上凌乱了。Google后发现,这个错误是由于使用这个方法造成的:

Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache(), 0, statusBarHeight, width,height-statusBarHeight);

这是Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height)方法的介绍:

Returns an immutable bitmap from the specified **subset of the sourcebitmap**. The new bitmap may be the same object as source, or a copymay have been made. It is initialized with the same density as the original bitmap.

该方法返回的是source的子集,所以就要求:

  • x + width <= source.width
  • y + height <= source.height

由于我的程序中是直接使用的屏幕宽度,所以出现了上面的错误。说道屏幕宽度,就引出了我下面的资料查找。

4. Android 获取获取屏幕高度、标题高度、状态栏高度

状态栏标题栏高度测量

该图片出自Android完美获取状态栏高度、标题栏高度、编辑区域高度的获取

  • 最大的草绿色区域是屏幕区域
  • 次大的红色区域是应用界面区域
  • 最小的紫色区域是View绘制区域
  • 屏幕顶端和应用界面顶端之间的部分为状态栏
  • 应用界面顶端与view绘制区域顶端之间的部分为标题栏

1. 下面介绍一些获取屏幕参数的方法

1.View 获取屏幕参数值的方法:

方法 影响区域 说明
onSizeChanged(int w,int h,int oldw,int oldh) view绘制区域 当前view屏幕宽高发生变化时调用,传递view的宽高,其中高度不包括标题高度
getWidth() view绘制区域 返回view的宽度
getHeight() view绘制区域 返回view的高度,不包括标题在内
getWindowVisibleDisplayFrame(Rect outRect) 应用界面区域 返回宽度和View的宽度相等,高度=view的高度 + 标题的高度
getDrawingRect(Rect outRect) view绘制区域 返回绘制区域的区域值,宽度和高度都和view的相等

2.Canvas对象获取画布宽高,由view的draw函数传递canvas对象,也是在view中创建

方法 影响区域 说明
canvas.getWidth() 屏幕区域 返回画布的宽度,即屏幕的宽度
canvas.getHeight() 屏幕区域 返回画布的高度,即屏幕的高度

3.Display对象获取屏幕宽高

通过Activity的`getWindowManager.getDefaultDisplay()`方法可以获取到`display`对象
方法 影响区域 说明
display.getWidth() 屏幕区域 返回界面的宽度,即屏幕的宽度
display.getHeight() 屏幕区域 返回界面的高度,即屏幕的高度

2. 状态栏高度的测量

1.方法一:通过系统尺寸资源获取

状态栏高度定义在Android系统尺寸资源中status_bar_height,但这并不是公开可直接使用的,例如像通常使用系统资源那样android.R.dimen.status_bar_height。但是系统给我们提供了一个Resource类,通过这个类可以获取资源文件,借此可以获取到status_bar_height:

public static int getStatusBarHeight(Context context) {        int statusBarHeight = -1;        /* 获取status_bar_height的资源ID*/        int resourceId = context.getResources().getIdentifier(                "status_bar_height", "dimen", "android");        if (resourceId > 0) {            // 根据资源ID获取响应的尺寸值            statusBarHeight = context.getResources().getDimensionPixelSize(                    resourceId);        }        return statusBarHeight;    }

2.方法二:通过R类的反射

public static int getStatusBarHeight(Context context) {        int statusBarHeight = -1;          try {              Class<?> clazz = Class.forName("com.android.internal.R$dimen");              Object object = clazz.newInstance();              int height = Integer.parseInt(clazz.getField("status_bar_height")                      .get(object).toString());              statusBarHeight = getResources().getDimensionPixelSize(height);          } catch (Exception e) {              e.printStackTrace();          }          return statusBarHeight;    }

3.方法三:利用应用区域Top属性

public static int getStatusBarHeight(Context context) {        /*获取windows中最顶层的view*/        View view = activity.getWindow().getDecorView();          //获取状态栏高度        Rect rect = new Rect();        view.getWindowVisibleDisplayFrame(rect);        int statusBarHeight = rect.top;        return statusBarHeight;    }

注意:

如果单单获取statusBar高度而不获取titleBar高度时,这种方法并不推荐大家使用,因为这种方法依赖于WMS(窗口管理服务的回调)。正是因为窗口回调机制,所以在Activity初始化时执行此方法得到的高度是0。这个方法推荐在回调方法onWindowFocusChanged()中执行,才能得到预期结果。

3. 标题栏高度的测量

正如上面介绍的,应用界面顶端view绘制区域顶端之间的部分为标题栏。所以自然会想到两种测量标题栏高度的方法:一个是使用Top-Top,另一个就是使用高度-高度。先介绍一下获取各区域宽高的代码:

//屏幕区域DisplayMetrics outMetrics = new DisplayMetrics();WindowManager windowManager = activity.getWindowManager();windowManager.getDefaultDisplay().getMetrics(outMetrics);int width = outMetrics.widthPixels;    //屏幕宽度int height = outMetrics.heightPixels;    //屏幕高度   //应用界面区域View view = activity.getWindow().getDecorView();Rect rect = new Rect();view.getWindowVisibleDisplayFrame(rect); int appTop = rect.top;    //状态栏高度,也是应用界面顶部的高度值int appHeight = rect.height();    //应用界面高度//view绘制区域Rect outRect2 = new Rect();  activity.getWindow().findViewById(Window.ID_ANDROID_CONTENT).getDrawingRect(outRect2);   int viewTop = getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();  //view绘制区域顶部的高度值int viewHeight = outRect2.height();   //view绘制界面的高度

1. 方法一:Top-Top

/** * 标题栏高度 = view绘制区域顶端高度值 - 应用界面区域顶端高度值  */ int titleHeight = viewTop - appTop;

2. 方法二:高度- 高度

/** * 标题栏高度 = 应用界面区域高度 - view绘制区域高度 */ int titleHeight =  appHeight - viewHeight;

4. 注意事项

  1. 不管你是否设置全屏模式,或是不显示标题栏,在使用获取状态栏高度方法1获取状态栏高度方法2都会测量到状态栏的高度,理解原理就不难解释——系统资源属性是固定的、真实的,不管你是否隐瞒(隐藏或者显示),它都在那里;

  2. 是若使用获取状态栏高度方法3,以及获取标题栏高度方法1和获取标题栏高度方法2都是依赖于WMS,是在界面构建后根据View获取的,所以显示了就有高度,不显示自然没高度了


参考文献

  • Android开发 获取当前activity的屏幕截图
  • Android屏幕截图那些事
  • java.lang.IllegalArgumentException: x 一定要小于 bitmap.width()
  • Android完美获取状态栏高度、标题栏高度、编辑区域高度的获取
  • Android 获取屏幕高度、标题高度、状态栏高度详解

更多相关文章

  1. 获取 Andorid 手机WIFI连接的Mac地址和IP地址
  2. 使用git工具获取android源代码
  3. android点击任意非EditText区域,隐藏键盘
  4. Android获取assets目录下的文件和图片
  5. delphixe10 android操作 打电话,摄像头,定位等
  6. Android中判断状态栏是否存在
  7. android 中如何获取camera当前状态
  8. android获取时间差的方法
  9. Android实现异步加载图片 ListView

随机推荐

  1. android软键盘把页面顶上去
  2. Android 中的DisplayMetrics类的用法
  3. Android 硬件抽象层(HAL)概要介绍和学习计
  4. Android UI框架 Android UI控件类简介 an
  5. Android平台各类恶意软件及病毒概览
  6. Android studio简单使用JNI实例
  7. AndroidX将替代 Android支持库(android.s
  8. Android(安卓)实现定时闹铃功能
  9. Android打包AAR及与unity通信方法
  10. 浅析Android(安卓)M新功能Adoptable Stor