android 截屏函数_(转)Android中使用代码截图的各种方法总结

1,基于Android SDK的截屏方法

(1)主要就是利用SDK提供的View.getDrawingCache()方法。网上已经有很多的实例了。首先创建一个android project,然后进行Layout,画一个按键(res/layout/main.xml):

HelloAndroid.java实现代码为:

packagecom.example.helloandroid;

importjava.io.FileOutputStream;

importjava.text.SimpleDateFormat;

importjava.util.Date;

importjava.util.Locale;

importandroid.app.Activity;

importandroid.graphics.Bitmap;

importandroid.os.Bundle;

importandroid.view.View;

importandroid.view.View.OnClickListener;

importandroid.widget.Button;

public class HelloAndroid extends Activity {

privateButton button;

@Override

publicvoidonCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

this.setContentView(R.layout.main);

this.button = (Button) this.findViewById(R.id.my_button);

this.button.setOnClickListener(new OnClickListener() {

publicvoidonClick(View v) {

SimpleDateFormat sdf = newSimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.US);

String fname = "/sdcard/"+ sdf.format(newDate()) + ".png";

View view = v.getRootView();

view.setDrawingCacheEnabled(true);

view.buildDrawingCache();

Bitmap bitmap = view.getDrawingCache();

if(bitmap != null) {

System.out.println("bitmap got!");

try{

FileOutputStream out = newFileOutputStream(fname);

bitmap.compress(Bitmap.CompressFormat.PNG,100, out);

System.out.println("file " + fname + "output done.");

}catch(Exception e) {

e.printStackTrace();

}

}else{

System.out.println("bitmap is NULL!");

}

}

});

}

}

这个代码会在按下app中按键的时候自动在手机的/sdcard/目录下生成一个时间戳命名的png截屏文件。

这种截屏有一个问题,就是只能截到一部分,比如电池指示部分就截不出来了。

(2)在APK中调用“adb shell screencap -pfilepath” 命令

该命令读取系统的framebuffer,需要获得系统权限:

(1). 在AndroidManifest.xml文件中添加 

(2). 修改APK为系统权限,将APK放到源码中编译, 修改Android.mk LOCAL_CERTIFICATE := platform

publicvoid takeScreenShot() {

String mSavedPath = Environment.getExternalStorageDirectory()+File. separator + "screenshot.png";

try {

Runtime. getRuntime().exec("screencap -p " + mSavedPath);

} catch (Exception e) {

e.printStackTrace();

}

}

(3).利用系统的API,实现Screenshot,这部分代码是系统隐藏的,需要在源码下编译,

1).修改Android.mk, 添加系统权限 LOCAL_CERTIFICATE := platform

2).修改AndroidManifest.xml 文件,添加权限 

public boolean takeScreenShot(String imagePath) {

if(imagePath.equals("" )){

imagePath = Environment.getExternalStorageDirectory()+File. separator+"Screenshot.png" ;

}

Bitmap mScreenBitmap;

WindowManager mWindowManager;

DisplayMetrics mDisplayMetrics;

Display mDisplay;

mWindowManager = (WindowManager) mcontext.getSystemService(Context.WINDOW_SERVICE);

mDisplay = mWindowManager.getDefaultDisplay();

mDisplayMetrics = new DisplayMetrics();

mDisplay.getRealMetrics(mDisplayMetrics);

float[] dims = {mDisplayMetrics.widthPixels , mDisplayMetrics.heightPixels };

mScreenBitmap = Surface. screenshot((int) dims[0], ( int) dims[1]);

if (mScreenBitmap == null) {

return false ;

}

try {

FileOutputStream out = new FileOutputStream(imagePath);

mScreenBitmap.compress(Bitmap.CompressFormat. PNG, 100, out);

} catch (Exception e) {

return false ;

}

return true ;

}

2 基于Android ddmlib进行截屏

