安卓基础(MediaProjection)
1. Display
类
- 作用:代表显示设备(手机屏幕、外接显示器)
- 常用方法:
display.getRotation() // 获取屏幕方向(横屏/竖屏)
display.getRefreshRate() // 获取屏幕刷新率(如:60Hz)
2. Point
类
- 结构:
public class Point {public int x; // 横坐标public int y; // 纵坐标
}
- 为什么用Point:比单独用两个
int
变量更规范
3. 单位说明
- 像素(px):代码获取的是像素值
- 换算公式:
// 像素转dp(适配不同密度屏幕)
float dp = px / (getResources().getDisplayMetrics().density);
完整代码
// 在Activity的onCreate方法中获取屏幕尺寸
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 获取屏幕尺寸Display display = getWindowManager().getDefaultDisplay();Point size = new Point();display.getSize(size); //把屏幕的大小记录到size的x和y上面int screenWidth = size.x;int screenHeight = size.y;// 实际应用:设置TextView显示尺寸TextView sizeInfo = findViewById(R.id.tv_size);sizeInfo.setText("屏幕宽度:" + screenWidth + "px\n屏幕高度:" + screenHeight + "px");// 打印到LogcatLog.d("ScreenSize", "宽度:" + screenWidth + " 高度:" + screenHeight);
}
ImageReader
// 参数说明:
// 宽度 | 高度 | 图像格式 | 最大缓存图像数(建议2-3)
ImageReader imageReader = ImageReader.newInstance(1080, 1920, ImageFormat.YUV_420_888, 3);
Bitmap
本质是什么?
- 像素集合:由
width x height
个像素点组成 - 内存结构:每个像素存储颜色信息(如红绿蓝透明度)
- 数据载体:可来自文件、摄像头、网络或手动绘制
// 1. 创建文件路径
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),"screenshot_" + System.currentTimeMillis() + ".png");// 2. 保存Bitmap到该路径
try (FileOutputStream out = new FileOutputStream(file)) {// 将 Bitmap 以 PNG 格式压缩并保存到文件中bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); Toast.makeText(this, "保存成功:" + file.getPath(), Toast.LENGTH_SHORT).show();
} catch (IOException e) {e.printStackTrace();
}
1. 确定保存位置
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
- 翻译:获取手机系统预设的「公共图片文件夹」
- 实际路径举例:
- 旧安卓:
/sdcard/Pictures/
- 新安卓(Android 10+):
/内部存储/DCIM/Pictures/
- 旧安卓:
2. 生成唯一文件名
"screenshot_" + System.currentTimeMillis() + ".png"
- 当前时间戳:
System.currentTimeMillis()
获取1970年至今的毫秒数(如1625043725123
) - 示例文件名:
screenshot_1625043725123.png
- 为什么用时间戳:防止文件名重复
createVirtualDisplay
的作用是创建一个虚拟的屏幕镜像
想象你有一台手机,现在要把它屏幕上的内容实时投影到另一台设备上。createVirtualDisplay
就相当于在这台手机内部安装了一个隐藏的摄像头,这个摄像头会持续拍摄屏幕内容,并将画面传递给处理单元(例如保存为图片或视频)。
实际应用场景
1. 屏幕截图功能
// 初始化ImageReader(接收画面)
ImageReader imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2);// 创建虚拟显示屏绑定到ImageReader
virtualDisplay = mediaProjection.createVirtualDisplay(...);// 从ImageReader获取图像
Image image = imageReader.acquireLatestImage();
Bitmap bitmap = imageToBitmap(image); // 转换为Bitmap
把虚拟屏幕展示传送给图片读取器的surface
imageReader.getSurface()
// 创建虚拟显示屏
virtualDisplay = mediaProjection.createVirtualDisplay("ScreenCapture",width, height, getResources().getDisplayMetrics().densityDpi,DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,imageReader.getSurface(), null, null);
完整代码
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.Image;
import android.media.ImageReader;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Bundle;
import android.os.Environment;
import android.view.Display;
import android.view.Surface;
import android.widget.Toast;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;// 主活动类,继承自 AppCompatActivity
public class MainActivity extends AppCompatActivity {// 屏幕捕获权限请求码private static final int REQUEST_CODE_SCREEN_CAPTURE = 1;// 写入外部存储权限请求码private static final int REQUEST_CODE_WRITE_EXTERNAL_STORAGE = 2;// MediaProjectionManager 实例,用于管理屏幕投影private MediaProjectionManager mediaProjectionManager;// MediaProjection 实例,用于实际的屏幕投影操作private MediaProjection mediaProjection;// ImageReader 实例,用于读取屏幕图像private ImageReader imageReader;// VirtualDisplay 实例,用于创建虚拟显示屏private VirtualDisplay virtualDisplay;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 通过系统服务获取 MediaProjectionManager 实例mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);// 检查是否有写入外部存储的权限if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {// 若没有权限,请求写入外部存储权限ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},REQUEST_CODE_WRITE_EXTERNAL_STORAGE);} else {// 若已有权限,请求屏幕捕获权限requestScreenCapturePermission();}}// 请求屏幕捕获权限的方法private void requestScreenCapturePermission() {// 创建屏幕捕获的 IntentIntent captureIntent = mediaProjectionManager.createScreenCaptureIntent();// 启动 Intent 并等待用户授权,使用请求码 REQUEST_CODE_SCREEN_CAPTUREstartActivityForResult(captureIntent, REQUEST_CODE_SCREEN_CAPTURE);}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);// 检查请求码是否为屏幕捕获请求码if (requestCode == REQUEST_CODE_SCREEN_CAPTURE) {if (resultCode == Activity.RESULT_OK) {// 若用户授予了屏幕捕获权限,通过 MediaProjectionManager 获取 MediaProjection 实例mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);// 开始屏幕捕获操作startScreenCapture();} else {// 若用户拒绝了屏幕捕获权限,显示提示信息Toast.makeText(this, "屏幕捕获权限被拒绝", Toast.LENGTH_SHORT).show();}}}// 开始屏幕捕获的方法private void startScreenCapture() {// 获取默认显示屏对象Display display = getWindowManager().getDefaultDisplay();// 创建一个 Point 对象用于存储屏幕尺寸Point size = new Point();// 获取屏幕的尺寸并存储到 Point 对象中display.getSize(size);// 获取屏幕的宽度int width = size.x;// 获取屏幕的高度int height = size.y;// 创建 ImageReader 实例,用于读取屏幕图像imageReader = ImageReader.newInstance(width, height, ImageFormat.RGB_565, 2);// 为 ImageReader 设置图像可用监听器imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {Image image = null;try {// 从 ImageReader 中获取最新的图像image = reader.acquireLatestImage();if (image != null) {// 获取图像的平面数组Image.Plane[] planes = image.getPlanes();// 获取第一个平面的 ByteBufferByteBuffer buffer = planes[0].getBuffer();// 获取像素步长int pixelStride = planes[0].getPixelStride();// 获取行步长int rowStride = planes[0].getRowStride();// 计算行填充int rowPadding = rowStride - pixelStride * width;// 创建一个 Bitmap 对象,用于存储图像数据Bitmap bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.RGB_565);// 将 ByteBuffer 中的数据复制到 Bitmap 中bitmap.copyPixelsFromBuffer(buffer);// 裁剪 Bitmap 去除多余的填充bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height);// 保存截图到文件saveBitmapToFile(bitmap);// 关闭 Image 对象,释放资源image.close();}} catch (Exception e) {// 若出现异常,打印异常信息e.printStackTrace();} finally {if (image != null) {// 确保 Image 对象被关闭image.close();}}}}, null);// 创建虚拟显示屏virtualDisplay = mediaProjection.createVirtualDisplay("ScreenCapture",width, height, getResources().getDisplayMetrics().densityDpi,DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,imageReader.getSurface(), null, null);}// 保存 Bitmap 到文件的方法private void saveBitmapToFile(Bitmap bitmap) {// 创建一个文件对象,指定保存路径和文件名File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),"screenshot_" + System.currentTimeMillis() + ".png");try (FileOutputStream fos = new FileOutputStream(file)) {// 将 Bitmap 以 PNG 格式压缩并保存到文件中bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);// 显示保存成功的提示信息Toast.makeText(this, "截图已保存到 " + file.getAbsolutePath(), Toast.LENGTH_SHORT).show();} catch (IOException e) {// 若保存过程中出现异常,打印异常信息并显示保存失败的提示信息e.printStackTrace();Toast.makeText(this, "保存截图失败", Toast.LENGTH_SHORT).show();}}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);// 检查请求码是否为写入外部存储权限请求码if (requestCode == REQUEST_CODE_WRITE_EXTERNAL_STORAGE) {if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {// 若权限授予成功,请求屏幕捕获权限requestScreenCapturePermission();} else {// 若权限授予失败,显示提示信息Toast.makeText(this, "写入外部存储权限被拒绝", Toast.LENGTH_SHORT).show();}}}@Overrideprotected void onDestroy() {super.onDestroy();if (virtualDisplay != null) {// 释放虚拟显示屏资源virtualDisplay.release();}if (mediaProjection != null) {// 停止 MediaProjection 操作mediaProjection.stop();}if (imageReader != null) {// 关闭 ImageReader 资源imageReader.close();}}
}
123