Android屏幕截圖實現方式 系統截屏源碼分析和三指截屏

Android截屏的方式:

  • 1.獲取DecorView截屏

通過獲取DecorView的方式來實現截屏(前提是當前Activity已經加載完成),DecorView爲整個Window界面的最頂層View,因此截屏不包含狀態欄(SystemUI)部分.

    View view = getWindow().getDecorView();     // 獲取DecorView
    // 方式一:
    view.setDrawingCacheEnabled(true);
    view.buildDrawingCache();
    Bitmap bitmap1 = view.getDrawingCache();     
    
    // 方式二:
    Bitmap bitmap2 = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas();
    canvas.setBitmap(bitmap2);
    view.draw(canvas);
    
    // 保存 bitmap1  bitmap2 均可保存截屏圖片
  • 2.使用MediaProjection截屏

實現方式與屏幕錄製類似 (Android屏幕錄製源碼Demo下載)

  • 3.調用系統源碼截屏(推薦使用)

系統截屏(獲取當前屏幕的Bitmap)調用的代碼如下:

Bitmap mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);

由於是hide api,通過反射調用如下:

public Bitmap takeScreenShot() {
        Bitmap bmp = null;
        mDisplay.getMetrics(mDisplayMetrics);
        float[] dims = {(float) mDisplayMetrics.widthPixels, (float) heightPixels};
        float degrees = getDegreesForRotation(mDisplay.getRotation());
        boolean requiresRotation = degrees > 0;
        if (requiresRotation) {
            mDisplayMatrix.reset();
            mDisplayMatrix.preRotate(-degrees);
            mDisplayMatrix.mapPoints(dims);
            dims[0] = Math.abs(dims[0]);
            dims[1] = Math.abs(dims[1]);

        }
        try {
            Class<?> demo = Class.forName("android.view.SurfaceControl");
            Method method = demo.getMethod("screenshot", new Class[]{Integer.TYPE, Integer.TYPE});
            bmp = (Bitmap) method.invoke(demo, new Object[]{Integer.valueOf((int) dims[0]), Integer.valueOf((int) dims[1])});
            if (bmp == null) {
                return null;
            }
            if (requiresRotation) {
                Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels, heightPixels, Bitmap.Config.RGB_565);
                Canvas c = new Canvas(ss);
                c.translate((float) (ss.getWidth() / 2), (float) (ss.getHeight() / 2));
                c.rotate(degrees);
                c.translate((-dims[0] / 2), (-dims[1] / 2));
                c.drawBitmap(bmp, 0, 0, null);
                c.setBitmap(null);
                bmp.recycle();
                bmp = ss;
            }
            if (bmp == null) {

                return null;
            }
            bmp.setHasAlpha(false);
            bmp.prepareToDraw();
            return bmp;
        } catch (Exception e) {
            e.printStackTrace();
            return bmp;
        }

    }

Android系統自帶截屏功能,在Framework層調用即可,普通應用無法調用.系統截屏源碼如下:

    // PATH: frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
    /** {@inheritDoc} */
    @Override
    public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
        final boolean keyguardOn = keyguardOn();
        final int keyCode = event.getKeyCode();
        // ... ...
        if (keyCode == KeyEvent.KEYCODE_S && event.isMetaPressed()
                && event.isCtrlPressed()) {
            if (down && repeatCount == 0) {
                int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION
                        : TAKE_SCREENSHOT_FULLSCREEN;
                mScreenshotRunnable.setScreenshotType(type);
                mHandler.post(mScreenshotRunnable);
                return -1;
            }
        }
        // ... ...
    }

攔截截屏的按鍵事件,觸發截屏.

    private class ScreenshotRunnable implements Runnable {
        private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;

        public void setScreenshotType(int screenshotType) {
            mScreenshotType = screenshotType;
        }

        @Override
        public void run() {
            takeScreenshot(mScreenshotType);
        }
    }

    private final ScreenshotRunnable mScreenshotRunnable = new ScreenshotRunnable();

