Android 大图加载显示
文章目录
- Android 大图加载显示
- 通过本文你能学到什么?
- 一、ImagerView直接放置一张几十M的图片会崩溃吗?
- 二、如何保证加载大图不发生崩溃?
- 三、Glide设置显示大图是否会发生崩溃
- 四、大图缩放滑动如何实现
- 五、大图缩放和滑动框架的使用
- 六、最后总结一下最开始目录学习的内容:
- 共勉:自强不息,才是生活的样子。
通过本文你能学到什么?
1
2
3
4
5
6
71、普通设置方法设置大图片是否会导致界面崩溃,多大的图片才会导致崩溃 2、如何保证加载大图不发生崩溃 3、Glide设置显示大图是否会发生崩溃 4、大图缩放滑动如何实现 5、大图缩放和滑动框架的使用
这里有个测试上面几个问题的demo,效果图如下:
代码和apk资源:
https://download.csdn.net/download/wenzhi20102321/85079242
代码不多也不太难,又兴趣的可以自己运行调试下。
本文主要分析大图加载为啥会崩溃,其他的可以参考demo代码。
下面开始正文内容:
一、ImagerView直接放置一张几十M的图片会崩溃吗?
测试代码如下:
1
2
3
4
5
6
7
8
9
10
11
12@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_bitmap); Log.d(TAG," onCreate"); ImageView image = findViewById(R.id.image); image.setImageResource(R.mipmap.test);//22M的图片 Log.d(TAG," show bitmap end"); }
测试的Demo代码和测试的资源都在上面打包的资源里面。
测试效果如下:
在模拟器上Activity直接崩溃了
日志:
1
2
3
4
5Process: com.liwenzhi.bitmapdemo, PID: 10090 java.lang.RuntimeException: Canvas: trying to draw too large(161566192bytes) bitmap. at android.graphics.RecordingCanvas.throwIfCannotDraw(RecordingCanvas.java:280) at android.graphics.BaseRecordingCanvas.drawBitmap(BaseRecordingCanvas.java:88)
在华为手机上显示卡了下,后显示白屏,Activity界面未崩溃:
日志如下:
1
2
3
4
5
6
7
8E/BitmapDrawable: Canvas: trying to use a recycled bitmap 2022-03-12 01:04:53.845 31366-31366/com.liwenzhi.bitmapdemo W/System.err: java.lang.RuntimeException: Canvas: trying to draw too large(161566192bytes) bitmap. 2022-03-12 01:04:53.845 31366-31366/com.liwenzhi.bitmapdemo W/System.err: at android.graphics.RecordingCanvas.throwIfCannotDraw(RecordingCanvas.java:282) 2022-03-12 01:04:53.845 31366-31366/com.liwenzhi.bitmapdemo W/System.err: at android.graphics.BaseRecordingCanvas.drawBitmap(BaseRecordingCanvas.java:88) 2022-03-12 01:04:53.845 31366-31366/com.liwenzhi.bitmapdemo W/System.err: at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:549) 2022-03-12 01:04:53.845 31366-31366/com.liwenzhi.bitmapdemo W/System.err: at android.widget.ImageView.onDraw(ImageView.java:1529)
从上面日志也可以很清楚看到,从ImagView到具体报错的类的地方:
1
2
3ImageView.onDraw -->BitmapDrawable.draw-->BaseRecordingCanvas.drawBitmap-->RecordingCanvas.throwIfCannotDraw是最后报错的方法
在联想平板上测试,整个应用崩溃了:
1
2
3
4
5
6
7
8
9
10
11
12
132022-03-12 01:20:23.443 11461-11461/? D/ShowBitmapActivity: onCreate 2022-03-12 01:20:26.083 11461-11461/? D/ShowBitmapActivity: show bitmap end 2022-03-12 01:20:26.103 1368-1673/? I/InputDispatcher: c5b948a com.liwenzhi.bitmapdemo/com.liwenzhi.bitmapdemo.MainActivity (server) spent 2713ms processing FocusEvent(hasFocus=false) 2022-03-12 01:20:26.109 7367-9126/? W/FMSC::AppStuckDetectionService: Receive App Stuck Event: PID: 11461 Package: com.liwenzhi.bitmapdemo Type: 3 Level: 0 Timestamp: 1647019226109 Duration: 2732152213 JankCnt: 1 2022-03-12 01:20:26.144 11461-11461/? E/AndroidRuntime: FATAL EXCEPTION: main Process: com.liwenzhi.bitmapdemo, PID: 11461 java.lang.RuntimeException: Canvas: trying to draw too large(161566192bytes) bitmap. at android.graphics.RecordingCanvas.throwIfCannotDraw(RecordingCanvas.java:283) at android.graphics.BaseRecordingCanvas.drawBitmap(BaseRecordingCanvas.java:88) ...// at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:956) 2022-03-12 01:20:26.162 1368-8778/? W/ActivityTaskManager: Force finishing activity com.liwenzhi.bitmapdemo/.ShowBitmapActivity
从上面的日志看,不管是哪个设备,加载到161566192bytes(160M多一点)会发生异常。
尝试添加一张12M的图片,是没问题的,大概一秒后就显示了。
使用联想平板,尝试显示一张18M的图片,应用崩溃了。
1
2
3
4
5
62022-03-12 01:40:54.274 12930-12930/com.liwenzhi.bitmapdemo E/AndroidRuntime: FATAL EXCEPTION: main Process: com.liwenzhi.bitmapdemo, PID: 12930 java.lang.RuntimeException: Canvas: trying to draw too large(120422400bytes) bitmap. at android.graphics.RecordingCanvas.throwIfCannotDraw(RecordingCanvas.java:283) ...
从上面日志看,是加载到120M左右就崩溃了。
那么13M的图片能不能正常显示呢?
测试结果是崩溃了。
日志入下:
1
2
3
4
5
6
7
8
9--------- beginning of crash 2022-03-12 01:48:38.555 13216-13216/com.liwenzhi.bitmapdemo E/AndroidRuntime: FATAL EXCEPTION: main Process: com.liwenzhi.bitmapdemo, PID: 13216 java.lang.RuntimeException: Canvas: trying to draw too large(194510848bytes) bitmap. at android.graphics.RecordingCanvas.throwIfCannotDraw(RecordingCanvas.java:283) at android.graphics.BaseRecordingCanvas.drawBitmap(BaseRecordingCanvas.java:88)
194510848bytes–>194 510 848 = 185.5M
不再去测试了,先总结一下情况:
从上面测试结果看,ImageView大概加载13M以上的图片就会崩溃。
下面找出抛出异常崩溃原因:
Android9.0 api28
RecordingCanvas.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23private static int getPanelFrameSize() { final int DefaultSize = 100 * 1024 * 1024; // 100 MB; return Math.max(SystemProperties.getInt("ro.hwui.max_texture_allocation_size", DefaultSize), DefaultSize); } /** @hide */ public static final int MAX_BITMAP_SIZE = getPanelFrameSize(); /** @hide */ @Override protected void throwIfCannotDraw(Bitmap bitmap) { super.throwIfCannotDraw(bitmap); int bitmapSize = bitmap.getByteCount(); if (bitmapSize > MAX_BITMAP_SIZE) { throw new RuntimeException( "Canvas: trying to draw too large(" + bitmapSize + "bytes) bitmap."); } }
所以结论是:系统从ro.hwui.max_texture_allocation_size属性值和100 * 1024 * 1024(100 MB)取其中一个最大的值,
如果图片的数据值大于这个值就会抛出异常错误。
但是我的联想平板Android11(api 30),看了下并没有属性ro.hwui.max_texture_allocation_size,应该是9.0之后就没这个属性了的。
所以查看一下源码发现:
Android11 api30
RecordingCanvas.java
1
2
3
4
5/** @hide */ public static final int MAX_BITMAP_SIZE = 100 * 1024 * 1024; // 100 MB
发现最大加载Bitmap大小是100M,但是为啥12M的图片没事,13M多的图片就会崩溃???
下面就跟大家慢慢分析了:
1
2
3
4
5
6
7先公布答案: 图片的像素大小和图片的文件大小并不代表数据大小,图片的数据大小指的是内存数据大小, 具体图片具体内存大小计算方式是每行的像素字节数*图片高度; 注意上面说的是每行的像素字节数,并不是像素数,每个像素占4个字节。
这里可以直接看源码一步步分析,但是我觉得还是先看看报错的实际原因和弄懂基本原理比较好。
下面看看12M 和13M图片的参数情况:
先看会崩溃的13M.jpg
1
2
3
4
5
6分辨率:13568*3584 其他的位深和分辨率单位那些参数都是不用管的,知道上面这个分辨率参数就行了。 上面的参数表示:图片一行有13568个像素,有3584行
13568*3584大概就40M左右的大小,为啥会导致崩溃呢?
这里就要注意一个概念了,一个像素并不是一个字节大小,而是4个字节,
图片是用一个int(4个字节)记录一个像素大大小的,为啥是4个不是1个或者2个,因为要存储ARGB所以要4个字节。
理解了上面这两句概念,应该就不难理解为啥14M图片直接显示会崩溃了吧。
因为1356843584 内存大小,已经有160M左右的大小了,远超100M,代码直接抛出异常了。
12M.jpg参数情况:
1
2
3分辨率:5184*3456
518443456 内存大小,大概只有60 M左右大小,未达到抛出异常条件。
所以我们是不是可以先计算图片的内存大小,再决定是否显示这个图片呢?
Bitmap.java 刚好暴露有这个方法是表示内存数据大小的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public final int getByteCount() { // int result permits bitmaps up to 46,340 x 46,340 return getRowBytes() * getHeight(); //getRowBytes表示行像素的字节数据大小 } //同样的图片,在不同的设备获取的每行像素值可能不同 //比如我同一张照片华为手机,比如普通平板的高了很多 public final int getRowBytes() { if (mRecycled) { Log.w(TAG, "Called getRowBytes() on a recycle()'d bitmap! This is undefined behavior!"); } return nativeRowBytes(mNativePtr);//每行的像素大小和设备相关 }
当然是可以用这种比较low的方法进行判断,防止设置图片界面崩溃。
但是还是有更好的解决方法的,比如固定图片的采样率,以及设置图片像素模式RGB888/565达到减少图片内存数据的目的
二、如何保证加载大图不发生崩溃?
保证图片的加载内存不超过100M即可
可以通过设置Options对象的inSampleSize 减少采样率(达到之前的几分之一)
也可以通过设置Options对象的inPreferredConfig为Bitmap.Config.RGB_565,
比如下面的这段代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30/** * 获取Bitmap对象 * * @param res Resource对象 * @param resId 图片资源ID * @return 返回Bitmap对象 */ private Bitmap decodeSampledBitmapFromResource(Resources res, int resId) { BitmapFactory.Options options = new BitmapFactory.Options(); //亮点:不加载像素,不占用内存 options.inJustDecodeBounds = true; //超大图缩放一般都有这样设置,否则在底层占用大量内存 BitmapFactory.decodeResource(res, resId, options); //不加载内存的情况下,无返回值 int height = options.outHeight; //图片的宽 int width = options.outWidth; //图片的高 //做一些其他的判断和处理,根据图片的宽高和View的宽高的情况,设置一些参数 //关键点: Calculate inSampleSize 几分之一 options.inSampleSize = 4; options.inPreferredConfig = Bitmap.Config.RGB_565; //默认是RGB_888 options.inJustDecodeBounds = false; //设置为false,才会从底层申请到内存 return BitmapFactory.decodeResource(res, resId, options); }
RGB_888到RGB_565,其实也是减少了采样率,大概是原图的5/8.
所有的大图加载框架都是用到了上面这段代码的思想:
第一次在不占用内存的情况加载测量图片的宽高,根据情况适配参数,第二次真正加载并设置相关参数。
三、Glide设置显示大图是否会发生崩溃
Glide加载大图是不会崩溃的
其关键点就是:
1
2
3
4
5
6
7把options.inJustDecodeBounds属性设置为true, 对比图片实际大小和ImageView实际大小的情况,设置对应参数,做出相应的缩放等操作。 options.inJustDecodeBounds
也就是加载两次
第一次读取配置,比如图片原生宽高
第二次结合布局的宽高设置图片宽高,
四、大图缩放滑动如何实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
221、写一个自定义View加载图片 2、设置setImage方法设置图片,测量图片宽高 3、调用requestLayout()重新加载图片 4、在onMeasure(...)方法中对比图片宽高和View的宽高, 5、定义使用区域解码器、手势对象、缩放手势,缩放因子等对象 //区域解码器 private BitmapRegionDecoder mDecode; //手势对象 private GestureDetector mGestureDetector; //缩放功能 ScaleGestureDetector mScaleGestureDetector; //滑动帮助类 private Scroller mScroller; //需要显示的区域 private Rect mRect; //图片缩放因子 private float mScale; 6、在onDraw(...)做对应的缩放和区域滑动 具体参考demo项目代码。
demo中手写的大图缩放功能,并不是非常完善,
试了下,部分大图正常显示和放大缩小,宽度较大的图片显示有问题,
谁有需求可以参考大图缩放框架自己完善下。
五、大图缩放和滑动框架的使用
大图处理神器: SubsamplingScaleImageView 框架
简称:Subsampling
用法也是比较简单的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class ShowFunctionBitmapActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_function_bitmap); SubsamplingScaleImageView imageView = (SubsamplingScaleImageView)findViewById(R.id.image); //自定义View imageView.setImage(ImageSource.resource(R.mipmap.test_22m)); //使用ImageSource的静态方法转换成ImageSource对象,加载到View中即可 } }
demo里面没有进行远程依赖,是把SubsamplingScaleImageView框架的几个类复制到demo使用,需要的可以自己进行适配修改。
Subsampling的其他相关介绍:https://blog.csdn.net/zhangphil/article/details/49557549
六、最后总结一下最开始目录学习的内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
421、普通设置方法设置大图片是否会导致界面崩溃,多大的图片才会导致崩溃 答:会发生异常,加载的图片数据内存大于100M会发生RuntimeException异常,系统框架未处理的情况会发生崩溃 因为Android系统就是这样限制的。 一般来说加载几M的图片没啥问题,但是加载十几M以上的图片就会有异常,出现图片不加载或者界面崩溃或者应用崩溃的现象。 2、如何保证加载大图不发生崩溃 答:可设置options.inSampleSize设置采样率和options.inPreferredConfig降低图片分辨率达到减少内存加载的目的 3、Glide设置显示大图是否会发生崩溃 Glide加载大图不会发生崩溃 Glide里面会加载两次图片 第一次读取配置,比如图片原生宽高,设置options.inJustDecodeBounds属性设置为true,不占用内存 第二次结合布局的宽高设置图片设置理想的采样率比例。 4、大图缩放滑动如何实现 (1)写一个自定义View加载图片 (2)设置setImage方法设置图片,测量图片宽高 (3)调用requestLayout()重新加载图片 (4)在onMeasure(...)方法中对比图片宽高和View的宽高, (5)定义使用区域解码器、手势对象、缩放手势,缩放因子等对象 (6)在onDraw(...)做对应的缩放和区域滑动 5、大图缩放和滑动框架的使用 大图处理神器: SubsamplingScaleImageView 框架 (1)定义缩放图片对象 SubsamplingScaleImageView imageView = (SubsamplingScaleImageView)findViewById(R.id.image); //自定义View (2)缩放图片对象中放入图片数据 imageView.setImage(ImageSource.resource(R.mipmap.test_22m)); //使用ImageSource的静态方法转换成ImageSource对象,加载到View中即可
共勉:自强不息,才是生活的样子。
最后
以上就是安静自行车最近收集整理的关于Android 大图加载显示Android 大图加载显示共勉:自强不息,才是生活的样子。的全部内容,更多相关Android内容请搜索靠谱客的其他文章。
发表评论 取消回复