Android 札记系列 (11):视频预加载库、屏幕截图和ADB 录屏

文章发布于我的博客:Android 札记系列 (11):视频预加载库、屏幕截图和ADB 录屏

屏幕截图

在 Android 中,实现屏幕截图没有官方的接口。所以我们需要另辟蹊径来获取『截图』。

View.getDrawingCache()

我们通过使用View.getDrawingCache()来获取当前view的缓存,然后将它存储到bitmap中。

用法

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(true);
Bitmap bitmap = view.getDrawingCache();
view.setDrawingCacheEnabled(false);
复制代码

先调用setDrawingCacheEnabled(true)开启缓存。这个方法实现里,设置了一个标志位DRAWING_CACHE_ENABLED,后续再getDrawingCache()的时候,会判断一下。 接着这里调了measure()layout()方法,强制该视图重新测量和布局,这样避免之前没有缓存而返回空的情况。 然后调用getDrawingCache()得到视图的缓存。

我们可以一起看一下源码实现,从getDrawingCache(boolean)进去,判断了一下DRAWING_CACHE_ENABLED标志位之后,调用了真正的实现buildDrawingCacheImpl()方法。 定位到View.java中的buildDrawingCacheImpl()方法 这个方法的逻辑就是:

  1. 通过AttachInfo这个类,来获取当前视图的信息

    A set of information given to a view when it is attached to its parent window

  2. 建立一个空bitmap

...
final AttachInfo attachInfo = mAttachInfo;
final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired;

if (autoScale && scalingRequired) {
    width = (int) ((width * attachInfo.mApplicationScale) + 0.5f);
    height = (int) ((height * attachInfo.mApplicationScale) + 0.5f);
}

final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;
final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque();
final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache;

final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);
final long drawingCacheSize =
        ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();
复制代码

这里做了一些判断,也就是是否需要对视图进行缩放(scale)。根据视图的宽高、背景透明度和使用的缓存位数来得到bitmap所占据的内存空间大小(projectedBitmapSize)。

try {
    bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
            width, height, quality);
    bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
    if (autoScale) {
        mDrawingCache = bitmap;
    } else {
        mUnscaledDrawingCache = bitmap;
    }
    if (opaque && use32BitCache) bitmap.setHasAlpha(false);
}
复制代码

这里对bitmap进行设置,包括屏幕的尺寸(mResources.getDisplayMetrics())、bitmap的宽高,压缩质量quality

然后根据前面setDrawingCache(boolean)传入的布尔值判断是否autoScale,如果是,将当前bitmap赋给mDrawingCahce,否则赋给mUnscaledDrawingCache。 通过opaqueuse32BitCache两个变量(是否透明、是否使用 32 位缓存)来设置bitmap的透明度。

实际上到这一步依旧没有获取到当前的视图的内容,这里基本都是在设置存放缓存的bitmap

下面开始把当前视图画上去:

Canvas canvas;
if (attachInfo != null) {
    canvas = attachInfo.mCanvas;
    if (canvas == null) {
        canvas = new Canvas();
    }
    canvas.setBitmap(bitmap);
    // Temporarily clobber the cached Canvas in case one of our children
    // is also using a drawing cache. Without this, the children would
    // steal the canvas by attaching their own bitmap to it and bad, bad
    // thing would happen (invisible views, corrupted drawings, etc.)
    attachInfo.mCanvas = null;
} else {
    // This case should hopefully never or seldom happen
    canvas = new Canvas(bitmap);
}

if (clear) {
    bitmap.eraseColor(drawingCacheBackgroundColor);
}

computeScroll();
final int restoreCount = canvas.save();

if (autoScale && scalingRequired) {
    final float scale = attachInfo.mApplicationScale;
    canvas.scale(scale, scale);
}

canvas.translate(-mScrollX, -mScrollY);

mPrivateFlags |= PFLAG_DRAWN;
if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
        mLayerType != LAYER_TYPE_NONE) {
    mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
}

// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    dispatchDraw(canvas);
    drawAutofilledHighlight(canvas);
    if (mOverlay != null && !mOverlay.isEmpty()) {
        mOverlay.getOverlayView().draw(canvas);
    }
} else {
    draw(canvas);
}

canvas.restoreToCount(restoreCount);
canvas.setBitmap(null);

if (attachInfo != null) {
    // Restore the cached Canvas for our siblings
    attachInfo.mCanvas = canvas;
}
复制代码

这里使用了一个Canvas类,把之前的bitmap赋给Canvas,一直到draw(canvas)从才算是真正才开画。

也就是说,真正画上去的部分,实际在draw(Canvas canvas)函数里实现。这个函数比较长,我挑一些对我有利的来讲 ∠( ᐛ 」∠)_

我们先看一下注释:

Manually render this view (and all of its children) to the given Canvas. The view must have already done a full layout before this function is called. When implementing a view, implement

可以看到,这里是说,把当前视图及其子视图,手动赋给传入的canvas变量,并且这个视图必须已经完成了layout步骤。 所以我们在最开始用的时候,最好就主动调用measure()layout()这两个方法。

再看注释:

/* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */

其实后续的源码已经分别标注了是哪一部分了。 先画背景,如果需要的情况下,保存canvas的层次状态,然后在第 5 步恢复,正常情况下忽略这两步。 接着画视图的内容、子视图,最后画其余挂件(滚动条等)

在这里我就不一一列出来了。有时间再把它仔细解析清楚(#Flag #坑)。

Tips

  1. 调用的时候尽量主动调用measure()layout()方法
  2. 如果调用了setDrawingCache(true)那就不需要自己destroyDrawingCache()
  3. getDrawingCache()并不是最好的方法,因为它的性能稍差。原本对视图进行缓存是为了提高性能,但是这种方式在配置好、开启了硬件加速的机器上来说已经没那么必要了,这个方法在 API 28 也被废弃了,转而使用getPixel()
  4. 对于继承Camcera或大部分视频播放视图,这个方法无法获取到那些视图的缓存。因为他们在底层都是用了另一个SurfaceView来渲染,这和当前View不在同一个SurfaceViwe

参看

ADB 录屏

adb shell screenrecord /sdcard/demo.mp4
复制代码

参看

AndroidVideoCache

AndroidVideoCache一个视频预缓存库,通过一个本地代理来把网络视频流操作得想本地文件流一样舒服~

附上一个主动Cache的示例。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值