調用takeScreenshot()函數截屏.

    // Assume this is called from the Handler thread.
    private void takeScreenshot(final int screenshotType) {
        synchronized (mScreenshotLock) {
            if (mScreenshotConnection != null) {    //截屏結束會關閉TakeScreenshotService服務和連接
                return;
            }
            final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
                    SYSUI_SCREENSHOT_SERVICE);
            final Intent serviceIntent = new Intent();
            serviceIntent.setComponent(serviceComponent);
            ServiceConnection conn = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    synchronized (mScreenshotLock) {
                        if (mScreenshotConnection != this) {
                            return;
                        }
                        Messenger messenger = new Messenger(service);
                        Message msg = Message.obtain(null, screenshotType);
                        final ServiceConnection myConn = this;
                        Handler h = new Handler(mHandler.getLooper()) {
                            @Override
                            public void handleMessage(Message msg) {
                                synchronized (mScreenshotLock) {
                                    if (mScreenshotConnection == myConn) {
                                        mContext.unbindService(mScreenshotConnection);
                                        mScreenshotConnection = null;
                                        mHandler.removeCallbacks(mScreenshotTimeout);
                                    }
                                }
                            }
                        };
                        msg.replyTo = new Messenger(h);
                        msg.arg1 = msg.arg2 = 0;
                        if (mStatusBar != null && mStatusBar.isVisibleLw())
                            msg.arg1 = 1;
                        if (mNavigationBar != null && mNavigationBar.isVisibleLw())
                            msg.arg2 = 1;
                        try {
                            messenger.send(msg);
                        } catch (RemoteException e) {
                        }
                    }
                }

                @Override
                public void onServiceDisconnected(ComponentName name) {
                    synchronized (mScreenshotLock) {
                        if (mScreenshotConnection != null) {
                            mContext.unbindService(mScreenshotConnection);
                            mScreenshotConnection = null;
                            mHandler.removeCallbacks(mScreenshotTimeout);
                            notifyScreenshotError();
                        }
                    }
                }
            };
            if (mContext.bindServiceAsUser(serviceIntent, conn,
                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                    UserHandle.CURRENT)) {
                mScreenshotConnection = conn;
                mHandler.postDelayed(mScreenshotTimeout, 10000);
            }
        }
    }

通過bindService啓動服務TakeScreenshotService執行截屏

同時通過mHandler發送一個10s的延遲任務,截屏程序在10s中沒有完成則觸發該任務mScreenshotTimeout(該Runnable主要是解綁TakeScreenshotService同時發送截屏錯誤的廣播,由ScreenshotServiceErrorReceiver接收然後發送錯誤通知);

服務綁定成功後通過"msg.replyTo = new Messenger(h);"發送一個Messenger給TakeScreenshotService服務,當截屏結束TakeScreenshotService會發送該Messenger,觸發handleMessage,執行解綁服務和移除mScreenshotTimeout超時任務.

    // PATH: frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
    public class TakeScreenshotService extends Service {
    private static final String TAG = "TakeScreenshotService";

    private static GlobalScreenshot mScreenshot;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            final Messenger callback = msg.replyTo;
            Runnable finisher = new Runnable() {
                @Override
                public void run() {
                    Message reply = Message.obtain(null, 1);
                    try {
                        callback.send(reply);
                    } catch (RemoteException e) {
                    }
                }
            };

            // If the storage for this user is locked, we have no place to store
            // the screenshot, so skip taking it instead of showing a misleading
            // animation and error notification.
            if (!getSystemService(UserManager.class).isUserUnlocked()) {
                Log.w(TAG, "Skipping screenshot because storage is locked!");
                post(finisher);
                return;
            }

            if (mScreenshot == null) {
                mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
            }

            switch (msg.what) {
                case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
                    mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
                    break;
                case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
                    mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0);
                    break;
            }
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return new Messenger(mHandler).getBinder();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        if (mScreenshot != null) mScreenshot.stopScreenshot();
        return true;
    }
}
    

初始化GlobalScreenshot對象,調用截圖函數;接收Messenger,構建Runnable用來發送消息(當截屏保存之後執行發送,如上所述解綁服務,移除超時任務)

