作者:阿风
最近太忙了,好久没有写东西了,itsCoder 也缺席了一期,感觉自己也好久没有学习新的东西了。不管再忙还是应该抽出时间来学习和锻炼,不然时刻感觉身体被掏空啊~
前言
图片处理在 Android 开发中是一个比较重要的技术,处理稍有不慎就可能会出现 oom 等情况,网上也有很多图片处理相关的文章,我在Volley 中的ImageLoader源码学习笔记这篇文章中也分析了一些解决方案。同时开源社区也有一些优秀的图片处理框架-Glide,Fresco,Picasso 等。这篇文章不会再讲这些解决方案。
我自己在业余时间做了一款图片应用,在实际的开发中我发现有一种特殊的情况,加载超长或者超宽的图片,并且随着拼图的越来越普及,以后这种长图的出现可能会变得更加的常见,这篇文章就来讲讲我在处理长图显示中遇到的坑。
Max Texture Size 问题
在实际的开发中我使用的是 Glide 作为图片框架,但是我发现依然会有一些图片无法显示出来。通过 log 定位,我发现这些图片都比较长,并且抓到了这样一个异常:
|
从字面意思来理解是当前图片的尺寸超过了 OpenGL
的渲染上限。最大值是 8192 而图片长度明显超过了最大长度。
OpenGL
是什么?
Open Graphics Library (OpenGL)[3][4] is a cross-language, cross-platform application programming interface (API) for rendering 2D and3D vector graphics. The API is typically used to interact with a graphics processing unit (GPU), to achieve hardware-acceleratedrendering).
维基百科上面是这样解释的。
我自己对 OpenGL
并没有什么了解,这里也只是本着解决问题的想法,所以想要深入了解 OpenGL
的同学可以自行学习。简单来说就是图片的尺寸超过了 OpenGL
渲染的限制。
解决方案
知道了问题的所在,就可以开始寻找解决方案了,寻找解决方案的过程就不细说了,直接抛出三个可行的方案来解决。
- 调整图片尺寸
- 局部显示图片
- 关闭硬件加速
目前我所寻找到的解决方案是这三种,其中第一种是最容易想到的,也是本篇文章重点讲解的。
Android 在使用 OpenGL
去渲染图片的时候实际上开了硬件加速(不知道这么说对不对),所以最简单粗暴的方法就是在 AndroidManifest.xml
文件中去关闭硬件加速,当然这么做是不怎么清真的,可能会引起其他的问题。
调整图片尺寸
resize 图片的尺寸大概每个开发者都会,因此这个方案里面最为重要的一个步骤是获取 max texture size ,通过测试发现不同的手机这个尺寸可能是不一样的。那么如何精确的获取当前手机的渲染限制大小呢?
上 StackOverflow 上面搜了一下,大部分答案都给出了如下代码
|
先别急着去试,如果你稍微仔细一点会发现这些答案后面基本都会有疑惑,这样获取到的值始终是 0。
所以又换了个问题,为什么会是 0?搜到了这个答案:
http://stackoverflow.com/questions/26985858/gles10-glgetintegerv-returns-0-in-lollipop-only
call to OpenGL ES API with no current context (logged once per thread)
You need a current OpenGL context in your thread before you can make any OpenGL calls, which includes your
glGetIntegerv()
call. This was always true. But it seems like in pre-Lollipop, there was an OpenGL context that was created in the frameworks, and that was sometimes (always?) current when app code was called.
在调用 OpenGL ES API 的时候没有一个 context。所以问题又变成了如何创建一个 OpenGL 的 context?因为这个回答已经相当详细,我只简单的翻译一下。
- 创建
GLSurfaceView
,这是最简单最方便的一种方法,但是该方法只有当你需要使用 OpenGL 来渲染展示画面的时候才具有实际意义。 - 使用
EGL14
来完成调用OpenGl
的相关 API 前的准备工作,并且不需要真正的渲染画面。
第一种方法在官方文档中给出了非常详细的教程-GLSurfaceView
第二种方法中的 EGL14
只支持到 API 17,如果要兼容到更低的系统需要使用 EGL10
。
创建 OpenGL Context
- 初始化
EGLDisplay
|
- 做一些配置,因为我们只是获取下 texture size ,不需要真正的渲染,所以这些配置不是特别重要
|
- 为了成为当前的 context,需要渲染一个
SurfaceView
,但是我们又不想真的去渲染一个view
,因此创建一个在屏幕外的小surface
|
- 创建
OpenGL context
|
- Make Current
|
到这儿我们的准备工作就做完了。
接下来就是获取 max texture size
|
有了这个值,怎么去调整图片就看你自己了~
销毁 OpenGL Context
获取到了这个值以后就可以将这个上下文给销毁了
|
局部显示图片
第一种方案虽然解决了大部分长图的显示问题,但是依然会面临 OOM 的风险。尤其是当对图片的显示要求比较高时(高清大图),上一种方案似乎也不够完美。
BitmapRegionDecoder
似乎是一个不错的选择。
BitmapRegionDecoder can be used to decode a rectangle region from an image. BitmapRegionDecoder is particularly useful when an original image is large and you only need parts of the image.
这个类也是我最近才接触的,还没有用到实际的项目中去,但是感觉会是一个不错解决方案。目前在网上有一篇写的比较好的文章-Android 高清加载巨图方案 拒绝压缩图片,介绍的非常详细,也有 demo 可以学习。这个方案我就不再细说了,等具体用到项目中了再来看有没有什么值得补充的。
总结
这篇文章并没有太多原创的内容,毕竟涉及到问题解决的时候,一般都可以从网上找到答案。而这个问题是我实际开发中所碰到的,索性把较好的解决方案抛出来,也方便有同样需求的同学,如果你有更好的解决方案也非常希望你能分享出来。