1. 程式人生 > >安卓API21及以上版本,利用 MediaProjectionManager 截圖

安卓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

  1. 已針對 LogCat 報出的異常,進行了修復。
  2. 能夠用程式碼在不 root 的前提下實現截圖,著實不易。希望愛鑽研的你們能夠實現:在低版本的、沒有 root 過的安卓手機上用程式碼實現截圖的功能。
  3. 繼續秉承拿來就能的原則!