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