内存
【内存的种类】
1、ROM->Read Only Memory
2、RAM->Random Access Memory
3、Cache->高速缓存
【RAM–随机存取存储器】
RAM 是可读可写的存储器,通常表现为计算机的“内存条”,或手机的“运行内存”
RAM 用于存储正在执行的程序和数据
RAM 是 CPU 与其他所有外部设备之间交换数据的桥梁
RAM 一旦断电,所有数据都会消失
RAM 容量越大,则整个硬件系统运行过程中,表现的越流畅
【JVM中的内存】
JVM 在运行 JAVA 程序时,会把内存区分为栈内存(stack)和堆内存(heap)
栈内存用于存储级别数据类型的变量与值、引用数据类型的引用、参数的副本
堆内存用于存储引用数据类型的数据
除了基本数据类型外,其他都是引用类型,例如 InputStream 和自己写的类全是引用类型
【关于参数的副本】
public class Main {
public static void main(String[] args) {
int i = 8;
add(i);//i->8
int[] arr = {9, 5, 2, 7};
add(arr);//arr[0]->10
}
public static void add(int x) {
x++;
}
public static void add(int[] array) {
array[0]++;
}
}
i
在栈内存,调用add()
方法时,会在栈内存中增加一个i
的副本,执行方法时,用参数的副本完成运算,完成后,副本消失,i还是原来的 8。
而数组arr
存在与堆内存中,它的内存地址是存在栈内存的,根据内存地址找到数组,在执行add()
方法时,会产生一个副本,也就是在栈内存中再复制它的一个内存地址,根据这个地址它找到的还是数组arr
,所以数据变了,虽然副本消失了。
内存溢出问题
正在执行的程序是在内存里的,也就是说,我们现在展示的图片也在内存中,那么一张图片到底会占多少内存呢?
可以用以下公式计算
也就是说这张图片占的二进制位是 1280 x 1024 x 24 = 31457280
8 个二进制位占一个字节,所以 31457280 / 8 = 3932160
3932160
是这张图加载到内存中占的字节的数量,换算成 kb 即3932160 / 1024 = 3840
换算成 M 即 3840 / 1024 = 3.75
然后我们可以看看一屏显示几张图片再做乘法运算,就可以粗略计算出一屏图片占用的内存。
所以这就涉及到内存溢出的问题了。内存溢出(OutOfMemoryError)简称 OOM,导致的原因是使用的内存超出了限制,如果内存占用太多就会出现 OutOfMemory 的 error 。例如我们上边显示相册的程序。当一次性显示多张高清图片时,很容易导致 Android APP 崩溃,可行的解决方案通常有:
1、压缩图片质量
2、缩小图片,即图片的封边率会更小
3、裁切,只显示图片的某个部分,例如滑动时再加载必要的部分的数据
我们选择第二种方法,来缩小图片,修改 ImageAdapter
public class ImageAdapter extends BaseAdapter<Image> {
public ImageAdapter(Context context, List data) {
super(context, data);
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder;
if (view == null) {
viewHolder = new ViewHolder();
view = getInflater().inflate(R.layout.item_image, null);
viewHolder.imageView = view.findViewById(R.id.img_item);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
Image image = getData().get(i);
Log.d("Image", "id:" + image.getId() + ";data:" + image.getData() + ";size:" + image.getSize() +
";displayname:" + image.getDisplayName() + ";dataTaken:" + image.getDateTaken() + ";尺寸:" + image.getWidth() +
"x" + image.getHeight());
//判断并计算图片的缩放比
//200单位是px
int rate = 1;
if (image.getWidth() > IMAGE_MAX_SIZE && image.getHeight() > IMAGE_MAX_SIZE) {
if (image.getWidth() < image.getHeight()) {
rate = image.getWidth() / IMAGE_MAX_SIZE;
} else {
rate = image.getHeight() / IMAGE_MAX_SIZE;
}
}
//创建解码图片选项参数
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = rate;
//解码图片得到图片的bitmap对象
Bitmap bitmap = BitmapFactory.decodeFile(image.getData(), opts);
Log.d("Image", "解码完成,bitmap尺寸为" + bitmap.getWidth() + "x" + bitmap.getHeight() + "bitmap占用内存:" + bitmap.getByteCount());
viewHolder.imageView.setImageBitmap(bitmap);
return view;
}
private static final int IMAGE_MAX_SIZE = 200;
class ViewHolder {
ImageView imageView;
}
}
如果 rate = 4,那么图片将被缩小为原来的 1/4,如果传入了一个比1小的值,程序处理时按 1 处理。
运行程序,查看 Log
>id:9370;data:/storage/emulated/0/DCIM/Camera/IMG_20201212_091204.jpg;size:3741069;displayname:IMG_20201212_091204.jpg;dataTaken:1607735525530;尺寸:3472x4624
解码完成,bitmap尺寸为204x272bitmap占用内存:221952
卡的问题
我们现在运行程序会发现图片上下滑动会很卡:
卡的原因只有一个就是主线程太卡,因此我们需要开线程,我们可以利用 AsyncTask 解决卡的问题。如果对线程不了解可以学习线程系列文章:---->传送门<----
修改 ImageAdapter
public class ImageAdapter extends BaseAdapter<Image> {
public ImageAdapter(Context context, List data) {
super(context, data);
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder;
if (view == null) {
viewHolder = new ViewHolder();
view = getInflater().inflate(R.layout.item_image, null);
viewHolder.imageView = view.findViewById(R.id.img_item);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
Image image = getData().get(i);
Log.d("Image", "id:" + image.getId() + ";data:" + image.getData() + ";size:" + image.getSize() +
";displayname:" + image.getDisplayName() + ";dataTaken:" + image.getDateTaken() + ";尺寸:" + image.getWidth() +
"x" + image.getHeight());
//创建任务
LoadImageTask task = new LoadImageTask(image, viewHolder.imageView);
task.execute();
//viewHolder.imageView.setImageBitmap(bitmap);
return view;
}
private static final int IMAGE_MAX_SIZE = 200;
class ViewHolder {
ImageView imageView;
}
//参数;是否更新进度;结果
private class LoadImageTask extends AsyncTask<Void, Void, Bitmap> {
private Image image;
private ImageView imageView;
public LoadImageTask(Image image, ImageView imageView) {
super();
this.image = image;
this.imageView = imageView;
}
@Override
protected Bitmap doInBackground(Void... voids) {
//判断并计算图片的缩放比
//200单位是px
int rate = 1;
if (image.getWidth() > IMAGE_MAX_SIZE && image.getHeight() > IMAGE_MAX_SIZE) {
if (image.getWidth() < image.getHeight()) {
rate = image.getWidth() / IMAGE_MAX_SIZE;
} else {
rate = image.getHeight() / IMAGE_MAX_SIZE;
}
}
//创建解码图片选项参数
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = rate;
//解码图片得到图片的bitmap对象
Bitmap bitmap = BitmapFactory.decodeFile(image.getData(), opts);
Log.d("Image", "解码完成,bitmap尺寸为" + bitmap.getWidth() + "x" + bitmap.getHeight() + "bitmap占用内存:" + bitmap.getByteCount());
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
imageView.setImageBitmap(bitmap);
}
}
}
运行程序:
可以看到现在下拉图片不卡了,但是出现了一个新的问题,图片闪。
图片闪烁
图片乱跳的原因是每次getView
导致新的列表项重新进入屏幕,就会重新创建任务,执行任务
修改后的ImageAdapter
public class ImageAdapter extends BaseAdapter<Image> {
public ImageAdapter(Context context, List data) {
super(context, data);
}
private Map<View, LoadImageTask> tasks = new HashMap<View, LoadImageTask>();
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder;
if (view == null) {
......
} else {
......
}
viewHolder.imageView.setImageResource(R.mipmap.ic_launcher);
Image image = getData().get(i);
//尝试获取当前控件对应的任务
LoadImageTask task;
task = tasks.get(view);
//判断当前是否存在任务
if (task != null) {
//当前存在任务,则取消现有任务
task.cancel(true);
}
//创建新任务
task = new LoadImageTask(image, viewHolder.imageView);
//将新的任务添加到集合中
tasks.put(view, task);
//执行任务
task.execute();
return view;
}
......
}
再次运行程序:
图片不会来回闪烁了,但是我们发现已经加载过的图片还是会重新加载,继续优化。
图片重复加载问题
Image 类中增加
private Bitmap bitmap;
public Bitmap getBitmap() {
return bitmap;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
修改ImageAdapter
public class ImageAdapter extends BaseAdapter<Image> {
......
//参数;是否更新进度;结果
private class LoadImageTask extends AsyncTask<Void, Void, Bitmap> {
......
@Override
protected Bitmap doInBackground(Void... voids) {
//判断是否需要解码图片
if (image.getBitmap() == null) {
......
Bitmap bitmap = BitmapFactory.decodeFile(image.getData(), opts);
//将解码得到的Bitmap封装到图片数据中
image.setBitmap(bitmap);
}
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
imageView.setImageBitmap(image.getBitmap());
}
}
}
完整的 ImageAdapter 代码如下:
public class ImageAdapter extends BaseAdapter<Image> {
public ImageAdapter(Context context, List data) {
super(context, data);
}
private Map<View, LoadImageTask> tasks = new HashMap<View, LoadImageTask>();
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder;
if (view == null) {
viewHolder = new ViewHolder();
view = getInflater().inflate(R.layout.item_image, null);
viewHolder.imageView = view.findViewById(R.id.img_item);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.imageView.setImageResource(R.mipmap.ic_launcher);
Image image = getData().get(i);
//尝试获取当前控件对应的任务
LoadImageTask task;
task = tasks.get(view);
//判断当前是否存在任务
if (task != null) {
//当前存在任务,则取消现有任务
task.cancel(true);
}
//创建新任务
task = new LoadImageTask(image, viewHolder.imageView);
//将新的任务添加到集合中
tasks.put(view, task);
//执行任务
task.execute();
return view;
}
private static final int IMAGE_MAX_SIZE = 200;
class ViewHolder {
ImageView imageView;
}
//参数;是否更新进度;结果
private class LoadImageTask extends AsyncTask<Void, Void, Bitmap> {
private Image image;
private ImageView imageView;
public LoadImageTask(Image image, ImageView imageView) {
super();
this.image = image;
this.imageView = imageView;
}
@Override
protected Bitmap doInBackground(Void... voids) {
//判断是否需要解码图片
if (image.getBitmap() == null) {
//判断并计算图片的缩放比
//200单位是px
int rate = 1;
if (image.getWidth() > IMAGE_MAX_SIZE && image.getHeight() > IMAGE_MAX_SIZE) {
if (image.getWidth() < image.getHeight()) {
rate = image.getWidth() / IMAGE_MAX_SIZE;
} else {
rate = image.getHeight() / IMAGE_MAX_SIZE;
}
}
//创建解码图片选项参数
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = rate;
//解码图片得到图片的bitmap对象
Bitmap bitmap = BitmapFactory.decodeFile(image.getData(), opts);
//Log.d("Image", "解码完成,bitmap尺寸为" + bitmap.getWidth() + "x" + bitmap.getHeight() + "bitmap占用内存:" + bitmap.getByteCount());
//将解码得到的Bitmap封装到图片数据中
image.setBitmap(bitmap);
}
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
imageView.setImageBitmap(image.getBitmap());
}
}
}
运行程序:
效果已经好了很多。