android底层截图,Android源码中屏幕截图的实现

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源码中通过组合键屏幕截图的整个流程有个大体了解了,一般我们在改动中熟悉按键的捕获原理,并且清楚调用的截屏函数接口即可,如果有兴趣的,可以继续探究更深的底层是如何实现的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值