系统截屏源码浅析
Posted 吴传龙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了系统截屏源码浅析相关的知识,希望对你有一定的参考价值。
android中实现截屏的方式有很多种,形如下面几种:
1、通过view.getDrawingCache获取屏幕的图像数据,这也是众多开发同行朋友经常使用的一种方式,可惜的是这种方式并不适用于surfaceview。
2、利用adb命令,adb shell screencap -p path,再利用runtime去执行,但是这种方式需要获得系统权限方可。
3、通过framebuffer实现截屏,帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,允许上层应用程序在图形模式下直接对显示缓冲区进行读写等操,这些都是由Framebuffer设备驱动来完成的。android中的framebuffer数据是存放在 /dev/graphics/fb0 文件中的,所以只要获取到framebuffer中的数据再转换成图片就实现截屏的功能啦,这不是半本片文章的重点介绍内容,这个后面或许会最为一个章节共享个大家。
4、 利用系统TakeScreenShotService截图。android设备可以通过电源键+音量下键可以实现截屏,很多手机设备上用手下拉状态栏也有截屏的选项,都是使用TakeScreenShotService截屏的,本文要介绍的是如何通过TakeScreenShotService实现截屏。
TakeScreenShotService源码分析,源码位于
frameworks\base\packages\SystemUI\src\com\android\systemui\screenshot\TakeScreenshotService.java
瞧瞧manifest文件先:
<service android:name=".screenshot.TakeScreenshotService"
android:process=":screenshot"
android:exported="false" />
这个service是设置了exported属性的,如果设置为true,则能够被调用或交互,否则不能。设置为false时,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。然而TakeScreenshotService所在应用程序的id是android.uid.systemui,所以一般的应用程序是没办法做到这一点的。
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();
}
}
TakeScreenshotService 源码就这么多,可以很清晰的看见截屏的功能是由mScreenshot.takeScreenshot实现的。
void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
.....
}
finisher是在截屏之后的回调,谁发起的截屏在截屏完成之后就需要告诉需要者已经完成了。第二个和第三个就是截屏时是否显示状态栏和导航栏。
上面也提到了手机上截屏在状态栏下拉时通常有个选项,所以我们就移步到PhoneStatusBar.java瞧瞧。
源码路径:frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\PhoneStatusBar.java
public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
.......
.......
private void takeScreenshot() {
// 截屏图片存放位置
String imageDir = Settings.System.getString(mContext.getContentResolver(), Settings.System.SCREENSHOT_LOCATION);
File file = new File(imageDir + UserHandle.myUserId() + "/Screenshots");
String text = null;
Log.e(">>>>>>", "imageDir=" + imageDir);
file.mkdir();
if (!file.exists()) {
if (imageDir.equals("/mnt/sdcard")) {
text = mContext.getResources().getString(R.string.sdcard_unmount);
} else if (imageDir.equals("/mnt/external_sd")) {
text = mContext.getResources().getString(R.string.external_sd_unmount);
} else if (imageDir.equals("/mnt/usb_storage")) {
text = mContext.getResources().getString(R.string.usb_storage_unmount);
}
Toast.makeText(mContext, text, 3000).show();
return;
}
synchronized (mScreenshotLock) {
if (mScreenshotConnection != null) {
return;
}
// 在这里绑定了截屏的TakeScreenshotService
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);
}
}
}
};
// 截屏完成后需要回调告知,由h来处理
msg.replyTo = new Messenger(h);
// 是否显示状态栏
msg.arg1 = 0;
// 是否显示导航栏
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);
}
}
}
.......
}
从上述代码中可以知道,TakeScreenshotService绑定成功后便开始往进行截屏操作,当截屏操作成功后,便会unbind这个service。
再回到TakeScreenshotService来,截屏是由GlobalScreenshot.takeScreenshot()来完成的,
/**
* 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 = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
if (mScreenBitmap == null) {
// 截取的图片为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);
// Recycle the previous bitmap
mScreenBitmap.recycle();
mScreenBitmap = ss;
}
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
// 展示动画,就是截屏后在页面上有个动画展示效果
startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
statusBarVisible, navBarVisible);
}
就下来就是要去重点了解下面的代码到底干了啥
SurfaceControl.screenshot((int) dims[0], (int) dims[1])
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, width, height, 0, 0, true);
}
终于发现截屏操作竟然是在natvie层实现的,native返回了一个bitmap对象。下面移步native。nativeScreenshot方法的实现在下面的源码文件中:
frameworks\base\core\jni\android_view_SurfaceControl.cpp
static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, jobject displayTokenObj,
jint width, jint height, jint minLayer, jint maxLayer, bool allLayers) {
sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj);
if (displayToken == NULL) {
return NULL;
}
// 持有图像的数据
ScreenshotPixelRef* pixels = new ScreenshotPixelRef(NULL);
if (pixels->update(displayToken, width, height,
minLayer, maxLayer, allLayers) != NO_ERROR) {
delete pixels;
return NULL;
}
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);
}
// 创建bitmap对象
return GraphicsJNI::createBitmap(env, bitmap,
GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL);
}
以上是关于系统截屏源码浅析的主要内容,如果未能解决你的问题,请参考以下文章
JDK——NIO的系统调用实现(selectpollepoll)源代码浅析