Android手机一般都自带有手机屏幕截图的功能:在手机任何界面(当然手机要是开机点亮状态),通过按组合键,屏幕闪一下,然后咔嚓一声,截图的照片会保存到当前手机的图库中,真是一个不错的功能!

以我手头的测试手机为例,是同时按电源键+音量下键来实现截屏,苹果手机则是电源键 +

HOME键,小米手机是菜单键+音量下键,而HTC一般是按住电源键再按左下角的“主页”键。那么Android源码中使用组合键是如何实现屏幕截图功能

呢?前段时间由于工作的原因仔细看了一下,这两天不忙,便把相关的知识点串联起来整理一下,分下面两部分简单分析下实现流程:

Android源码中对组合键的捕获。

Android源码中对按键的捕获位于文件PhoneWindowManager.java(alps\frameworks\base

\policy\src\com\android\internal\policy\impl)中,这个类处理所有的键盘输入事件,其中函数

interceptKeyBeforeQueueing()会对常用的按键做特殊处理。以我手头的测试机为例,是同时按电源键和音量下键来截屏,那么在这

个函数中我们会看到这么两段代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

.......

case KeyEvent.KEYCODE_VOLUME_DOWN:

case KeyEvent.KEYCODE_VOLUME_UP:

case KeyEvent.KEYCODE_VOLUME_MUTE: {

if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {

if (down) {

if (isScreenOn && !mVolumeDownKeyTriggered

&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {

mVolumeDownKeyTriggered = true;

mVolumeDownKeyTime = event.getDownTime();

mVolumeDownKeyConsumedByScreenshotChord = false;

cancelPendingPowerKeyAction();

interceptScreenshotChord();

}

} else {

mVolumeDownKeyTriggered = false;

cancelPendingScreenshotChordAction();

}

......

case KeyEvent.KEYCODE_POWER: {

result &= ~ACTION_PASS_TO_USER;

if (down) {

if (isScreenOn && !mPowerKeyTriggered

&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {

mPowerKeyTriggered = true;

mPowerKeyTime = event.getDownTime();

interceptScreenshotChord();

}

......

可以看到正是在这里(响应Down事件)捕获是否按了音量下键和电源键的,而且两个地方都会进入函数interceptScreenshotChord()中,那么接下来看看这个函数干了什么工作:

1

2

3

4

5

6

7

8

9

10

11

12

13

private void interceptScreenshotChord() {

if (mVolumeDownKeyTriggered && mPowerKeyTriggered && !mVolumeUpKeyTriggered) {

final long now = SystemClock.uptimeMillis();

if (now <= mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS

&& now <= mPowerKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {

mVolumeDownKeyConsumedByScreenshotChord = true;

cancelPendingPowerKeyAction();

mHandler.postDelayed(mScreenshotChordLongPress,

ViewConfiguration.getGlobalActionKeyTimeout());

}

}

}

在这个函数中,用两个布尔变量判断是否同时按了音量下键和电源键后,再计算两个按键响应Down事件之间的时间差不超过150毫秒,也就认为是同时按了这两个键后,算是真正的捕获到屏幕截屏的组合键。

附言:文件PhoneWindowManager.java类是拦截键盘消息的处理类,在此类中还有对home键、返回键等好多按键的处理。

Android源码中调用屏幕截图的接口。

捕获到组合键后,我们再看看android源码中是如何调用屏幕截图的函数接口。在上面的函数interceptScreenshotChord中我们看到用handler判断长按组合键500毫秒之后,会进入如下函数:

1

2

3

4

5

private final Runnable mScreenshotChordLongPress = new Runnable() {

public void run() {

takeScreenshot();

}

};

在这里启动了一个线程来完成截屏的功能,接着看函数takeScreenshot():

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

private void takeScreenshot() {

synchronized (mScreenshotLock) {

if (mScreenshotConnection != null) {

return;

}

ComponentName cn = new ComponentName("com.android.systemui",

"com.android.systemui.screenshot.TakeScreenshotService");

Intent intent = new Intent();

intent.setComponent(cn);

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, 1);

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) {}

};

if (mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)) {

mScreenshotConnection = conn;

mHandler.postDelayed(mScreenshotTimeout, 10000);

}

}

}

可以看到这个函数使用AIDL绑定了service服务

到"com.android.systemui.screenshot.TakeScreenshotService",注意在service连接成功

时,对message的msg.arg1和msg.arg2两个参数的赋值。其中在mScreenshotTimeout中对服务service做了超时

处理。接着我们找到实现这个服务service的类TakeScreenshotService,看看其实现的流程:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

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) {

switch (msg.what) {

case 1:

final Messenger callback = msg.replyTo;

if (mScreenshot == null) {

mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);

}

mScreenshot.takeScreenshot(new Runnable() {

@Override public void run() {

Message reply = Message.obtain(null, 1);

try {

callback.send(reply);

} catch (RemoteException e) {

}

}

}, msg.arg1 > 0, msg.arg2 > 0);

}

}

};