public class ScreenShot {

private BufferedImage image = null;

/**

* @param args

*/

public static void main(String[] args) {

AndroidDebugBridge.init(false);

ScreenShot screenshot = new ScreenShot();

IDevice device = screenshot.getDevice();

for (int i = 0; i<10; i++) {

Date date=newDate();

SimpleDateFormat df=newSimpleDateFormat("MM-dd-HH-mm-ss");

String nowTime= df.format(date);screenshot.getScreenShot(device, "Robotium" + nowTime);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

public void getScreenShot(IDevice device,String filename) {

RawImage rawScreen= null;try {

rawScreen= device.getScreenshot();} catch (TimeoutException e) {

e.printStackTrace();

} catch (AdbCommandRejectedException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

if (rawScreen != null){

Boolean landscape= false;int width2= landscape? rawScreen.height : rawScreen.width;

int height2= landscape? rawScreen.width : rawScreen.height;

if (image==null) {

image= newBufferedImage(width2, height2, BufferedImage.TYPE_INT_RGB);

} else {

if (image.getHeight() != height2|| image.getWidth() != width2){

image= newBufferedImage(width2, height2, BufferedImage.TYPE_INT_RGB);

}

}

int index= 0;int indexInc= rawScreen.bpp>> 3;

for (int y = 0; y

for (int x= 0;x < rawScreen.width; x++, index += indexInc){

int value= rawScreen.getARGB(index);if (landscape)

image.setRGB(y, rawScreen.width - x - 1, value);

else

image.setRGB(x, y, value);

}

}

try {

ImageIO.write((RenderedImage) image, "PNG", new File("D:/" + filename + ".jpg"));

} catch (IOException e) {

e.printStackTrace();

}

}

}

/**

* 获取得到device对象

* @return

*/

private IDevice getDevice(){

IDevice device;

AndroidDebugBridge bridge= AndroidDebugBridge.createBridge("adb",true);//如果代码有问题请查看API,修改此处的参数值试一下

waitDevicesList(bridge);

IDevice devices[]= bridge.getDevices();device= devices[0];return device;

}

/**

* 等待查找device

* @param bridge

*/

private void waitDevicesList(AndroidDebugBridge bridge) {

int count= 0;while (bridge.hasInitialDeviceList()==false) {

try {

Thread.sleep(500);

count++;

} catch (InterruptedException e) { }

if (count>240) {

System.err.print("等待获取设备超时");

break;

}

}

}

}

3 Android本地编程(Native Programming)读取framebuffer

(1)命令行,框架的截屏功能是通过framebuffer来实现的,所以我们先来介绍一下framebuffer。

framebuffer介绍

帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,他允许上层应用程序在图形模式下直接对显示缓冲区进行 读写操作。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。

Linux FrameBuffer 本质上只是提供了对图形设备的硬件抽象,在开发者看来,FrameBuffer 是一块显示缓存,往显示缓存中写入特定格式的数据就意味着向屏幕输出内容。所以说FrameBuffer就是一块白板。例如对于初始化为16 位色的FrameBuffer 来说, FrameBuffer中的两个字节代表屏幕上一个点,从上到下,从左至右,屏幕位置与内存地址是顺序的线性关系。

帧缓存有个地址,是在内存里。我们通过不停的向frame buffer中写入数据, 显示控制器就自动的从frame buffer中取数据并显示出来。全部的图形都共享内存中同一个帧缓存。

Android截屏实现思路

Android系统是基于Linux内核的,所以也存在framebuffer这个设备,我们要实现截屏的话只要能获取到framebuffer中的数据,然后把数据转换成图片就可以了,android中的framebuffer数据是存放在 /dev/graphics/fb0 文件中的,所以我们只需要来获取这个文件的数据就可以得到当前屏幕的内容。

现在我们的测试代码运行时候是通过RC(remote controller)方式来运行被测应用的,那就需要在PC机上来访问模拟器或者真机上的framebuffer数据,这个的话可以通过android的ADB命令来实现。

具体实现

/***********************************************************************

*

* ScreenShot.java

***********************************************************************/

import java.awt.image.BufferedImage;

import java.io.ByteArrayOutputStream;

import java.io.DataInput;

import java.io.EOFException;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.InputStream;

import javax.imageio.ImageIO;

import org.openqa.selenium.OutputType;

import org.openqa.selenium.internal.Base64Encoder;

import com.google.common.io.Closeables;

import com.google.common.io.LittleEndianDataInputStream;

/**

*/

public class ScreenShot {

/**

* @param args

* @throws InterruptedException

*/

public static void main(String[] args) throws InterruptedException {

try {

//分辨率大小,后续可以通过代码来获取到当前的分辨率

int xResolution = 320;

int yResolution = 480;

//执行adb命令,把framebuffer中内容保存到fb1文件中

Runtime.getRuntime().exec("adb pull /dev/graphics/fb0 C:/fb1");

//等待几秒保证framebuffer中的数据都被保存下来,如果没有保存完成进行读取操作会有IO异常

Thread.sleep(15000);

//读取文件中的数据

InputStream in = (InputStream)new FileInputStream("C:/fb1");

DataInput frameBuffer = new LittleEndianDataInputStream(in);

BufferedImage screenImage = new BufferedImage(xResolution, yResolution, BufferedImage.TYPE_INT_ARGB);

int[] oneLine = new int[xResolution];

for (int y = 0; y

//从frameBuffer中计算出rgb值

convertToRgba32(frameBuffer, oneLine);

//把rgb值设置到image对象中

screenImage.setRGB(0, y, xResolution, 1, oneLine, 0, xResolution);

}

Closeables.closeQuietly(in);

ByteArrayOutputStream rawPngStream= newByteArrayOutputStream();

try {

if (!ImageIO.write(screenImage, "png", rawPngStream)) {

throw new RuntimeException( "This Java environment does not support converting to PNG.");

}

} catch (IOException exception) {

// This should never happen because rawPngStream is an in-memory stream.

System.out.println("IOException=" + exception);

}

byte[] rawPngBytes = rawPngStream.toByteArray();

String base64Png = new Base64Encoder().encode(rawPngBytes);

File screenshot = OutputType.FILE.convertFromBase64Png(base64Png);

System.out.println("screenshot=="+ screenshot.toString());

screenshot.renameTo(new File("C:\\screenshottemp.png"));

} catch (IOException e) {

e.printStackTrace();

System.out.println(e);

}

}

public static void convertToRgba32(DataInput frameBuffer, int[] into) {

try {

for (int x= 0;x < into.length; x++) {

try{

int rgb= frameBuffer.readShort()& 0xffff;

int red= rgb>> 11;

red = (red<< 3) | (red>> 2);

int green = (rgb >> 5) & 63;

green = (green<< 2) | (green>> 4);

int blue = rgb & 31;

blue = (blue<< 3) | (blue>> 2);

into[x] = 0xff000000 | (red<< 16) | (green << 8) | blue;

}catch (EOFException e){

System.out.println("EOFException=" + e);

}

}

} catch (IOException exception) {

System.out.println("convertToRgba32Exception=" + exception);

}

}

}

(2)

首先是直接移植SystemUI的代码,实现截图效果,这部分的代码就不贴出来了,直接去下载代码吧, 关键的代码没有几句,最最主要的是:Surface.screenshot(),请看代码吧。

[java]package org.winplus.ss;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.text.SimpleDateFormat;

import java.util.Date;

import android.app.Activity;

import android.content.Context;

import android.graphics.Bitmap;

import android.graphics.Canvas;

import android.graphics.Matrix;

import android.os.Bundle;

import android.util.DisplayMetrics;

import android.util.Log;

import android.view.Display;

import android.view.Surface;

import android.view.WindowManager;

import android.os.SystemProperties;

public class SimpleScreenshotActivity extends Activity {

private Display mDisplay;

private WindowManager mWindowManager;

private DisplayMetrics mDisplayMetrics;

private Bitmap mScreenBitmap;

private Matrix mDisplayMatrix;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

new Thread(new Runnable() {

@Override

public void run() {

takeScreenshot();

}

}).start();

}

private float getDegreesForRotation(int value) {

switch (value) {

case Surface.ROTATION_90:

return 360f - 90f;

case Surface.ROTATION_180:

return 360f - 180f;

case Surface.ROTATION_270:

return 360f - 270f;

}

return 0f;

}

private void takeScreenshot() {

mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

mDisplay = mWindowManager.getDefaultDisplay();

mDisplayMetrics = new DisplayMetrics();

mDisplay.getRealMetrics(mDisplayMetrics);

mDisplayMatrix = new Matrix();

float[] dims = { mDisplayMetrics.widthPixels,

mDisplayMetrics.heightPixels };

int value = mDisplay.getRotation();

String hwRotation = SystemProperties.get("ro.sf.hwrotation", "0");

if (hwRotation.equals("270") || hwRotation.equals("90")) {

value = (value + 3) % 4;

}

float degrees = getDegreesForRotation(value);

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

}

mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);

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;

}

// If we couldn't take the screenshot, notify the user

if (mScreenBitmap == null) {

return;

}

// Optimizations

mScreenBitmap.setHasAlpha(false);

mScreenBitmap.prepareToDraw();

try {

saveBitmap(mScreenBitmap);

} catch (IOException e) {

System.out.println(e.getMessage());

}

}

