Skip to content

Commit 455893c

Browse files
JoeStevenJaeger
authored andcommitted
add article in phase5 by joe (#103)
1 parent b69ed2d commit 455893c

File tree

1 file changed

+168
-0
lines changed

1 file changed

+168
-0
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
---
2+
title: Android 中的长图片处理
3+
date: 2016-10-22 16:38:21
4+
categories: Android
5+
tags:
6+
---
7+
8+
最近太忙了,好久没有写东西了,itsCoder 也缺席了一期,感觉自己也好久没有学习新的东西了。不管再忙还是应该抽出时间来学习和锻炼,不然时刻感觉身体被掏空啊~
9+
10+
### 前言ß
11+
12+
图片处理在 Android 开发中是一个比较重要的技术,处理稍有不慎就可能会出现 oom 等情况,网上也有很多图片处理相关的文章,我在[Volley 中的ImageLoader源码学习笔记](http://extremej.itscoder.com/volley_imageloader_source/)这篇文章中也分析了一些解决方案。同时开源社区也有一些优秀的图片处理框架-Glide,Fresco,Picasso 等。这篇文章不会再讲这些解决方案。
13+
14+
我自己在业余时间做了一款图片应用,在实际的开发中我发现有一种特殊的情况,加载超长或者超宽的图片,并且随着拼图的越来越普及,以后这种长图的出现可能会变得更加的常见,这篇文章就来讲讲我在处理长图显示中遇到的坑。
15+
16+
### Max Texture Size 问题
17+
18+
在实际的开发中我使用的是 Glide 作为图片框架,但是我发现依然会有一些图片无法显示出来。通过 log 定位,我发现这些图片都比较长,并且抓到了这样一个异常:
19+
20+
```
21+
[WARN] : OpenGLRenderer: Bitmap too large to be uploaded into a texture (752x8546, max=8192x8192)
22+
```
23+
24+
从字面意思来理解是当前图片的尺寸超过了 `OpenGL` 的渲染上限。最大值是 8192 而图片长度明显超过了最大长度。
25+
26+
- `OpenGL` 是什么?
27+
28+
> **Open Graphics Library** (**OpenGL**)[[3\]](https://en.wikipedia.org/wiki/OpenGL#cite_note-3)[[4\]](https://en.wikipedia.org/wiki/OpenGL#cite_note-4) is a [cross-language](https://en.wikipedia.org/wiki/Language-independent_specification)[cross-platform](https://en.wikipedia.org/wiki/Cross-platform) [application programming interface](https://en.wikipedia.org/wiki/Application_programming_interface) (API) for rendering [2D](https://en.wikipedia.org/wiki/2D_computer_graphics) and[3D](https://en.wikipedia.org/wiki/3D_computer_graphics) [vector graphics](https://en.wikipedia.org/wiki/Vector_graphics). The API is typically used to interact with a [graphics processing unit](https://en.wikipedia.org/wiki/Graphics_processing_unit) (GPU), to achieve [hardware-accelerated](https://en.wikipedia.org/wiki/Hardware_acceleration)[rendering](https://en.wikipedia.org/wiki/Rendering_(computer_graphics)).
29+
30+
维基百科上面是这样解释的。
31+
32+
我自己对 `OpenGL` 并没有什么了解,这里也只是本着解决问题的想法,所以想要深入了解 `OpenGL` 的同学可以自行学习。简单来说就是图片的尺寸超过了 `OpenGL` 渲染的限制。
33+
34+
### 解决方案
35+
36+
知道了问题的所在,就可以开始寻找解决方案了,寻找解决方案的过程就不细说了,直接抛出三个可行的方案来解决。
37+
38+
- 调整图片尺寸
39+
- 局部显示图片
40+
- 关闭硬件加速
41+
42+
目前我所寻找到的解决方案是这三种,其中第一种是最容易想到的,也是本篇文章重点讲解的。
43+
44+
Android 在使用 `OpenGL` 去渲染图片的时候实际上开了硬件加速(不知道这么说对不对),所以最简单粗暴的方法就是在 `AndroidManifest.xml` 文件中去关闭硬件加速,当然这么做是不怎么清真的,可能会引起其他的问题。
45+
46+
### 调整图片尺寸
47+
48+
resize 图片的尺寸大概每个开发者都会,因此这个方案里面最为重要的一个步骤是获取 max texture size ,通过测试发现不同的手机这个尺寸可能是不一样的。那么如何精确的获取当前手机的渲染限制大小呢?
49+
50+
上 StackOverFlow 上面搜了一下,大部分答案都给出了如下代码
51+
52+
```java
53+
int[] maxTextureSize = new int[1];
54+
GLES10.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);
55+
```
56+
57+
先别急着去试,如果你稍微仔细一点会发现这些答案后面基本都会有疑惑,这样获取到的值始终是 0。
58+
59+
所以又换了个问题,为什么会是 0?搜到了这个答案:
60+
61+
http://stackoverflow.com/questions/26985858/gles10-glgetintegerv-returns-0-in-lollipop-only
62+
63+
> call to OpenGL ES API with no current context (logged once per thread)
64+
>
65+
> 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.
66+
67+
在调用 OpenGL ES API 的时候没有一个 context。所以问题又变成了如何创建一个 OpenGL 的 context?因为这个回答已经相当详细,我只简单的翻译一下。
68+
69+
- 创建 `GLSurfaceView`,这是最简单最方便的一种方法,但是该方法只有当你需要使用 OpenGL 来渲染展示画面的时候才具有实际意义。
70+
- 使用 `EGL14`来完成调用 `OpenGl` 的相关 API 前的准备工作,并且不需要真正的渲染画面。
71+
72+
第一种方法在官方文档中给出了非常详细的教程-[GLSurfaceView](https://developer.android.com/reference/android/opengl/GLSurfaceView.html)
73+
74+
第二种方法中的 `EGL14` 只支持到 API 17,如果要兼容到更低的系统需要使用 `EGL10`
75+
76+
#### 创建 OpenGL Context
77+
78+
- 初始化 `EGLDisplay`
79+
80+
```java
81+
EGLDisplay dpy = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
82+
int[] vers = new int[2];
83+
EGL14.eglInitialize(dpy, vers, 0, vers, 1);
84+
```
85+
86+
- 做一些配置,因为我们只是获取下 texture size ,不需要真正的渲染,所以这些配置不是特别重要
87+
88+
```java
89+
int[] configAttr = {
90+
EGL14.EGL_COLOR_BUFFER_TYPE, EGL14.EGL_RGB_BUFFER,
91+
EGL14.EGL_LEVEL, 0,
92+
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
93+
EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
94+
EGL14.EGL_NONE
95+
};
96+
EGLConfig[] configs = new EGLConfig[1];
97+
int[] numConfig = new int[1];
98+
EGL14.eglChooseConfig(dpy, configAttr, 0,
99+
configs, 0, 1, numConfig, 0);
100+
if (numConfig[0] == 0) {
101+
// TROUBLE! No config found.
102+
}
103+
EGLConfig config = configs[0];
104+
```
105+
106+
- 为了成为当前的 context,需要渲染一个 `SurfaceView`,当是我们又不想真的去渲染一个 `view` ,因此创建一个在屏幕外的小 `surface`
107+
108+
```java
109+
int[] surfAttr = {
110+
EGL14.EGL_WIDTH, 64,
111+
EGL14.EGL_HEIGHT, 64,
112+
EGL14.EGL_NONE
113+
};
114+
EGLSurface surf = EGL14.eglCreatePbufferSurface(dpy, config, surfAttr, 0);
115+
```
116+
117+
- 创建 `OpenGL context`
118+
119+
```java
120+
int[] ctxAttrib = {
121+
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
122+
EGL14.EGL_NONE
123+
};
124+
EGLContext ctx = EGL14.eglCreateContext(dpy, config, EGL14.EGL_NO_CONTEXT, ctxAttrib, 0);
125+
```
126+
127+
- Make Current
128+
129+
```java
130+
EGL14.eglMakeCurrent(dpy, surf, surf, ctx);
131+
```
132+
133+
到这儿我们的准备工作就做完了。
134+
135+
接下来就是获取 max texture size
136+
137+
```java
138+
int[] maxSize = new int[1];
139+
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxSize, 0);
140+
```
141+
142+
有了这个值,怎么去调整图片就看你自己了~
143+
144+
#### 销毁 OpenGL Context
145+
146+
获取到了这个值以后就可以将这个上下文给销毁了
147+
148+
```java
149+
EGL14.eglMakeCurrent(dpy, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
150+
EGL14.EGL_NO_CONTEXT);
151+
EGL14.eglDestroySurface(dpy, surf);
152+
EGL14.eglDestroyContext(dpy, ctx);
153+
EGL14.eglTerminate(dpy);
154+
```
155+
156+
### 局部显示图片
157+
158+
第一种方案虽然解决了大部分长图的显示问题,但是依然会面临 OOM 的风险。尤其是当对图片的显示要求比较高时(高清大图),上一种方案似乎也不够完美。
159+
160+
`BitmapRegionDecoder` 似乎是一个不错的选择。
161+
162+
> 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.
163+
164+
这个类也是我最近才接触的,还没有用到实际的项目中去,但是感觉会是一个不错解决方案。目前在网上有一篇写的比较好的文章-[Android 高清加载巨图方案 拒绝压缩图片](http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/1021/3607.html),介绍的非常详细,也有 demo 可以学习。这个方案我就不再细说了,等具体用到项目中了再来看有没有什么值得补充的。
165+
166+
### 总结
167+
168+
这篇文章并没有太多原创的内容,毕竟涉及到问题解决的时候,一般都可以从网上找到答案。而这个问题是我实际开发中所碰到的,索性把较好的解决方案抛出来,也方便有同样需求的同学,如果你有更好的解决方案也非常希望你能分享出来。

0 commit comments

Comments
 (0)