前言

代码贴的比较多,请耐心看;整个截屏流程是详细的,其他的或许就没分析了。

一般截屏都是电源键+音量减键,而这些按键的处理都是在 PhoneWindowManager 中进行的,但在该类中有两个主要处理按键的方法:

  • interceptKeyBeforeQueueing():主要处理音量键、电源键(Power键)、耳机键等。
  • interceptKeyBeforeDispatching():处理一般性的按键和动作。

参数含义:

  • interactive:是否亮屏
  • KeyEvent.FLAG_FALLBACK:不被应用处理的按键事件或一些在键值映射中不被处理的事件(例:轨迹球事件等).

这里我们直接看 PhoneWindowManager#interceptKeyBeforeQueueing() 方法:

// PhoneWindowManager.java@Override    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {       final int keyCode = event.getKeyCode();       final boolean down = event.getAction() == KeyEvent.ACTION\_DOWN;      boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG\_WAKE) != 0               || event.isWakeKey();       if (!mSystemBooted) {           // 省略部分代码......           return 0;       }      // 省略部分代码......       // This could prevent some wrong state in multi-displays environment,       // the default display may turned off but interactive is true.        final boolean isDefaultDisplayOn = Display.isOnState(mDefaultDisplay.getState());      final boolean interactiveAndOn = interactive && isDefaultDisplayOn;       if ((event.getFlags() & KeyEvent.FLAG\_FALLBACK) == 0) {            // 这里面有·组合键处理,Android 13 与之前版本不一样            // 在Android 13 有专门的组合键处理,可自行添加规则(即:组合键)           handleKeyGesture(event, interactiveAndOn);     }       // 省略部分代码......       switch (keyCode) {         // 省略部分代码......       return result;   }

上述代码里说的组合键添加,在 initKeyCombinationRules() 方法中,并在 PhoneWindowManager的 init() 方法中初始化。关于 initKeyCombinationRules() 方法,下文会有讲述。

下面接着看 PhoneWindowManager#handleKeyGesture() 方法:

// PhoneWindowManager.javaprivate void handleKeyGesture(KeyEvent event, boolean interactive) {        // 在 if 判断中,调用组合键判断;      // 在将键事件发送到窗口之前,检查键事件是否可以被组合键规则拦截。       // 如果键事件可以触发任何活动规则,则返回 true,否则返回 false。       if (mKeyCombinationManager.interceptKey(event, interactive)) {            // handled by combo keys manager.            mSingleKeyGestureDetector.reset();          return;        }       if (event.getKeyCode() == KEYCODE\_POWER && event.getAction() == KeyEvent.ACTION\_DOWN) {           // 触发KEYCODE\_POWER 和 ACTION\_DOWN事件            mPowerKeyHandled = handleCameraGesture(event, interactive);            if (mPowerKeyHandled) {               // handled by camera gesture.                mSingleKeyGestureDetector.reset();              return;            }      }        mSingleKeyGestureDetector.interceptKey(event, interactive);  }

这里我们主要看 mKeyCombinationManager.interceptKey(event, interactive) 方法就行了;

KeyCombinationManager#interceptKey():

// KeyCombinationManager.javaboolean interceptKey(KeyEvent event, boolean interactive) {        synchronized (mLock) {          return interceptKeyLocked(event, interactive);      }   }   private boolean interceptKeyLocked(KeyEvent event, boolean interactive) {      final boolean down = event.getAction() == KeyEvent.ACTION\_DOWN;      final int keyCode = event.getKeyCode();     final int count = mActiveRules.size();      final long eventTime = event.getEventTime();     if (interactive && down) {          // 省略部分代码......           if (mDownTimes.size() == 1) {              // 省略部分代码......          } else {               // 如果规则已经触发则忽略.              if (mTriggeredRule != null) {                  return true;             }              // 发送给客户端之前的过度延迟。                forAllActiveRules((rule) -> {                  if (!rule.shouldInterceptKeys(mDownTimes)) {                       return false; }                 Log.v(TAG, "Performing combination rule : " + rule);                   // 主要是这个方法,会执行 execute() 方法,                   // 该方法是一个 抽象方法,会在添加组合键规则的地方实现;                    mHandler.post(rule::execute);                    mTriggeredRule = rule;                  return true;              });               mActiveRules.clear();             if (mTriggeredRule != null) {                    mActiveRules.add(mTriggeredRule);                 return true;              }         }      } else {             // 省略部分代码......      }       return false;  }

