Skip to content

Latest commit

 

History

History
268 lines (221 loc) · 7.67 KB

File metadata and controls

268 lines (221 loc) · 7.67 KB

Android 图片加载优化 (Bitmap 处理)

在 Android 应用中,图片加载和处理是内存消耗的主要来源之一。不合理的 Bitmap 使用会导致内存溢出(OOM)、界面卡顿等问题。以下是全面的 Bitmap 处理优化方案:

一、基础优化策略

1. 图片尺寸适配

// 获取目标视图大小
int targetWidth = imageView.getWidth();
int targetHeight = imageView.getHeight();

// 计算采样率
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.large_image, options);
int inSampleSize = calculateInSampleSize(options, targetWidth, targetHeight);

// 实际加载缩小后的图片
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
Bitmap scaledBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.large_image, options);

// 计算采样率的方法
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {
        final int halfHeight = height / 2;
        final int halfWidth = width / 2;
        while ((halfHeight / inSampleSize) >= reqHeight 
                && (halfWidth / inSampleSize) >= reqWidth) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}

2. 使用合适的像素格式

options.inPreferredConfig = Bitmap.Config.RGB_565; // 每个像素占2字节(默认ARGB_8888占4字节)

二、内存管理优化

1. Bitmap 复用

// 使用inBitmap属性复用内存(Android 4.4+)
options.inMutable = true;
options.inBitmap = reusableBitmap;

2. 及时回收

if (bitmap != null && !bitmap.isRecycled()) {
    bitmap.recycle();
    bitmap = null;
}

3. 使用 LRU 缓存

// 内存缓存
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8; // 使用1/8的可用内存
LruCache<String, Bitmap> memoryCache = new LruCache<String, Bitmap>(cacheSize) {
    @Override
    protected int sizeOf(String key, Bitmap bitmap) {
        return bitmap.getByteCount() / 1024;
    }
};

// 磁盘缓存
DiskLruCache diskLruCache = DiskLruCache.open(cacheDir, 1, 1, 10 * 1024 * 1024);

三、高级优化技术

1. 大图分块加载 (RegionDecoder)

BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(inputStream, false);
Bitmap regionBitmap = decoder.decodeRegion(new Rect(0, 0, 500, 500), null);

2. 使用 WebP 格式

<!-- 在build.gradle中启用WebP支持 -->
android {
    defaultConfig {
        vectorDrawables.useSupportLibrary = true
    }
}

3. 预加载与懒加载结合

// 预加载可见区域图片
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            preloadImages();
        }
    }
});

四、主流图片加载库对比

特性 Glide Picasso Fresco Coil
作者 Google Square Facebook Kotlin
内存优化 优秀 良好 最佳 优秀
磁盘缓存 智能 基本 智能 智能
GIF支持 是 否 是 是
体积 中等 小 大 小
Kotlin支持 良好 良好 良好 最佳

Glide 最佳实践

Glide.with(context)
    .load(url)
    .apply(new RequestOptions()
        .placeholder(R.drawable.placeholder)
        .error(R.drawable.error)
        .diskCacheStrategy(DiskCacheStrategy.ALL)
        .override(targetWidth, targetHeight)
        .format(DecodeFormat.PREFER_RGB_565))
    .into(imageView);

五、特殊场景处理

1. 长图/超大图处理

// 使用SubsamplingScaleImageView库
SubsamplingScaleImageView imageView = findViewById(R.id.imageView);
imageView.setImage(ImageSource.uri("path/to/image.jpg"));

2. 图片列表优化

// RecyclerView适配器中
@Override
public void onViewRecycled(ViewHolder holder) {
    Glide.with(context).clear(holder.imageView);
}

3. 后台线程处理

// 使用RxJava处理
Observable.fromCallable(() -> processBitmap(bitmap))
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(result -> imageView.setImageBitmap(result));

六、性能监测工具

  1. Android Profiler

    • 内存分析
    • CPU使用情况
    • 网络请求监控
  2. StrictMode

// 在Application中启用
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
    .detectDiskReads()
    .detectDiskWrites()
    .penaltyLog()
    .build());
  1. LeakCanary
dependencies {
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
}

通过综合应用以上优化策略,可以显著提升应用的图片加载性能,减少内存占用,避免OOM问题,并提供更流畅的用户体验。、

Android 图片加载的内存存储位置

在 Android 中,图片(Bitmap)加载后的内存存储位置经历了几个重要的演变阶段:

关键结论

✅ Android 3.0 (API 11) 之前:Bitmap 像素数据存储在 Native 堆
✅ Android 3.0 - 7.1 (API 11-25):Bitmap 像素数据存储在 Dalvik/ART 堆
✅ Android 8.0+ (API 26+):Bitmap 像素数据重新回到 Native 堆

详细说明

1. Android 2.3 及更早版本

  • 存储位置:Native 堆内存
  • 特点:
    • 像素数据不在 Java 堆上分配
    • 但 Bitmap 对象本身仍在 Java 堆
    • 需要手动调用 recycle() 释放 Native 内存

2. Android 3.0 - 7.1 (API 11-25)

  • 存储位置:Dalvik/ART 堆内存
  • 改变原因:
    • 为了解决 Native 内存泄漏问题
    • 使 Bitmap 内存受 GC 管理
  • 问题:
    • 导致 Java 堆更容易 OOM
    • 大图片容易触发 OutOfMemoryError

3. Android 8.0+ (API 26+)

  • 存储位置:Native 堆内存
  • 改进:
    • 像素数据回到 Native 堆
    • 通过 NativeAllocationRegistry 自动管理
    • 不再需要手动 recycle()
    • Java 堆只保留 Bitmap 对象元数据

内存管理对比

版本 存储位置 是否需要 recycle() 受 GC 管理 典型问题
<3.0 Native 堆 是 否 Native 泄漏
3.0-7.1 Java 堆 否 是 Java OOM
8.0+ Native 堆 否 自动管理 较少

最佳实践

  1. 适配不同版本:

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        // 旧版本需要手动回收
        bitmap.recycle();
    }
  2. 监控内存使用:

    // 获取Bitmap内存大小
    int byteCount = bitmap.getAllocationByteCount();
  3. 使用现代图片加载库(如Glide、Coil等),它们已处理好版本兼容性问题

  4. 大图片处理:

    // 使用inSampleSize加载缩小版本
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inSampleSize = 4; // 缩小为1/4
    Bitmap smallBitmap = BitmapFactory.decodeFile(path, options);
  5. 检查内存状态:

    ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
    ((ActivityManager) getSystemService(ACTIVITY_SERVICE)).getMemoryInfo(memoryInfo);
    if (memoryInfo.lowMemory) {
        // 低内存状态下使用更激进的内存策略
    }

理解 Bitmap 内存存储机制的变化,有助于在不同 Android 版本上优化图片加载策略,避免内存相关的问题。