其他分享
首页 > 其他分享> > Android 中截屏功能的实现

Android 中截屏功能的实现

作者:互联网

之前写过一个关于截图的功能,但那个是基于咱们控件的截取,如果出了自己的项目,或者层次结构复杂了,一般不好操作了就。今天学习了一个截屏工具的制作方法,在这里记录一下。

我们的代码全部基于Android中自定义悬浮窗编写

1.首先我们先把TrafficService.java替换为CaptureService.java,代码如下

CaptureService.java

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class CaptureService extends Service implements FloatWindow.FloatClickListener {
    private static final String TAG = "CaptureService";
    private MediaProjectionManager mMpMgr; // 声明一个媒体投影管理器对象
    private MediaProjection mMP; // 声明一个媒体投影对象
    private ImageReader mImageReader; // 声明一个图像读取器对象
    private String mImagePath, mImageName; // 文件路径,文件名
    private int mScreenWidth, mScreenHeight, mScreenDensity; // 屏幕宽度,屏幕高度,每英寸中的像素数
    private VirtualDisplay mVirtualDisplay; // 声明一个虚拟显示层对象
    private FloatWindow mFloatWindow; // 声明一个悬浮窗对象

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @SuppressLint("WrongConstant")
    @Override
    public void onCreate() {
        super.onCreate();
        // 生成截图文件的保存路径
        mImagePath = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/ScreenShots/";
        // 从全局变量中获取媒体投影管理器
        mMpMgr = MyApplication.getInstance().getMpMgr();
        // 获得屏幕的宽度
        mScreenWidth = ScreenUtil.getScreenWidth(this);
        // 获得屏幕的高度
        mScreenHeight = ScreenUtil.getScreenHeight(this);
        // 获得屏幕每英寸中的像素数
        mScreenDensity = getScreenDensityDpi(this);
        // 根据屏幕宽高创建一个新的图像读取器
        mImageReader = ImageReader.newInstance(mScreenWidth, mScreenHeight, PixelFormat.RGBA_8888, 2);
        if (mFloatWindow == null) {
            // 创建一个新的悬浮窗
            mFloatWindow = new FloatWindow(MyApplication.getInstance());
            // 设置悬浮窗的布局内容
            mFloatWindow.setLayout(R.layout.float_capture);
        }
        // 设置悬浮窗的点击监听器
        mFloatWindow.setOnFloatListener(this);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (mFloatWindow != null && !mFloatWindow.isShow()) {
            mFloatWindow.show(); // 显示悬浮窗
        }
        return super.onStartCommand(intent, flags, startId);
    }

    // 在点击悬浮窗时触发
    public void onFloatClick(View v) {
        Toast.makeText(this, "准备截图", Toast.LENGTH_SHORT).show();
        // 延迟100毫秒后启动屏幕准备任务
        mHandler.postDelayed(mStartVirtual, 100); // 准备屏幕
        // 延迟500毫秒后启动屏幕截取任务
        mHandler.postDelayed(mCapture, 500); // 进行截图
        // 延迟1000毫秒后启动屏幕释放任务
        mHandler.postDelayed(mStopVirtual, 1000); // 释放屏幕
    }

    // 创建一个处理器对象
    private Handler mHandler = new Handler();
    // 定义一个屏幕准备任务
    private Runnable mStartVirtual = new Runnable() {
        @Override
        public void run() {
            // 截图过程中先隐藏悬浮窗
            mFloatWindow.mContentView.setVisibility(View.INVISIBLE);
            if (mMP == null) {
                // 根据结果代码和结果意图,从媒体投影管理器中创建一个新的媒体投影
                mMP = mMpMgr.getMediaProjection(MyApplication.getInstance().getResultCode(),
                        MyApplication.getInstance().getResultIntent());
            }
            // 从媒体投影管理器中创建一个新的虚拟显示层
            mVirtualDisplay = mMP.createVirtualDisplay("capture_screen", mScreenWidth, mScreenHeight,
                    mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                    mImageReader.getSurface(), null, null);
        }
    };

    // 定义一个屏幕截取任务
    private Runnable mCapture = new Runnable() {
        @Override
        public void run() {
            // 生成截图的文件名
            long nowTime = System.currentTimeMillis();
            mImageName = nowTime + ".png";
            Log.d(TAG, "mImageName=" + mImageName);
            // 从图像读取器中获取最近的一个Image对象
            Image image = mImageReader.acquireLatestImage();
            // 把Image对象转换成位图对象
            Bitmap bitmap = getBitmap(image);
            if (bitmap != null) {
                // 创建文件。如果文件所在的目录不存在,就先创建目录
                createFile(mImagePath, mImageName);
                // 把位图对象保存为图片文件
                saveBitmap(mImagePath + mImageName, bitmap, "PNG", 100);
                Toast.makeText(CaptureService.this, "截图成功:" + mImagePath + mImageName, Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(CaptureService.this, "截图失败:未截到屏幕图片", Toast.LENGTH_SHORT).show();
            }
        }
    };

    // 定义一个屏幕释放任务
    private Runnable mStopVirtual = new Runnable() {
        @Override
        public void run() {
            // 完成截图后再恢复悬浮窗
            mFloatWindow.mContentView.setVisibility(View.VISIBLE);
            if (mVirtualDisplay != null) {
                mVirtualDisplay.release(); // 释放虚拟显示层资源
                mVirtualDisplay = null;
            }
        }
    };

    @Override
    public void onDestroy() {
        if (mFloatWindow != null && mFloatWindow.isShow()) {
            mFloatWindow.close(); // 关闭悬浮窗
        }
        if (mMP != null) {
            mMP.stop(); // 停止媒体投影
        }
        super.onDestroy();
    }

    // 获得屏幕每英寸中的像素数
    private int getScreenDensityDpi(Context ctx) {
        // 从系统服务中获取窗口管理器
        WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        // 从默认显示器中获取显示参数保存到dm对象中
        wm.getDefaultDisplay().getMetrics(dm);
        return dm.densityDpi; // 返回每英寸中的像素数
    }

    // 把Image对象转换成位图对象
    private Bitmap getBitmap(Image image) {
        int width = image.getWidth();
        int height = image.getHeight();
        Image.Plane[] planes = image.getPlanes();
        ByteBuffer buffer = planes[0].getBuffer();
        int pixelStride = planes[0].getPixelStride();
        int rowStride = planes[0].getRowStride();
        int rowPadding = rowStride - pixelStride * width;
        Bitmap bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride,
                height, Bitmap.Config.ARGB_8888);
        bitmap.copyPixelsFromBuffer(buffer);
        bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height);
        image.close();
        return bitmap;
    }

    // 创建文件
    private File createFile(String path, String file_name) {
        createDir(path);
        File file = new File(path, file_name);
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return file;
    }

    // 创建目录
    private void createDir(String path) {
        File dir = new File(path);
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    // 把位图对象保存为指定路径的图片文件
    private void saveBitmap(String path, Bitmap bitmap, String format, int quality) {
        Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.JPEG;
        if (format.toUpperCase(Locale.getDefault()).equals("PNG")) {
            compressFormat = Bitmap.CompressFormat.PNG;
        }
        try {
            // 根据指定文件路径构建缓存输出流对象
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path));
            // 把位图数据压缩到缓存输出流中
            bitmap.compress(compressFormat, quality, bos);
            // 完成缓存输出流的写入动作
            bos.flush();
            // 关闭缓存输出流
            bos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

记得把清单文件中的Service配置改为当前的CaptureService

2.删除布局文件float_traffic.xml

3.添加布局文件float_capture.xml,代码如下

float_capture.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    
    <ImageView
        android:id="@+id/iv_capture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_capture" />

</LinearLayout>

4.添加图片,这个图片就是第3步中代码中的ic_capture图片。这个自己找一个就好。

5.编写MyApplication内容。代码如下

MyApplication.java

public class MyApplication extends Application {

    private Intent mResultIntent = null; // 结果意图
    private int mResultCode = 0; // 结果代码
    private MediaProjectionManager mMpMgr; // 声明一个媒体投影管理器对象
    //1.获取全局上下文第一步
    private static MyApplication mApplication = null;

    @Override
    public void onCreate() {
        super.onCreate();
        //2.获取全局上下文第二步
        mApplication = this;
    }

    public Intent getResultIntent() {
        return mResultIntent;
    }

    public void setResultIntent(Intent mResultIntent) {
        this.mResultIntent = mResultIntent;
    }

    public int getResultCode() {
        return mResultCode;
    }

    public void setResultCode(int mResultCode) {
        this.mResultCode = mResultCode;
    }

    public MediaProjectionManager getMpMgr() {
        return mMpMgr;
    }

    public void setMpMgr(MediaProjectionManager mMpMgr) {
        this.mMpMgr = mMpMgr;
    }

    //3.获取全局上下文第三步
    public static MyApplication getInstance(){
        return mApplication;
    }
}

这个也是要在清单文件中进行配置的。

6.activity_main.xml页面布局内容,代码如下:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="5dp">

    <Button
        android:id="@+id/btn_start_capture"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="屏幕截图"
        android:textColor="#000000"
        android:textSize="17sp" />

</LinearLayout>

7. 编写MainActivity页面的内容,代码如下

MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private static final String TAG = "ScreenCaptureActivity";
    private MediaProjectionManager mMpMgr; // 声明一个媒体投影管理器对象
    private int REQUEST_MEDIA_PROJECTION = 1; // 媒体投影授权的请求代码
    private Intent mResultIntent = null; // 结果意图
    private int mResultCode = 0; // 结果代码
    private boolean backFromSetting;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn_start_capture).setOnClickListener(this);
        // 从系统服务中获取媒体投影管理器
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mMpMgr = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        }
        // 从全局变量中获取结果意图
        mResultIntent = MyApplication.getInstance().getResultIntent();
        // 从全局变量中获取结果代码
        mResultCode = MyApplication.getInstance().getResultCode();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (backFromSetting){
            backFromSetting = false;
            if (checkAlertWindowsPermission(this)){
                //已经允许
                MyApplication.getInstance().setResultCode(mResultCode);
                MyApplication.getInstance().setResultIntent(mResultIntent);
                MyApplication.getInstance().setMpMgr(mMpMgr);
                // 启动录屏服务
                startService(new Intent(this, CaptureService.class));
            }else{
                //已经禁止
                ToastUtil.toastWord(this,"您未允许悬浮窗权限,无法打开悬浮窗");
            }
        }
    }

    /**
     * 判断 悬浮窗口权限是否打开
     * @param context
     * @return true 允许  false禁止
     */
    public boolean checkAlertWindowsPermission(Context context) {
        try {
            Object object = context.getSystemService(Context.APP_OPS_SERVICE);
            if (object == null) {
                return false;
            }
            Class localClass = object.getClass();
            Class[] arrayOfClass = new Class[3];
            arrayOfClass[0] = Integer.TYPE;
            arrayOfClass[1] = Integer.TYPE;
            arrayOfClass[2] = String.class;
            Method method = localClass.getMethod("checkOp", arrayOfClass);
            if (method == null) {
                return false;
            }
            Object[] arrayOfObject1 = new Object[3];
            arrayOfObject1[0] = 24;
            arrayOfObject1[1] = Binder.getCallingUid();
            arrayOfObject1[2] = context.getPackageName();
            int m = ((Integer) method.invoke(object, arrayOfObject1));
            return m == AppOpsManager.MODE_ALLOWED;
        } catch (Exception ex) {

        }
        return false;
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn_start_capture) {
            startCapture(); // 开始截图操作
        }
    }

    // 开始截图操作
    private void startCapture() {
        if (mResultIntent != null && mResultCode != 0) { // 不是首次截图或录屏
            // 启动截图服务
            startService(new Intent(this, CaptureService.class));
        } else { // 是首次截图或录屏
            // 在YunOS上报错“android.content.ActivityNotFoundException: Unable to find explicit activity class {com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity}; have you declared this activity in your AndroidManifest.xml?”
            // 即使添加了权限定义与Activity声明,也仍然报错
            // 怀疑是该操作系统为了安全把这个组件删除了
            try {
                // 弹出授权截图的对话框
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    startActivityForResult(mMpMgr.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);
                }
            } catch (Exception e) {
                e.printStackTrace();
                Toast.makeText(this, "当前系统不支持截屏功能", Toast.LENGTH_SHORT).show();
            }
        }
    }

    // 从授权截图的对话框返回时触发
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Log.d(TAG, "onActivityResult requestCode=" + requestCode + ", resultCode=" + resultCode);
        if (requestCode == REQUEST_MEDIA_PROJECTION) {
            if (resultCode == RESULT_OK) { // 允许授权
                // AppOpsManager.OP_SYSTEM_ALERT_WINDOW是隐藏变量(值为24),不能直接引用
                mResultCode = resultCode;
                mResultIntent = data;
                if (checkAlertWindowsPermission(this)) { // 未开启悬浮窗权限
                    //已经允许
                    // 下面把结果代码、结果意图等等信息保存到全局变量中
                    MyApplication.getInstance().setResultCode(resultCode);
                    MyApplication.getInstance().setResultIntent(data);
                    MyApplication.getInstance().setMpMgr(mMpMgr);
                    // 启动录屏服务
                    startService(new Intent(this, CaptureService.class));
                } else { // 已开启悬浮窗权限
                    //已经禁止
                    ToastUtil.toastWord(this,"您未允许悬浮窗权限,无法打开悬浮窗");
                }
            }
        }
    }
}

根据自己实际的代码情况进行融入和修改,就可以实现一个自己的截屏工具。

标签:功能,int,void,中截屏,private,MyApplication,Android,null,public
来源: https://blog.csdn.net/weixin_38322371/article/details/119249109