沒有UI的Fragment的妙用——截屏

最近測試了一下Android的截圖工具,最開始是這麼設計的:

public class ScreenCaptor{

    public static final int REQUEST_CAPTURE = 4;
    public interface OnCaptureFinishedListener {
        void onCaptureSuccess(Bitmap bitmap);
        void onCaptureFailure();
    }
    public void startCapture(int requestCode, OnCaptureFinishedListener onCaptureFinishedListener) {
        //...
    }

    public void setOnCaptureGranted(int resultCode, Intent resultData) {
        //...
    }

}
// 在Activity中使用時:
public class ScreenCaptorTestActivity extends AppCompatActivity implements ScreenCaptor.OnCaptureFinishedListener {

    private ScreenCaptor mScreenCaptor;
    private ImageView mImageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_api_shot);
        mImageView = findViewById(R.id.image_view);
        mScreenCaptor = new ScreenCaptor(this);
    }

    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.start_capture:
                mScreenCaptor.startCapture(REQUEST_CAPTURE,this);
                break;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_CAPTURE) {
            mScreenCaptor.setOnCaptureGranted(resultCode,data);
        }
    }

    @Override
    public void onCaptureSuccess(Bitmap bitmap) {
        mImageView.setImageBitmap(bitmap);
    }

    @Override
    public void onCaptureFailure() {

    }
}

因爲截圖需要請求權限,在使用截圖工具的Activity中都要重寫onActivityResult(int,int,Intent)方法,調用ScreenCaptor的setOnCaptureGranted(int,Intent)。由此想到Fragment中也有onActivityResult方法,Fragment有不帶UI的用法。於是截圖工具可以這樣做:

public class ScreenCaptureFragment extends Fragment {

    private static final String TAG = "ScreenCaptureFragment";
    private static final int REQUEST_CAPTURE = 2;

    public interface OnCaptureFinishedListener {
        void onCaptureSuccess(Bitmap bitmap);

        void onCaptureFailure();
    }

    private OnCaptureFinishedListener mOnCaptureFinishedListener;
    private MediaProjectionManager mMediaProjectionManager;
    private int mScreenWidth;
    private int mScreenHeight;
    private int mScreenDensity;
    private ImageReader mImageReader;
    private MediaProjection mMediaProjection;
    private Intent mResultData;
    private int mResultCode;
    private VirtualDisplay mVirtualDisplay;
    private ImageToBitmapAsyncTask mImageToBitmapTask;

