一、截图原理
我们的手机一般同时按下音量-键和电源键就会将当前屏幕显示的内容截取下来,那里面具体经过哪些流程呢?
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;
}