安卓基础(MediaProjection)

安卓基础(MediaProjection)_第1张图片
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");
    
    // 打印到Logcat
    Log.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;

    @Override
    protected 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() {
        // 创建屏幕捕获的 Intent
        Intent captureIntent = mediaProjectionManager.createScreenCaptureIntent();
        // 启动 Intent 并等待用户授权,使用请求码 REQUEST_CODE_SCREEN_CAPTURE
        startActivityForResult(captureIntent, REQUEST_CODE_SCREEN_CAPTURE);
    }

    @Override
    protected 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() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                Image image = null;
                try {
                    // 从 ImageReader 中获取最新的图像
                    image = reader.acquireLatestImage();
                    if (image != null) {
                        // 获取图像的平面数组
                        Image.Plane[] planes = image.getPlanes();
                        // 获取第一个平面的 ByteBuffer
                        ByteBuffer 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();
        }
    }

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

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (virtualDisplay != null) {
            // 释放虚拟显示屏资源
            virtualDisplay.release();
        }
        if (mediaProjection != null) {
            // 停止 MediaProjection 操作
            mediaProjection.stop();
        }
        if (imageReader != null) {
            // 关闭 ImageReader 资源
            imageReader.close();
        }
    }
}    

123

你可能感兴趣的:(android)