安卓API21及以上版本,利用 MediaProjectionManager 截圖
0) 宣告
本文是根據 @goodbranch 的《Android 5.0及以上實現螢幕截圖 》,進行修改過之後,整理出來的。因作者未回覆私信,加之在不 root 的前提下用程式碼實現截圖著實不易,為了讓更多的人少走彎路,故編寫此文。如有冒犯,請及時提醒,以便刪除此文。
地址 : http://blog.csdn.net/consumer11/article/details/51967340
1) MainActivity 類
⑴ 宣告一個常量
private static final int REQUEST_CODE = 1;
⑵ 新建一個按鈕,並新增點事件
???.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { requestCapturePermission(); } });
⑶ 例項化 MediaProjectionManager
private void requestCapturePermission() {
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
}
⑷ 在 onActivityResult 中傳入引數
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case REQUEST_CODE: if (null != data) { if (RESULT_OK == resultCode) { ScreenShot.setUpMediaProjection(MainActivity.this, data); ScreenShot.getWH(MainActivity.this); ScreenShot.createImageReader(); ScreenShot.beginScreenShot(MainActivity.this, data); } } break; } }
2) ScreenShot 類
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
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.Handler;
import android.util.DisplayMetrics;
import android.view.Surface;
import android.view.WindowManager;
public class ScreenShot {
private static WindowManager windowManager;
private static int screenDensity;
private static int screenWidth;
private static int screenHeight;
private static MediaProjection mediaProjection;
private static VirtualDisplay virtualDisplay;
private static ImageReader imageReader;
public static Surface surface;
public static void setUpMediaProjection(Activity activity, Intent scIntent) {
if (scIntent == null) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
activity.startActivity(intent);
} else {
mediaProjection = getMediaProjectionManager(activity).getMediaProjection(Activity.RESULT_OK,
scIntent);
}
}
public static void getWH(Activity activity) {
DisplayMetrics metrics = new DisplayMetrics();
windowManager = (WindowManager) activity.getSystemService(activity.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getMetrics(metrics);
screenDensity = metrics.densityDpi;
screenWidth = metrics.widthPixels;
screenHeight = metrics.heightPixels;
}
public static void createImageReader() {
imageReader = ImageReader.newInstance(screenWidth, screenHeight, PixelFormat.RGBA_8888, 1);
}
public static void beginScreenShot(final Activity activity, final Intent intent) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
beginVirtual(activity, intent);
}
}, 0);
handler.postDelayed(new Runnable() {
@Override
public void run() {
beginCapture(activity, intent);
}
}, 150);
}
private static void beginVirtual(Activity activity, Intent intent) {
if (null != mediaProjection) {
virtualDisplay();
} else {
setUpMediaProjection(activity, intent);
virtualDisplay();
}
}
private static void virtualDisplay() {
surface = imageReader.getSurface();
virtualDisplay = mediaProjection.createVirtualDisplay("screen-mirror", screenWidth,
screenHeight, screenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface,
null, null);
}
private static MediaProjectionManager getMediaProjectionManager(Activity activity) {
return (MediaProjectionManager) activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
}
private static void beginCapture(Activity activity, Intent intent) {
Image acquireLatestImage = null;
try {
acquireLatestImage = imageReader.acquireLatestImage();
} catch (IllegalStateException e) {
if (null != acquireLatestImage) {
acquireLatestImage.close();
acquireLatestImage = null;
acquireLatestImage = imageReader.acquireLatestImage();
}
}
if (acquireLatestImage == null) {
beginScreenShot(activity, intent);
} else {
SaveTask saveTask = new SaveTask();
AsyncTaskCompat.executeParallel(saveTask, acquireLatestImage);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
releaseVirtual();
stopMediaProjection();
}
}, 1000);
}
}
private static void releaseVirtual() {
if (null != virtualDisplay) {
virtualDisplay.release();
virtualDisplay = null;
}
}
private static void stopMediaProjection() {
if (null != mediaProjection) {
mediaProjection.stop();
mediaProjection = null;
}
}
}
3) SaveTask 類
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import android.graphics.Bitmap;
import android.media.Image;
import android.media.Image.Plane;
import android.os.AsyncTask;
import android.os.Environment;
public class SaveTask extends AsyncTask<Image, Void, Bitmap> {
@Override
protected Bitmap doInBackground(Image... args) {
if (null == args || 1 > args.length || null == args[0]) {
return null;
}
Image image = args[0];
int width;
int height;
try {
width = image.getWidth();
height = image.getHeight();
} catch (IllegalStateException e) {
return null;
}
final Plane[] planes = image.getPlanes();
final 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();
File fileImage = null;
if (null != bitmap) {
FileOutputStream fos = null;
try {
fileImage = new File(createFile());
if (!fileImage.exists()) {
fileImage.createNewFile();
fos = new FileOutputStream(fileImage);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
}
} catch (IOException e) {
fileImage = null;
} finally {
if (null != fos) {
try {
fos.close();
} catch (IOException e) {
}
}
if (null != bitmap && !bitmap.isRecycled()) {
bitmap.recycle();
bitmap = null;
}
}
}
if (null != fileImage) {
return bitmap;
}
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (ScreenShot.surface.isValid()) {
ScreenShot.surface.release();
}
}
// 輸出目錄
private String createFile() {
String outDir = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
String date = simpleDateFormat.format(new Date());
return outDir + date + ".png";
}
}
4) AsyncTaskCompat 類
import android.os.AsyncTask;
public final class AsyncTaskCompat {
@SafeVarargs
public static <Params, Progress, Result> AsyncTask<Params, Progress, Result> executeParallel(
AsyncTask<Params, Progress, Result> task, Params... params) {
if (task == null) {
throw new IllegalArgumentException("task can not be null");
}
AsyncTaskCompatHoneycomb.executeParallel(task, params);
return task;
}
private AsyncTaskCompat() {
}
}
5) AsyncTaskCompatHoneycomb 類
import android.os.AsyncTask;
class AsyncTaskCompatHoneycomb {
@SafeVarargs
static <Params, Progress, Result> void executeParallel(AsyncTask<Params, Progress, Result> task,
Params... params) {
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
}
}
6) 新增SD卡寫入許可權
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
7) 關於懸浮窗許可權
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
⑴ 定義一個常量
int PERMISSION_CODE = 999;
⑵ 寫一個檢查許可權的方法,在程式初始化時呼叫
@SuppressLint({ "NewApi", "InlinedApi" })
private boolean checkPermission(Activity activity) {
if (Settings.canDrawOverlays(activity)) {
return true;
}
Toast.makeText(activity, "請開啟【允許在其他應用的上層顯示】許可權!", Toast.LENGTH_LONG).show();
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + activity.getPackageName()));
activity.startActivityForResult(intent, PERMISSION_CODE);
return false;
}
checkPermission(MainActivity.this); // 呼叫
⑶ 在MainActivity的onActivityResult(int requestCode, int resultCode, Intent data) 方法中檢查回撥
if (PERMISSION_CODE == resultCode) {
if (!Settings.canDrawOverlays(this)) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("提示").setMessage("授權失敗,無法使用程式").setCancelable(false)
.setNegativeButton("退出程式", new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialog, int which) { System.exit(0); } })
.create().show();
} else {
Toast.makeText(MainActivity.this, "授權成功!", Toast.LENGTH_SHORT).show();
requestCapturePermission();
}
}
8) Tips
- 已針對 LogCat 報出的異常,進行了修復。
- 能夠用程式碼在不 root 的前提下實現截圖,著實不易。希望愛鑽研的你們能夠實現:在低版本的、沒有 root 過的安卓手機上用程式碼實現截圖的功能。
- 繼續秉承拿來就能的原則!