/**
     * @param context everything needs a context :(
     */
    public GlobalScreenshot(Context context) {
        Resources r = context.getResources();
        mContext = context;
        LayoutInflater layoutInflater = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        // Inflate the screenshot layout
        mDisplayMatrix = new Matrix();
        mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
        mBackgroundView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_background);
        mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot);
        mScreenshotFlash = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
        mScreenshotSelectorView = (ScreenshotSelectorView) mScreenshotLayout.findViewById(
                R.id.global_screenshot_selector);
        mScreenshotLayout.setFocusable(true);
        mScreenshotSelectorView.setFocusable(true);
        mScreenshotSelectorView.setFocusableInTouchMode(true);
        mScreenshotLayout.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // Intercept and ignore all touch events
                return true;
            }
        });

        // Setup the window that we are going to use
        mWindowLayoutParams = new WindowManager.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
                WindowManager.LayoutParams.TYPE_SCREENSHOT,
                WindowManager.LayoutParams.FLAG_FULLSCREEN
                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
                    | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
                PixelFormat.TRANSLUCENT);
        mWindowLayoutParams.setTitle("ScreenshotAnimation");
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        mNotificationManager =
            (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        mDisplay = mWindowManager.getDefaultDisplay();
        mDisplayMetrics = new DisplayMetrics();
        mDisplay.getRealMetrics(mDisplayMetrics);

        // Get the various target sizes
        mNotificationIconSize =
            r.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);

        // Scale has to account for both sides of the bg
        mBgPadding = (float) r.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding);
        mBgPaddingScale = mBgPadding /  mDisplayMetrics.widthPixels;

        // determine the optimal preview size
        int panelWidth = 0;
        try {
            panelWidth = r.getDimensionPixelSize(R.dimen.notification_panel_width);
        } catch (Resources.NotFoundException e) {
        }
        if (panelWidth <= 0) {
            // includes notification_panel_width==match_parent (-1)
            panelWidth = mDisplayMetrics.widthPixels;
        }
        mPreviewWidth = panelWidth;
        mPreviewHeight = r.getDimensionPixelSize(R.dimen.notification_max_height);

        // Setup the Camera shutter sound
        mCameraSound = new MediaActionSound();
        mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
    }

構造函數中,初始化窗體佈局的位置等參數;初始化通知欄服務mNotificationManager;初始化截屏聲音mCameraSound

    void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
        mDisplay.getRealMetrics(mDisplayMetrics);
        takeScreenshot(finisher, statusBarVisible, navBarVisible, 0, 0, mDisplayMetrics.widthPixels,
                mDisplayMetrics.heightPixels);
    }

    /**
     * Takes a screenshot of the current display and shows an animation.
     */
    void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible,
            int x, int y, int width, int height) {
        // We need to orient the screenshot correctly (and the Surface api seems to take screenshots
        // only in the natural orientation of the device :!)
        mDisplay.getRealMetrics(mDisplayMetrics);
        // 屏幕的高度和寬度
        float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
        float degrees = getDegreesForRotation(mDisplay.getRotation());
        boolean requiresRotation = (degrees > 0);
        if (requiresRotation) {
            // Get the dimensions of the device in its native orientation
            mDisplayMatrix.reset();
            mDisplayMatrix.preRotate(-degrees);
            mDisplayMatrix.mapPoints(dims);
            dims[0] = Math.abs(dims[0]);
            dims[1] = Math.abs(dims[1]);
        }

        // Take the screenshot  截屏
        mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
        if (mScreenBitmap == null) {    // 截屏失敗
            notifyScreenshotError(mContext, mNotificationManager,
                    R.string.screenshot_failed_to_capture_text);
            finisher.run();
            return;
        }

        if (requiresRotation) {
            // Rotate the screenshot to the current orientation
            Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
                    mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
            Canvas c = new Canvas(ss);
            c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
            c.rotate(degrees);
            c.translate(-dims[0] / 2, -dims[1] / 2);
            c.drawBitmap(mScreenBitmap, 0, 0, null);
            c.setBitmap(null);
            // Recycle the previous bitmap
            mScreenBitmap.recycle();
            mScreenBitmap = ss;
        }

        if (width != mDisplayMetrics.widthPixels || height != mDisplayMetrics.heightPixels) {
            // Crop the screenshot to selected region
            Bitmap cropped = Bitmap.createBitmap(mScreenBitmap, x, y, width, height);
            mScreenBitmap.recycle();
            mScreenBitmap = cropped;
        }

        // Optimizations
        mScreenBitmap.setHasAlpha(false);
        mScreenBitmap.prepareToDraw();

        // Start the post-screenshot animation
        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
                statusBarVisible, navBarVisible);
    }

