查看原文
其他

安卓10源码学习开发定制(32)系统截屏流程之Java层分析

QDroid88888 卓码星球 2022-05-09


一、前言

    安卓手机中可以通过同时按下"音量-"键和"电源键"实现屏幕截屏,某些手机也可以通过屏幕下拉快捷菜单中的"截屏"功能实现截屏。本文章分析测试的是lineageOS 17.1的系统通过按下"音量-"键和"电源键"实现屏幕截屏的流程分析。


二、PhoneWindowManager简单说明


     在安卓系统中常见的按键比如HOME、电源键、返回键、音量键等处理是在PhoneWindowManager类中首先进行处理。所以如果需要对这些按键特殊逻辑处理就需要修改PhoneWindowManager类源码。本篇中分析的屏幕截图涉及到电源键和音量键操作,所以以下我们从该类开始分析截屏的流程。


三、流程分析


 


1.PhoneWindowManager中的流程分析


     该类源码路径位于:


frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java

     该类中处理按键的函数为interceptKeyBeforeQueueing,该方法中关键调用如下:


@Override public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { ... // Handle special keys. switch (keyCode) { case KeyEvent.KEYCODE_BACK: { ... } case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_MUTE: { ... //按下音量-键 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) { ... //interceptScreenshotChord处理判断屏幕截图 interceptScreenshotChord(); interceptAccessibilityShortcutChord(); } } else { mScreenshotChordVolumeDownKeyTriggered = false; cancelPendingScreenshotChordAction(); cancelPendingAccessibilityShortcutAction(); if (mClickPartialScreenshot && mScreenshotChordVolumeDownKeyConsumed) { mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_SELECTED_REGION); mHandler.post(mScreenshotRunnable); } } } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { ... } ... //处理按下电源键的情况 case KeyEvent.KEYCODE_POWER: { ... //电源键按下 if (down) { interceptPowerKeyDown(event, interactive); } else { interceptPowerKeyUp(event, interactive, canceled); } break; } ... }

    在以上分析中按下音量键-执行了方法interceptScreenshotChord。按下电源键执行interceptPowerKeyDown方法。interceptPowerKeyDown方法逻辑如下:


 

private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { ... // Latch power key state to detect screenshot chord. //以下调用interceptScreenshotChord判断是否截屏 if (interactive && !mScreenshotChordPowerKeyTriggered && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { mScreenshotChordPowerKeyTriggered = true; mScreenshotChordPowerKeyTime = event.getDownTime(); interceptScreenshotChord(); interceptRingerToggleChord(); } ... }

     由上分析按下电源键、音量-键都需要走interceptScreenshotChord方法进行判断是否截屏。interceptScreenshotChord方法逻辑如下:


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); //使用handler分发延时处理截屏任务 //截屏逻辑转到mScreenshotRunnable中 mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay()); } } }

        截屏逻辑走到了mScreenshotRunnable中,该变量定义如下:


private final ScreenshotRunnable mScreenshotRunnable = new ScreenshotRunnable();

        mScreenshotRunnable为ScreenshotRunnable类,该类定义在当前文件中,定义如下:




private class ScreenshotRunnable implements Runnable { private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN; public void setScreenshotType(int screenshotType) { mScreenshotType = screenshotType; } @Override public void run() { //使用mDefaultDisplayPolicy类截屏 mDefaultDisplayPolicy.takeScreenshot(mScreenshotType); } }

         以上流程走到了mDefaultDisplayPolicy.takeScreenshot。mDefaultDisplayPolicy的定义如下:


DisplayPolicy mDefaultDisplayPolicy;

       以下接着分析DisplayPolicyle类的实现情况。


2. DisplayPolicy中的流程分析  


      DisplayPolicy类的源文件如下:


frameworks\base\services\core\java\com\android\server\wm\DisplayPolicy.java

     该类中takeScreenshot方法实现如下:


public void takeScreenshot(int screenshotType) { if (mScreenshotHelper != null) { //使用了mScreenshotHelper执行屏幕j截图 mScreenshotHelper.takeScreenshot(screenshotType, mStatusBar != null && mStatusBar.isVisibleLw(), mNavigationBar != null && mNavigationBar.isVisibleLw(), mHandler, null /* completionConsumer */); } }

     以上方法中执行了mScreenshotHelper 对象的takeScreenshot方法。mScreenshotHelper 变量定义如下:


private final ScreenshotHelper mScreenshotHelper;

       根据变量定义的类型逻辑流程跳转到ScreenshotHelper类中。


3.ScreenshotHelper类中的流程分析


     ScreenshotHelper类源文件如下:


frameworks\base\services\core\java\com\android\server\wm\ScreenshotHelper.java

   该类中takeScreenshot方法的实现如下:


public void takeScreenshot(final int screenshotType, final boolean hasStatus, final boolean hasNav, @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) takeScreenshot(screenshotType, hasStatus, hasNav, SCREENSHOT_TIMEOUT_MS, handler, completionConsumer); }public void takeScreenshot(final int screenshotType, final boolean hasStatus, final boolean hasNav, long timeoutMs, @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) { ... //此处创建了需要连接的service final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE, SYSUI_SCREENSHOT_SERVICE); final Intent serviceIntent = new Intent(); ... serviceIntent.setComponent(serviceComponent); //连接service的回调方法 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(handler.getLooper()) { @Override public void handleMessage(Message msg) { synchronized (mScreenshotLock) { if (mScreenshotConnection == myConn) { mContext.unbindService(mScreenshotConnection); mScreenshotConnection = null; handler.removeCallbacks(mScreenshotTimeout); } } if (completionConsumer != null) { completionConsumer.accept((Uri) msg.obj); } } }; msg.replyTo = new Messenger(h); msg.arg1 = hasStatus ? 1 : 0; msg.arg2 = hasNav ? 1 : 0; try { messenger.send(msg); } catch (RemoteException e) { Log.e(TAG, "Couldn't take screenshot: " + e); if (completionConsumer != null) { completionConsumer.accept(null); } } } } @Override public void onServiceDisconnected(ComponentName name) { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { mContext.unbindService(mScreenshotConnection); mScreenshotConnection = null; handler.removeCallbacks(mScreenshotTimeout); notifyScreenshotError(); } } } }; //通过bindServiceAsUser方法进行binder进程间通信启动service if (mContext.bindServiceAsUser(serviceIntent, conn, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, UserHandle.CURRENT)) { mScreenshotConnection = conn; ... } } }

       以上代码中构造了service。如下抽取了关键的代码:


//包名 private static final String SYSUI_PACKAGE = "com.android.systemui"; //需要启动的service类名 private static final String SYSUI_SCREENSHOT_SERVICE = "com.android.systemui.screenshot.TakeScreenshotService"; //通过ComponentName构造启动的service组件 final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE, SYSUI_SCREENSHOT_SERVICE);

       经过以上分析截屏逻辑转到了“com.android.systemui.screenshot.TakeScreenshotService”类中。该类为SystemUI程序的组件。



4.SystemUI中TakeScreenshotService流程分析


       TakeScreenshotService类源码路径如下:


frameworks\base\packages\SystemUI\src\com\android\systemui\screenshot\TakeScreenshotService.java

     该类中处理截图逻辑代码如下:


//定义mScreenshot变量private static GlobalScreenshot mScreenshot; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { ... if (mScreenshot == null) { //创建GlobalScreenshot对象 mScreenshot = new GlobalScreenshot(TakeScreenshotService.this); } switch (msg.what) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: //全屏j截图 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; default: Log.d(TAG, "Invalid screenshot option: " + msg.what); } } };

     以上分析可知截屏跳转到了GlobalScreenshot中的takeScreenshot方法。   


5.GlobalScreenshot中的截屏流程分析


     GlobalScreenShot类源文件如下:

frameworks\base\packages\SystemUI\src\com\android\systemui\screenshot\GlobalScreenShot.java

      该类中takeScreenshot方法实现如下:


private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible, Rect crop) { ... // Take the screenshot //调用了SurfaceControl类的screenshot实现截屏 mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot); if (mScreenBitmap == null) { notifyScreenshotError(mContext, mNotificationManager, R.string.screenshot_failed_to_capture_text); finisher.accept(null); return; } ... }

        以上关键调用变成了SurfaceControl.screenshot。


 


6.SurfaceControl类中的截屏流程


    SurfaceControl类源码路径位于:


frameworks\base\core\java\android\view\SurfaceControl.java

    类中screenshot方法的逻辑实现如下:


//调用1 public static Bitmap screenshot(Rect sourceCrop, int width, int height, int rotation) { return screenshot(sourceCrop, width, height, false, rotation); }//调用2 public static Bitmap screenshot(Rect sourceCrop, int width, int height, boolean useIdentityTransform, int rotation) { ... //调用screenshotToBuffer final ScreenshotGraphicBuffer buffer = screenshotToBuffer(displayToken, sourceCrop, width, height, useIdentityTransform, rotation); if (buffer == null) { Log.w(TAG, "Failed to take screenshot"); return null; } return Bitmap.wrapHardwareBuffer(buffer.getGraphicBuffer(), buffer.getColorSpace()); }//调用3public static ScreenshotGraphicBuffer screenshotToBuffer(IBinder display, Rect sourceCrop, int width, int height, boolean useIdentityTransform, int rotation) { ... //调用nativeScreenshot return nativeScreenshot(display, sourceCrop, width, height, useIdentityTransform, rotation, false /* captureSecureLayers */); }//调用4 该方法为native方法,具体实现在jni层去了private static native ScreenshotGraphicBuffer nativeScreenshot(IBinder displayToken, Rect sourceCrop, int width, int height, boolean useIdentityTransform, int rotation, boolean captureSecureLayers );

      

    SurfaceControl中最终截屏调用调用了nativeScreenshot本地方法。该方法的实现在jni层。


总结


  1. 通过以上对Java层截屏分析,App有访问屏幕数据权限的情况下,Java层想截屏可以通过调用如下接口:


        SurfaceControl.screenshot(crop, width, height, rot);

由于SurfaceControl为非公开类,所以App中需要通过反射调用。



如果你对安卓相关的开发学习感兴趣:


       可加作者的QQ群(1017017661),本群专注安卓方面的技术,欢迎加群技术交流。



 点击屏末 | 阅读原文 | 获取更多文章列表信息



扫描下方二维码关注公众号



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存