Android启动相册+显示图片+内存优化
今天说下调用系统系统相册选一张照片并且显示所选择的照片。本文针对基础薄弱的朋友,如果您有什么七八年开发经验,那你可以忽略本文,哈哈哈哈哈不开玩笑了,进入正文。
先写一个按钮(用于启动相册/绑定事件)和图片组件(用于显示相册中选择的图片)。一般启动活动,调用系统的什么的,都要用到Intent(有人称为意图)。首先我们也要构建一个Intent,让它去调用启动相册。
这个Intent也是傻乎乎的,你构建它之后它也是一脸懵逼,不知道要干嘛。我们应该要指定Intent任务(属性)也就是告诉Intent它要干嘛,进行改造。
首先看方法中第一行"new Intent(Intent.ACTION_PICK)" 构造函数中的参数Intent.ACTION_PICK指定这个意图的Action(可以理解为具体动作)是PICK(挑选)。也就相当于告诉我们的Intent:“我要挑选个东西”。具体挑选什么东西呢?我们也来告诉Intent。
看intent.setType()这个方法,这个就是告诉Intent挑选什么东西了。中间的参数image/*就告诉Intent挑选的是图片。当然你也可以让Intent挑选其他东西。video/*告诉Intent我要选择一个视频,audio/*就是告诉Intent我要选择一个音频。
然后最后一行"startActivityForResult()"这个方法可以理解为对着Intent大声喊:"去吧,皮卡丘"哦不,是Intent。然后这个Intent就去根据我们刚刚上面指定的动作去干活了(调用相册选择一张图片)。这个方法中间有两个参数,第一个参数类型是Intent类型,放一个Intent。代表叫哪个Intent去干活。第二个参数就代表emmmmm,比如我们构造了很多个Intent,他们带着我们想要的东西回来了,我们怎么分辨他们呢?我们怎么知道这个Intent是从相册挑选一张照片的Intent还是打开相机拍张照片的Intent呢?你们都叫Intent啊。所以,如果你想启动一个带返回值(不同于java方法中的返回值,可以理解为带回来的东西,本文带回来的就是图片)的Intent,就要指定一个标志(学术界称为请求码)。请求码是一个Int类型的值,主要用于分辨Intent。我使用0x01(0x开头表示16进制的数字,0x01也就等于1)纯属装逼用,你们也可以直接在最后一行的方法的第二个参数写个1。然后你们在要启动相册的地方掉用这个openAlbum()这个方法就好了。比如按钮的单击事件中。
想必各位已经可以跳转到相册然后选择一张照片了。接下来我们就来获取所选择的照片。
当我们选择了一张照片,就会回调到这个方法,有Intent结果返回都会回调到这个方法。这个方法有三个参数,第一个是请求码,就是我上面介绍的。第二个是结果码,暂时不讲解。第三个就是Intent,我们要判断这个Intent是不是我们刚刚派遣的Intent,如果是的,这个Intent里面有我们想要的东西,可以通过方法来获取。通过requestCode来判断,如果这个请求码与我们startActivityForReustl()中第二个参数指定的请求码相等,则就代表这个Intent是我们请求相册的Intent。根据我的理解这个Intent已经不再是我们之前所创建的Intent了,其实我也不敢肯定,等我再学几年Android,对Android内核有所理解了再来告诉你们。但是这个Intent里面有我们想要的东西。
首先对requestCode(请求码)进行判断,看是不是我们刚刚启动这个Intent指定的请求码。如果是的,那么我们就处理这个Intent。下面是处理的方法。
是不是很简单?这个Intent的getData方法会返回一个Uri类型的属性,这个Uri中包含了被选中图片的地址。然后我们的ImageView有个setImageURI的方法,mPortrait是一个ImageView,大家不要怕。然后ImageView就会显示我们在相册中所选择的图片。我之前看了郭神的《第一行代码》第二版,上面也有调用相册的方法,但是我这个方法比那个简单。
你们以为这就完了???笑话,Android会这么简单的事情?下面是内存使用情况。
我们选择了一张图片并显示之后,看看内存情况。震惊,多选几张岂不是直接OOM(out of memory,学术界称成内存溢出)。作为一个有情怀,有梦想的程序猿,怎么能容忍这种事情的发生???所以我们要再次改造。看内存情况可以点击AndroidStudio底部的Android Profiler进入。
我们通过另外一种方式来显示图片。以bitmap(位图,如果不理解暂时可以理解成一种特殊的图片)的形式来显示图片,可以为bitmap设置属性,比如设置显示的宽高,是否只加载宽高,质量,抗锯齿等等,从而降低bitmap的质量。以下部分不能理解的朋友要多看几遍。
首先还是获取Intent中的uri,进行判断,是空的下面就不需要执行了,也没办法执行了。看104行,BitmapFactory是一个加载Bitmap的工厂方法,可以从很多格式数据中获取一个bitmap,此处就是从一个流中获取bitmap,decodeStream的第一个参数就是从url中获取的流,第二个参数我不知道,第三个参数我们要获取的bitmap的属性,是个BitmapFactory.Options类型的数据。可以为这个bitmap指定宽高,质量等属性。传入的BitmapFactory.Options是怎么样的,返回的Bitmap就是怎么样的。null就代表使用默认的。
如果大家104行后面部分的代码看不懂暂时可以理解为通过uri来获取一个bitmap。然后ImageView有个ImageBitmap的方法,可以通过设置一个Bitmap来显示图片。不出意外的话手机显示结果跟刚刚的设置uri方法显示是一样的。
看不懂没关系,先这样理解,我们首先获得了一个Intent,这个Intent中有个uri,这个uri可以解析出一个bitmap,我们对这个bitmap处理(降低质量),然后再调用ImageView.setImageBitmap方法可以显示这个bitmap就可以了,当然这个bitmap显示的是我们从相册中选择图片。
接下来就是处理这个Bitmap,比如我们显示的是一张小的图片,肯定要对bitmap压缩一下,而且肉眼也看不出效果。也就是处理这个bitmap的属性,比如降低bitmap的显示质量,缩小宽度高度等等,这样bitmap占用内存就小些了。bitmap的属性用BitmapFactory.Options这个类设置。
还有一个dealOptions方法等下再贴出来。我们一行一行来讲解。上面对uri判断没啥讲的。103行大家看好,新建一个BitmapFactory.Options,大家可以当成bitmap的属性方法,bitmap加载成啥样都由这个东西决定。104,设置只加载宽高,因为我们要根据原图宽高,和要显示的宽高来压缩原图。比如,原图是1000*1000的大小,而我们要显示的ImageView却是100*100的,那么原图可以压缩成100*100大小。而且肉眼还看不出来,但是运行内存确实少了。我们要在用户肉眼看不出的情况下尽可能压缩图片,程序运行起来也更加流畅。
105行还是之前使用过的方法,但是第三个参数不是null,是我们设置只加载宽高的options,运行105行之后,options里面就包含了原图的宽高。因为设置的是只加载宽高,所以105行方法返回的bitmap为空,所以我就没有接收了。107行:dealOptions这个方法是我自己写的,方法中有三个参数,第一个参数是options,这个options中包含了原图的宽高,第二个参数是要显示ImageView的宽,第三个是ImageView的高。dealOptions方法就根据要需要显示的宽高,来处理options。返回一个处理过后的options。
109行,依然是那个方法,第三个参数是我们处理过后的options,所以返回的Bitmap也是经过处理的。然后110行显示。
还是一行一行讲解,125新建一个options。127和128分别获取原图的宽高,原图的宽高有了,所需显示的宽高也有了,那就来压缩呗。130首先定义缩放比为1。131-138就是根据宽高来设置压缩比,具体为什么这样我也说不清,大家先按这样的判断来压缩。大家知道可以告诉我。然后139设置这个新建的options的inSampleSize(压缩比)为上面几行计算出来的simpleSize。140再设置inJustDecodeBounds=false,不然有只加载宽高,内容却不加载。141设置图片的质量为RGB_565,原本是8888。142返回这个处理过后的options。
都写完了之后,我们再来看内存使用情况。
我刚刚从相册选择了一张图片,显示之后的情况。你看只是高了一点点。相比之前的是一个天上,一个地下。
如果大家没看懂,可以多看几遍,也可以直接问我,一开始了解这些东西肯定难以理解,我开始学的时候也是接近放弃,但是每当我想放弃的时候,心中总有一句话回响起来"如果你想修成佛,那你就要学安卓"。我知道的话也会回答。我也是新手,如果您看出我的某些地方错了,希望您能指正,大家一起成长。此文原创。转载请标明出处。祝大家学习Android愉快。