這裏爲調用截屏native函數執行截屏mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);返回當前屏幕的mScreenBitmap圖像.截屏結束,調用startAnimation()播放截屏的動畫.

    /**
     * Starts the animation after taking the screenshot
     */
    private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible,
            boolean navBarVisible) {
        // Add the view for the animation
        mScreenshotView.setImageBitmap(mScreenBitmap);
        mScreenshotLayout.requestFocus();

        // Setup the animation with the screenshot just taken
        if (mScreenshotAnimation != null) {
            if (mScreenshotAnimation.isStarted()) {
                mScreenshotAnimation.end();
            }
            mScreenshotAnimation.removeAllListeners();
        }

        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
        ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
        ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
                statusBarVisible, navBarVisible);
        mScreenshotAnimation = new AnimatorSet();
        mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
        mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                // Save the screenshot once we have a bit of time now
                saveScreenshotInWorkerThread(finisher);
                mWindowManager.removeView(mScreenshotLayout);

                // Clear any references to the bitmap
                mScreenBitmap = null;
                mScreenshotView.setImageBitmap(null);
            }
        });
        mScreenshotLayout.post(new Runnable() {
            @Override
            public void run() {
                // Play the shutter sound to notify that we've taken a screenshot
                mCameraSound.play(MediaActionSound.SHUTTER_CLICK);

                mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                mScreenshotView.buildLayer();
                mScreenshotAnimation.start();
            }
        });
    }

創建一個截屏的動畫集,screenshotDropInAnim和screenshotFadeOutAnim分別表示截屏進入和退出動畫.同時添加動畫結束的監聽.動畫結束,調用saveScreenshotInWorkerThread()保存截圖

    /**
     * Creates a new worker thread and saves the screenshot to the media store.
     */
    private void saveScreenshotInWorkerThread(Runnable finisher) {
        SaveImageInBackgroundData data = new SaveImageInBackgroundData();
        data.context = mContext;
        data.image = mScreenBitmap; 
        data.iconSize = mNotificationIconSize;
        data.finisher = finisher;
        data.previewWidth = mPreviewWidth;
        data.previewheight = mPreviewHeight;
        if (mSaveInBgTask != null) {
            mSaveInBgTask.cancel(false);
        }
        mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager)
                .execute();
    }

這裏的finisher是TakeScreenshotService.java中的Runnable任務.用來回復PhoneWindowManager啓動截屏服務後10s延遲任務的檢測.Service中的創建一個任務SaveImageInBackgroundTask對象,然後在子線程中保存截圖

class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {

    // ... ...
    
    @Override
    protected Void doInBackground(Void... params) {
        if (isCancelled()) {
            return null;
        }

        // By default, AsyncTask sets the worker thread to have background thread priority, so bump
        // it back up so that we save a little quicker.
        Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);

        Context context = mParams.context;
        Bitmap image = mParams.image;
        Resources r = context.getResources();

