移动端对大批量图片加载的优化方法(二)

移动端对大批量图片加载的优化方法(二)Android

本篇主要从Android开发中可以使用到的对大批量图片加载的优化方法进行整理。

1.合适的图片格式

详情请参考移动端对大批量图片加载的优化方法(一)

2.异步加载

图片加载可能会阻塞UI线程,导致界面的卡顿;

a.AsyncTask

Android中一个轻量级的异步类,允许在后台线程上执行一些操作然后在主线程上更新UI,从Android 11开始弃用。

复制代码
public class ImageLoadTask extends AsyncTask<String, Void, Bitmap> {  
  
    private ImageView imageView;  
  
    public ImageLoadTask(ImageView imageView) {  
        this.imageView = imageView;  
    }  
  
    @Override  
    protected Bitmap doInBackground(String... urls) {  
        String urlString = urls[0];  
        Bitmap bitmap = null;  
        try {  
            URL url = new URL(urlString);  
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();  
            connection.setDoInput(true);  
            connection.connect();  
            InputStream inputStream = connection.getInputStream();  
            bitmap = BitmapFactory.decodeStream(inputStream);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return bitmap;  
    }  
  
    @Override  
    protected void onPostExecute(Bitmap result) {  
        if (result != null) {  
            imageView.setImageBitmap(result);  
        } else {  
            // 处理加载失败的情况,例如显示一个默认图片或加载指示器  
        }  
    }  
}
b.线程

使用线程来执行图片加载。

复制代码
class ImageLoadingThread extends Thread {  
    private String imageUrl;  
    private ImageView imageView;  
    private Bitmap bitmap;  
  
    public ImageLoadingThread(String url, ImageView imageView) {  
        this.imageUrl = url;  
        this.imageView = imageView;  
    }  
  
    @Override  
    public void run() {  
        try {  
            URL url = new URL(imageUrl);  
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();  
            connection.setDoInput(true);  
            connection.connect();  
            InputStream inputStream = connection.getInputStream();  
            bitmap = BitmapFactory.decodeStream(inputStream);  
        } catch (Exception e) {  
            e.printStackTrace();  
        } finally {  
            // 在主线程中更新UI  
            runOnUiThread(new Runnable() {  
                @Override  
                public void run() {  
                    if (bitmap != null) {  
                        imageView.setImageBitmap(bitmap);  
                    } else {  
                        // 处理加载失败的情况,例如显示一个默认图片或加载指示器  
                    }  
                }  
            });  
        }  
    }  
}
c.Handler

Android提供的消息传递系统,可以在主线程和其他线程之间传递消息。

复制代码
// 创建Handler实例  
private Handler handler = new Handler(Looper.getMainLooper()) {  
    @Override  
    public void handleMessage(Message msg) {  
        // 处理从子线程传回的数据,更新UI等操作  
        super.handleMessage(msg);  
    }  
};  
  
// 创建并启动子线程来加载图片  
new Thread(new Runnable() {  
    @Override  
    public void run() {  
        Bitmap bitmap = loadImageFromNetwork("http://example.com/image.jpg");  
        // 将Bitmap对象发送到主线程更新UI  
        Message message = handler.obtainMessage();  
        message.obj = bitmap; // 将Bitmap对象附加到Message对象上传递给Handler  
        handler.sendMessage(message);  
    }  
}).start();
d.Coroutines

从Android Jetpack Compose 1.0开始后可以使用Coroutines来执行异步操作,也是一种轻量级的线程模型。

复制代码
val scope = viewModelScope // 如果你使用 ViewModel  
// 或 val scope = lifecycleScope[LifecycleOwner].coroutineScope // 如果你使用 LifecycleOwner

fun loadImageFromNetwork(): Flow<Bitmap> {  
    return flow {  
        try {  
            val url = URL("http://example.com/image.jpg") // 替换为你的图片URL  
            val connection = url.openConnection() as HttpURLConnection  
            connection.requestMethod = "GET"  
            val inputStream = connection.inputStream  
            val imageBytes = ByteArray(1024)  
            var readCount: Int = 0  
            while (inputStream.read(imageBytes, 0, 1024) != -1) {  
                readCount += 1024  
                imageBytes.resize(readCount)  
            }  
            val bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)  
            emit(bitmap) // 发射图片数据到 Flow 中  
        } catch (e: IOException) {  
            e.printStackTrace()  
            throw e  
        }  
    }.flowOn(Dispatchers.IO) // 在 IO 线程上执行网络请求和图片解码操作  
}
e.第三方库