这里我们看下组合键添加,及触发回调。

initKeyCombinationRules()

// PhoneWindowManager.javaprivate void initKeyCombinationRules() {        mKeyCombinationManager = new KeyCombinationManager(mHandler);     final boolean screenshotChordEnabled = mContext.getResources().getBoolean(                com.android.internal.R.bool.config\_enableScreenshotChord);      if (screenshotChordEnabled) {            mKeyCombinationManager.addRule(                  // 截屏组合键的添加                    new TwoKeysCombinationRule(KEYCODE\_VOLUME\_DOWN, KEYCODE\_POWER) {                        // 触发组合键后回调                        @Override                        void execute() {                            mPowerKeyHandled = true;                         // 发消息准备屏幕截图                            interceptScreenshotChord(TAKE\_SCREENSHOT\_FULLSCREEN,                                    SCREENSHOT\_KEY\_CHORD, getScreenshotChordLongPressDelay());                       }                       // 取消                        @Override                        void cancel() {                           cancelPendingScreenshotChordAction();                     }            });   }     // 省略部分代码......  }

上面通过 handle 发了一个消息,将会调用 handleScreenShot() 方法,处理截屏:

PhoneWindowManager# handleScreenShot()

// PhoneWindowManager.javaprivate void handleScreenShot(@WindowManager.ScreenshotType int type,            @WindowManager.ScreenshotSource int source) {      // 回调到 DisplayPolicy.java        mDefaultDisplayPolicy.takeScreenshot(type, source);    }

DisplayPolicy#takeScreenshot()

// DisplayPolicy.java// 请求截取屏幕截图    public void takeScreenshot(int screenshotType, int source) {       if (mScreenshotHelper != null) {     mScreenshotHelper.takeScreenshot(screenshotType,    getStatusBar() != null && getStatusBar().isVisible(),                          getNavigationBar() != null && getNavigationBar().isVisible(),  source, mHandler, null /\* completionConsumer \*/);     }   }

继续往下看 ScreenshotHelper#takeScreenshot()

// ScreenshotHelper.javapublic void takeScreenshot(final int screenshotType, final boolean hasStatus,            final boolean hasNav, int source, @NonNull Handler handler,            @Nullable Consumer<Uri> completionConsumer) {        // 截图请求        ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, hasStatus, hasNav);        takeScreenshot(screenshotType, SCREENSHOT\_TIMEOUT\_MS, handler, screenshotRequest,                completionConsumer);    }    //到了 Binder调用环节, 此为客户端, 服务端为SystemUI中的 TakeScreenshotService    private void takeScreenshot(final int screenshotType, long timeoutMs, @NonNull Handler handler,                ScreenshotRequest screenshotRequest, @Nullable Consumer<Uri> completionConsumer) {            synchronized (mScreenshotLock) {                final Runnable mScreenshotTimeout = () -> {                    synchronized (mScreenshotLock) {                        if (mScreenshotConnection != null) {                            // 在获取屏幕截图捕获响应之前超时                            Log.e(TAG, "Timed out before getting screenshot capture response");                            // 重置连接                            resetConnection();                            // 通知截屏错误                            notifyScreenshotError();                        }                    }                    if (completionConsumer != null) {                        completionConsumer.accept(null);                    }                };                Message msg = Message.obtain(null, screenshotType, screenshotRequest);                Handler h = new Handler(handler.getLooper()) {                    @Override                    public void handleMessage(Message msg) {                        switch (msg.what) {                            case SCREENSHOT\_MSG\_URI:                                if (completionConsumer != null) {                                    completionConsumer.accept((Uri) msg.obj);                                }                                handler.removeCallbacks(mScreenshotTimeout);                                break;                            case SCREENSHOT\_MSG\_PROCESS\_COMPLETE:                                synchronized (mScreenshotLock) {                                    resetConnection();                                }                                break;                        }                    }                };                msg.replyTo = new Messenger(h);                if (mScreenshotConnection == null || mScreenshotService == null) {                    // 一个标准的Service连接                    // config\_screenshotServiceComponent == com.android.systemui/com.android.systemui.screenshot.TakeScreenshotService                    final ComponentName serviceComponent = ComponentName.unflattenFromString(                            mContext.getResources().getString(                                    com.android.internal.R.string.config\_screenshotServiceComponent));                    final Intent serviceIntent = new Intent();                    serviceIntent.setComponent(serviceComponent);                    ServiceConnection conn = new ServiceConnection() {                        @Override                        // 当Service连接成功之后                        public void onServiceConnected(ComponentName name, IBinder service) {                            synchronized (mScreenshotLock) {                                if (mScreenshotConnection != this) {                                    return;                                }                                mScreenshotService = service;                                Messenger messenger = new Messenger(mScreenshotService);                                try {                                    // 进程通信,发送请求截图消息                                    messenger.send(msg);                                } catch (RemoteException e) {                                    Log.e(TAG, "Couldn't take screenshot: " + e);                                    if (completionConsumer != null) {                                        completionConsumer.accept(null);                                    }                                }                            }                        }                        @Override                        // 当Service断开连接时                        public void onServiceDisconnected(ComponentName name) {                            synchronized (mScreenshotLock) {                                if (mScreenshotConnection != null) {                                    resetConnection();                                    // only log an error if we're still within the timeout period                                    if (handler.hasCallbacks(mScreenshotTimeout)) {                                        handler.removeCallbacks(mScreenshotTimeout);                                        notifyScreenshotError();                                    }                                }                            }                        }                    };                    // 绑定服务 TakeScreenshotService;                    // 绑定成功为true,不成功则发绑定超时消息                    if (mContext.bindServiceAsUser(serviceIntent, conn,                            Context.BIND\_AUTO\_CREATE | Context.BIND\_FOREGROUND\_SERVICE,                            UserHandle.CURRENT)) {                        mScreenshotConnection = conn;                        handler.postDelayed(mScreenshotTimeout, timeoutMs);                    }                } else {                    // 如果已经连接则直接发送Message                    Messenger messenger = new Messenger(mScreenshotService);                    try {                        messenger.send(msg);                    } catch (RemoteException e) {                        Log.e(TAG, "Couldn't take screenshot: " + e);                        if (completionConsumer != null) {                            completionConsumer.accept(null);                        }                    }                    handler.postDelayed(mScreenshotTimeout, timeoutMs);                }            }    }

