2024年Android截屏方案,阿里面试官级别很高

尾声

开发是需要一定的基础的,我是08年开始进入Android这行的,在这期间经历了Android的鼎盛时期,和所谓的Android”凉了“。中间当然也有着,不可说的心酸,看着身边朋友,同事一个个转前端,换行业,其实当时我的心也有过犹豫,但是我还是坚持下来了,这次的疫情就是一个好的机会,大浪淘沙,优胜劣汰。再等等,说不定下一个黄金浪潮就被你等到了。

  • 330页 PDF Android核心笔记

  • 几十套阿里 、字节跳动、腾讯、华为、美团等公司2020年的面试题

  • PDF和思维脑图,包含知识脉络 + 诸多细节

  • Android进阶系统学习视频

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

//返回到顶部
while (mRecyclerView.canScrollVertically(-1)) {
mRecyclerView.scrollBy(0, -oneScreenHeight);
}

//绘制截图的背景
final Bitmap bigBitmap = Bitmap.createBitmap(mRecyclerView.getMeasuredWidth(), shotHeight, Bitmap.Config.ARGB_8888);
final Canvas bigCanvas = new Canvas(bigBitmap);
Drawable lBackground = mRecyclerView.getBackground();
if (lBackground instanceof ColorDrawable) {
ColorDrawable lColorDrawable = (ColorDrawable) lBackground;
int lColor = lColorDrawable.getColor();
bigCanvas.drawColor(lColor);
}

final int[] drawOffset = {0};
final Canvas canvas = new Canvas();
if (shotHeight <= oneScreenHeight) {
//仅有一页
Bitmap bitmap = Bitmap.createBitmap(mRecyclerView.getWidth(), mRecyclerView.getHeight(), Bitmap.Config.ARGB_8888);
canvas.setBitmap(bitmap);
mRecyclerView.draw(canvas);
if (callBack != null)
callBack.onRecFinished(bitmap);
} else {
//超过一页
final int finalShotHeight = shotHeight;
mRecyclerView.postDelayed(new Runnable() {
@Override
public void run() {
if ((drawOffset[0] + oneScreenHeight < finalShotHeight)) {
//超过一屏
Bitmap bitmap = Bitmap.createBitmap(mRecyclerView.getWidth(), mRecyclerView.getHeight(), Bitmap.Config.ARGB_8888);
canvas.setBitmap(bitmap);
mRecyclerView.draw(canvas);
bigCanvas.drawBitmap(bitmap, 0, drawOffset[0], paint);
drawOffset[0] += oneScreenHeight;
mRecyclerView.scrollBy(0, oneScreenHeight);
try {
bitmap.recycle();
} catch (Exception ex) {
ex.printStackTrace();
}
mRecyclerView.postDelayed(this, 10);
} else {
//不足一屏时的处理
int leftHeight = finalShotHeight - drawOffset[0] - footHight;
mRecyclerView.scrollBy(0, leftHeight);
int top = oneScreenHeight - (finalShotHeight - drawOffset[0]);
if (top > 0 && leftHeight > 0) {
Bitmap bitmap = Bitmap.createBitmap(mRecyclerView.getWidth(), mRecyclerView.getHeight(), Bitmap.Config.ARGB_8888);
canvas.setBitmap(bitmap);
mRecyclerView.draw(canvas);
//截图,只要补足的那块图
bitmap = Bitmap.createBitmap(bitmap, 0, top, bitmap.getWidth(), leftHeight, null, false);
bigCanvas.drawBitmap(bitmap, 0, drawOffset[0], paint);
try {
bitmap.recycle();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (callBack != null)
callBack.onRecFinished(bigBitmap);
}
}
}, 10);
}
}
}

public interface RecycleViewRecCallback {
void onRecFinished(Bitmap bitmap);
}

相信有不少小伙伴用BRVH第三方库来做recycleview的适配器的。使用这个库的话再用上面的方法会报角标越界的错误,看了BRVH的源码

public void onBindViewHolder(ViewHolder holder, int positions) {
int viewType = holder.getItemViewType();
switch(viewType) {
case 0:
this.convert((BaseViewHolder)holder, this.mData.get(holder.getLayoutPosition() - this.getHeaderLayoutCount()));
case 273:
case 819:
case 1365:
break;
case 546:
this.addLoadMore(holder);
break;
default:
this.convert((BaseViewHolder)holder, this.mData.get(holder.getLayoutPosition() - this.getHeaderLayoutCount()));
this.onBindDefViewHolder((BaseViewHolder)holder, this.mData.get(holder.getLayoutPosition() - this.getHeaderLayoutCount()));
}

}

在调用adapter.onBindViewHolder时,因为里面的position参数未使用,里面用的计算holder.getLayoutPosition() - this.getHeaderLayoutCount()的值一直是-1导致角标越界报错。

本人理解,RecyclerView的截屏原理是,首先构造每个item的ViewHolder,然后调用具体设置数据到每个item的方法,此时cache中就存有item的内容,此时绘制就能获取到完整的内容。采用v7包中的onBindViewHolder方法即可,或者是BRVH的convert方法,可以看到BRVH中没有暴露出这个方法,而且唯一暴露出的onBindViewHolder还会报角标越界错误,此时我们就需要在BRVH的基础上暴露出convert即可,代码如下

