Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.blue_bg);
// 默认情况下 BitmapFactory 使用 Bitmap.Config.ARGB_8888 的存储方式来加载图片内容,而在这种存储模式下,每一个像素需要占用 4 个字节
// 1280x800x4 = 4096000 byte 核算大约4000kb = 4M
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d("zyl--","bitmap = " + bitmap.getAllocationByteCount() +", ="+bitmap.getByteCount());
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.blue_bg,options);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d("zyl--","bitmap = " + bitmap.getAllocationByteCount() +", ="+bitmap.getByteCount());
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
// options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.blue_bg,options);
// 默认情况下 BitmapFactory 使用 Bitmap.Config.ARGB_8888 的存储方式来加载图片内容,而在这种存储模式下,每一个像素需要占用 4 个字节
// 1280x800x4 = 4096000 byte 核算大约4000kb = 4M
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d("zyl--","bitmap = " + bitmap.getAllocationByteCount() +", ="+bitmap.getByteCount());
}
Bitmap one = BitmapFactory.decodeResource(getResources(), R.drawable.one,options);
// 默认情况下 BitmapFactory 使用 Bitmap.Config.ARGB_8888 的存储方式来加载图片内容,而在这种存储模式下,每一个像素需要占用 4 个字节
// 1280x800x4 = 4096000 byte 核算大约4000kb = 4M
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d("zyl--","one = " + one.getAllocationByteCount() +", ="+one.getByteCount());
}
通过日志我们发现,jpg图片对于设置inPreferredConfig属性生效了,内存占用减少了一半。进一步证实了图片格式对于inPreferredConfig属性是否生效有一定影响。
BitmapFactory.Options options = new BitmapFactory.Options();
// options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.blue_bg,options);
// 默认情况下 BitmapFactory 使用 Bitmap.Config.ARGB_8888 的存储方式来加载图片内容,而在这种存储模式下,每一个像素需要占用 4 个字节
// 1280x800x4 = 4096000 byte 核算大约4000kb = 4M
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d("zyl--","bitmap = " + bitmap.getAllocationByteCount() +", ="+bitmap.getByteCount());
}
通过日志分析,得到当采样率为2的时候,图片占用内存为
: bitmap = 1024000,
内存减少了1/4。
public class MainActivity extends AppCompatActivity {
private int resIndex;
int[] resIds = {R.drawable.one, R.drawable.two};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView hello = findViewById(R.id.hello);
final ImageView iv = findViewById(R.id.iv);
hello.setOnClickListener(new View.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public void onClick(View v) {
iv.setImageBitmap(getBitmap());
}
});
reuseBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.one);
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private Bitmap getBitmap() {
return BitmapFactory.decodeResource(getResources(), resIds[resIndex++ % 2], options);
}
以上代码运行后,发现当我们切换图片时,内存情况如图:
每次切换图片都需要通过 BitmapFactory 创建一个新的 Bitmap 对象。当方法执行完毕后,这个 Bitmap 又会被 GC 回收,这就造成不断地创建和销毁比较大的内存对象,从而导致频繁 GC(或者叫内存抖动)。像 Android App 这种面相最终用户交互的产品,如果因为频繁的 GC 造成 UI 界面卡顿,还是会影响到用户体验的。可以在 Android Studio Profiler 中查看内存情况。
public class MainActivity extends AppCompatActivity {
private int resIndex;
int[] resIds = {R.drawable.one, R.drawable.two};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView hello = findViewById(R.id.hello);
final ImageView iv = findViewById(R.id.iv);
hello.setOnClickListener(new View.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public void onClick(View v) {
// Intent intent = new Intent(MainActivity.this,MyActivity.class);
// startActivity(intent);
iv.setImageBitmap(getBitmap());
}
});
BitmapFactory.Options options = new BitmapFactory.Options();
//设置为true,使这块内存能够复用
options.inMutable =true;
reuseBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.one,options);
}
/**
* 重用bitmap
*/
private Bitmap reuseBitmap;
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private Bitmap getBitmap() {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
//为true 只解析bitmap的占用内存大小,不加载bitmap到内存中
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), resIds[resIndex % 2], options);
if (canUseForInBitmap(reuseBitmap, options)) {
options.inMutable = true;
options.inBitmap = reuseBitmap;
}
//恢复设置
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(getResources(), resIds[resIndex++ % 2], options);
}
static boolean canUseForInBitmap(
Bitmap candidate, BitmapFactory.Options targetOptions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// From Android 4.4 (KitKat) onward we can re-use if the byte size of
// the new bitmap is smaller than the reusable bitmap candidate
// allocation byte count.
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;
int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
return byteCount <= candidate.getAllocationByteCount();
}
// On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
return candidate.getWidth() == targetOptions.outWidth
&& candidate.getHeight() == targetOptions.outHeight
&& targetOptions.inSampleSize == 1;
}
/**
* A helper function to return the byte usage per pixel of a bitmap based on its configuration.
*/
static int getBytesPerPixel(Bitmap.Config config) {
if (config == Bitmap.Config.ARGB_8888) {
return 4;
} else if (config == Bitmap.Config.RGB_565) {
return 2;
} else if (config == Bitmap.Config.ARGB_4444) {
return 2;
} else if (config == Bitmap.Config.ALPHA_8) {
return 1;
}
return 1;
}
}