客户端通过向服务端发送 message 来将截屏任务交给 service,由 service 处理后面的操作。

// TakeScreenshotService.java// 通过 Binder (Messenger) 响应传入消息    @MainThread    private boolean handleMessage(Message msg) {        // 获取客户端传的 Messenger 对象        final Messenger replyTo = msg.replyTo;        // reportUri(replyTo, uri)  方法,Messenger 双向通信,        // 在服务端用远程客户端的 Messenger 对象给客户端发送信息        final Consumer<Uri> uriConsumer = (uri) -> reportUri(replyTo, uri);        RequestCallback requestCallback = new RequestCallbackImpl(replyTo);        // 如果此用户的存储空间被锁定,我们就没有地方可以存储屏幕截图,        // 因此请跳过截屏,而不是显示误导性的动画和错误通知。        if (!mUserManager.isUserUnlocked()) {            mNotificationsController.notifyScreenshotError(                    R.string.screenshot\_failed\_to\_save\_user\_locked\_text);            requestCallback.reportError();            return true;        }        if (mDevicePolicyManager.getScreenCaptureDisabled(null, UserHandle.USER\_ALL)) {            mBgExecutor.execute(() -> {                // 跳过屏幕截图,因为 IT 管理员已禁用设备上的“+”屏幕截图                String blockedByAdminText = mDevicePolicyManager.getResources().getString(                        SCREENSHOT\_BLOCKED\_BY\_ADMIN,                        () -> mContext.getString(R.string.screenshot\_blocked\_by\_admin));                mHandler.post(() ->                        Toast.makeText(mContext, blockedByAdminText, Toast.LENGTH\_SHORT).show());                requestCallback.reportError();            });            return true;        }        ScreenshotHelper.ScreenshotRequest screenshotRequest =                (ScreenshotHelper.ScreenshotRequest) msg.obj;        ComponentName topComponent = screenshotRequest.getTopComponent();        mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()), 0,                topComponent == null ? "" : topComponent.getPackageName());        switch (msg.what) {            case WindowManager.TAKE\_SCREENSHOT\_FULLSCREEN:                // 全屏截图                mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, requestCallback);                break;            case WindowManager.TAKE\_SCREENSHOT\_SELECTED\_REGION:                // 截取所选区域                mScreenshot.takeScreenshotPartial(topComponent, uriConsumer, requestCallback);                break;            case WindowManager.TAKE\_SCREENSHOT\_PROVIDED\_IMAGE:                // 截取提供的图像                Bitmap screenshot = ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap(                        screenshotRequest.getBitmapBundle());                Rect screenBounds = screenshotRequest.getBoundsInScreen();                Insets insets = screenshotRequest.getInsets();                int taskId = screenshotRequest.getTaskId();                int userId = screenshotRequest.getUserId();                if (screenshot == null) {                    // 从屏幕截图消息中获得空位图                    mNotificationsController.notifyScreenshotError(                            R.string.screenshot\_failed\_to\_capture\_text);                    requestCallback.reportError();                } else {                    mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,                            taskId, userId, topComponent, uriConsumer, requestCallback);                }                break;            default:                // 无效的屏幕截图选项                Log.w(TAG, "Invalid screenshot option: " + msg.what);                return false;        }        return true;    }