public class MyAdapter extends BaseQuickAdapter {

public MyAdapter() {
super(getItemLayoutResId(), datas);
}

/**

  • 用于对外暴露convert方法,构造缓存视图(截屏用)
  • @param viewHolder
  • @param t
    */
    public void startConvert(BaseViewHolder viewHolder, T t){
    convert(viewHolder,t);
    }

@Override
protected void convert(BaseViewHolder viewHolder, T t) {
bindView(viewHolder, t);
}
}

然后将上面所述的获取Bitmap方法修改一下

/**

  • 截取recycler view
    */
    public static Bitmap getRecyclerViewScreenshot(RecyclerView view) {
    BaseListFragment.MyAdapter adapter = (BaseListFragment.MyAdapter) view.getAdapter();
    Bitmap bigBitmap = null;
    if (adapter != null) {
    int size = adapter.getData().size();
    int height = 0;
    Paint paint = new Paint();
    int iHeight = 0;
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
LruCache<String, Bitmap> bitmaCache = new LruCache<>(cacheSize);
for (int i = 0; i < size; i++) {
BaseViewHolder holder = (BaseViewHolder) adapter.createViewHolder(view, adapter.getItemViewType(i));
//此处需要调用convert方法,否则绘制出来的都是空的item
adapter.startConvert(holder, adapter.getData().get(i));
holder.itemView.measure(
View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(),
holder.itemView.getMeasuredHeight());
holder.itemView.setDrawingCacheEnabled(true);
holder.itemView.buildDrawingCache();
Bitmap drawingCache = holder.itemView.getDrawingCache();
if (drawingCache != null) {

bitmaCache.put(String.valueOf(i), drawingCache);
}
height += holder.itemView.getMeasuredHeight();
}

bigBitmap = Bitmap.createBitmap(view.getMeasuredWidth(), height, Bitmap.Config.ARGB_8888);
Canvas bigCanvas = new Canvas(bigBitmap);
Drawable lBackground = view.getBackground();
if (lBackground instanceof ColorDrawable) {
ColorDrawable lColorDrawable = (ColorDrawable) lBackground;
int lColor = lColorDrawable.getColor();
bigCanvas.drawColor(lColor);
}

for (int i = 0; i < size; i++) {
Bitmap bitmap = bitmaCache.get(String.valueOf(i));
bigCanvas.drawBitmap(bitmap, 0f, iHeight, paint);
iHeight += bitmap.getHeight();
bitmap.recycle();
}
}
return bigBitmap;
}

合成Bitmap

比如四张合成一张

/**

  • 将四张图拼成一张
  • @param pic1 图一
  • @param pic2 图二
  • @param pic3 图三
  • @param pic4 图四
  • @return only_bitmap
  • 详情见说明:{@link com.bertadata.qxb.util.ScreenShotUtils}
    */
    public static Bitmap combineBitmapsIntoOnlyOne(Bitmap pic1, Bitmap pic2, Bitmap pic3, Bitmap pic4, Activity context) {
    int w_total = pic2.getWidth();
    int h_total = pic1.getHeight() + pic2.getHeight() + pic3.getHeight() + pic4.getHeight();
    int h_pic1 = pic1.getHeight();
    int h_pic4 = pic4.getHeight();
    int h_pic12 = pic1.getHeight() + pic2.getHeight();
    //此处为防止OOM需要对高度做限制
    if (h_total > HEIGHTLIMIT) {
    return null;
    }

Bitmap only_bitmap = Bitmap.createBitmap(w_total, h_total, Bitmap.Config.ARGB_4444);
Canvas canvas = new Canvas(only_bitmap);
canvas.drawColor(ContextCompat.getColor(context, R.color.color_content_bg));
canvas.drawBitmap(pic1, 0, 0, null);
canvas.drawBitmap(pic2, 0, h_pic1, null);
canvas.drawBitmap(pic3, 0, h_pic12, null);
canvas.drawBitmap(pic4, 0, h_total - h_pic4, null);
return only_bitmap;
}

图片后期处理

/**

  • 将传入的Bitmap合理压缩后输出到系统截屏目录下
  • 命名格式为:Screenshot+时间戳+启信宝报名.jpg
  • 同时通知系统重新扫描系统文件
  • @param pic1 图一 标题栏截图
  • @param pic2 图二 scrollview截图
  • @param context 用于通知重新扫描文件系统,为提升性能可去掉
  •            详情见说明:{@link com.bertadata.qxb.util.ScreenShotUtils}
    

*/
public static void savingBitmapIntoFile(final Bitmap pic1, final Bitmap pic2, final Activity context, final BitmapAndFileCallBack callBack) {
if (context == null || context.isFinishing()) {
return;
}
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
String fileReturnPath = “”;
int w = pic1.getWidth();
Bitmap bottom = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_picture_combine_bottom);
Bitmap top_banner = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_picture_combine_top);

