Android 10.0截屏流程詳解
引言
這里我們就要明白事件是在哪里進行分發(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(); // 這個方法應(yīng)該是消耗、攔截事件的,避免改變音量、鈴聲等。 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(); // 攔截相關(guān)快捷鍵 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(); // 設(shè)置截圖的類型,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é), 此為客戶端, 服務(wù)端為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) { // 在獲取屏幕截圖捕獲響應(yīng)之前超時 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); } } }
客戶端通過向服務(wù)端發(fā)送 message 來將截屏任務(wù)交給 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 雙向通信,在服務(wù)端用遠程客戶端的 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) { } }; // 判斷用戶的設(shè)備是否為解鎖狀態(tài) // 如果用戶的存儲被鎖定,我們沒有地方存儲截圖,所以跳過它,而不是顯示一個誤導(dǎo)性的動畫和錯誤通知。 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()) { // 用戶設(shè)置尚未完成,不應(yīng)該向用戶展示 分享和編輯 , 只顯示一個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)造方法中初始化。對應(yīng)布局文件:global_screenshot.xml mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); } // 動畫相關(guān)的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)預(yù)覽不可見(消失),以便我們可以在屏幕上查詢其位置 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 // 只需記錄預(yù)先存在的屏幕截圖的成功失敗 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"/> <!--截屏動畫相關(guān)的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中, 截屏右上角的關(guān)閉縮略圖按鈕 也在此layout中 --> <include layout="@layout/global_screenshot_static"/> </FrameLayout>
以上就是Android 10.0截屏流程詳解的詳細內(nèi)容,更多關(guān)于Android10 截屏流程的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android中使用BitmapShader類來制作各種圖片的圓角
這篇文章主要介紹了Android中使用BitmapShader類來制作各種圖片的圓角的方法,文中隨教程講解帶出的例子可以輕松控制圖片圓形的變換,很好很強大,需要的朋友可以參考下2016-04-04Android?獲取實時網(wǎng)速實現(xiàn)詳解
這篇文章主要為大家介紹了Android?獲取實時網(wǎng)速實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11Android使用GridView實現(xiàn)日歷的方法
本篇文章主要介紹了Android使用GridView實現(xiàn)日歷的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08