TakeScreenshotService 调用 ScreenshotController.java 的 takeScreenshotFullscreen();

ScreenshotController#takeScreenshotFullscreen()

// ScreenshotController.javavoid takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher,            RequestCallback requestCallback) {        // 断言,是主线程则继续执行,不是则抛出异常。        Assert.isMainThread();        mCurrentRequestCallback = requestCallback;        DisplayMetrics displayMetrics = new DisplayMetrics();        getDefaultDisplay().getRealMetrics(displayMetrics);        takeScreenshotInternal(                topComponent, finisher,                new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));    }    // 获取当前显示的屏幕截图并显示动画。    private void takeScreenshotInternal(ComponentName topComponent, Consumer<Uri> finisher,            Rect crop) {        mScreenshotTakenInPortrait =                mContext.getResources().getConfiguration().orientation == ORIENTATION\_PORTRAIT;        // 复制输入 Rect,因为 SurfaceControl.screenshot 可以改变它        Rect screenRect = new Rect(crop);        // 截图        Bitmap screenshot = captureScreenshot(crop);        // 屏幕截图位图为空        if (screenshot == null) {            mNotificationsController.notifyScreenshotError(                    R.string.screenshot\_failed\_to\_capture\_text);            if (mCurrentRequestCallback != null) {                mCurrentRequestCallback.reportError();            }            return;        }        // 保存截图        saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true);        mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT\_ACTION),                ClipboardOverlayController.SELF\_PERMISSION);    }

如何截图的呢?这里我们看 captureScreenshot() 方法;

ScreenshotController#captureScreenshot()

// ScreenshotController.javaprivate Bitmap captureScreenshot(Rect crop) {        int width = crop.width();        int height = crop.height();        Bitmap screenshot = null;        final Display display = getDefaultDisplay();        final DisplayAddress address = display.getAddress();        if (!(address instanceof DisplayAddress.Physical)) {            Log.e(TAG, "Skipping Screenshot - Default display does not have a physical address: "                    + display);        } else {            final DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) address;            final IBinder displayToken = SurfaceControl.getPhysicalDisplayToken(                    physicalAddress.getPhysicalDisplayId());            // 捕获参数            final SurfaceControl.DisplayCaptureArgs captureArgs =                    new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)                            .setSourceCrop(crop)                            .setSize(width, height)                            .build();            // 屏幕截图硬件缓存            final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =                    SurfaceControl.captureDisplay(captureArgs);            // 截图缓存            screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();        }        return screenshot;    }

上面是捕获图片的过程,里面到底如何捕获的。这点我目前还没弄清。

接着拿到截屏的 Bitmap 后就可以进行图片保存,显示等等一些操作。

接着看 ScreenshotController#saveScreenshot()

