(1)主要就是利用SDK提供的View.getDrawingCache()方法。網上已經有不少的實例了。首先建立一個android project,而後進行Layout,畫一個按鍵(res/layout/main.xml):java
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
android:orientation
=
"vertical"
android:layout_width
=
"fill_parent"
android:layout_height
=
"fill_parent"
>
<
TextView
android:layout_width
=
"fill_parent"
android:layout_height
=
"wrap_content"
android:text
=
"@string/hello"
/>
<
Button
android:text
=
"NiceButton"
android:id
=
"@+id/my_button"
android:layout_width
=
"fill_parent"
android:layout_height
=
"wrap_content"
android:layout_alignParentBottom
=
"true"
></
Button
>
</
LinearLayout
>
|
HelloAndroid.java實現代碼爲:android
package
com.example.helloandroid;
import
java.io.FileOutputStream;
import
java.text.SimpleDateFormat;
import
java.util.Date;
import
java.util.Locale;
import
android.app.Activity;
import
android.graphics.Bitmap;
import
android.os.Bundle;
import
android.view.View;
import
android.view.View.OnClickListener;
import
android.widget.Button;
public
class
HelloAndroid
extends
Activity {
private
Button button;
/** Called when the activity is first created. */
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
this
.setContentView(R.layout.main);
this
.button = (Button)
this
.findViewById(R.id.my_button);
this
.button.setOnClickListener(
new
OnClickListener() {
public
void
onClick(View v) {
SimpleDateFormat sdf =
new
SimpleDateFormat(
"yyyy-MM-dd_HH-mm-ss"
, Locale.US);
String fname =
"/sdcard/"
+ sdf.format(
new
Date()) +
".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 =
new
FileOutputStream(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截屏文件。c++
這種截屏有一個問題,就是隻能截到一部分,好比電池指示部分就截不出來了。web
(2)在APK中調用「adb shell screencap -pfilepath」 命令shell
(1). 在AndroidManifest.xml文件中添加< uses-permission android:name = "android.permission.READ_FRAME_BUFFER" />(2). 修改APK爲系統權限,將APK放到源碼中編譯, 修改Android.mkLOCAL_CERTIFICATE := platform
publicvoid takeScreenShot(){編程
String mSavedPath = Environment.getExternalStorageDirectory()+File. separator + "screenshot.png" ;api
try { 緩存
Runtime. getRuntime().exec("screencap -p " + mSavedPath);app
} catch (Exception e) {框架
e.printStackTrace();
}
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
|
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
;
}
|
public class ScreenShot { private BufferedImage image = null; /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub AndroidDebugBridge.init(false); // ScreenShot screenshot = new ScreenShot(); IDevice device = screenshot.getDevice(); for (int i = 0; i < 10; i++) { Date date=new Date(); SimpleDateFormat df=new SimpleDateFormat("MM-dd-HH-mm-ss"); String nowTime = df.format(date); screenshot.getScreenShot(device, "Robotium" + nowTime); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public void getScreenShot(IDevice device,String filename) { RawImage rawScreen = null; try { rawScreen = device.getScreenshot(); } catch (TimeoutException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (AdbCommandRejectedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block 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 = new BufferedImage(width2, height2, BufferedImage.TYPE_INT_RGB); } else { if (image.getHeight() != height2 || image.getWidth() != width2) { image = new BufferedImage(width2, height2, BufferedImage.TYPE_INT_RGB); } } int index = 0; int indexInc = rawScreen.bpp >> 3; for (int y = 0; y < rawScreen.height; 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) { // TODO Auto-generated catch block 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; } } }
(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 < yResolution; y++) { //從frameBuffer中計算出rgb值 convertToRgba32(frameBuffer, oneLine); //把rgb值設置到image對象中 screenImage.setRGB(0, y, xResolution, 1, oneLine, 0, xResolution); } Closeables.closeQuietly(in); ByteArrayOutputStream rawPngStream = new ByteArrayOutputStream(); 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) { // TODO Auto-generated catch block 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] <SPAN style="FONT-SIZE: 16px">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(); } } } </SPAN> 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:一、須要在AndroidManifest.xml中加入代碼:android:sharedUserId="android.uid.system" 二、因爲調用了@hide的API,因此編譯得時候請使用makefile編譯。或者經過在Eclipse中添加Jar文件經過編譯。 三、此代碼只在Android4.0中使用過,2.3的就沒去作測試了。
Android手機通常都自帶有手機屏幕截圖的功能:在手機任何界面(固然手機要是開機點亮狀態),經過按組合鍵,屏幕閃一下,而後咔嚓一聲,截圖的照片會保存到當前手機的圖庫中,真是一個不錯的功能!
以我手頭的測試手機爲例,是同時按電源鍵+音量下鍵來實現截屏,蘋果手機則是電源鍵 + HOME鍵,小米手機是菜單鍵+音量下鍵,而HTC通常是按住電源鍵再按左下角的「主頁」鍵。那麼Android源碼中使用組合鍵是如何實現屏幕截圖功能呢?前段時間因爲工做的緣由仔細看了一下,這兩天不忙,便把相關的知識點串聯起來整理一下,分下面兩部分簡單分析下實現流程:
Android源碼中對按鍵的捕獲位於文件PhoneWindowManager.java(alps\frameworks\base\policy\src\com\android\internal\policy\impl)中,這個類處理全部的鍵盤輸入事件,其中函數interceptKeyBeforeQueueing()會對經常使用的按鍵作特殊處理。以我手頭的測試機爲例,是同時按電源鍵和音量下鍵來截屏,那麼在這個函數中咱們會看到這麼兩段代碼:
能夠看到正是在這裏(響應Down事件)捕獲是否按了音量下鍵和電源鍵的,並且兩個地方都會進入函數interceptScreenshotChord()中,那麼接下來看看這個函數幹了什麼工做:
在這個函數中,用兩個布爾變量判斷是否同時按了音量下鍵和電源鍵後,再計算兩個按鍵響應Down事件之間的時間差不超過150毫秒,也就認爲是同時按了這兩個鍵後,算是真正的捕獲到屏幕截屏的組合鍵。
附言:文件PhoneWindowManager.java類是攔截鍵盤消息的處理類,在此類中還有對home鍵、返回鍵等好多按鍵的處理。
捕獲到組合鍵後,咱們再看看android源碼中是如何調用屏幕截圖的函數接口。在上面的函數interceptScreenshotChord中咱們看到用handler判斷長按組合鍵500毫秒以後,會進入以下函數:
在這裏啓動了一個線程來完成截屏的功能,接着看函數takeScreenshot():
能夠看到這個函數使用AIDL綁定了service服務到"com.android.systemui.screenshot.TakeScreenshotService",注意在service鏈接成功時,對message的msg.arg1和msg.arg2兩個參數的賦值。其中在mScreenshotTimeout中對服務service作了超時處理。接着咱們找到實現這個服務service的類TakeScreenshotService,看看其實現的流程:
在這個類中,咱們主要看調用接口,用到了mScreenshot.takeScreenshot()傳遞了三個參數,第一個是個runnable,第二和第三個是以前message傳遞的兩個參數msg.arg1和msg.arg2。最後咱們看看這個函數takeScreenshot(),位於文件GlobalScreenshot.java中(跟以前的函數重名可是文件路徑不同):
這段代碼的註釋比較詳細,其實看到這裏,咱們算是真正看到截屏的操做了,具體的工做包括對屏幕大小、旋轉角度的獲取,而後調用Surface類的screenshot方法截屏保存到bitmap中,以後把這部分位圖填充到一個畫布上,最後再啓動一個延遲的拍照動畫效果。若是再往下探究screenshot方法,發現已是一個native方法了:
使用JNI技術調用底層的代碼,若是再往下走,會發現映射這這個jni函數在文件android_view_Surface.cpp中,這個真的已是底層c++語言了,統一調用的底層函數是:
因爲對C++不熟,我這裏就不敢多言了。其實到這裏,算是對手機android源碼中經過組合鍵屏幕截圖的整個流程有個大致瞭解了,通常咱們在改動中熟悉按鍵的捕獲原理,而且清楚調用的截屏函數接口便可,若是有興趣的,能夠繼續探究更深的底層是如何實現的。