@Override

public IBinder onBind(Intent intent) {

return new Messenger(mHandler).getBinder();

}

}

在这个类中,我们主要看调用接口,用到了mScreenshot.takeScreenshot()传

递了三个参数,第一个是个runnable,第二和第三个是之前message传递的两个参数msg.arg1和msg.arg2。最后我们看看这个函数

takeScreenshot(),位于文件GlobalScreenshot.java中(跟之前的函数重名但是文件路径不一样):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

/**

* Takes a screenshot of the current display and shows an animation.

*/

void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {

// 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 = Surface.screenshot((int) dims[0], (int) dims[1]);

if (mScreenBitmap == null) {

notifyScreenshotError(mContext, mNotificationManager);

finisher.run();

return;

}

if (requiresRotation) {

// Rotate the screenshot to the current orientation

Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,

mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);

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);

mScreenBitmap = ss;

}

// Optimizations

mScreenBitmap.setHasAlpha(false);

mScreenBitmap.prepareToDraw();

// Start the post-screenshot animation

startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,

statusBarVisible, navBarVisible);

}

这段代码的注释比较详细,其实看到这里,我们算是真正看到截屏的操作了,具体的工作包括对屏幕大小、旋

转角度的获取,然后调用Surface类的screenshot方法截屏保存到bitmap中,之后把这部分位图填充到一个画布上,最后再启动一个延迟的

拍照动画效果。如果再往下探究screenshot方法,发现已经是一个native方法了:

1

2

3

4

5

6

7

/**

* Like {@link #screenshot(int, int, int, int)} but includes all

* Surfaces in the screenshot.

*

* @hide

*/

public static native Bitmap screenshot(int width, int height);

使用JNI技术调用底层的代码,如果再往下走,会发现映射这这个jni函数在文件android_view_Surface.cpp中,这个真的已经是底层c++语言了,统一调用的底层函数是:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

static jobject doScreenshot(JNIEnv* env, jobject clazz, jint width, jint height,

jint minLayer, jint maxLayer, bool allLayers)

{

ScreenshotPixelRef* pixels = new ScreenshotPixelRef(NULL);

if (pixels->update(width, height, minLayer, maxLayer, allLayers) != NO_ERROR) {

delete pixels;

return 0;

}

uint32_t w = pixels->getWidth();

uint32_t h = pixels->getHeight();

uint32_t s = pixels->getStride();

uint32_t f = pixels->getFormat();

ssize_t bpr = s * android::bytesPerPixel(f);

SkBitmap* bitmap = new SkBitmap();

bitmap->setConfig(convertPixelFormat(f), w, h, bpr);

if (f == PIXEL_FORMAT_RGBX_8888) {

bitmap->setIsOpaque(true);

}

if (w > 0 && h > 0) {

bitmap->setPixelRef(pixels)->unref();

bitmap->lockPixels();

} else {

// be safe with an empty bitmap.

delete pixels;

bitmap->setPixels(NULL);

}

return GraphicsJNI::createBitmap(env, bitmap, false, NULL);

}

由于对C++不熟,我这里就不敢多言了。其实到这里,算是对手机android源码中通过组合键屏幕截图的整个流程有个大体了解了,一般我们在改动中熟悉按键的捕获原理,并且清楚调用的截屏函数接口即可,如果有兴趣的,可以继续探究更深的底层是如何实现的。