        try {
            // Create screenshot directory if it doesn't exist
            mScreenshotDir.mkdirs();

            // media provider uses seconds for DATE_MODIFIED and DATE_ADDED, but milliseconds
            // for DATE_TAKEN
            long dateSeconds = mImageTime / 1000;

            // Save
            OutputStream out = new FileOutputStream(mImageFilePath);
            image.compress(Bitmap.CompressFormat.PNG, 100, out);
            out.flush();
            out.close();

            // Save the screenshot to the MediaStore
            ContentValues values = new ContentValues();
            ContentResolver resolver = context.getContentResolver();
            values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath);
            values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName);
            values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName);
            values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime);
            values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds);
            values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds);
            values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
            values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth);
            values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight);
            values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());
            Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

            // Create a share intent
            String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
            String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
            Intent sharingIntent = new Intent(Intent.ACTION_SEND);
            sharingIntent.setType("image/png");
            sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
            sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);

            // Create a share action for the notification
            PendingIntent chooseAction = PendingIntent.getBroadcast(context, 0,
                    new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
            Intent chooserIntent = Intent.createChooser(sharingIntent, null,
                    chooseAction.getIntentSender())
                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
            PendingIntent shareAction = PendingIntent.getActivity(context, 0, chooserIntent,
                    PendingIntent.FLAG_CANCEL_CURRENT);
            Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
                    R.drawable.ic_screenshot_share,
                    r.getString(com.android.internal.R.string.share), shareAction);
            mNotificationBuilder.addAction(shareActionBuilder.build());

            // Create a delete action for the notification
            PendingIntent deleteAction = PendingIntent.getBroadcast(context,  0,
                    new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
                            .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()),
                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
            Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
                    R.drawable.ic_screenshot_delete,
                    r.getString(com.android.internal.R.string.delete), deleteAction);
            mNotificationBuilder.addAction(deleteActionBuilder.build());

            mParams.imageUri = uri;
            mParams.image = null;
            mParams.errorMsgResId = 0;
        } catch (Exception e) {
            // IOException/UnsupportedOperationException may be thrown if external storage is not
            // mounted
            mParams.clearImage();
            mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
        }

        // Recycle the bitmap data
        if (image != null) {
            image.recycle();
        }

        return null;
    }
    
    @Override
    protected void onPostExecute(Void params) {
        if (mParams.errorMsgResId != 0) {
            // Show a message that we've failed to save the image to disk
            GlobalScreenshot.notifyScreenshotError(mParams.context, mNotificationManager,
                    mParams.errorMsgResId);
        } else {
            // Show the final notification to indicate screenshot saved
            Context context = mParams.context;
            Resources r = context.getResources();

            // Create the intent to show the screenshot in gallery
            Intent launchIntent = new Intent(Intent.ACTION_VIEW);
            launchIntent.setDataAndType(mParams.imageUri, "image/png");
            launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            Log.d("ansen","uri: "+mParams.imageUri);

            final long now = System.currentTimeMillis();

            // Update the text and the icon for the existing notification
            mPublicNotificationBuilder
                    .setContentTitle(r.getString(R.string.screenshot_saved_title))
                    .setContentText(r.getString(R.string.screenshot_saved_text))
                    .setContentIntent(PendingIntent.getActivity(mParams.context, 0, launchIntent, 0))
                    .setWhen(now)
                    .setAutoCancel(true)
                    .setColor(context.getColor(
                            com.android.internal.R.color.system_notification_accent_color));
            mNotificationBuilder
                .setContentTitle(r.getString(R.string.screenshot_saved_title))
                .setContentText(r.getString(R.string.screenshot_saved_text))
                .setContentIntent(PendingIntent.getActivity(mParams.context, 0, launchIntent, 0))
                .setWhen(now)
                .setAutoCancel(true)
                .setColor(context.getColor(
                        com.android.internal.R.color.system_notification_accent_color))
                .setPublicVersion(mPublicNotificationBuilder.build())
                .setFlag(Notification.FLAG_NO_CLEAR, false);

            mNotificationManager.notify(R.id.notification_screenshot, mNotificationBuilder.build());
        }
        mParams.finisher.run();     //移除PhoneWindowManager10s截屏超時任務檢測
        mParams.clearContext();
    }
    
    // ... ...
}

doInBackground中保存截圖文件,同時寫入多媒體數據庫便於及時在圖庫中更新;onPostExecute()中,如果成功則發送成功的通知.

  • 4.Framebuffer截屏

幀緩衝(Framebuffer)是Linux系統爲顯示設備提供的一個接口,它將顯示緩衝區抽象,屏蔽圖像硬件的底層差異,允許上層應用程序在圖形模式下直接對顯示緩衝區進行讀寫操作.

  • 5.DDMS截屏(無法在代碼中實現)

手機連接DDMS實現截屏,調用sdk/tools/lib/ddmlib.jar


三指截屏

