尾声
你不踏出去一步,永远不知道自己潜力有多大,千万别被这个社会套在我们身上的枷锁给捆住了,30岁我不怕,35岁我一样不怕,去做自己想做的事,为自己拼一把吧!不试试怎么知道你不行呢?
改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
Android 上展示一张图像,都需要将图像解码成 Bitmap 对象,Bitmap 表示图像像素的集合,像素占用的内存大小取决 Bitmap 配置,目前 Android 支持的配置有如下:
- ALPHA_8
只存储透明度通道
- ARGB_4444
每个像素使用 2 字节存储
- ARGB_8888
每个像素使用 4 字节存储(默认)
- HARDWARE
特殊配置,Bitmap 数据存储在专门的图形内存(Native)
- RGBA_F16
每个像素使用 8 字节存储
- RGB_565
每个像素使用 2 字节存储,只有 RGB 通道。
源码解析
通常我们可以调用 BitmapFactory.decodeStream
方法从图像流中解码,Java 层只是个简单的入口,相关实现都在 Native 层的 BitmapFactory.doDecode
方法中。
// frameworks/base/libs/hwui/jni/BitmapFactory.cpp
static jobject doDecode(JNIEnv* env, std::unique_ptr stream,jobject padding, jobject options, jlong inBitmapHandle,jlong colorSpaceHandle) {
// …
}
一. 初始化相关参数
- sampleSize
采样率
- onlyDecodeSize
是否只解码尺寸
- prefCodeType
优先使用的颜色类型
- isHardware
是否存储在专门的图像内存
- isMutable
是否可变
- scale
缩放系数
- requireUnpremultiplied
颜色通道是否不需要"预乘"透明通道
- javaBitmap
可复用的 Bitmap
二. 创建解码器
根据解码的图像格式,创建不同的解码器 SkCodec。
| 图像格式 | SkCodec |
| — | — |
| JPEG | SkJpegCodec |
| WebP | SkWebpCodec |
| Gif | SkGifCodec |
| PNG | SkPngCodec |
SkCodec 负责核心实现,SkAndroidCodec 则是 SkCodec 的包装类,用于提供一些 Android 特有的 API。同样的,SkAndroidCodec 也是根据图像格式,创建不同的 SkAndroidCodec。
| 图像格式 | SkAndroidCodec |
| — | — |
| JPEG,PNG,Gif | SkSampledCodec |
| WebP | SkAndroidCodecadapter |
三. 创建内存分配器
根据是否存在可复用的 Bitmap,和是否需要缩放,使用不同的内存分配器 Allocator。
四. 分配像素内存
调用 SkBitmap.tryAllocPixels
方法尝试分配所需的像素内存,存在以下情况,可能会导致分配失败。
-
Java Heap OOM
-
Native Heap OOM
-
使用的可复用 Bitmap 太小
五. 执行解码
调用 SkAndroidCodec.getAndroidPixels
方法开始执行编码操作。
SkCodec::Result SkAndroidCodec::getAndroidPixels(const SkImageInfo& requestInfo,
void* requestPixels, size_t requestRowBytes, const AndroidOptions* options) {
// …
return this->onGetAndroidPixels(requestInfo,requestPixels,requestRowBytes,*options);
}
SkAndroid.onGetAndroidPixels
方法有两个实现,分别是 SkSampledCodec 和 SkAndroidCodecadapter。
这里我们以 JPEG 图像解码为例,从上文可知,它使用的是 SkSampledCodec 和 SkJpegCodec,SkJpegCodec 是核心实现。
Android 除了支持使用 BitmapFactory 进行完整的解码,也支持使用 BitmapRegionDecoder 进行局部解码,这个在处理特大的图像时特别有用。
Android 在图像压缩上一直有个令人诟病的问题,同等大小的图像文件,iOS 显示上总是更加细腻,也就是压缩效果更好,关于这个问题更详细的讨论,可以看这篇文章:github.com/bither/bith…
总的来说,就是 Android 底层使用的自家维护的一个开源 2D 渲染引擎 Skia,Skia 在 JPEG 图像文件的解编码上依赖的是 libjpeg 库,libjpeg 压缩参数叫:optimize_coding,这个参数为 TRUE,可以带来更好的压缩效果,同时也会消耗更多的 时间。
在 7.0 以下,Google 为了兼容性能较差的设备,而将这个值设置为 FALSE,7.0 及其以上,已经设置为 TRUE。
关于 optimize_coding 为 FALSE,更多的讨论可以看 groups.google.com/g/skia-disc…
7.0 以下:androidxref.com/6.0.1_r10/x…
7.0 及其以上:androidxref.com/7.0.0_r1/xr…
所以,现在比较主流的做法是,在 7.0 以下版本,可以基于 libjpeg-turbo 实现 JPEG 图像文件的压缩。
源码解析
可以通过调用 Bitmap.compress
方法来进行图像压缩,可选配置有:
- format
压缩图像格式,有 JPEG、PNG、WEBP。
- quality
压缩质量,可选值有 0-100。
同样的,Java 层只是提供 API 入口,实现还是在 Native 层的 Bitmap.Bitmap_comperss()
方法。
// framework/base/libs/hwui/jni/Bitmap.cpp
static jboolean Bitmap_compress(JNIEnv* env, jobject clazz, jlong bitmapHandle,jint format, jint quality,jobject jstream, jbyteArray jstorage) {
}
一. 创建编码器
根据图像格式创建不同的编码器。
| 图像格式 | 编码器 |
| — | — |
| JPEG | SkJpegEncoder |
| PNG | SkPngEnccoder |
| WebP | SkWebpEncoder |
二. 设置编码参数
Android JPEG 解码是依赖于 libjpeg 和 libjpeg-turbo。
在开始压缩编码之前,会先设置一系列参数。
-
图像尺寸
-
颜色类型
常用的颜色类型有:
JCS_EXT_BGRA, /* blue/green/red/alpha */
JCS_EXT_BGRA, /* blue/green/red/alpha */
- 下采样率
目前 Android 支持 “4:2:0”(默认),“4:2:2” 和 “4:4:4”。
- 最佳霍夫曼编码表
默认为 true,表示使用最佳霍夫曼编码表,虽然会降低压缩性能,但提高了压缩效率。
// Tells libjpeg-turbo to compute optimal Huffman coding tables
// for the image. This improves compression at the cost of
// slower encode performance.
fCInfo.optimize_coding = TRUE;
- 质量
这个参数会影响 JPEG 编码中 “量化” 这个步骤
三. 执行编码
// external/skia/src/imagess/SkImageEncoder.cpp
bool SkEncoder::encodeRows(int numRows) {
// …
this->onEncodeRows(numRows);
}
JPEG 图像编码由 SkJpegEncoder 实现。
// txternal/skia/src/images/SkJpegEncoder.cpp
bool SkJpegEncoder::onEncodeRows(int numRows) {
// …
for (int i = 0; i < numRows; i++) {
// 执行 libjpeg-turbo 编码操作
jpeg_write_scanlines(fEncoderMgr->cinfo(), &jpegSrcRow, 1);
}
}
当调整图像的尺寸时,就需要对原始图像像素数据进行重新处理,这称为图像的采样处理。
目前 Android 默认支持 Nearest neighbor(邻近采样) 和 Bilinear(双线性采样) 这两种采样算法。
- Nearest neighbor(邻近采样)
重新采样的栅格中每个像素获取与原始栅格中的最近像素相同的值,这个处理时间是最快的,但也会导致图像产生锯齿。
- Bilinear(双线性采样)
重新采样的栅格中的每个像素都是原始栅格中 2x2 4 个最近像素的加权平均值的结果。
除了以上两种,还有以下几种效果的更好的算法:
- Bicubic(双立方采样)
重新采样的栅格中的每个像素都是原始栅格中 4x4 16 个最近像素值的加权值的结果,更接近的像素会有更高的权重。
- Lanczos
高阶插值算法,它考虑了更多周围像素,并保留了最多的图像信息。
- Magic Kernel
快速又高效,却能产生惊人的清晰和锐利的结果,更详细的介绍:www.johncostella.com/magic/。
Spectrum 是 Facebook 开源的跨平台图像转码依赖库,与 Android 系统默认自带的 jpeg-turbo 相对,它有以下优势:
-
JPEG 编码基于 mozjpeg,相对于 jpeg-turbo,它提高了压缩率,但也增加了压缩处理时间。
-
支持 Bicubic(双立方采样)和 Magic Kernel 采样算法。
-
核心使用 CPP 实现,可以同时在 Android 和 iOS 平台实现一致的压缩效果。
-
支持更多自定义配置,包括色度采样模式等等。
学习宝典
对我们开发者来说,一定要打好基础,随时准备战斗。不论寒冬是否到来,都要把自己的技术做精做深。虽然目前移动端的招聘量确实变少了,但中高端的职位还是很多的,这说明行业只是变得成熟规范起来了。竞争越激烈,产品质量与留存就变得更加重要,我们进入了技术赋能业务的时代。
不论遇到什么困难,都不应该成为我们放弃的理由!
很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,对此我针对Android程序员,我这边给大家整理了一套学习宝典!包括不限于高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
【Android核心高级技术PDF文档,BAT大厂面试真题解析】
【算法合集】
【延伸Android必备知识点】
【Android部分高级架构视频学习资源】
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
【Android核心高级技术PDF文档,BAT大厂面试真题解析】
[外链图片转存中…(img-HABd3YSv-1715840720639)]
【算法合集】
[外链图片转存中…(img-JLVeV8wV-1715840720639)]
【延伸Android必备知识点】
[外链图片转存中…(img-p3S0QFeb-1715840720640)]
【Android部分高级架构视频学习资源】
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!