Android 5.0以下截图Dialog

2020-03-04  本文已影响0人  萝卜小青菜丶

在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处理显示

所以,如果只是单纯需要截图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());
    }
}

上一篇下一篇

猜你喜欢

热点阅读