【Android】多种方式实现截图(屏幕截图、View截图、长图)

2025-05-12 21:59:51

目录

一、截图原理二、实现方式1. View截图2. WebView截图3. 屏幕截图

三、格式转换方法

一、截图原理

我们的手机一般同时按下音量-键和电源键就会将当前屏幕显示的内容截取下来,那里面具体经过哪些流程呢?

Android中每一个页面都是一个Activity,通过Window对象实现页面的显示,每个Window对象实际上都是PhoneWindow的实例,当我们在Activity页面点击屏幕的时候,会触发点击事件,这个事件会一层层分发到处理它的view上,大致会经过这些view: 先会调PhoneWindowManager中的dispatchUnhandledKey方法,一层层往下,这里不详细展开,我们往下找会发现最终会调用一个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.bindServiceAsUser(

intent, conn, Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {

mScreenshotConnection = conn;

mHandler.postDelayed(mScreenshotTimeout, 10000);

}

}

}

这里通过反射机制调用了TakeScreenshotService的bindServiceAsUser方法,创建TakeScreenshotService服务,再通过其内部的SurfaceControl.screenshot 生成 bitmap,生成图片成功会给系统发送通知。

系统截图的大致流程就是这样,在里面截图原理大致就是:获取需要截屏的区域的宽高,创建一个画布,然后区域内的内容绘制在画布上,最后生成bitmap图片。

二、实现方式

Android 截图主要为四种:View 截图、WebView 截图、屏幕截图、系统截图和 adb 截图。后两种截图不常用,不详细展开。

1. View截图

可以截取到View不可见的部分,生成长图,状态栏和导航栏无法截到

fun screenshotView(view: ViewGroup):Bitmap?{

var h = 0

var bitmap:Bitmap?=null

for(i in 0 until view.childCount){

h += view.getChildAt(i).height

view.getChildAt(i).setBackgroundColor(Color.parseColor("#6CC287"))

}

bitmap = Bitmap.createBitmap(view.width, h, Bitmap.Config.RGB_565)

val canvas = Canvas(bitmap)

view.draw(canvas)

//重新赋色

for(i in 0 until view.childCount){

view.getChildAt(i).setBackgroundDrawable(null)

}

return bitmap

}

2. WebView截图

WebView 作为一种特殊的控件,不能像其他系统 View 或者截屏的方式来截图,有特定的Api

// 1.capturePicture方法废弃

// 2.getScale方法废弃

// 3.getDrawingCache方法

private static byte[] screenshotWebView() {

Bitmap bitmap = webView.getDrawingCache();

byte[] drawByte = getBitmapByte(bmp);

return drawByte;

}

// 4.draw方法

private static byte[] screenshotWebView() {

// webView.setDrawingCacheEnabled(true); 设置缓存

Bitmap bitmap = Bitmap.createBitmap(webView.getWidth(), webView.getHeight(), Bitmap.Config.ARGB_8888);

Canvas canvas = new Canvas(bitmap);

webView.draw(canvas);

webView.destroyDrawingCache();

byte[] drawByte = getBitmapByte(bitmap);

return drawByte;

}

可能会截取不到 cavans 元素,原因是开启了硬件加速(关闭硬件加速可能导致页面异常),可在 AndroidManifest.xml 中设置:

android:hardwareAccelerated="false"

截长图的话需要配置:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

WebView.enableSlowWholeDocumentDraw();

}

setContentView(R.layout.webview);

3. 屏幕截图

截取应用当前屏幕的图片:

/**

* 获取当前屏幕截图,包含状态栏

*

* @param activity activity

* @return Bitmap

*/

public static Bitmap captureWithStatusBar(Activity activity) {

View view = activity.getWindow().getDecorView();

view.setDrawingCacheEnabled(true);

view.buildDrawingCache();

Bitmap bmp = view.getDrawingCache();

int width = getScreenWidth(activity);

int height = getScreenHeight(activity);

Bitmap ret = Bitmap.createBitmap(bmp, 0, 0, width, height);

view.destroyDrawingCache();

return ret;

}

/**

* 获取当前屏幕截图,不包含状态栏

*

* @param activity activity

* @return Bitmap

*/

public static Bitmap captureWithoutStatusBar(Activity activity) {

View view = activity.getWindow().getDecorView();

view.setDrawingCacheEnabled(true);

view.buildDrawingCache();

Bitmap bmp = view.getDrawingCache();

int statusBarHeight = getStatusBarHeight(activity);

int width = getScreenWidth(activity);

int height = getScreenHeight(activity);

Bitmap ret = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height - statusBarHeight);

view.destroyDrawingCache();

return ret;

}

/**

* 得到屏幕的高

*

* @param context

* @return

*/

public static int getScreenHeight(Context context) {

WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

int height = wm.getDefaultDisplay().getHeight();

return height;

}

/**

* 得到屏幕的宽

*

* @param context

* @return

*/

public static int getScreenWidth(Context context) {

WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

int width = wm.getDefaultDisplay().getWidth();

return width;

}

/**

* 获取状态栏高度

*

* @param context 上下文

* @return 状态栏高度

*/

public static int getStatusBarHeight(Context context) {

int result = 0;

int resourceId = context.getResources()

.getIdentifier("status_bar_height", "dimen", "android");

if (resourceId > 0) {

result = context.getResources().getDimensionPixelSize(resourceId);

}

return result;

}

三、格式转换方法

下面列出了一些常用的转换方法:

// Bitmap 转 Base64

private static String getBitmapString(Bitmap bitmap) {

String result = null;

ByteArrayOutputStream out = null;

try {

if (bitmap != null) {

out = new ByteArrayOutputStream();

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

out.flush();

out.close();

byte[] bitmapBytes = out.toByteArray();

result = Base64.encodeToString(bitmapBytes, Base64.DEFAULT);

}

} catch (IOException e) {

e.printStackTrace();

} finally {

try {

if (out != null) {

out.flush();

out.close();

}

} catch (IOException e) {

e.printStackTrace();

}

}

return result;

}

// Bitmap 转 Byte

private static byte[] getBitmapByte(Bitmap bitmap){

ByteArrayOutputStream out = new ByteArrayOutputStream();

// 转换类型,压缩质量,字节流资源

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

try {

out.flush();

out.close();

} catch (IOException e) {

e.printStackTrace();

}

return out.toByteArray();

}

// Drawable 转 Bitmap

public static Bitmap toBitmap(Drawable drawable) {

if (drawable instanceof BitmapDrawable) {

return ((BitmapDrawable) drawable).getBitmap();

} else if (drawable instanceof ColorDrawable) {

//color

Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);

Canvas canvas = new Canvas(bitmap);

canvas.drawColor(((ColorDrawable) drawable).getColor());

return bitmap;

} else if (drawable instanceof NinePatchDrawable) {

//.9.png

Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),

drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);

Canvas canvas = new Canvas(bitmap);

drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());

drawable.draw(canvas);

return bitmap;

}

return null;

}