// ScreenshotController.javaprivate void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,            Insets screenInsets, ComponentName topComponent, boolean showFlash) {        withWindowAttached(() ->                mScreenshotView.announceForAccessibility(                        mContext.getResources().getString(R.string.screenshot\_saving\_title)));        // 判断缩略图的那个窗口是否已附加上去了。        // ScreenshotView :附件窗口的布局;有:略缩图,编辑按钮、长截屏按钮等一些其他布局。        if (mScreenshotView.isAttachedToWindow()) {            // if we didn't already dismiss for another reason            if (!mScreenshotView.isDismissing()) {                mUiEventLogger.log(ScreenshotEvent.SCREENSHOT\_REENTERED, 0, mPackageName);            }            if (DEBUG\_WINDOW) {                Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "                        + "(dismissing=" + mScreenshotView.isDismissing() + ")");            }            // 视图的状态重置,例如:可见性、透明度等。            mScreenshotView.reset();        }        // 省略部分代码......        // 在工作线程中保存屏幕截图。        saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady,                this::showUiOnQuickShareActionReady);        // The window is focusable by default        setWindowFocusable(true);        // Wait until this window is attached to request because it is        // the reference used to locate the target window (below).        // 这个方法没看明白。        withWindowAttached(() -> {            // 请求滚动捕获,捕获长截屏的。            requestScrollCapture();            mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(                    new ViewRootImpl.ActivityConfigCallback() {                        @Override                        public void onConfigurationChanged(Configuration overrideConfig,                                int newDisplayId) {                                  // 省略部分代码......                        }                        @Override                        public void requestCompatCameraControl(boolean showControl,                                boolean transformationApplied,                                ICompatCameraControlCallback callback) {                                // 省略部分代码......                        }                    });        });        // 创建附加窗口        attachWindow();        // 省略部分代码......        // 设置缩略图,ScreenBitmap 为所截的图片        mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);        // 将 ScreenshotView 添加到附加窗口        setContentView(mScreenshotView);        // 省略部分代码......    }

截屏布局 screenshot_static.xml

<com.android.systemui.screenshot.DraggableConstraintLayoutxmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:layout\_width="match\_parent"    android:layout\_height="match\_parent">    <ImageView        android:id="@+id/actions\_container\_background"        android:visibility="gone"        android:layout\_height="0dp"        android:layout\_width="0dp"        android:elevation="4dp"        android:background="@drawable/action\_chip\_container\_background"        android:layout\_marginStart="@dimen/overlay\_action\_container\_margin\_horizontal"        app:layout\_constraintBottom\_toBottomOf="@+id/actions\_container"        app:layout\_constraintStart\_toStartOf="parent"        app:layout\_constraintTop\_toTopOf="@+id/actions\_container"        app:layout\_constraintEnd\_toEndOf="@+id/actions\_container"/>    <!-- 缩略图下方的几个按钮 -->    <HorizontalScrollView        android:id="@+id/actions\_container"        android:layout\_width="0dp"        android:layout\_height="wrap\_content"        android:layout\_marginEnd="@dimen/overlay\_action\_container\_margin\_horizontal"        android:layout\_marginBottom="4dp"        android:paddingEnd="@dimen/overlay\_action\_container\_padding\_right"        android:paddingVertical="@dimen/overlay\_action\_container\_padding\_vertical"        android:elevation="4dp"        android:scrollbars="none"        app:layout\_constraintHorizontal\_bias="0"        app:layout\_constraintWidth\_percent="1.0"        app:layout\_constraintWidth\_max="wrap"        app:layout\_constraintBottom\_toBottomOf="parent"        app:layout\_constraintStart\_toEndOf="@+id/screenshot\_preview\_border"        app:layout\_constraintEnd\_toEndOf="parent">        <LinearLayout            android:id="@+id/screenshot\_actions"            android:layout\_width="wrap\_content"            android:layout\_height="wrap\_content">            <include layout="@layout/overlay\_action\_chip"                     android:id="@+id/screenshot\_share\_chip"/>            <include layout="@layout/overlay\_action\_chip"                     android:id="@+id/screenshot\_edit\_chip"/>            <include layout="@layout/overlay\_action\_chip"                     android:id="@+id/screenshot\_scroll\_chip"                     android:visibility="gone" />        </LinearLayout>    </HorizontalScrollView>    <!-- 缩略图边框,使用 android:elevation="7dp" 属性,确定哪个覆盖在哪个上面,值大的布局显示在上方 -->    <View        android:id="@+id/screenshot\_preview\_border"        android:layout\_width="0dp"        android:layout\_height="0dp"        android:layout\_marginStart="@dimen/overlay\_offset\_x"        android:layout\_marginBottom="12dp"        android:elevation="7dp"        android:alpha="0"        android:background="@drawable/overlay\_border"        app:layout\_constraintStart\_toStartOf="parent"        app:layout\_constraintBottom\_toBottomOf="parent"        app:layout\_constraintEnd\_toEndOf="@id/screenshot\_preview\_end"        app:layout\_constraintTop\_toTopOf="@id/screenshot\_preview\_top"/>    <!-- constraintlayout 这种布局方式的,一个属性。 -->    <androidx.constraintlayout.widget.Barrier        android:id="@+id/screenshot\_preview\_end"        android:layout\_width="wrap\_content"        android:layout\_height="wrap\_content"        app:barrierMargin="@dimen/overlay\_border\_width"        app:barrierDirection="end"        app:constraint\_referenced\_ids="screenshot\_preview"/>    <androidx.constraintlayout.widget.Barrier        android:id="@+id/screenshot\_preview\_top"        android:layout\_width="wrap\_content"        android:layout\_height="wrap\_content"        app:barrierDirection="top"        app:barrierMargin="@dimen/overlay\_border\_width\_neg"        app:constraint\_referenced\_ids="screenshot\_preview"/>    <!-- 缩略图 -->    <ImageView        android:id="@+id/screenshot\_preview"        android:visibility="invisible"        android:layout\_width="@dimen/overlay\_x\_scale"        android:layout\_margin="@dimen/overlay\_border\_width"        android:layout\_height="wrap\_content"        android:layout\_gravity="center"        android:elevation="7dp"        android:contentDescription="@string/screenshot\_edit\_description"        android:scaleType="fitEnd"        android:background="@drawable/overlay\_preview\_background"        android:adjustViewBounds="true"        android:clickable="true"        app:layout\_constraintBottom\_toBottomOf="@id/screenshot\_preview\_border"        app:layout\_constraintStart\_toStartOf="@id/screenshot\_preview\_border"        app:layout\_constraintEnd\_toEndOf="@id/screenshot\_preview\_border"        app:layout\_constraintTop\_toTopOf="@id/screenshot\_preview\_border">    </ImageView>    <!--add by jingtao.guo TFBAAA-2325 添加截图"X"图标-->    <FrameLayout        android:id="@+id/screenshot\_dismiss\_button"        android:layout\_width="@dimen/overlay\_dismiss\_button\_tappable\_size"        android:layout\_height="@dimen/overlay\_dismiss\_button\_tappable\_size"        android:elevation="10dp"        app:layout\_constraintStart\_toEndOf="@id/screenshot\_preview"        app:layout\_constraintEnd\_toEndOf="@id/screenshot\_preview"        app:layout\_constraintTop\_toTopOf="@id/screenshot\_preview"        app:layout\_constraintBottom\_toTopOf="@id/screenshot\_preview"        android:contentDescription="@string/screenshot\_dismiss\_description">        <ImageView            android:id="@+id/screenshot\_dismiss\_image"            android:layout\_width="match\_parent"            android:layout\_height="match\_parent"            android:layout\_margin="@dimen/overlay\_dismiss\_button\_margin"            android:src="@drawable/overlay\_cancel"/>    </FrameLayout>    <ImageView        android:id="@+id/screenshot\_scrollable\_preview"        android:layout\_width="wrap\_content"        android:layout\_height="wrap\_content"        android:scaleType="matrix"        android:visibility="gone"        app:layout\_constraintStart\_toStartOf="@id/screenshot\_preview"        app:layout\_constraintTop\_toTopOf="@id/screenshot\_preview"        android:elevation="7dp"/>
</com.android.systemui.screenshot.DraggableConstraintLayout>