利用第三方图片加载库,例如Glide,它提供了异步加载和缓存机制。

复制代码
Glide.initialize(this) // 在Activity或Application中初始化Glide

Glide.with(context) // 获取Glide的实例  
     .load("http://example.com/image.jpg") // 指定图片URL  
     .into(imageView) // 将加载的图片设置到ImageView中

3.懒加载

只有当图片即将被显示时才加载,可以显著提高应用的性能减少不必要的网络和磁盘I/O操作;

a.第三方库

Glide和Picasso,提供了内置的懒加载支持。

复制代码
Glide.with(context)  
     .load("http://example.com/image.jpg")  
     .defer() // 延迟加载图片,直到它进入屏幕可视区域  
     .into(imageView)

Picasso.get().context = this // 在Activity或Application中初始化Picasso
Picasso.get().load("http://example.com/image.jpg").fit().into(imageView) // 加载并懒加载图片到ImageView中
b.自定义View

在View的onDraw方法中,你可以检查图片是否已经加载,如果没有,则开始加载图片。

复制代码
public class LazyImageView extends View {  
    private Bitmap mBitmap;  
    private boolean isLoaded = false;  
  
    public LazyImageView(Context context) {  
        super(context);  
    }  
  
    public void setBitmap(Bitmap bitmap) {  
        mBitmap = bitmap;  
        invalidate(); // 重新绘制View  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
        if (isLoaded) {  
            canvas.drawBitmap(mBitmap, 0, 0, null); // 在View上绘制图片  
        } else {  
            // 可以设置一个占位符图片或空图片  
            // canvas.drawBitmap(placeholderBitmap, 0, 0, null);  
        }  
    }  
  
    public void loadImage(String url) {  
        // 在这里实现图片的懒加载逻辑,例如使用Glide加载图片  
        // 当图片加载完成后,调用setBitmap方法设置图片并调用invalidate方法重新绘制View  
    }  
}
c.ListView或RecyclerView

如果使用ListView或RecyclerView显示图片列表,可以利用组件的滚动监听机制来懒加载图片。

d.缓存机制

当图片首次加载完成后,你可以将其缓存到本地存储中;

下次需要显示该图片时,可以从缓存中直接读取,而不是重新从网络上下载。

复制代码
Glide.with(this) // 获取Glide实例  
     .load("http://example.com/image.jpg") // 指定图片URL  
     .into(new CustomTarget<Bitmap>() { // 自定义目标  
         @Override  
         public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {  
             // 图片加载完成,将其缓存到本地存储中  
             saveBitmapToCache(resource);  
         }  
  
         @Override  
         public void onLoadCleared(@Nullable Drawable placeholder) {  
             // 当View被清除时调用,可以释放资源  
         }  
     });

private void saveBitmapToCache(Bitmap bitmap) {  
    // 获取缓存目录的路径  
    File cacheDir = getExternalFilesDir(null);  
    // 构建保存文件的路径  
    File cacheFile = new File(cacheDir, "cached_image.jpg");  
    try (FileOutputStream out = new FileOutputStream(cacheFile)) {  
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); // 压缩图片并写入文件  
    } catch (IOException e) {  
        e.printStackTrace();  
    }  
}

Glide.with(this)  
     .load(cacheFile) // 指定缓存文件的路径  
     .into(imageView); // 将图片设置到ImageView中

