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

2024-01-09 12:34:36

移动端对大批量图片加载的优化方法(二)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.预加载

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

文章来源:https://blog.csdn.net/weixin_42979360/article/details/135474954
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。