通常未通過特殊定制的 Android 系統(tǒng)叔遂,截屏都是經(jīng)過同時按住音量下鍵和電源鍵來截屏憋飞。本篇文章就只討論使用這些特殊按鍵來進行截屏霎苗。
這里我們就要明白事件是在哪里進行分發(fā)攔截的。通過源碼的分析榛做,我們發(fā)現(xiàn)是在PhoneWindowManager.java 中唁盏。
PhoneWindowManager#interceptKeyBeforeQueueing()
// frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
if (!mSystemBooted) {
// If we have not yet booted, don't let key events do anything.
return 0;
}
// 省略部分代碼......
// Handle special keys.
switch (keyCode) {
......
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
// 按下音量鍵調(diào)用
handleVolumeKey(event, policyFlags);
......
break;
}
......
case KeyEvent.KEYCODE_POWER: {
......
if (down) {
// 按下電源鍵將調(diào)用
interceptPowerKeyDown(event, interactive);
} else {
interceptPowerKeyUp(event, interactive, canceled);
}
break;
}
}
return result;
}
1、電源鍵處理
PhoneWindowManager#interceptPowerKeyDown()
// PhoneWindowManager.java
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
// 省略部分代碼......
// Latch power key state to detect screenshot chord.
if (interactive && !mScreenshotChordPowerKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
// power鍵按下的標志
mScreenshotChordPowerKeyTriggered = true;
// 獲取 Power 鍵的觸發(fā)時間
mScreenshotChordPowerKeyTime = event.getDownTime();
// 處理屏幕截圖事件
interceptScreenshotChord();
// 這個方法應該是消耗检眯、攔截事件的厘擂,避免改變音量、鈴聲等锰瘸。
interceptRingerToggleChord();
}
// 省略部分代碼......
}
interceptScreenshotChord()該方法下面再說刽严,先介紹電源按鍵、音量按鍵的處理避凝。
2舞萄、音量鍵處理
PhoneWindowManager#handleVolumeKey()
// PhoneWindowManager.java
public void handleVolumeKey(KeyEvent event, int policyFlags) {
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final int keyCode = event.getKeyCode();
final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
if (down) {
// Any activity on the vol down button stops the ringer toggle shortcut
cancelPendingRingerToggleChordAction();
if (interactive && !mScreenshotChordVolumeDownKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
// Volume鍵按下的標志
mScreenshotChordVolumeDownKeyTriggered = true;
// 獲取 Volume 鍵的觸發(fā)時間
mScreenshotChordVolumeDownKeyTime = event.getDownTime();
// 賦值 false 該屬性為了防止截屏的時候音量下鍵生效出現(xiàn)調(diào)節(jié)音量的 dialog 狀態(tài)值
mScreenshotChordVolumeDownKeyConsumed = false;
// 防止觸發(fā) Power 鍵長按功能
cancelPendingPowerKeyAction();
//處理屏幕截圖事件
interceptScreenshotChord();
// 攔截相關快捷鍵
interceptAccessibilityShortcutChord();
}
} else {
// 省略部分代碼......
}
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
// 省略部分代碼......
}
return;
}
3眨补、截屏事件處理 interceptScreenshotChord()
PhoneWindowManager#interceptScreenshotChord()
// PhoneWindowManager.java
private void interceptScreenshotChord() {
/*
* if 判斷參數(shù)介紹
* mScreenshotChordEnabled 其值為mContext.getResources().getBoolean(com.android.internal.R.bool.config_enableScreenshotChord);
* mScreenshotChordVolumeDownKeyTriggered 音量下鍵按下時值為true
* mScreenshotChordPowerKeyTriggered 電源鍵按下時值為true
* mA11yShortcutChordVolumeUpKeyTriggered 音量上(加)鍵抬起時為false , 按下時為true
**/
if (mScreenshotChordEnabled
&& mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered
&& !mA11yShortcutChordVolumeUpKeyTriggered) {
// 獲取當前時間
final long now = SystemClock.uptimeMillis();
// 當前時間小于 音量下鍵按下時間 + 150ms
// 當前時間小于 power鍵按下時間 + 150ms
if (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
&& now <= mScreenshotChordPowerKeyTime
+ SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {
boolean inLongScreenshot = Settings.System.getIntForUser(mContext.getContentResolver(),
LONGSCREENSHOT_SETTING, 0, UserHandle.USER_CURRENT_OR_SELF) == 1;
if (hasInPowerUtrlSavingMode() || inLongScreenshot) {
return;
}
// 長按音量下鍵,達到截屏條件倒脓,將該事件消費掉撑螺。
mScreenshotChordVolumeDownKeyConsumed = true;
// 防止觸發(fā) Power 鍵長按功能
cancelPendingPowerKeyAction();
// 設置截圖的類型,TAKE_SCREENSHOT_FULLSCREEN 為全屏
mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
// 截圖的方式崎弃,(例如:按鍵甘晤、三指下滑 等等)
mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_CHORD);
//執(zhí)行 mScreenshotRunnable
mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());
}
}
}
繼續(xù)查看ScreenshotRunnable,此時會一步步向下調(diào)用饲做,最終到SystemUI线婚。
// PhoneWindowManager.java
private class ScreenshotRunnable implements Runnable {
private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;
private int mScreenshotSource = SCREENSHOT_KEY_OTHER;
public void setScreenshotType(int screenshotType) {
mScreenshotType = screenshotType;
}
public void setScreenshotSource(int screenshotSource) {
mScreenshotSource = screenshotSource;
}
@Override
public void run() {
// 回調(diào)到 DisplayPolicy.java
mDefaultDisplayPolicy.takeScreenshot(mScreenshotType, mScreenshotSource);
}
}
DisplayPolicy#takeScreenshot()
// DisplayPolicy.java
// 請求截取屏幕截圖
public void takeScreenshot(int screenshotType, int source) {
if (mScreenshotHelper != null) {
mScreenshotHelper.takeScreenshot(screenshotType,
mStatusBar != null && mStatusBar.isVisibleLw(),
mNavigationBar != null && mNavigationBar.isVisibleLw(),
source, mHandler, null /* completionConsumer */);
}
}
繼續(xù)往下看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調(diào)用環(huán)節(jié), 此為客戶端, 服務端為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();
}
}
}
}
};
// bindService
if (mContext.bindServiceAsUser(serviceIntent, conn,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
UserHandle.CURRENT)) {
mScreenshotConnection = conn;
handler.postDelayed(mScreenshotTimeout, timeoutMs);
}
} else {
// 如果已經(jīng)連接則直接發(fā)送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);
}
}
}
客戶端通過向服務端發(fā)送 message 來將截屏任務交給 service,由 service 處理后面的操作盆均。
// TakeScreenshotService.java
private Handler mHandler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
// 獲取客戶端傳的 Messenger 對象
final Messenger callback = msg.replyTo;
Consumer<Uri> uriConsumer = uri -> {
Message reply = Message.obtain(null, SCREENSHOT_MSG_URI, uri);
try {
/ /Messenger 雙向通信塞弊,在服務端用遠程客戶端的 Messenger 對象給客戶端發(fā)送信息
callback.send(reply);
} catch (RemoteException e) {
}
};
Runnable onComplete = () -> {
Message reply = Message.obtain(null, SCREENSHOT_MSG_PROCESS_COMPLETE);
try {
callback.send(reply);
} catch (RemoteException e) {
}
};
// 判斷用戶的設備是否為解鎖狀態(tài)
// 如果用戶的存儲被鎖定,我們沒有地方存儲截圖缀踪,所以跳過它,而不是顯示一個誤導性的動畫和錯誤通知虹脯。
if (!mUserManager.isUserUnlocked()) {
Log.w(TAG, "Skipping screenshot because storage is locked!");
post(() -> uriConsumer.accept(null));
post(onComplete);
return;
}
ScreenshotHelper.ScreenshotRequest screenshotRequest =
(ScreenshotHelper.ScreenshotRequest) msg.obj;
mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()));
switch (msg.what) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: // 全屏截圖
// 我們在PhoneWindowManager傳入的type為全屏截圖,所以需要執(zhí)行全屏截圖流程
mScreenshot.takeScreenshot(uriConsumer, onComplete);
break;
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: // 區(qū)域截圖
mScreenshot.takeScreenshot(uriConsumer, onComplete);
break;
case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
Bitmap screenshot = BitmapUtil.bundleToHardwareBitmap(
screenshotRequest.getBitmapBundle());
Rect screenBounds = screenshotRequest.getBoundsInScreen();
Insets insets = screenshotRequest.getInsets();
int taskId = screenshotRequest.getTaskId();
int userId = screenshotRequest.getUserId();
ComponentName topComponent = screenshotRequest.getTopComponent();
mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
taskId, userId, topComponent, uriConsumer, onComplete);
break;
default:
Log.d(TAG, "Invalid screenshot option: " + msg.what);
}
}
};
TakeScreenshotService調(diào)用GlobalScreenshot.java的takeScreenshot()驴娃;
GlobalScreenshot#takeScreenshot()
// GlobalScreenshot.java
/**
*截取當前顯示的屏幕截圖并顯示動畫。.
*/
private void takeScreenshot(Consumer<Uri> finisher, Rect crop) {
// copy the input Rect, since SurfaceControl.screenshot can mutate it
Rect screenRect = new Rect(crop);
int rot = mDisplay.getRotation();
int width = crop.width();
int height = crop.height();
takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect,
Insets.NONE, true);
}
private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
Insets screenInsets, boolean showFlash) {
// 此方法會清除上一次的截圖信息--連續(xù)截圖行為
dismissScreenshot("new screenshot requested", true);
mScreenBitmap = screenshot;
if (mScreenBitmap == null) {
// 如果沒有Bitmap則報告錯誤信息
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
finisher.accept(null);
mOnCompleteRunnable.run();
return;
}
if (!isUserSetupComplete()) {
// 用戶設置尚未完成,不應該向用戶展示 分享和編輯 , 只顯示一個Toast并保存圖片
saveScreenshotAndToast(finisher);
return;
}
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
onConfigChanged(mContext.getResources().getConfiguration());
if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
mDismissAnimation.cancel();
}
// 獲取焦點
setWindowFocusable(true);
// 開始截圖后動畫
startAnimation(finisher, screenRect, screenInsets, showFlash);
}
/**
* 截屏后開始動畫
*/
private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets,
boolean showFlash) {
if (mScreenshotIng == false) {//unisoc: Modify for bug1360276
mScreenshotIng = true;
// 如果開啟了省電模式循集,顯示 toast唇敞,以便有一些視覺提示已截取屏幕截圖
PowerManager powerManager =(PowerManager) mContext . getSystemService (Context.POWER_SERVICE);
if (powerManager.isPowerSaveMode()) {
Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
}
mScreenshotHandler.post(() -> {
if (!mScreenshotLayout.isAttachedToWindow()) {
// mScreenshotLayout是截屏的縮略圖的父View
// mScreenshotLayout 在 GlobalScreenshot.java 的構(gòu)造方法中初始化。對應布局文件:global_screenshot.xml
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
}
// 動畫相關的View
mScreenshotAnimatedView.setImageDrawable(
createScreenDrawable(mScreenBitmap, screenInsets));
setAnimatedViewSize(screenRect.width(), screenRect.height());
// 顯示動畫何時開始
mScreenshotAnimatedView.setVisibility(View.GONE);
//縮略圖顯示的View,將native層返回的Bitmap加載到此View上
mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets));
// 使靜態(tài)預覽不可見(消失)咒彤,以便我們可以在屏幕上查詢其位置
mScreenshotPreview.setVisibility(View.INVISIBLE);
mScreenshotHandler.post(() -> {
mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
// 創(chuàng)建動畫
mScreenshotAnimation =
createScreenshotDropInAnimation(screenRect, showFlash);
// 保存截圖
saveScreenshotInWorkerThread(finisher, new ActionsReadyListener () {
@Override
void onActionsReady (SavedImageData imageData) {
showUiOnActionsReady(imageData);
mScreenshotIng = false;
}
});
// 播放快門聲音以通知我們已截屏
mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
if (mScreenshotPreview.isAttachedToWindow()) {
mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mScreenshotPreview.buildLayer();
}
// 開始執(zhí)行動畫
mScreenshotAnimation.start();
});
});
}
}
/**
* 創(chuàng)建一個新的工作線程并將屏幕截圖保存到媒體存儲
*/
private void saveScreenshotInWorkerThread(
Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) {
SaveImageInBackgroundData data = new SaveImageInBackgroundData();
data.image = mScreenBitmap; // native 層返回的 Bitmap
data.finisher = finisher;
data.mActionsReadyListener = actionsReadyListener;
if (mSaveInBgTask != null) {
// just log success/failure for the pre-existing screenshot
// 只需記錄預先存在的屏幕截圖的成功失敗
mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() {
@Override
void onActionsReady(SavedImageData imageData) {
logSuccessOnActionsReady(imageData);
}
});
}
// 截圖的一些信息存儲在 SaveImageInBackgroundTask 中構(gòu)建
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data);
mSaveInBgTask.execute();
}
到此截屏流程完畢,可以查看下截圖的View的xml文件:global_screenshot.xml
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/global_screenshot_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/global_screenshot_actions_background"
android:layout_height="@dimen/screenshot_bg_protection_height"
android:layout_width="match_parent"
android:layout_gravity="bottom"
android:alpha="0.0"
android:src="@drawable/screenshot_actions_background_protection"/>
<!--截屏動畫相關的View -->
<ImageView
android:id="@+id/global_screenshot_animated_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|start"
android:visibility="gone"
android:elevation="@dimen/screenshot_preview_elevation"
android:background="@drawable/screenshot_rounded_corners" />
<ImageView
android:id="@+id/global_screenshot_flash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:elevation="@dimen/screenshot_preview_elevation"
android:src="@android:color/white"/>
<com.android.systemui.screenshot.ScreenshotSelectorView
android:id="@+id/global_screenshot_selector"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:pointerIcon="crosshair"/>
<!-- 此處包含了一個layout, 而縮略圖的View就在此layout中疆柔,
截屏右上角的關閉縮略圖按鈕 也在此layout中 -->
<include layout="@layout/global_screenshot_static"/>
</FrameLayout>