public void saveBitmap(Bitmap bitmap) throws IOException {

String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss")

.format(new Date(System.currentTimeMillis()));

File file = new File("/mnt/sdcard/Pictures/"+imageDate+".png");

if(!file.exists()){

file.createNewFile();

}

FileOutputStream out;

try {

out = new FileOutputStream(file);

if (bitmap.compress(Bitmap.CompressFormat.PNG, 70, out)) {

out.flush();

out.close();

}

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

}package org.winplus.ss;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.text.SimpleDateFormat;

import java.util.Date;

import android.app.Activity;

import android.content.Context;

import android.graphics.Bitmap;

import android.graphics.Canvas;

import android.graphics.Matrix;

import android.os.Bundle;

import android.util.DisplayMetrics;

import android.util.Log;

import android.view.Display;

import android.view.Surface;

import android.view.WindowManager;

import android.os.SystemProperties;

public class SimpleScreenshotActivity extends Activity {

private Display mDisplay;

private WindowManager mWindowManager;

private DisplayMetrics mDisplayMetrics;

private Bitmap mScreenBitmap;

private Matrix mDisplayMatrix;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

new Thread(new Runnable() {

@Override

public void run() {

takeScreenshot();

}

}).start();

}

private float getDegreesForRotation(int value) {

switch (value) {

case Surface.ROTATION_90:

return 360f - 90f;

case Surface.ROTATION_180:

return 360f - 180f;

case Surface.ROTATION_270:

return 360f - 270f;

}

return 0f;

}

private void takeScreenshot() {

mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

mDisplay = mWindowManager.getDefaultDisplay();

mDisplayMetrics = new DisplayMetrics();

mDisplay.getRealMetrics(mDisplayMetrics);

mDisplayMatrix = new Matrix();

float[] dims = { mDisplayMetrics.widthPixels,

mDisplayMetrics.heightPixels };

int value = mDisplay.getRotation();

String hwRotation = SystemProperties.get("ro.sf.hwrotation", "0");

if (hwRotation.equals("270") || hwRotation.equals("90")) {

value = (value + 3) % 4;

}

float degrees = getDegreesForRotation(value);

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

}

mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);

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;

}

