今天做了一个demo,需求是一个scollerView里面嵌套一个图片,图片是一个长图,我用了imageView去设置图片,当图片加载完成后设置出现空白的问题,在
某些手机会但是有占位,某些手机就不会出现这问题。
后来分析了一下,发现测试这个demo的手机只有1G内存,是不是内存不足?然后拿了另外一个1G内存的手机拿去测试,结果也发现了这个问题!!
我猜想是内存不足的问题,就往这个方向去思考,后来看到log打印的时候出现一个警告warn:
Bitmap too large to be uploaded into a texture exception
然后后面有个最大值max = 4934*4934,还有该图片的大小734*5349,看英文意思很明显就是图片过大 了,于是copy这段话去百度了一下原因。出错原因分析:
当开启硬件加速的时候,GPU对于openglRender 有一个限制,这个不同的手机会有不同的限制:
这个限制值可以通过canvas.getMaximumBitmapHeight()和canvas.getMaximumBitmapWidth()来获得。
安卓3.0之后图形绘制是需要硬件去辅助加速的,加速对图片大小是有限制的,这样手机进行操作时才会感觉流畅。
有一个最简单的解决办法是
<application android:hardwareAccelerated=
"false"
...>去关闭硬件加速,这样虽然能解决问题,但是会导致滑动会特别卡,因为没有了硬件加速辅助。所以这个办法
不是很可行。找另外一种解决办法。。。。
安卓里面有一个类叫Bitmapregiondecoder类
BitmapRegionDecoder
主要用于显示图片的某一块矩形区域,如果你需要显示某个图片的指定区域,那么这个类非常合适。
对于该类的用法,非常简单,既然是显示图片的某一块区域,那么至少只需要一个方法去设置图片;一个方法传入显示的区域即可;详见:
-
BitmapRegionDecoder提供了一系列的newInstance方法来构造对象,支持传入文件路径,文件描述符,文件的inputstrem等。
例如:
<code class="language-java hljs has-numbering"> BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, <span class="hljs-keyword">false</span>); </code>
-
上述解决了传入我们需要处理的图片,那么接下来就是显示指定的区域。
<code class="language-java hljs has-numbering">bitmapRegionDecoder.decodeRegion(rect, options);</code>
参数一很明显是一个rect,参数二是BitmapFactory.Options,你可以控制图片的
inSampleSize
,inPreferredConfig
等
在github上面下载了一个largeImagelib的工具类,里面封装了
bitmapRegionDecoder进行
包的项目结构是这样的
要加载长图只需用到他里面的自定义控件
<com.shizhefei.view.largeimage.LongImageView android:id="@+id/longimageView" android:layout_width="match_parent" android:layout_height="wrap_content" />在代码里直接找到所对应的对象,然后得到图片解析的流进行set就可以显示巨图了
longimageView.setImage(in);然后我们来分析下里面的源码。首先看setImage()方法
public void setImage(InputStream inputStream) { mScale.setScale(1); mScale.fromX = 0; mScale.fromY = 0; imageManager.load(inputStream); }里面封装了ImageManager进行加载。继续看下load方法
public void load(InputStream inputStream) { load(new InputStreamBitmapRegionDecoderFactory(inputStream)); }
private void load(BitmapRegionDecoderFactory factory) { release(this.mLoadData); this.mLoadData = new LoadData(factory); if (handler != null) { handler.removeCallbacksAndMessages(null); handler.sendEmptyMessage(MESSAGE_LOAD); }里面创建了一个
BitmapRegionDecoderFactory对象,然后通过handler进行发送消息
看下发送消息到了哪里
if (msg.what == MESSAGE_LOAD) {
if (loadData.mFactory != null) {
try {
loadData.mDecoder = loadData.mFactory.made();
loadData.mImageWidth = loadData.mDecoder.getWidth();
loadData.mImageHeight = loadData.mDecoder.getHeight();
if (onImageLoadListenner != null) {
onImageLoadListenner.onImageLoadFinished(loadData.mImageWidth, loadData.mImageHeight);
}
} catch (IOException e) {
e.printStackTrace();
}
}
这个生成了一个BitmapRegionDecoder对象,然后把该对象的宽高赋给要加载的图片的宽高。回调了一个接口,然后在LargeImageView类里面进行
@Override
public void onImageLoadFinished(int imageWidth, int imageHeight) {
notifyInvalidate();
}
进行绘制,
else {
LargeImageView.this.post(runnable = new Runnable() {
@Override
public void run() {
preInvalidateTime = SystemClock.uptimeMillis();
runnable = null;
Log.d("eeee", "preInvalidateTime:" + preInvalidateTime);
invalidate(getVisiableRect());
}
});
绘制时要传入一个矩阵,调用了一个getVisiableRect()方法,看下此方法,
protected Rect getVisiableRect() {
Rect visiableRect = new Rect();
getGlobalVisibleRect(visiableRect);
int[] location = new int[2];
getLocationOnScreen(location);
visiableRect.left = visiableRect.left - location[0];
visiableRect.right = visiableRect.right - location[0];
visiableRect.top = visiableRect.top - location[1];
visiableRect.bottom = visiableRect.bottom - location[1];
return visiableRect;
}
矩阵的边距根据屏幕动态改变的。在onDraw里面进行绘制图片,我们来看下onDraw里面调用了ImageManager一个方法
点进去看下,该方法传了一个缩放值跟一个Image的矩阵。然后再 看下该方法
通过handlerf发送一个消息,在这里接收
在这里进行解析,得到图片,通过接口回调出去解析不断绘制该图片。