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