android底层截图,Android源码中屏幕截图的实现相关推荐

  1. Android之在BaseAdapter源码中了解观察者模式

    转载请标明出处: http://blog.csdn.net/hai_qing_xu_kong/article/details/76146635 本文出自:[顾林海的博客] 个人开发的微信小程序,目前功 ...

  2. Android直播APP源码中排行榜功能如何实现

    刚进公司的时候,听技术人员说起直播APP源码中的"排行榜"功能,小编最先想到的是学生时期的成绩排行,上榜的沾沾自喜到下次考试,下榜的哭哭啼啼,其实就算上榜也并没有什么实质性的奖励, ...

  3. 【Android 内存优化】Android 原生 API 图片压缩原理 ( 图片质量压缩方法 | 查找 Java 源码中的 native 方法对应的 C++ 源码 )

    文章目录 一. 图片质量压缩方法 二. 查找对应的 Native 方法源码 三. 分析 Bitmap.cpp 中动态注册 Native 方法 在博客 [Android 内存优化]图片文件压缩 ( An ...

  4. android源码使用方法,android源码中使用到的设计模式(创建型)

    1.单例模式 1.1定义 确保某个类只有一个实例,而且自行实例化并向整个系统提供者个实例. 1.2单例的形式 饿汉模式:第一次就加载,用空间换时间. public class SingleTon { ...

  5. android系统源码中添加app源码(源码部署移植)

    涉及到系统定制,需要在系统中加入自己的apk工程,但是上网找了很多资料都是不够全面的,或者看了还是没搞懂,我自己也是一点点摸索过来的,花了不少的时间,也是踩了不少的坑,因此特开一文,帮助大家渡河. 申 ...

  6. 华硕Tinker Board开发板(rk3288处理器)开发 ---- 将Android Studio开发的apk源码放到Android7.1.2源码中进行编译

    备注:此Android Studio项目并没有jni,没有使用动态so库,只是一个简单的项目. 1.将Android studio开发的项目移植到Android7.1.2源码中,我们只需要Androi ...

  7. android 指令模式,Android 源码中的命令模式

    原标题:Android 源码中的命令模式 (点击上方公众号,可快速关注) 来源:伯乐在线专栏作者 - PleaseCallMeCoder 链接:http://android.jobbole.com/8 ...

  8. 【Android 逆向】ART 脱壳 ( InMemoryDexClassLoader 脱壳 | DexFile 构造函数及相关调用函数 | Android 源码中查找 native 函数 )

    文章目录 一.DexFile 构造函数 二.DexFile.openInMemoryDexFile 函数 三.Android 源码中查找 native 函数 一.DexFile 构造函数 上一篇博客 ...

  9. android 源码中的单例,Android源码中的一种单例实现

    单例模式的实现方式有懒汉,饿汉,双重校验锁,枚举,内部类等等,写法就不全部列举了.Android源码中有一个单例辅助类/frameworks/base/core/java/android/util/S ...

最新文章

  1. GridView标题行换行之我见 (转)
  2. 硬盘满了 mysql启不来_MySQL数据库之磁盘已满造成的mysql启动失败问题分享
  3. Exchange 2007更改用户收发邮件大小限制
  4. 如何定位并修复 HttpCore5 中的 HTTP2 流量控制问题
  5. paddlepaddle系列之三行代码从入门到精通
  6. Docker 精通之 docker-compose
  7. C语言之strstr函数
  8. 6.2-3 局部性+内存层次
  9. 内网地址映射到公网地址
  10. DSQLTools的使用——合天网安实验室学习笔记
  11. 阿里云访问控制简要说明
  12. 不容忽视的细节——线面积分中的奇点
  13. 游戏开发19课 tilemap 创建瓦片
  14. 智能扫地机器人好用吗
  15. 《初.中级网管要掌握的技术》我的技术我做主
  16. 2022-2028全球两级旋片泵行业调研及趋势分析报告
  17. 【数据蒋堂】第47期:Hadoop - 一把杀鸡用的牛刀
  18. C语言:念数字(结构体)
  19. 原油价格小幅回升但仍然力度有限
  20. 数据分析师7大技能:梳理指标体系

热门文章

  1. 斗罗大陆3d游戏策划
  2. html盒子代码div6,WEB入门.六 盒子模型
  3. 64位ubuntu 16.04 LTS安装搜狗输入法过程
  4. python分隔符是干啥_python文件路径分隔符的详细分析
  5. 比較經典有味道的句子
  6. 剪辑音乐要很久?3 行语句 Python 瞬间搞定
  7. [转] PS技术 在学校里 学三年 也学不到这么多
  8. 华为抓取错误日志在哪里_抓取android手机log的介绍
  9. 网站推广的108个实用方法(精)
  10. 关于C语言数组赋值方法