4.适当的Bitmap配置

在加载图片时,可以指定Bitmap的配置,例如inPreferredConfig、inJustDecodeBounds等,以优化内存使用;

a.使用合适的Bitmap位深度

如果你知道图片的像素格式,确保使用适当的位深度;

例如,对于ARGB_8888格式的图片,使用BitmapFactory.Options的inPreferredConfig属性设置为Bitmap.Config.ARGB_8888。

b.设置Bitmap的宽度和高度

在BitmapFactory.Options中设置inJustDecodeBounds为true,以仅获取图片的原始尺寸;

然后可以根据这些尺寸动态地调整图片大小。

c.避免OOM(OutOfMemoryError)

确保应用不会因内存不足而崩溃,如果图片太大而无法适应内存,考虑将其缩小或分割成更小的部分;

使用BitmapFactory.Options的inSampleSize属性来缩放图片,这可以通过指定一个大于1的数值来实现。

d.定期清理不必要的Bitmap

当不再需要某个Bitmap时,调用其recycle()方法来释放内存。

e.考虑使用Vector Drawables或Xfermodes

对于支持的设备,考虑使用Vector Drawables代替PNG或JPEG图片,因为它们占用更少的空间并支持任意缩放;

对于复杂的图形效果,使用Xfermodes来创建自定义绘图效果。

5.适当的图片大小

根据需要显示的大小来加载图片,而不是直接加载原始大小的图片。

复制代码
public Bitmap loadBitmapFromFile(String filePath, int width, int height) {  
    // 创建BitmapFactory.Options对象  
    BitmapFactory.Options options = new BitmapFactory.Options();  
  
    // 设置inJustDecodeBounds属性为true,仅获取图片的原始尺寸  
    options.inJustDecodeBounds = true;  
    BitmapFactory.decodeFile(filePath, options);  
  
    // 计算合适的inSampleSize,用于缩小图片尺寸  
    options.inSampleSize = calculateInSampleSize(options, width, height);  
  
    // 重新加载图片,使用计算出的inSampleSize缩小图片尺寸  
    return BitmapFactory.decodeFile(filePath, options);  
}  
  
private 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;  
  
        // 计算最大的inSampleSize值,该值是2的幂且能缩小图片高度和宽度到所需的尺寸  
        while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {  
            inSampleSize *= 2;  
        }  
    }  
  
    return inSampleSize;  
}

5.预加载

用户需要某个资源之前,提前加载该资源到内存中,这样可以减少用户在需要时等待的时间,提高响应速度。

相关推荐
专注API从业者22 分钟前
Open Claw 京东商品监控选品实战:一键抓取、实时监控、高效选品
java·服务器·数据库
摇滚侠39 分钟前
DBeaver 导入数据库 导入 SQL 文件 MySQL 备份恢复
java·数据库·mysql
keep one's resolveY1 小时前
SpringBoot实现重试机制的四种方案
java·spring boot·后端
天空属于哈夫克32 小时前
企业微信API常见的错误和解决方案
java·数据库·企业微信
摇滚侠2 小时前
VMvare 虚拟机 Oracle19c 安装步骤,远程连接 Oracle19c,百度网盘安装包
java·oracle
梁萌2 小时前
idea报错找不到XX包的解决方法
java·intellij-idea·启动报错·缺少包
Agent产品评测局3 小时前
生产排期与MES/ERP系统打通,实操方法详解 —— 2026企业级智能体自动化选型与实战指南
java·运维·人工智能·ai·chatgpt·自动化
阿丰资源3 小时前
基于Spring Boot的电影城管理系统(直接运行)
java·spring boot·后端
呱牛do it3 小时前
企业级门户网站设计与实现:基于SpringBoot + Vue3的全栈解决方案(Day 8)
java
消失的旧时光-19434 小时前
Spring Boot 工程化进阶:统一返回 + 全局异常 + AOP 通用工具包
java·spring boot·后端·aop·自定义注解