学习笔记:代码贴的比较多,请耐心看;整个截屏流程是详细的,其他的或许就没分析了。
一般截屏都是电源键+音量减键,而这些按键的处理都是在 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.java
private 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.java
boolean 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.java
private 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.java
private 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.java
public 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.java
void 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.java
private 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.java
private 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.DraggableConstraintLayout
xmlns: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.java
private 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.java
private 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);
}
勉勉强强长截图也完成了吧,自己也还是有点不太清楚,完全没有任何资料可以参考。
网友评论