    public ScreenCaptureFragment() {
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mMediaProjectionManager = ((MediaProjectionManager) getActivity().getSystemService(Context.MEDIA_PROJECTION_SERVICE));
        setupImageReader();
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    private void setupImageReader() {
        DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
        mScreenDensity = displayMetrics.densityDpi;
        mScreenWidth = displayMetrics.widthPixels;
        mScreenHeight = displayMetrics.heightPixels;
//      圖片格式PixelFormat.RGBA_8888不是亂寫的,與Bitmap.Config的圖片格式對應
        mImageReader = ImageReader.newInstance(mScreenWidth, mScreenHeight, PixelFormat.RGBA_8888, 1);
    }

    /**
     * 啓動截屏
     *
     * @param onCaptureFinishedListener 截屏完成的監聽器
     */
    public void startCapture(OnCaptureFinishedListener onCaptureFinishedListener) {
        mOnCaptureFinishedListener = onCaptureFinishedListener;
        if (mMediaProjection != null) {// 如果已經有令牌,直接拓印屏幕內容
            setupVirtualDisplay();
        } else if (mResultData != null && mResultCode != 0) {// 如果已經獲得錄屏權限,構建MediaProjection,然後拓印屏幕內容
            setupMediaProjection();
            setupVirtualDisplay();
        } else {// 如果沒有錄屏權限:請求錄屏權限
            requestCapturePermission(REQUEST_CAPTURE);
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_CAPTURE) {
            if (resultCode != RESULT_OK) {
                Log.e(TAG, "截屏權限被禁止!");
                return;
            }
            mResultCode = resultCode;
            mResultData = data;
            setupMediaProjection();
            setupVirtualDisplay();
        }
    }

    /*獲得MediaProjection:一個允許程序截取屏幕內容的令牌*/
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void setupMediaProjection() {
        mMediaProjection = mMediaProjectionManager.getMediaProjection(mResultCode, mResultData);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void requestCapturePermission(int requestCode) {
        startActivityForResult(mMediaProjectionManager.createScreenCaptureIntent(), requestCode);
    }

    /*獲得虛擬顯示,可理解爲拓印屏幕內容*/
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void setupVirtualDisplay() {
        if (mImageToBitmapTask != null && mImageToBitmapTask.isRunning()) {
            Log.e(TAG,"There is already a task running");
            return;
        }
        Image image;
        do {
            mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenCaptor",
                    mScreenWidth, mScreenHeight, mScreenDensity,
                    DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                    mImageReader.getSurface(), null, null);
            image = mImageReader.acquireNextImage();
        } while (image == null);
        mImageToBitmapTask = new ImageToBitmapAsyncTask(mOnCaptureFinishedListener);
        mImageToBitmapTask.execute(image);
    }

    private static class ImageToBitmapAsyncTask extends AsyncTask<Image, Void, Bitmap> {

        private boolean isRunning;
        private WeakReference<OnCaptureFinishedListener> mListenerWeakReference;

        public ImageToBitmapAsyncTask(OnCaptureFinishedListener listener) {
            mListenerWeakReference = new WeakReference<>(listener);
        }

        @Override
        protected void onPreExecute() {
            isRunning = true;
        }

        @Override
        protected Bitmap doInBackground(Image... images) {
            Image image = null;
            if (images != null && images.length > 0) {
                image = images[0];
            }
            if (image != null) {
                return imageToBitmap(images[0]);
            }
            return null;
        }

        @TargetApi(Build.VERSION_CODES.KITKAT)
        private Bitmap imageToBitmap(Image image) {
            if (image == null) {
                return null;
            }
            try {
                int width = image.getWidth();
                int height = image.getHeight();
                final Image.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);
                return Bitmap.createBitmap(bitmap, 0, 0, width, height);
            } catch (ArithmeticException e) {
                return null;
            } finally {
                image.close();
            }
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            isRunning = false;
            OnCaptureFinishedListener listener = mListenerWeakReference.get();
            if (listener == null) {
                return;
            }
            if (bitmap != null) {
                listener.onCaptureSuccess(bitmap);
            } else {
                listener.onCaptureFailure();
            }
        }

    @Override
        protected void onCancelled() {
            isRunning = false;
        }

        public boolean isRunning() {
            return isRunning;
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        stopMediaProjection();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mImageToBitmapTask != null && !mImageToBitmapTask.isRunning()) {
            mImageToBitmapTask.cancel(true);
            mImageToBitmapTask = null;
        }
        releaseVirtualDisplay();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void stopMediaProjection() {
        if (mMediaProjection != null) {
            mMediaProjection.stop();
            mMediaProjection = null;
        }
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    private void releaseVirtualDisplay() {
        if (mVirtualDisplay == null) {
            return;
        }
        mVirtualDisplay.release();
        mVirtualDisplay = null;
    }
}

使用時不需要再重寫onActivityResult方法了:

public class ScreenCaptureActivity extends AppCompatActivity implements ScreenCaptureFragment.OnCaptureFinishedListener {

    private ImageView mImageView;
    protected ScreenCaptureFragment mCaptureFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_api_shot);
        mImageView = findViewById(R.id.image_view);
        mCaptureFragment = new ScreenCaptureFragment();
        getSupportFragmentManager().beginTransaction().add(mCaptureFragment,"capture").commit();
    }

    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.start_capture:
                mCaptureFragment.startCapture(this);
                break;
        }
    }

    @Override
    public void onCaptureSuccess(Bitmap bitmap) {
        mImageView.setImageBitmap(bitmap);
    }

    @Override
    public void onCaptureFailure() {

    }
}

貼上源碼地址:https://github.com/sheaye/ScreenCaptureTest

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章