Bitmap bitmap_bottom = anyRatioCompressing(bottom, (float) w / bottom.getWidth(), (float) w / bottom.getWidth());
Bitmap bitmap_top = anyRatioCompressing(top_banner, (float) w / bottom.getWidth(), (float) w / bottom.getWidth());
final Bitmap only_bitmap = combineBitmapsIntoOnlyOne(bitmap_top, pic1, pic2, bitmap_bottom, context);

// 获取当前时间
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd-HH-mm-ss-ms”, Locale.getDefault());
String data = sdf.format(new Date());

// 获取内存路径
// 设置图片路径+命名规范
// 声明输出文件
String storagePath = Environment.getExternalStorageDirectory().getAbsolutePath();
String fileTitle = “Screenshot_” + data + “_com.bertadata.qxb.biz_info.jpg”;
String filePath = storagePath + “/DCIM/”;
final String fileAbsolutePath = filePath + fileTitle;
File file = new File(fileAbsolutePath);

/**

  • 质压与比压结合
  • 分级压缩
  • 输出文件
    */
    if (only_bitmap != null) {
    try {
    // 首先,对原图进行一步质量压缩,形成初步文件
    FileOutputStream fos = new FileOutputStream(file);
    only_bitmap.compress(Bitmap.CompressFormat.JPEG, 50, fos);

// 另建一个文件other_file预备输出
String other_fileTitle = “Screenshot_” + data + “_com.bertadata.qxb.jpg”;
String other_fileAbsolutePath = filePath + other_fileTitle;
File other_file = new File(other_fileAbsolutePath);
FileOutputStream other_fos = new FileOutputStream(other_file);

// 其次,要判断质压之后的文件大小,按文件大小分级进行处理
long file_size = file.length() / 1024; // size of file(KB)
if (file_size < 0 || !(file.exists())) {
// 零级: 文件判空
throw new NullPointerException();
} else if (file_size > 0 && file_size <= 256) {
// 一级: 直接输出
deleteFile(other_file);
// 通知刷新文件系统,显示最新截取的图文件
fileReturnPath = fileAbsolutePath;
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse(“file://” + fileAbsolutePath)));
} else if (file_size > 256 && file_size <= 768) {
// 二级: 简单压缩:压缩为原比例的3/4,质压为50%
anyRatioCompressing(only_bitmap, (float) 3 / 4, (float) 3 / 4).compress(Bitmap.CompressFormat.JPEG, 40, other_fos);
deleteFile(file);
// 通知刷新文件系统,显示最新截取的图文件
fileReturnPath = other_fileAbsolutePath;
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse(“file://” + other_fileAbsolutePath)));
} else if (file_size > 768 && file_size <= 1280) {
// 三级: 中度压缩:压缩为原比例的1/2,质压为40%
anyRatioCompressing(only_bitmap, (float) 1 / 2, (float) 1 / 2).compress(Bitmap.CompressFormat.JPEG, 40, other_fos);
deleteFile(file);
// 通知刷新文件系统,显示最新截取的图文件
fileReturnPath = other_fileAbsolutePath;
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse(“file://” + other_fileAbsolutePath)));
} else if (file_size > 1280 && file_size <= 2048) {
// 四级: 大幅压缩:压缩为原比例的1/3,质压为40%
anyRatioCompressing(only_bitmap, (float) 1 / 3, (float) 1 / 3).compress(Bitmap.CompressFormat.JPEG, 40, other_fos);
deleteFile(file);
// 通知刷新文件系统,显示最新截取的图文件
fileReturnPath = other_fileAbsolutePath;
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse(“file://” + other_fileAbsolutePath)));
} else if (file_size > 2048) {
// 五级: 中度压缩:压缩为原比例的1/2,质压为40%
anyRatioCompressing(only_bitmap, (float) 1 / 2, (float) 1 / 2).compress(Bitmap.CompressFormat.JPEG, 40, other_fos);
deleteFile(file);
// 通知刷新文件系统,显示最新截取的图文件
fileReturnPath = other_fileAbsolutePath;
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse(“file://” + other_fileAbsolutePath)));
}

// 注销fos;
fos.flush();
other_fos.flush();
other_fos.close();
fos.close();
//callback用于回传保存成功的路径以及Bitmap
callBack.onSuccess(only_bitmap, fileReturnPath);
} catch (Exception e) {
e.printStackTrace();
}
} else callBack.onSuccess(null, “”);
}
});
thread.start();
}

/**

  • 可实现任意宽高比例压缩(宽高压比可不同)的压缩方法(主要用于微压)
  • @param bitmap 源图
  • @param width_ratio 宽压比(float)(0<&&<1)
  • @param height_ratio 高压比(float)(0<&&<1)
  • @return 目标图片

*/
public static Bitmap anyRatioCompressing(Bitmap bitmap, float width_ratio, float height_ratio) {
Matrix matrix = new Matrix();
matrix.postScale(width_ratio, height_ratio);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
}

本文参考http://www.cnblogs.com/BoBoMEe/p/4556917.html,并结合自己实际项目操作完成
下面是本人的微信公众号,欢迎大家关注👏👏👏👏👏👏~~
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

【附】相关架构及资料

往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

存中…(img-xLsLDcbU-1715596011656)]

往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值