Android 5.0以下截图Dialog

在5.0之后Google开放了截屏录屏的API,使用比较方便

相关类

MediaProjection:可以用来捕获屏幕内容或系统声音,可以通过MediaProjectionManager的createScreenCaptureIntent方法创建的Intent来启动。
MediaProjectionManager:MediaProjection的管理类。通过Context.getSystemService()方法获取实例,参数传Context.MEDIA_PROJECTION_SERVICE。
VirtualDisplay:VirtualDisplay将屏幕内容渲染在一个Surface上,当进程终止时会被自动的释放。当不再使用他时,你也应该调用release() 方法来释放资源。通过调用DisplayManager 类的 createVirtualDisplay()方法来创建。

基本流程

1.请求用户授予捕获屏幕内容的权限
2.获取MediaProjection实例
3.获取VirtualDisplay实例
4.通过ImageReader获取截图
5.对图片进行区域裁剪

但是在5.0以下版本就没那么容易了,一般来说在低版本上实现截屏,使用的是View的cacheDrawable,但缺点是截不到状态栏、Activity的上层Dialog等。

基础知识

首先了解一下Activity 、 Window 、 View之间的关系,先上一张图:


Activity本身是没办法处理显示什么控件(view)的,是通过PhoneWindow进行显示的

Activity会调用PhoneWindow的setContentView()将layout布局添加到DecorView上,而此时的DecorView就是那个最底层的View。然后通过LayoutInflater.infalte()方法加载布局生成View对象并通过addView()方法添加到Window上,(一层一层的叠加到Window上)所以,Activity其实不是显示视图,Window才是真正的显示视图。

一个Activity构造的时候只能初始化一个Window(PhoneWindow),另外这个PhoneWindow有一个View容器 mContentParent,这个
View容器是一个ViewGroup,是最初始的跟视图,然后通过addView方法将View一个个层叠到mContentParent上,这些层叠的View最终放在Window这个载体上面。

换句话说:activity就是在造PhoneWindow,显示的那些view都交给了PhoneWindow处理显示

  • 1、在Activity创建时调用attach方法:
  • 2、attach方法中会调用PolicyManager.makeNewWindow(),实际工作的是IPolicy接口的makeNewWindow方法
    ①、其中创建了一个window(可以比喻为一个房子上造了一个窗户):mWindow = PolicyManager.makeNewWindow(this);
    ②、在window这个类中,才调用了setContentView(),这是最终的调用
    在Activity的setContentView方法中,实际上是调用:getWindow().setContentView(view, params);
    这里的getWindow()就是获取到一个Window对象
  • 3、在IPolicy的实现类中创建了PhoneWindow:
    ①、由mWindow = PolicyManager.makeNewWindow(this);,
    ②、这里的makeNewWindow(this);方法中,返回的是:return sPolicy.makeNewWindow(context);
    ③、这个sPolicy实际是一个接口,其实现类是Policy,其中只是创建了一个PhoneWindow
  • 4、在PhoneWindow的setContentView中向ViewGroup(root)中添加了需要显示的内容
    ①、PhoneWindow是继承Window的
    ②、setContentView这个方法中,需要先判断一个mContentParent是否为空,因为在默认进来的时候,什么都没创建呢
    此时需要创建:installDecor(),DecorView是最根上的显示的
    可以通过adt中的的tools中有个hierarchyviewer.bat的工具,可以查看手机的结构
    ③、DecorView:是继承与FrameLayout的,作为parent存在,最初显示的
    ④、下次再加载的时候,mContentParent就不为空了,会将其中的所有的view移除掉,然后在通过布局填充器加载布局

所以,如果只是单纯需要截图activity,可以用使用activity.getWindow().getDecorView()的cacheDrawable来获取当前截图,但为什么在弹出dialog的时候,确没有获取到dialog的截图呢。

下面再来简单了解一下,dialog的创建过程。