至此,全截屏流程就到此结束,saveScreenshotInWorkerThread() 这里不做分析。

下面分析长截屏:

在上述代码中,有讲到 requestScrollCapture(),请求滚动捕获,即长截屏。

ScreenshotController#requestScrollCapture()

// ScreenshotController.javaprivate void requestScrollCapture() {        if (!allowLongScreenshots()) {            Log.d(TAG, "Long screenshots not supported on this device");            return;        }        mScrollCaptureClient.setHostWindowToken(mWindow.getDecorView().getWindowToken());        if (mLastScrollCaptureRequest != null) {            mLastScrollCaptureRequest.cancel(true);        }        // 请求长截图捕获        final ListenableFuture<ScrollCaptureResponse> future =                mScrollCaptureClient.request(DEFAULT\_DISPLAY);        mLastScrollCaptureRequest = future;        mLastScrollCaptureRequest.addListener(() ->                onScrollCaptureResponseReady(future), mMainExecutor);    }

长截图捕获流程: mScrollCaptureClient.request() 请求捕获→mWindowManagerService.requestScrollCapture()→ViewRootImpl#requestScrollCapture()→ViewRootImpl#handleScrollCaptureRequest() 处理滚动捕获请求,拿到捕获目标。→ViewGroup#dispatchScrollCaptureSearch() 通过检查此视图,处理滚动捕获搜索请求,然后检查每个子视图。该隐藏的隐藏,设置视图偏移等等。