// If we couldn't take the screenshot, notify the user

if (mScreenBitmap == null) {

return;

}

// Optimizations

mScreenBitmap.setHasAlpha(false);

mScreenBitmap.prepareToDraw();

try {

saveBitmap(mScreenBitmap);

} catch (IOException e) {

System.out.println(e.getMessage());

}

}

public void saveBitmap(Bitmap bitmap) throws IOException {

String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss")

.format(new Date(System.currentTimeMillis()));

File file = new File("/mnt/sdcard/Pictures/"+imageDate+".png");

if(!file.exists()){

file.createNewFile();

}

FileOutputStream out;

try {

out = new FileOutputStream(file);

if (bitmap.compress(Bitmap.CompressFormat.PNG, 70, out)) {

out.flush();

out.close();

}

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

}

PS:1、需要在AndroidManifest.xml中加入代码:android:sharedUserId="android.uid.system"

2、由于调用了@hide的API,所以编译得时候请使用makefile编译。或者通过在Eclipse中添加Jar文件通过编译。

3、此代码只在Android4.0中使用过,2.3的就没去做测试了。

4 利用TakeScreenShotService截图

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

以我手头的测试手机为例,是同时按电源键+音量下键来实现截屏,苹果手机则是电源键 + HOME键,小米手机是菜单键+音量下键,而HTC一般是按住电源键再按左下角的“主页”键。那么Android源码中使用组合键是如何实现屏幕截图功能呢?前段时间由于工作的原因仔细看了一下,这两天不忙,便把相关的知识点串联起来整理一下,分下面两部分简单分析下实现流程:

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

Android源码中对按键的捕获位于文件PhoneWindowManager.java(alps\frameworks\base\policy\src\com\android\internal\policy\impl)中,这个类处理所有的键盘输入事件,其中函数interceptKeyBeforeQueueing()会对常用的按键做特殊处理。以我手头的测试机为例,是同时按电源键和音量下键来截屏,那么在这个函数中我们会看到这么两段代码:

.......

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()中,那么接下来看看这个函数干了什么工作:

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毫秒之后,会进入如下函数:

private final Runnable mScreenshotChordLongPress = new Runnable() {

public void run() {

takeScreenshot();

}

};

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

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,看看其实现的流程:

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中(跟之前的函数重名但是文件路径不一样):

/**

* 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方法了:

/**

* 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++语言了,统一调用的底层函数是:

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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android 平台上,可以使用 Lua 脚本实现屏幕截图,但需要依赖第三方库,如 luajava 或 slua。以下是一个示例代码,用于在 Android 平台上使用 Lua 实现屏幕截图: 1. 首先,在 Android 应用的 Java 代码,创建一个 `takeScreenshot` 方法,用于实现屏幕截图: ```java public Bitmap takeScreenshot() { View rootView = getWindow().getDecorView().getRootView(); rootView.setDrawingCacheEnabled(true); Bitmap bitmap = Bitmap.createBitmap(rootView.getDrawingCache()); rootView.setDrawingCacheEnabled(false); return bitmap; } ``` 2. 在 Lua 脚本,调用 `luajava.bindClass` 函数将 Java 类绑定到 Lua : ```lua -- 加载 luajava 库 local luajava = require("luajava") -- 绑定 MainActivity 类 local MainActivity = luajava.bindClass("com.package.name.MainActivity") ``` 3. 调用 `MainActivity:takeScreenshot()` 方法获取屏幕截图: ```lua -- 调用 takeScreenshot 方法获取屏幕截图 local bitmap = MainActivity:takeScreenshot() ``` 以上代码,通过 `luajava.bindClass` 函数将 Java 的 `MainActivity` 类绑定到 Lua 。在 Lua ,可以通过 `MainActivity:takeScreenshot()` 调用 `takeScreenshot` 方法获取屏幕截图。 需要注意的是,在 Android 应用集成 Lua,需要使用第三方库,如 luajava 或 slua。可以通过 Gradle 依赖管理工具,在 Android 应用添加 luajava 或 slua 的依赖库。同时,需要在 Android 应用的 AndroidManifest.xml 文件添加以下权限: ```xml <uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> ``` 这是因为 Android 平台上,需要获取读取帧缓冲区的权限才能够实现屏幕截图

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值