Dialog创建类似activity创建,它通过new PhoneWindow()创建Window对象,再通过setContentView()方法,将布局文件添加到DectorView中,最后通过show()方法,把View添加到window上去。

 Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        ...
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        ...
        w.setWindowManager(mWindowManager, null, null);
        ...
    }  

 public void show() {
        ...
        mWindowManager.addView(mDecor, l);
        mShowing = true;
        sendShowMessage();
 }

也就是说,想通过activity.getWindow().getDecorView()的cacheDrawable来获取到dialog的截图是不可能的,因为它们两都有一个PhoneWindow(getWindow直接返回的是PhoneWindow的实例,在Window的官方文档中已经说明,The only existing implementation of this abstract class is android.view.PhoneWindow,PhoneWindow是目前Window的唯一实现类)

截图实现

知道创建过程了,就有相应的方法能获取到,这里分享一种实现方式,大致思路是这样的

1.通过WindowManager得到所有的rootView
2.把rootView一层层绘制上去,生成一张新的bitmap,实现截图

下面直接上代码:

package com.test.utils.screenshot;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.view.View;
import android.view.WindowManager;

import java.util.List;

public class ScreenShotUtils {
    /**
     * android 5.0以下 截屏
     */
    public static Bitmap getScreenshotBitmap(Activity activity) {
        if (activity == null) {
            throw new IllegalArgumentException("Parameter activity cannot be null.");
        }
        final List<RootViewInfo> viewRoots = FieldHelper.getRootViews(activity);
        View main = activity.getWindow().getDecorView();
        final Bitmap bitmap;
        try {
            bitmap = Bitmap.createBitmap(main.getWidth(), main.getHeight(), Bitmap.Config.ARGB_8888);
        } catch (final IllegalArgumentException e) {
            return null;
        }
        drawRootsToBitmap(viewRoots, bitmap);
        return bitmap;
    }

    private static void drawRootsToBitmap(List<RootViewInfo> viewRoots, Bitmap bitmap) {
        if (null != viewRoots) {
            for (RootViewInfo rootData : viewRoots) {
                drawRootToBitmap(rootData, bitmap);
            }
        }
    }

    private static void drawRootToBitmap(final RootViewInfo rootViewInfo, Bitmap bitmap) {
        if ((rootViewInfo.getLayoutParams().flags & WindowManager.LayoutParams.FLAG_DIM_BEHIND) == WindowManager.LayoutParams.FLAG_DIM_BEHIND) {
            Canvas dimCanvas = new Canvas(bitmap);
            int alpha = (int) (255 * rootViewInfo.getLayoutParams().dimAmount);
            dimCanvas.drawARGB(alpha, 0, 0, 0);
        }
        final Canvas canvas = new Canvas(bitmap);
        canvas.translate(rootViewInfo.getRect().left, rootViewInfo.getRect().top);
        rootViewInfo.getView().draw(canvas);
    }
}

package com.test.utils.screenshot;

import android.graphics.Rect;
import android.view.View;
import android.view.WindowManager;

public class RootViewInfo {

    private final View view;
    private final Rect rect;
    private final WindowManager.LayoutParams layoutParams;

    public RootViewInfo(View view, Rect rect,
            WindowManager.LayoutParams layoutParams) {
        this.view = view;
        this.rect = rect;
        this.layoutParams = layoutParams;
    }

    public View getView() {
        return view;
    }

    public Rect getRect() {
        return rect;
    }

    public WindowManager.LayoutParams getLayoutParams() {
        return layoutParams;
    }
}

package com.test.utils.screenshot;

import android.app.Activity;
import android.graphics.Rect;
import android.os.Build;
import android.view.View;
import android.view.WindowManager;

import java.lang.reflect.Field;
import java.util.List;

public class FieldHelper {

