涉及到的源碼有html
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.javajava
vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\screenshot\TakeScreenshotService.java vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\screenshot\GlobalScreenshot.javaandroid
按鍵處理都是在 PhoneWindowManager 中,真正截屏的功能實如今 GlobalScreenshot 中, PhoneWindowManager 和 systemui 經過 bind TakeScreenshotService 來實現截屏功能shell
通常未通過特殊定製的 Android 系統,截屏都是經過同時按住音量下鍵和電源鍵來截屏,後來咱們使用的一些華爲、oppo等廠商的系統你會發現能夠經過三指滑動來截屏,下一篇咱們會定製此功能,並且截屏顯示風格相似 iphone 在左下角顯示截屏縮略圖,點擊可跳轉放大查看,3s 無操做後向左自動滑動消失。api
好了,如今咱們先來理一下系統截屏的流程app
system_process D/WindowManager: interceptKeyTi keyCode=25 down=true repeatCount=0 keyguardOn=false mHomePressed=false canceled=false metaState:0 system_process D/WindowManager: interceptKeyTq keycode=25 interactive=true keyguardActive=false policyFlags=22000000 down =false canceled = false isWakeKey=false mVolumeDownKeyTriggered =true result = 1 useHapticFeedback = false isInjected = false system_process D/WindowManager: interceptKeyTi keyCode=25 down=false repeatCount=0 keyguardOn=false mHomePressed=false canceled=false metaState:0 system_process D/WindowManager: interceptKeyTq keycode=26 interactive=true keyguardActive=false policyFlags=22000000 down =false canceled = false isWakeKey=false mVolumeDownKeyTriggered =false result = 1 useHapticFeedback = false isInjected = false
上面是按下音量下鍵和電源鍵的日誌,音量下鍵對應 keyCode=25 ,電源鍵對應 keyCode=26,來看到 PhoneWindowManager 中的 interceptKeyBeforeQueueing() 方法,在此到處理按鍵操做iphone
/** {@inheritDoc} */ @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; } ..... if (DEBUG_INPUT) { Log.d(TAG, "interceptKeyTq keycode=" + keyCode + " interactive=" + interactive + " keyguardActive=" + keyguardActive + " policyFlags=" + Integer.toHexString(policyFlags)); } ..... // Handle special keys. switch (keyCode) { ....... case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_MUTE: { if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { if (down) { if (interactive && !mScreenshotChordVolumeDownKeyTriggered && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { mScreenshotChordVolumeDownKeyTriggered = true; mScreenshotChordVolumeDownKeyTime = event.getDownTime(); mScreenshotChordVolumeDownKeyConsumed = false; cancelPendingPowerKeyAction(); interceptScreenshotChord(); interceptAccessibilityShortcutChord(); } } else { mScreenshotChordVolumeDownKeyTriggered = false; cancelPendingScreenshotChordAction(); cancelPendingAccessibilityShortcutAction(); } } .... }
看到 KEYCODE_VOLUME_DOWN 中,記錄當前按下音量下鍵的時間 mScreenshotChordVolumeDownKeyTime,cancelPendingPowerKeyAction() 移除電源鍵長按消息 MSG_POWER_LONG_PRESS,來看下核心方法 interceptScreenshotChord()ide
// Time to volume and power must be pressed within this interval of each other. private static final long SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS = 150; private void interceptScreenshotChord() { if (mScreenshotChordEnabled && mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered && !mA11yShortcutChordVolumeUpKeyTriggered) { final long now = SystemClock.uptimeMillis(); if (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS && now <= mScreenshotChordPowerKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) { mScreenshotChordVolumeDownKeyConsumed = true; cancelPendingPowerKeyAction(); mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay()); } } }
只有當電源鍵按下時 mScreenshotChordPowerKeyTriggered 才爲 true, 當兩個按鍵的按下時間都大於 150 時,延時執行截屏任務 mScreenshotRunnableoop
private long getScreenshotChordLongPressDelay() { if (mKeyguardDelegate.isShowing()) { // Double the time it takes to take a screenshot from the keyguard return (long) (KEYGUARD_SCREENSHOT_CHORD_DELAY_MULTIPLIER * ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); } return ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout(); }
若當前輸入框是打開狀態,則延時時間爲輸入框關閉時間加上系統配置的按鍵超時時間,若當前輸入框沒有打開則直接是系統配置的按鍵超時處理時間post
緊接着看下 mScreenshotRunnable 都作了什麼操做
private class ScreenshotRunnable implements Runnable { private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN; public void setScreenshotType(int screenshotType) { mScreenshotType = screenshotType; } @Override public void run() { takeScreenshot(mScreenshotType); } } private final ScreenshotRunnable mScreenshotRunnable = new ScreenshotRunnable();
能夠看到在線程中調用了 takeScreenshot(),默認不設置截屏類型就是全屏,截屏類型有 TAKE_SCREENSHOT_SELECTED_REGION 選定的區域 和 TAKE_SCREENSHOT_FULLSCREEN 全屏兩種類型
// Assume this is called from the Handler thread. private void takeScreenshot(final int screenshotType) { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { return; } final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE, SYSUI_SCREENSHOT_SERVICE); final Intent serviceIntent = new Intent(); serviceIntent.setComponent(serviceComponent); ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mScreenshotLock) { if (mScreenshotConnection != this) { return; } Messenger messenger = new Messenger(service); Message msg = Message.obtain(null, screenshotType); final ServiceConnection myConn = this; Handler h = new Handler(mHandler.getLooper()) { @Override public void handleMessage(Message msg) { synchronized (mScreenshotLock) { if (mScreenshotConnection == myConn) { mContext.unbindService(mScreenshotConnection); mScreenshotConnection = null; mHandler.removeCallbacks(mScreenshotTimeout); } } } }; msg.replyTo = new Messenger(h); msg.arg1 = msg.arg2 = 0; if (mStatusBar != null && mStatusBar.isVisibleLw()) msg.arg1 = 1; if (mNavigationBar != null && mNavigationBar.isVisibleLw()) msg.arg2 = 1; try { messenger.send(msg); } catch (RemoteException e) { } } } @Override public void onServiceDisconnected(ComponentName name) { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { mContext.unbindService(mScreenshotConnection); mScreenshotConnection = null; mHandler.removeCallbacks(mScreenshotTimeout); notifyScreenshotError(); } } } }; if (mContext.bindServiceAsUser(serviceIntent, conn, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, UserHandle.CURRENT)) { mScreenshotConnection = conn; mHandler.postDelayed(mScreenshotTimeout, 10000); } } }
takeScreenshot 中經過 bind SystemUI中的 TakeScreenshotService 創建鏈接,鏈接成功後經過 Messenger 在兩個進程中傳遞消息通行,有點相似 AIDL,關於 Messenger 的介紹可參考 Android進程間通信之 messenger Messenger 主要傳遞當前的 mStatusBar 和 mNavigationBar 是否可見,再來看 TakeScreenshotService 中如何接收處理
public class TakeScreenshotService extends Service { private static final String TAG = "TakeScreenshotService"; private static GlobalScreenshot mScreenshot; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { final Messenger callback = msg.replyTo; Runnable finisher = new Runnable() { @Override public void run() { Message reply = Message.obtain(null, 1); try { callback.send(reply); } catch (RemoteException e) { } } }; // If the storage for this user is locked, we have no place to store // the screenshot, so skip taking it instead of showing a misleading // animation and error notification. if (!getSystemService(UserManager.class).isUserUnlocked()) { Log.w(TAG, "Skipping screenshot because storage is locked!"); post(finisher); return; } if (mScreenshot == null) { mScreenshot = new GlobalScreenshot(TakeScreenshotService.this); } switch (msg.what) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0); break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0); break; } } }; @Override public IBinder onBind(Intent intent) { return new Messenger(mHandler).getBinder(); } @Override public boolean onUnbind(Intent intent) { if (mScreenshot != null) mScreenshot.stopScreenshot(); return true; } }
能夠看到經過 mHandler 接收傳遞的消息,獲取截屏類型和是否要包含狀態欄、導航欄,經過建立 GlobalScreenshot 對象(真正幹活的來了),調用 takeScreenshot 執行截屏操做,繼續跟進
void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) { mDisplay.getRealMetrics(mDisplayMetrics); takeScreenshot(finisher, statusBarVisible, navBarVisible, 0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels); } /** * Takes a screenshot of the current display and shows an animation. */ void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible, int x, int y, int width, int height) { // We need to orient the screenshot correctly (and the Surface api seems to take screenshots // only in the natural orientation of the device :!) mDisplay.getRealMetrics(mDisplayMetrics); float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels}; float degrees = getDegreesForRotation(mDisplay.getRotation()); boolean requiresRotation = (degrees > 0); if (requiresRotation) { // Get the dimensions of the device in its native orientation mDisplayMatrix.reset(); mDisplayMatrix.preRotate(-degrees); mDisplayMatrix.mapPoints(dims); dims[0] = Math.abs(dims[0]); dims[1] = Math.abs(dims[1]); } // Take the screenshot mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]); if (mScreenBitmap == null) { notifyScreenshotError(mContext, mNotificationManager, R.string.screenshot_failed_to_capture_text); finisher.run(); return; } if (requiresRotation) { // Rotate the screenshot to the current orientation Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888, mScreenBitmap.hasAlpha(), mScreenBitmap.getColorSpace()); Canvas c = new Canvas(ss); c.translate(ss.getWidth() / 2, ss.getHeight() / 2); c.rotate(degrees); c.translate(-dims[0] / 2, -dims[1] / 2); c.drawBitmap(mScreenBitmap, 0, 0, null); c.setBitmap(null); // Recycle the previous bitmap mScreenBitmap.recycle(); mScreenBitmap = ss; } if (width != mDisplayMetrics.widthPixels || height != mDisplayMetrics.heightPixels) { // Crop the screenshot to selected region Bitmap cropped = Bitmap.createBitmap(mScreenBitmap, x, y, width, height); mScreenBitmap.recycle(); mScreenBitmap = cropped; } // Optimizations mScreenBitmap.setHasAlpha(false); mScreenBitmap.prepareToDraw(); // Start the post-screenshot animation startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, statusBarVisible, navBarVisible); }
獲取屏幕的寬高和當前屏幕方向以肯定是否須要旋轉圖片,而後經過 SurfaceControl.screenshot 截屏,好吧,再繼續往下看到
public static Bitmap screenshot(int width, int height) { // TODO: should take the display as a parameter IBinder displayToken = SurfaceControl.getBuiltInDisplay( SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN); return nativeScreenshot(displayToken, new Rect(), width, height, 0, 0, true, false, Surface.ROTATION_0); }
這裏調用的是 nativeScreenshot 方法,它是一個 native 方法,具體的實如今JNI層,這裏就不作過多的介紹了。繼續回到咱們的 takeScreenshot 方法,在調用了截屏方法 screentshot 以後,判斷是否截屏成功: 截屏失敗則調用 notifyScreenshotError 發送通知。截屏成功,則調用 startAnimation 播放動畫,來分析下動畫,後面咱們會改這個動畫的效果
/** * Starts the animation after taking the screenshot */ private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible, boolean navBarVisible) { // If power save is on, show a toast so there is some visual indication that a screenshot // has been taken. PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); if (powerManager.isPowerSaveMode()) { Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show(); } // Add the view for the animation mScreenshotView.setImageBitmap(mScreenBitmap); mScreenshotLayout.requestFocus(); // Setup the animation with the screenshot just taken if (mScreenshotAnimation != null) { if (mScreenshotAnimation.isStarted()) { mScreenshotAnimation.end(); } mScreenshotAnimation.removeAllListeners(); } mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation(); ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible); mScreenshotAnimation = new AnimatorSet(); mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim); mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { // Save the screenshot once we have a bit of time now saveScreenshotInWorkerThread(finisher); mWindowManager.removeView(mScreenshotLayout); // Clear any references to the bitmap mScreenBitmap = null; mScreenshotView.setImageBitmap(null); } }); mScreenshotLayout.post(new Runnable() { @Override public void run() { // Play the shutter sound to notify that we've taken a screenshot mCameraSound.play(MediaActionSound.SHUTTER_CLICK); mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null); mScreenshotView.buildLayer(); mScreenshotAnimation.start(); } }); }
先判斷是不是低電量模式,如果發出已抓取屏幕截圖的 toast,而後經過 WindowManager 在屏幕中間添加一個裝有截屏縮略圖的 view,同時建立兩個動畫組合,經過 mCameraSound 播放截屏咔嚓聲並執行動畫,動畫結束後移除剛剛添加的 view,同時調用 saveScreenshotInWorkerThread 保存圖片到媒體庫,咱們直接來看 SaveImageInBackgroundTask
class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { ..... SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data, NotificationManager nManager) { ...... mNotificationBuilder = new Notification.Builder(context, NotificationChannels.SCREENSHOTS) .setTicker(r.getString(R.string.screenshot_saving_ticker) + (mTickerAddSpace ? " " : "")) .setContentTitle(r.getString(R.string.screenshot_saving_title)) .setContentText(r.getString(R.string.screenshot_saving_text)) .setSmallIcon(R.drawable.stat_notify_image) .setWhen(now) .setShowWhen(true) .setColor(r.getColor(com.android.internal.R.color.system_notification_accent_color)) .setStyle(mNotificationStyle) .setPublicVersion(mPublicNotificationBuilder.build()); mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true); SystemUI.overrideNotificationAppName(context, mNotificationBuilder); mNotificationManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT, mNotificationBuilder.build()); } @Override protected Void doInBackground(Void... params) { if (isCancelled()) { return null; } // By default, AsyncTask sets the worker thread to have background thread priority, so bump // it back up so that we save a little quicker. Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); Context context = mParams.context; Bitmap image = mParams.image; Resources r = context.getResources(); try { // Create screenshot directory if it doesn't exist mScreenshotDir.mkdirs(); // media provider uses seconds for DATE_MODIFIED and DATE_ADDED, but milliseconds // for DATE_TAKEN long dateSeconds = mImageTime / 1000; // Save OutputStream out = new FileOutputStream(mImageFilePath); image.compress(Bitmap.CompressFormat.PNG, 100, out); out.flush(); out.close(); // Save the screenshot to the MediaStore ContentValues values = new ContentValues(); ContentResolver resolver = context.getContentResolver(); values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath); values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName); values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName); values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime); values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds); values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds); values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png"); values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth); values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight); values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length()); Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); // Create a share intent String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime)); String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate); Intent sharingIntent = new Intent(Intent.ACTION_SEND); sharingIntent.setType("image/png"); sharingIntent.putExtra(Intent.EXTRA_STREAM, uri); sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject); // Create a share action for the notification. Note, we proxy the call to ShareReceiver // because RemoteViews currently forces an activity options on the PendingIntent being // launched, and since we don't want to trigger the share sheet in this case, we will // start the chooser activitiy directly in ShareReceiver. PendingIntent shareAction = PendingIntent.getBroadcast(context, 0, new Intent(context, GlobalScreenshot.ShareReceiver.class) .putExtra(SHARING_INTENT, sharingIntent), PendingIntent.FLAG_CANCEL_CURRENT); Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder( R.drawable.ic_screenshot_share, r.getString(com.android.internal.R.string.share), shareAction); mNotificationBuilder.addAction(shareActionBuilder.build()); // Create a delete action for the notification PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0, new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class) .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( R.drawable.ic_screenshot_delete, r.getString(com.android.internal.R.string.delete), deleteAction); mNotificationBuilder.addAction(deleteActionBuilder.build()); mParams.imageUri = uri; mParams.image = null; mParams.errorMsgResId = 0; } catch (Exception e) { // IOException/UnsupportedOperationException may be thrown if external storage is not // mounted Slog.e(TAG, "unable to save screenshot", e); mParams.clearImage(); mParams.errorMsgResId = R.string.screenshot_failed_to_save_text; } // Recycle the bitmap data if (image != null) { image.recycle(); } return null; } @Override protected void onPostExecute(Void params) { if (mParams.errorMsgResId != 0) { // Show a message that we've failed to save the image to disk GlobalScreenshot.notifyScreenshotError(mParams.context, mNotificationManager, mParams.errorMsgResId); } else { // Show the final notification to indicate screenshot saved Context context = mParams.context; Resources r = context.getResources(); // Create the intent to show the screenshot in gallery Intent launchIntent = new Intent(Intent.ACTION_VIEW); launchIntent.setDataAndType(mParams.imageUri, "image/png"); launchIntent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION); final long now = System.currentTimeMillis(); // Update the text and the icon for the existing notification mPublicNotificationBuilder .setContentTitle(r.getString(R.string.screenshot_saved_title)) .setContentText(r.getString(R.string.screenshot_saved_text)) .setContentIntent(PendingIntent.getActivity(mParams.context, 0, launchIntent, 0)) .setWhen(now) .setAutoCancel(true) .setColor(context.getColor( com.android.internal.R.color.system_notification_accent_color)); mNotificationBuilder .setContentTitle(r.getString(R.string.screenshot_saved_title)) .setContentText(r.getString(R.string.screenshot_saved_text)) .setContentIntent(PendingIntent.getActivity(mParams.context, 0, launchIntent, 0)) .setWhen(now) .setAutoCancel(true) .setColor(context.getColor( com.android.internal.R.color.system_notification_accent_color)) .setPublicVersion(mPublicNotificationBuilder.build()) .setFlag(Notification.FLAG_NO_CLEAR, false); mNotificationManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT, mNotificationBuilder.build()); } mParams.finisher.run(); mParams.clearContext(); } @Override protected void onCancelled(Void params) { // If we are cancelled while the task is running in the background, we may get null params. // The finisher is expected to always be called back, so just use the baked-in params from // the ctor in any case. mParams.finisher.run(); mParams.clearImage(); mParams.clearContext(); // Cancel the posted notification mNotificationManager.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT); } }
簡單說下, SaveImageInBackgroundTask 構造方法中作了大量的準備工做,截屏圖片的時間命名格式、截屏通知對象建立,在 doInBackground 中將截屏圖片經過 ContentResolver 存儲至 MediaStore,再建立兩個 PendingIntent,用於分享和刪除截屏圖片,在 onPostExecute 中發送剛剛建立的 Notification 至 statuBar 顯示,到此截屏的流程就結束了。
咱們再回到 PhoneWindowManager 中看下,經過上面咱們知道要想截屏只需經過以下兩行代碼便可
mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); mHandler.post(mScreenshotRunnable);
經過搜索上面的關鍵代碼,咱們發現還有另外兩處也調用了截屏的代碼,一塊兒來看下
@Override public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) { final boolean keyguardOn = keyguardOn(); final int keyCode = event.getKeyCode(); ..... else if (keyCode == KeyEvent.KEYCODE_S && event.isMetaPressed() && event.isCtrlPressed()) { if (down && repeatCount == 0) { int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION : TAKE_SCREENSHOT_FULLSCREEN; mScreenshotRunnable.setScreenshotType(type); mHandler.post(mScreenshotRunnable); return -1; } } .... else if (keyCode == KeyEvent.KEYCODE_SYSRQ) { if (down && repeatCount == 0) { mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); mHandler.post(mScreenshotRunnable); } return -1; } ...... }
也是在攔截按鍵消息分發以前的方法中,查看 KeyEvent 源碼,第一種狀況大概網上搜索了下,應該是接外設時,同時按下 S 鍵 + Meta鍵 + Ctrl鍵便可截屏,關於 Meta 介紹可參考Meta鍵始末 第二種狀況是按下截屏鍵時,對應 keyCode 爲 120,能夠用 adb shell input keyevent 120 模擬發現也能截屏
/** Key code constant: 'S' key. */ public static final int KEYCODE_S = 47; /** Key code constant: System Request / Print Screen key. */ public static final int KEYCODE_SYSRQ = 120;
經常使用按鍵對應值
這樣文章開頭提到的三指截屏操做,咱們就能夠加在 PhoneWindowManager 中,當手勢監聽獲取到三指時,只需調用截屏的兩行代碼便可
在 PhoneWindowManager 的 dispatchUnhandledKey 方法中處理App沒法處理的按鍵事件,固然也包括音量減小鍵和電源按鍵的組合按鍵
經過一系列的調用啓動 TakeScreenshotService 服務,並經過其執行截屏的操做。
具體的截屏代碼是在 native 層實現的。
截屏操做時候,若截屏失敗則直接發送截屏失敗的 notification 通知。
截屏以後,若截屏成功,則先執行截屏的動畫,並在動畫效果執行完畢以後,發送截屏成功的 notification 的通知。
參考文章
Android 截屏方法總結 Android KeyCode列表
原文出處:https://www.cnblogs.com/cczheng-666/p/11365869.html