美文网首页
Android 5.0以下截图Dialog

Android 5.0以下截图Dialog

作者: 萝卜小青菜丶 | 来源:发表于2020-03-04 21:00 被阅读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处理显示

    • 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());
        }
    }
    
    

    相关文章

      网友评论

          本文标题:Android 5.0以下截图Dialog

          本文链接:https://www.haomeiwen.com/subject/ymwqictx.html