走完上述流程,才会继续往下执行;

接着看 ScreenshotController#onScrollCaptureResponseReady()

// ScreenshotController.javaprivate void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture) {        try {            // 上次滚动捕获响应            if (mLastScrollCaptureResponse != null) {                mLastScrollCaptureResponse.close();                mLastScrollCaptureResponse = null;            }            // 长截屏响应,这和 网络请求中,响应头类似, response 里有很多的数据。            if (responseFuture != null) {                if (responseFuture.isCancelled()) {                    return;                }                // 将本次滚动捕获响应 设置成 滚动捕获响应                mLastScrollCaptureResponse = responseFuture.get();            } else {                Log.e(TAG, "onScrollCaptureResponseReady responseFuture is null!");            }            if (mLastScrollCaptureResponse != null && !mLastScrollCaptureResponse.isConnected()) {                // No connection means that the target window wasn't found                // or that it cannot support scroll capture.                Log.d(TAG, "ScrollCapture: " + mLastScrollCaptureResponse.getDescription() + " \["                        + mLastScrollCaptureResponse.getWindowTitle() + "\]");                return;            }            Log.d(TAG, "ScrollCapture: connected to window \["                    + mLastScrollCaptureResponse.getWindowTitle() + "\]");            // 滚动捕获响应,这和 网络请求中,响应头类似, response 里有很多的数据。            final ScrollCaptureResponse response = mLastScrollCaptureResponse;            // 截取更多内容按钮,即长截屏按钮;这里确实奇怪:还没点击长截屏,有些数据就已经捕获好了,例如:显示范围内的窗口边界、窗口空间中滚动内容的边界、当前窗口标题等等数据。            mScreenshotView.showScrollChip(response.getPackageName(), /\* onClick \*/ () -> {                DisplayMetrics displayMetrics = new DisplayMetrics();                getDefaultDisplay().getRealMetrics(displayMetrics);                // 新的位图 Bitmap  。                Bitmap newScreenshot = captureScreenshot(                        new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));                // 设置视图,这里只是一个缩略图,和普通截图一样大;                mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,                        mScreenshotTakenInPortrait);                // delay starting scroll capture to make sure the scrim is up before the app moves                // 捕获视图。长截图会在 LongScreenshotActivity 显示。                mScreenshotView.post(() -> runBatchScrollCapture(response));            });        } catch (InterruptedException | ExecutionException e) {            Log.e(TAG, "requestScrollCapture failed", e);        }    }
private void runBatchScrollCapture(ScrollCaptureResponse response) {// Clear the reference to prevent close() in dismissScreenshot    mLastScrollCaptureResponse = null;    if (mLongScreenshotFuture != null) {        mLongScreenshotFuture.cancel(true);    }    // 通过 response 得到 LongScreen 的视图。    mLongScreenshotFuture = mScrollCaptureController.run(response);    mLongScreenshotFuture.addListener(() -> {        ScrollCaptureController.LongScreenshot longScreenshot;        try {            // 获取 longScreenshot 。            longScreenshot = mLongScreenshotFuture.get();        } catch (CancellationException e) {            Log.e(TAG, "Long screenshot cancelled");            return;        } catch (InterruptedException | ExecutionException e) {            Log.e(TAG, "Exception", e);            mScreenshotView.restoreNonScrollingUi();            return;        }        if (longScreenshot.getHeight() == 0) {            mScreenshotView.restoreNonScrollingUi();            return;        }        // 相当于数据保存,把截图数据设置进去,但这里不是存储在本地。        mLongScreenshotHolder.setLongScreenshot(longScreenshot);        mLongScreenshotHolder.setTransitionDestinationCallback(                (transitionDestination, onTransitionEnd) ->                        mScreenshotView.startLongScreenshotTransition(                                transitionDestination, onTransitionEnd,                                longScreenshot));        final Intent intent = new Intent(mContext, LongScreenshotActivity.class);        intent.setFlags(                Intent.FLAG\_ACTIVITY\_NEW\_TASK | Intent.FLAG\_ACTIVITY\_CLEAR\_TOP);        // 跳转到编辑界面,也可以叫预览界面吧。        mContext.startActivity(intent,                ActivityOptions.makeCustomAnimation(mContext, 0, 0).toBundle());        RemoteAnimationAdapter runner = new RemoteAnimationAdapter(                SCREENSHOT\_REMOTE\_RUNNER, 0, 0);        try {            WindowManagerGlobal.getWindowManagerService()                    .overridePendingAppTransitionRemote(runner, DEFAULT\_DISPLAY);        } catch (Exception e) {            Log.e(TAG, "Error overriding screenshot app transition", e);        }    }, mMainExecutor);
}

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集