    private final static String FIELD_NAME_WINDOW_MANAGER = "mWindowManager";
    private final static String FIELD_NAME_GLOBAL = "mGlobal";
    private final static String FIELD_NAME_ROOTS = "mRoots";
    private final static String FIELD_NAME_PARAMS = "mParams";
    private final static String FIELD_NAME_ATTACH_INFO = "mAttachInfo";
    private final static String FIELD_NAME_WINDOW_TOP = "mWindowTop";
    private final static String FIELD_NAME_WINDOW_LEFT = "mWindowLeft";
    private final static String FIELD_NAME_WINDOW_FRAME = "mWinFrame";
    private final static String FIELD_NAME_VIEW = "mView";

    @SuppressWarnings("unchecked")
    public static List<RootViewInfo> getRootViews(Activity activity) {
        List<RootViewInfo> rootViews = new ArrayList<RootViewInfo>();
        try {
            Object globalWindowManager;
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
                globalWindowManager = getFieldValue(FIELD_NAME_WINDOW_MANAGER,activity.getWindowManager());
            } else {
                globalWindowManager = getFieldValue(FIELD_NAME_GLOBAL,activity.getWindowManager());
            }
            Object rootObjects = getFieldValue(FIELD_NAME_ROOTS,globalWindowManager);
            Object paramsObject = getFieldValue(FIELD_NAME_PARAMS,globalWindowManager);
            Object[] roots;
            WindowManager.LayoutParams[] params;
            // There was a change to ArrayList implementation in 4.4
//          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//              roots = ((List<?>) rootObjects).toArray();
//              List<WindowManager.LayoutParams> paramsList = (List<WindowManager.LayoutParams>) paramsObject;
//              params = paramsList.toArray(new WindowManager.LayoutParams[paramsList.size()]);
//          } else {
//              roots = (Object[]) rootObjects;
//              params = (WindowManager.LayoutParams[]) paramsObject;
//          }
            if (rootObjects instanceof List) {
                roots = ((List<?>) rootObjects).toArray();
            } else {
                roots = (Object[]) rootObjects;
            }
            if (paramsObject instanceof List) {
                List<WindowManager.LayoutParams> paramsList = (List<WindowManager.LayoutParams>) paramsObject;
                params = paramsList.toArray(new WindowManager.LayoutParams[paramsList.size()]);
            } else {
                params = (WindowManager.LayoutParams[]) paramsObject;
            }
            for (int i = 0; i < roots.length; i++) {
                Object root = roots[I];
                Object attachInfo = getFieldValue(FIELD_NAME_ATTACH_INFO, root);
                int top = Integer.parseInt(getFieldValue(FIELD_NAME_WINDOW_TOP, attachInfo).toString());
                int left = Integer.parseInt(getFieldValue(FIELD_NAME_WINDOW_LEFT, attachInfo).toString());
                Rect winFrame = (Rect) getFieldValue(FIELD_NAME_WINDOW_FRAME, root);
                Rect area = new Rect(left, top, left + winFrame.width(), top + winFrame.height());
                View view = (View) getFieldValue(FIELD_NAME_VIEW, root);
                rootViews.add(new RootViewInfo(view, area, params[i]));
            }
        } catch (Exception e) {
            // TODO: handle exception
        }
        return rootViews;
    }

    private static Object getFieldValue(String fieldName, Object target) {
        try {
            Field field = findField(fieldName, target.getClass());
            field.setAccessible(true);
            return field.get(target);
        } catch (Exception e) {
            // TODO: handle exception
        }
        return null;
    }

    private static Field findField(String name, Class<?> clazz) throws NoSuchFieldException {
        Class<?> currentClass = clazz;
        while (currentClass != Object.class) {
            for (Field field : currentClass.getDeclaredFields()) {
                if (name.equals(field.getName())) {
                    return field;
                }
            }
            currentClass = currentClass.getSuperclass();
        }
        throw new NoSuchFieldException("The field " + name + " isn't found for " + clazz.toString());
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容