最近又遇到了截圖相關的需求,聯合之前的截圖需求后,抽時間整理了一下截屏的常見需求,特此記錄 ~
關聯篇
- 花樣百出的截屏需求
- View、Activity實時截圖、截屏
- 專案全域動態實時截屏,獲取用戶當前操作行為
針對每個人不同的需求,可采用不同的方法 - - ~
- 截取當前螢屏界面(activity)
- 截取某個控制元件或區域(view)
- 截取長屏
- ScrollView 實作截屏
- ListView實作截屏
- WebView實作截屏
- 區域、全域實時截屏
- 圖片壓縮
- 截圖、壓縮
- 保存圖片到本地
- 截屏擴展
- 截取帶導航欄的整個螢屏
- 截取非含當前應用的螢屏部分(最佳官方方案)
截取當前螢屏界面(activity)
一般呼叫對應方法時出現呼叫不到的場景時,我們需要根據提示進行修改,這里應該需要通過activity進行方法呼叫 ~
別人的方法
public Bitmap screen() {
View dView = getWindow().getDecorView();
dView.setDrawingCacheEnabled(true);
dView.buildDrawingCache();
return Bitmap.createBitmap(dView.getDrawingCache());
}
我的方法 - activity截圖
//缺點:無法截取WebView頁面,截屏后是白屏!
public static Bitmap capture(Activity activity) {
activity.getWindow().getDecorView().setDrawingCacheEnabled(true);
Bitmap bmp = activity.getWindow().getDecorView().getDrawingCache();
return bmp;
}
截圖并保存
public void screen() {
View dView = getWindow().getDecorView();
dView.setDrawingCacheEnabled(true);
dView.buildDrawingCache();
Bitmap bitmap = Bitmap.createBitmap(dView.getDrawingCache());
if(bitmap !=null ) {
try {
// 獲取內置SD卡路徑
String sdCardPath = Environment.getExternalStorageDirectory().getPath();
// 圖片檔案路徑(自行定義)
String filePath = sdCardPath + File.separator +"screenshot.png";
File file =new File(filePath);
FileOutputStream os = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG,100, os);
os.flush();
os.close();
} catch(Exception e) {
}
}
}
截取某個控制元件或區域(view)
可作用于根view或某個控制元件view
- 簡潔版
/**
* 截取指定View為圖片
*/
public static Bitmap captureView(View view) throws Throwable {
Bitmap bm = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
view.draw(new Canvas(bm));
return bm;
}
截圖并保存
public void captureView(View view) {
//圖片存盤路徑,可以不關注
String imgPath = "/sdcard/test.png";
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bitmap = view.getDrawingCache();
//圖片存盤部分可以不關注
if (bitmap != null) {
try {
FileOutputStream out = new FileOutputStream(imgPath);
bitmap.compress(Bitmap.CompressFormat.PNG, 100,
out);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 繁瑣版
public void screen(View view){
View dView = view;
dView.setDrawingCacheEnabled(true);
dView.buildDrawingCache();
//以下倆種方式,一種是取圖片快取,一種是重新繪制,可自行調節嘗試
//Bitmap bitmap = Bitmap.createBitmap(dView.getDrawingCache());
Bitmap bitmap = Bitmap.createBitmap(dView.getWidth(), dView.getHeight(), Bitmap.Config.ARGB_8888);
//使用Canvas,呼叫自定義view控制元件的onDraw方法,繪制圖片
Canvas canvas =new Canvas(bitmap);
dView.draw(canvas);
}
- view截圖(有人說截取view轉bitmap后為null,可用這種方式清理快取,
未嘗試)
public static Bitmap getViewBp(View v) {
if (null == v) {
return null;
}
v.setDrawingCacheEnabled(true);
v.buildDrawingCache();
if (Build.VERSION.SDK_INT >= 11) {
v.measure(View.MeasureSpec.makeMeasureSpec(v.getWidth(),
View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(
v.getHeight(), View.MeasureSpec.EXACTLY));
v.layout((int) v.getX(), (int) v.getY(),
(int) v.getX() + v.getMeasuredWidth(),
(int) v.getY() + v.getMeasuredHeight());
} else {
v.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
}
Bitmap b = Bitmap.createBitmap(v.getDrawingCache(), 0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
v.setDrawingCacheEnabled(false);
v.destroyDrawingCache();
return b;
}
- view截圖(有人說截取view轉bitmap后為null,可用這種方式清理快取,
未嘗試)
public static Bitmap convertViewToBitmap(View view) {
view.setDrawingCacheEnabled(true);
view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
view.buildDrawingCache();
Bitmap bitmap = view.getDrawingCache();
view.setDrawingCacheEnabled(false);
return bitmap;
}
截取長屏
? 截取長屏其實原理就是截取整個ScrollView或者ListView的視圖,因此實作原理跟上面中提到的截取某個控制元件的View基本一致,
ScrollView 實作截屏
/*
* 截取scrollview的螢屏
**/
public static Bitmap getScrollViewBitmap(ScrollView scrollView) {
int h = 0;
Bitmap bitmap;
for ( int i = 0 ; i < scrollView.getChildCount(); i++) {
h += scrollView.getChildAt(i).getHeight();
}
// 創建對應大小的bitmap
bitmap = Bitmap.createBitmap(scrollView.getWidth(), h,Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
scrollView.draw(canvas);
return bitmap;
}
ListView實作截屏
/**
* 截圖listview
**/
public static Bitmap getListViewBitmap(ListView listView) {
int h = 0;
Bitmap bitmap;
// 獲取listView實際高度
for (int i = 0 ; i < listView.getChildCount(); i++) {
h += listView.getChildAt(i).getHeight();
}
Log.d(TAG, "實際高度:" + h);
Log.d(TAG, "list 高度:" + listView.getHeight());
// 創建對應大小的bitmap
bitmap = Bitmap.createBitmap(listView.getWidth(), h,
Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
listView.draw(canvas);
return bitmap;
}
WebView實作截屏
//這是webview的,利用了webview的api
private static Bitmap captureWebView(WebView webView) {
Picture snapShot = webView.capturePicture();
Bitmap bmp = Bitmap.createBitmap(snapShot.getWidth(),
snapShot.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
snapShot.draw(canvas);
return bmp;
}
區域、全域實時截屏
關于應用內全域截屏的需求,我在 如何優雅的實時獲取用戶操作界面 中詳細的解釋了使用方式
在日常開發中,一般截屏后的圖片,我們都會進行壓縮、保存,這里一并進行記錄
圖片壓縮
/**
* 壓縮圖片
*
* @param bgimage
* @param newWidth
* @param newHeight
* @return
*/
public static Bitmap zoomImage(Bitmap bgimage, double newWidth, double newHeight) {
// 獲取這個圖片的寬和高
float width = bgimage.getWidth();
float height = bgimage.getHeight();
// 創建操作圖片用的matrix物件
Matrix matrix = new Matrix();
// 計算寬高縮放率
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// 縮放圖片動作
//matrix.postScale(scaleWidth, scaleHeight);//TODO 因為寬高不確定的因素,所以不縮放
Bitmap bitmap = Bitmap.createBitmap(bgimage, 0, 0, (int) width,
(int) height, matrix, true);
return bitmap;
}
截圖、壓縮
Bitmap bitmap = null;
try {
bitmap = captureView(mShareBackgroundSign);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
//圖片壓縮,加快使用速度~
zoomImage(bitmap, 720, 1280);
保存圖片到本地
注意:切記適配6.0、7.0
public static void savePhotoToSDCard(Bitmap photoBitmap, String path, String photoName) {
if (checkSDCardAvailable()) {
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
File photoFile = new File(path, photoName + ".png");
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(photoFile);
if (photoBitmap != null) {
if (photoBitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)) {
fileOutputStream.flush();
}
}
} catch (FileNotFoundException e) {
photoFile.delete();
e.printStackTrace();
} catch (IOException e) {
photoFile.delete();
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
截屏擴展
以下部分其實我并未在有限的開發中使用到,僅當做個人興趣的知識延伸
截取帶導航欄的整個螢屏
采用adb命令方式截屏的操作,屈指可數,可當做知識擴展,沒有必要深讀
- 優點:只需在代碼中執行截屏的命令即可,可根據需求封裝一個方法,傳入保存的路徑和檔案名即可;
- 缺點:手機需要獲取ROOT權限,呼叫命令之前需要先請求su獲取ROOT權限;
adb 命令:這里指的不是連接電腦進行adb操控,而是在App內部實作adb命令的操控
在APK中呼叫 "adb shell screencap -p filepath" 命令
注意:該命令讀取系統的framebuffer,需要獲得系統權限
- 在AndroidManifest.xml檔案中添加
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
- 修改APK為系統權限,將APK放到原始碼中編譯, 修改Android.mk
LOCAL_CERTIFICATE:= platform
public void takeScreenShot () {
String mSavedPath = Environment.getExternalStorageDirectory() + File.separator + "screenshot.png";
try {
Runtime.getRuntime().exec("screencap -p " + mSavedPath);
} catch (Exception e) {
e.printStackTrace();
}
}
利用系統的隱藏API,實作Screenshot,這部分代碼是系統隱藏的,需要在原始碼下編譯,
- 修改Android.mk, 添加系統權限
LOCAL_CERTIFICATE := platform
- 修改AndroidManifest.xml 檔案,添加權限
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
Android本地編程(Native Programming)讀取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命令來實作,
各大手機自帶的按鍵組合進行截屏,Android原始碼中對按鍵的捕獲位于檔案PhoneWindowManager.java(alps\frameworks\base\policy\src\com\android\internal\policy\impl)中,這個類處理所有的鍵盤輸入事件,其中函式interceptKeyBeforeQueueing()會對常用的按鍵做特殊處理,
截取非含當前應用的螢屏部分(最佳官方方案)
Android 在5.0 之后支持了實時錄屏的功能;通過實時錄屏我們可以拿到截屏的影像,同時可以通過在Service中處理實作后臺的錄屏(具體的類講解大家自行網上查閱)
類似使用手機的系統截屏(音量下鍵+電源鍵),針對的并非一個view,而是整個螢屏,在呼叫這個服務的時候,會彈出一個權限確認的彈框;同時需注意,這一方法只能在Android 5.0的系統設備上使用,
詳細步驟
- 初始化一個MediaProjectionManager
MediaProjectionManager mMediaProjectionManager = (MediaProjectionManager)getApplication().getSystemService(Context.MEDIA_PROJECTION_SERVICE);
- 創建intent,并啟動Intent(注意:這里是startActivityForResult)
startActivityForResult(mMediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);
- 在onActivityResult中拿到Mediaprojection
mResultCode = resultCode;
mResultData = data;
mMediaProjection = mMediaProjectionManager.getMediaProjection(mResultCode, mResultData);
- 設定VirtualDisplay 將影像和展示的View關聯起來;一般來說我們會將影像展示到SurfaceView,這里為了為了便于拿到截圖,我們使用ImageReader,他內置有SurfaceView
mImageReader = ImageReader.newInstance(windowWidth, windowHeight, 0x1 ,2);
//ImageFormat.RGB_565
mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror" ,windowWidth, windowHeight, mScreenDensity,DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mImageReader.getSurface(), null , null);
- 通過ImageReader拿到截圖
strDate = dateFormat.format(new java.util.Date());
nameImage = pathImage+strDate+ ".png";
Image image = mImageReader.acquireLatestImage();
int width = image.getWidth();
int height = image.getHeight();
final Image.Plane[] planes = image.getPlanes();
final ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * width;
Bitmap bitmap = Bitmap.createBitmap(width+rowPadding/pixelStride, height, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
bitmap = Bitmap.createBitmap(bitmap, 0, 0 ,width, height);
image.close();
- 注意截屏之后要及時關閉VirtualDisplay ,因為VirtualDisplay 是十分消耗記憶體和電量的
if(mVirtualDisplay == null) {
return ;
}
mVirtualDisplay.release();
mVirtualDisplay = null;
常用步驟
- 在Activity中開啟截屏服務
- 鏈式
if (Build.VERSION.SDK_INT >= 21) {
startActivityForResult(((MediaProjectionManager) getSystemService("media_projection")).createScreenCaptureIntent(), 1);
} else {
Log.e("TAG", "版本過低,無法截屏");
}
- 平常
final MediaProjectionManager projectionManager = (MediaProjectionManager)
getSystemService(Context.MEDIA_PROJECTION_SERVICE);
Intent intent = projectionManager.createScreenCaptureIntent();
startActivityForResult(intent, REQUEST_CODE);
- 重寫onActivityResult方法
- 鏈式 - 回傳結果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1 && data != null) {
parseData(data);
}
}
private void parseData(Intent data){
MediaProjection mMediaProjection = (MediaProjectionManager).getSystemService(
Context.MEDIA_PROJECTION_SERVICE).getMediaProjection(Activity.RESULT_OK,data);
ImageReader mImageReader = ImageReader.newInstance(
getScreenWidth(),
getScreenHeight(),
PixelFormat.RGBA_8888,1);
VirtualDisplay mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",
getScreenWidth(),
getScreenHeight(),
Resources.getSystem().getDisplayMetrics().densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mImageReader.getSurface(), null, null);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
Image image = mImageReader.acquireLatestImage();
// TODO 將image保存到本地即可
}
}, 300);
mVirtualDisplay.release();
mVirtualDisplay = null;
}
- 平常 - 回傳結果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
handleScreenShotIntent(resultCode, data);
}
private void handleScreenShotIntent(int resultCode, Intent data) {
onScreenshotTaskBegan();
final MediaProjectionManager projectionManager = (MediaProjectionManager)
getSystemService(Context.MEDIA_PROJECTION_SERVICE);
final MediaProjection mProjection = projectionManager.getMediaProjection(resultCode, data);
Point size = Utils.getScreenSize(this);
final int mWidth = size.x;
final int mHeight = size.y;
final ImageReader mImageReader = ImageReader.newInstance(mWidth, mHeight, PixelFormat
.RGBA_8888, 2);
final VirtualDisplay display = mProjection.createVirtualDisplay("screen-mirror", mWidth,
mHeight, DisplayMetrics.DENSITY_MEDIUM,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION, mImageReader.getSurface(),
null, null);
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader mImageReader) {
Image image = null;
try {
image = mImageReader.acquireLatestImage();
if (image != null) {
final Image.Plane[] planes = image.getPlanes();
if (planes.length > 0) {
final ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * mWidth;
// create bitmap
Bitmap bmp = Bitmap.createBitmap(mWidth + rowPadding / pixelStride,
mHeight, Bitmap.Config.ARGB_8888);
bmp.copyPixelsFromBuffer(buffer);
Bitmap croppedBitmap = Bitmap.createBitmap(bmp, 0, 0, mWidth, mHeight);
//保存圖片
saveBitmap(croppedBitmap);
if (croppedBitmap != null) {
croppedBitmap.recycle();
}
if (bmp != null) {
bmp.recycle();
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (image != null) {
image.close();
}
if (mImageReader != null) {
mImageReader.close();
}
if (display != null) {
display.release();
}
mImageReader.setOnImageAvailableListener(null, null);
mProjection.stop();
onScreenshotTaskOver();
}
}
}, getBackgroundHandler());
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/293381.html
標籤:其他
