ACTION_MANAGE_OVERLAY_PERMISSION
的作用就是 打开系统设置的「悬浮窗权限管理页面」
Intent intent = new Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName())
);
startActivity(intent);
直接跳转目标应用的权限页
Uri.parse("package:" + getPackageName())
的作用是直接把用户带到 当前应用的悬浮窗权限设置页,而不是让用户在系统设置中手动寻找。
Settings.canDrawOverlays(this)
—— 检查有没有「贴纸许可证」
从A页面使用startActivityForResult()跳转到B页面,B页面点击返回时将新写入的值传回到A页面。
startActivityForResult
的作用
启动子 Activity 并等待结果
当需要从另一个 Activity 获取数据(如用户选择照片、输入文本等)时,使用此方法启动子 Activity。子 Activity 关闭后,父 Activity 的onActivityResult()
方法会被调用,接收返回的数据。数据双向传递
普通startActivity
只能单向传递数据(父 → 子),而startActivityForResult
支持子 Activity 将数据回传(子 → 父)。
为什么需要请求码(Request Code)?
请求码是一个 唯一整型标识符,用于在父 Activity 中区分不同的子 Activity 请求。它的必要性体现在以下场景:
多个子 Activity 返回结果到同一个父 Activity
例如,父 Activity 同时启动“选择图片”和“拍照”两个子 Activity,两者都可能返回图片数据。通过不同的请求码,父 Activity 可以判断数据来源并做相应处理。同一子 Activity 多次启动
若多次启动同一个子 Activity(如多次选择不同文件),请求码可帮助区分是哪一次调用返回的结果。
创建悬浮窗(Floating Window)
-
createFloatingWindow()
- 悬浮窗参数:使用
TYPE_APPLICATION_OVERLAY
类型,允许悬浮在其他应用上方。 - 视图布局:加载
R.layout.floating_window
,包含“截图”和“确认”按钮。 - 拖动逻辑:通过
OnTouchListener
实现悬浮窗的拖拽移动。 - 按钮交互:
- 截图按钮:点击后显示可调整的截图框(
showCaptureFrame()
)。 - 确认按钮:点击后执行截图(
takeScreenshot()
),并切换按钮状态。
- 截图按钮:点击后显示可调整的截图框(
- 悬浮窗参数:使用
设置按钮点击事件
截图按钮点击
captureButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showCaptureFrame(); // 显示截图框
confirmButton.setVisibility(View.VISIBLE); // 显示确认按钮
captureButton.setVisibility(View.GONE); // 隐藏截图按钮
}
});
- 功能:点击后显示截图框,并切换按钮状态,让用户确认截图区域。
确认按钮点击
confirmButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
takeScreenshot(); // 执行截图
confirmButton.setVisibility(View.GONE); // 隐藏确认按钮
captureButton.setVisibility(View.VISIBLE); // 显示截图按钮
}
});
- 功能:触发截图操作,并在截图完成后恢复按钮初始状态。
showCaptureFrame()
创建截图框布局参数
captureParams = new WindowManager.LayoutParams(
800, // 初始宽度(像素)
600, // 初始高度(像素)
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, // 类型:系统悬浮窗
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, // 标志:不获取焦点
PixelFormat.TRANSLUCENT // 透明背景
);
captureParams.gravity = Gravity.TOP | Gravity.START; // 定位基准:左上角
captureParams.x = 100; // 初始X坐标(距离屏幕左边缘100像素)
captureParams.y = 100; // 初始Y坐标(距离屏幕顶部100像素)
- TYPE_APPLICATION_OVERLAY:Android 8.0+ 的悬浮窗类型。
- FLAG_NOT_FOCUSABLE:允许用户操作穿透截图框,避免影响其他应用。
加载截图框布局
captureFrame = LayoutInflater.from(this).inflate(R.layout.capture_frame, null);
final View resizeHandle = captureFrame.findViewById(R.id.resizeHandle); // 调整大小的手柄视图
final FrameLayout captureArea = captureFrame.findViewById(R.id.captureArea); // 可拖动的区域
- 布局文件:
R.layout.capture_frame
定义截图框的UI结构(如边框、调整手柄)。 - 视图元素:
resizeHandle
:用于拖动调整截图框大小的手柄(通常位于右下角)。captureArea
:用户可拖动的区域(通常是截图框的整个区域)。
代码注释原理
// 创建ImageReader实例,用于从Surface中读取图像数据
imageReader = ImageReader.newInstance(
screenWidth, // 图像宽度(屏幕宽度)
screenHeight, // 图像高度(屏幕高度)
PixelFormat.RGBA_8888, // 像素格式(32位RGBA,每个通道8位)
1 // 缓冲区数量(仅保留最新一帧)
);
// 创建VirtualDisplay虚拟显示设备,将屏幕内容投射到ImageReader的Surface
virtualDisplay = mediaProjection.createVirtualDisplay(
"Screenshot", // 虚拟显示名称(任意标识符)
screenWidth, // 显示宽度(与屏幕一致)
screenHeight, // 显示高度(与屏幕一致)
screenDensity, // 显示密度(像素密度DPI)
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, // 标志位(自动镜像显示方向)
imageReader.getSurface(), // 输出目标Surface(绑定到ImageReader)
null, // 回调接口(此处未使用)
handler // 事件处理器(指定操作线程)
);
// 延迟300毫秒执行截图操作,等待屏幕内容稳定
handler.postDelayed(new Runnable() {
@Override
public void run() {
Image image = null;
try {
// 从ImageReader获取最新的图像对象
image = imageReader.acquireLatestImage();
if (image != null) {
// 获取图像平面数据(RGBA格式只有一个平面)
Image.Plane[] planes = image.getPlanes();
// 获取第一个平面的像素缓冲区
ByteBuffer buffer = planes[0].getBuffer();
// 单个像素的字节跨度(RGBA_8888格式为4字节)
int pixelStride = planes[0].getPixelStride();
// 每行的字节跨度(可能包含填充字节)
int rowStride = planes[0].getRowStride();
// 计算行填充字节数(实际数据宽度与声明的宽度差异)
int rowPadding = rowStride - pixelStride * screenWidth;
// 创建全屏位图(考虑行填充调整实际宽度)
Bitmap fullScreenBitmap = Bitmap.createBitmap(
screenWidth + rowPadding / pixelStride, // 调整后的宽度
screenHeight, // 高度不变
Bitmap.Config.ARGB_8888 // 配置为ARGB_8888格式
);
// 将缓冲区数据复制到位图中
fullScreenBitmap.copyPixelsFromBuffer(buffer);
// 保存全屏截图到公开相册目录
saveScreenshot(fullScreenBitmap, "Fullscreen_" + System.currentTimeMillis() + ".jpg", true);
// 创建调试用的全屏截图副本
Bitmap debugCopy = fullScreenBitmap.copy(Bitmap.Config.ARGB_8888, true);
saveDebugScreenshot(debugCopy, "fullscreen_" + System.currentTimeMillis() + ".jpg");
// 在调试截图上绘制用户选择的红色选框
debugCopy = fullScreenBitmap.copy(Bitmap.Config.ARGB_8888, true);
Canvas canvas = new Canvas(debugCopy); // 创建画布
Paint paint = new Paint(); // 创建画笔
paint.setColor(Color.RED); // 设置红色
paint.setStyle(Paint.Style.STROKE); // 描边样式
paint.setStrokeWidth(10); // 线宽10像素
canvas.drawRect(captureX, captureY, // 绘制矩形框
captureX + captureWidth,
captureY + captureHeight,
paint);
saveDebugScreenshot(debugCopy, "marked_" + System.currentTimeMillis() + ".jpg");
// 计算合法的裁剪区域(防止超出屏幕边界)
int x = Math.max(0, captureX); // X起点不小于0
int y = Math.max(0, captureY); // Y起点不小于0
int width = Math.min(captureWidth, screenWidth - x); // 宽度不超过屏幕右侧
int height = Math.min(captureHeight, screenHeight - y); // 高度不超过屏幕底部
// 尝试裁剪指定区域
Bitmap croppedBitmap = null;
try {
croppedBitmap = Bitmap.createBitmap(
fullScreenBitmap, // 源位图
x, y, // 起始坐标
width, height // 裁剪尺寸
);
Log.d(TAG, "成功裁剪: x=" + x + ", y=" + y
+ ", width=" + width + ", height=" + height);
} catch (IllegalArgumentException e) {
// 处理非法参数异常(如负坐标或超界)
Log.e(TAG, "裁剪失败: " + e.getMessage() + ", 尝试默认值");
// 使用默认居中区域(800x600)
width = Math.min(800, screenWidth);
height = Math.min(600, screenHeight);
x = (screenWidth - width) / 2; // 水平居中
y = (screenHeight - height) / 2;// 垂直居中
croppedBitmap = Bitmap.createBitmap(
fullScreenBitmap, x, y, width, height);
}
// 释放全屏位图内存
fullScreenBitmap.recycle();
if (croppedBitmap != null) {
// 保存裁剪后的截图到相册
saveScreenshot(croppedBitmap, "Cropped_" + System.currentTimeMillis() + ".jpg", false);
croppedBitmap.recycle(); // 释放裁剪位图内存
} else {
Toast.makeText(FloatingWindowService.this,
"截图失败:无法裁剪图像", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(FloatingWindowService.this,
"截图失败:无法获取图像", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
// 捕获并记录所有异常
Log.e(TAG, "Error capturing screen", e);
Toast.makeText(FloatingWindowService.this,
"截图失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
} finally {
// 确保资源释放
if (image != null) {
image.close(); // 关闭Image对象
}
// 恢复截图框可见
if (captureFrame != null) {
captureFrame.setVisibility(View.VISIBLE);
}
// 恢复确认按钮可见
Button confirmButton = floatingView.findViewById(R.id.confirmButton);
if (confirmButton != null) {
confirmButton.setVisibility(View.VISIBLE);
}
// 释放MediaProjection相关资源
releaseMediaProjection();
isCapturing = false; // 重置截图状态
}
}
}, 300); // 延迟300毫秒执行
123
启动截图 → 初始化MediaProjection → 创建VirtualDisplay → 延迟捕获图像 → 处理像素数据 → 裁剪区域 → 保存结果 → 清理资源
123