二、源码解析合集

三、开源框架合集

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓

Android 13 截屏流程相关推荐

  1. Android实现截屏方式

    本文介绍了Android 实现截屏方式整理,分享给大家.希望对大家有帮助 可能的需求: 截自己的屏 截所有的屏 带导航栏截屏 不带导航栏截屏 截屏并编辑选取一部分 自动截取某个空间或者布局 截取长图 ...

  2. 截屏流程 - 安卓R

    截屏类型有三种,分别是全屏截屏.区域截屏和使用提供的图片作为截屏,定义在frameworks/base/core/java/android/view/WindowManager.java中: /*** ...

  3. Android系统截屏的实现(附代码)

    1.背景 写博客快两年了,写了100+的文章,最火的文章也是大家最关注的就是如何实现android系统截屏.其实我们google android_screen_shot就会找到很对办法,但那些都是很多 ...

  4. android 截长图 方法,Android实现截屏与截长图功能

    本文实例为大家分享了Android实现截屏与截长图功能展示的具体代码,供大家参考,具体内容如下 Demo在GitHub的地址:ScreenShoot 在Android开发中,有时候会遇到需要截屏分享到 ...

  5. android后台截屏实现(2)--screencap源码修改

    首先找到screencap类在Android源码中的位置,/442/frameworks/base/cmds/screencap/screencap.cpp. 源码如下: [cpp] view pla ...

  6. android长截屏代码,android长截屏原理及实现代码

    android长截屏原理及实现代码 发布时间:2020-08-31 06:55:16 来源:脚本之家 阅读:158 作者:Android笔记 小米系统自带的长截屏应该很多人都用过,效果不错.当长截屏时 ...

  7. Android手机截屏

    对于android手机截屏,据我所知,现在主要有三种方法. 第一种,通过DDMS. 把手机连接上电脑,运行DDMS,选中你的手机设备,然后点击菜单"设备"->"Sc ...

  8. android关于截屏,关于android截屏知识的学习

    最近要做手机截取当前屏幕的开发,发了大半天时间在网上找了很多资料,终于有了一个大概的头绪和思路,若有问题望指点,谢谢! 目前而言个人了解android有三种截屏方法: 1.android SDK提供的 ...

  9. android自动截图实现,Android实现截屏功能

    原标题:Android实现截屏功能 该方法主要利用SDK提供的view.getDrawingCache()方法,主要步骤如下: 设置view.setDrawingCacheEnabled(true) ...

最新文章

  1. java使用HttpClient传输json格式的参数
  2. 中山大学2016年硕士研究生入学考试复试基本分数线
  3. Rsync + Sersync 实现数据增量同步
  4. FW : 一只小青蛙的一生(图片连载)
  5. C语言 模拟实现 strlen strcat strcpy函数
  6. SVN clean失败解决方法
  7. C++---vector与list之间的区别
  8. atitit.js的 字符串内容 转义  js处理html
  9. 11种刷新按钮的方法
  10. 亲密关系沟通-【情感勒索】建立良性沟通
  11. web安全设置(含IIS,php,ASP.NET)与目录权限设置
  12. 锐起2540无盘教程
  13. Linux基础——脚本
  14. 丁磊推荐《你的灯亮着吗》为三大管理必读书
  15. Linux本地信息收集
  16. uniapp路线规划
  17. idea激活到2100年
  18. SSH超市进销存管理系统
  19. Android Provision源码分析
  20. 如何 修改 系统 用户名称 和登陆名称

热门文章

  1. Unity 场景资源检查工具
  2. B. Bit Flipping
  3. linux splunk,splunk linux安装
  4. 浅谈BTTZ矿物绝缘电缆的抗火灾能力
  5. 携程、飞猪厮杀升级 OTA平台爆发“补贴”大战
  6. char类型转int类型
  7. 我用计算机写文章教学设计,江西科技版 信息技术 四年级上册 第4章 用计算机写作文(教案)(2页)-原创力文档...
  8. 算法图解第十章笔记与习题(KNN算法)
  9. 运营数据分析模型—用户画像
  10. 磊科路由器后门蜜罐捕获的事件分布情况