實現三指截屏主要思路就是在觸摸事件傳遞的最上層判斷三指下滑的手勢,然後調用系統截屏功能從而實現三指截屏.

  • 在ViewGroup中實現 過濾三指截屏的手勢
    // PATH: frameworks/base/core/java/android/view/ViewGroup.java
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        // ... ...
        // Check for cancelation.
        boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        if (touchAction(ev)) {
            intercepted = true;
            canceled = true;
        }
            
        // ... ...
    }

    // ansen 170830 @{
    private static final float OFFSET_Y = 150;
    private float mFirstPointStartY;
    private float mSecondPointStartY;
    private float mThirdPointStartY;
    private boolean mEnableThreeFingerScreenshot;
    private boolean isRelease;
    private float mStartDownY;
    private boolean only3Pointer;


    private boolean touchAction(MotionEvent ev) {
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                // only check this status when receiving first down event.
                mEnableThreeFingerScreenshot = Settings.System.getInt(mContext.getContentResolver(),
                        "three_finger_screenshot", 1) == 1;
                if(mEnableThreeFingerScreenshot){
                    mStartDownY = ev.getRawY();
                }
                break;

            case MotionEvent.ACTION_POINTER_DOWN:
                if (mEnableThreeFingerScreenshot) {
                    if (ev.getPointerCount() == 3) {
                        mFirstPointStartY = ev.getY(0);
                        mSecondPointStartY = ev.getY(1);
                        mThirdPointStartY = ev.getY(2);
                        only3Pointer=true;
                    }else{
                        only3Pointer=false;
                    }
                }
                break;

            case MotionEvent.ACTION_MOVE:
                if (mEnableThreeFingerScreenshot) {
                    if (only3Pointer && ev.getPointerCount() == 3) {
                        if (((Math.abs(ev.getY(0) - mFirstPointStartY)) > OFFSET_Y)
                                && ((Math.abs(ev.getY(1) - mSecondPointStartY)) > OFFSET_Y)
                                && ((Math.abs(ev.getY(2) - mThirdPointStartY)) > OFFSET_Y)) {
                            Intent intent = new Intent("com.qucii.screenshot");
                            //Send broadcast to PhoneWindowManager to cature screen
                            getContext().sendBroadcast(intent);
                            Log.d(TAG, "Intercept the key Three finger to capture screen");
                            only3Pointer=false;
                        }
                        if((ev.getRawY()- mStartDownY)>5){
//                            MotionEvent eventClone = MotionEvent.obtain(ev);
//                            eventClone.setAction(MotionEvent.ACTION_CANCEL);
//                            dispatchTouchEvent(eventClone);
                            if(!isRelease){
                                return true;
                            }
                        }
                    }else{
                        if((Math.abs(ev.getRawY() - mStartDownY)) > OFFSET_Y){
                            isRelease =true;
                        }
                        break;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                isRelease =false;
                break;
        }

        return false;
    }
    // ansen @}
  • 監聽到截屏的指令,執行截屏邏輯
    // PATH: frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
    /** {@inheritDoc} */
    @Override
    public void init(Context context, IWindowManager windowManager,
            WindowManagerFuncs windowManagerFuncs) {
        // ... ...
        // register for multiuser-relevant broadcasts
        filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
        context.registerReceiver(mMultiuserReceiver, filter);

        filter = new IntentFilter("com.qucii.screenshot");
        context.registerReceiver(mScreenShotReceiver, filter);

        // monitor for system gestures
        // ... ...    
            
    }
    
    BroadcastReceiver mScreenShotReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {

            if ("com.qucii.screenshot".equals(intent.getAction())) {

                mHandler.post(mScreenshotRunnable); // 調用系統代碼,執行截屏邏輯
            }
        }
    };

 Tips:
    
    分屏狀態下ViewGroup中獲取不到手指按在分屏上下部分而產生的三指操作.
    SystemGesturesPointerEventListener --> onPointerEvent 獲取分屏下三指觸摸事件
    // PointerEventDispatcher --> registerInputEventListener

Android屏幕錄製源碼Demo下載

Android長截屏(滾動截屏)實現原理

發佈了78 篇原創文章 · 獲贊 157 · 訪問量 46萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章