加载网络图片:从基础实现到开源框架

一、自定义ImageView实现基础功能

当需要在ImageView中直接显示网络图片时,原生ImageView不支持直接联网,需自定义实现。

1. 创建自定义MyImageView

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class MyImageView extends ImageView {
    private static final int GET_DATA_SUCCESS = 1;
    private static final int NETWORK_ERROR = 2;
    private static final int SERVER_ERROR = 3;
    private Handler handler = new Handler(msg -> {
        switch (msg.what) {
            case GET_DATA_SUCCESS:
                setImageBitmap((Bitmap) msg.obj);
                break;
            case NETWORK_ERROR:
                Toast.makeText(getContext(), "网络连接失败", Toast.LENGTH_SHORT).show();
                break;
            case SERVER_ERROR:
                Toast.makeText(getContext(), "服务器错误", Toast.LENGTH_SHORT).show();
                break;
        }
        return true;
    });

    public MyImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public MyImageView(Context context) {
        super(context);
    }

    public MyImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    // 核心方法:加载网络图片
    public void setImageURL(final String path) {
        new Thread(() -> {
            try {
                URL url = new URL(path);
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET");
                connection.setConnectTimeout(10000);
                int code = connection.getResponseCode();
                if (code == 200) {
                    InputStream inputStream = connection.getInputStream();
                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                    Message msg = Message.obtain();
                    msg.obj = bitmap;
                    msg.what = GET_DATA_SUCCESS;
                    handler.sendMessage(msg);
                    inputStream.close();
                } else {
                    handler.sendEmptyMessage(SERVER_ERROR);
                }
            } catch (IOException e) {
                handler.sendEmptyMessage(NETWORK_ERROR);
            }
        }).start();
    }
}

2. 布局文件使用自定义控件

<com.example.demo.MyImageView
    android:id="@+id/image_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

3. Activity中调用

MyImageView imageView = findViewById(R.id.image_view);
Button loadBtn = findViewById(R.id.load_button);
loadBtn.setOnClickListener(v -> imageView.setImageURL("https://example.com/image.png"));

4. 添加权限

<uses-permission android:name="android.permission.INTERNET" />

二、优化:图片压缩与缓存

原生实现未处理大图片导致的内存溢出问题,需添加压缩和缓存功能。

1. 图片压缩逻辑

// 获取ImageView实际宽高
private int realImageViewWith() {
    return getWidth() > 0 ? getWidth() : getLayoutParams().width;
}

private int realImageViewHeight() {
    return getHeight() > 0 ? getHeight() : getLayoutParams().height;
}

// 计算压缩比例
private int getInSampleSize(BitmapFactory.Options options) {
    int inSampleSize = 1;
    int outWidth = options.outWidth;
    int outHeight = options.outHeight;
    int targetWidth = realImageViewWith();
    int targetHeight = realImageViewHeight();
    if (outWidth > targetWidth || outHeight > targetHeight) {
        inSampleSize = Math.max(outWidth / targetWidth, outHeight / targetHeight);
    }
    return inSampleSize;
}

// 压缩图片
private Bitmap getCompressBitmap(InputStream input) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(input, null, options);
    options.inSampleSize = getInSampleSize(options);
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeStream(input, null, options);
}

2. 缓存实现

// 缓存开关与路径
public boolean isUseCache = false;
private String imagePath;

// 优先加载缓存
public void setImageURL(String path) {
    imagePath = path;
    if (isUseCache) {
        useCacheImage();
    } else {
        useNetWorkImage();
    }
}

// 从网络加载并缓存
private void useNetWorkImage() {
    new Thread(() -> {
        try {
            URL url = new URL(imagePath);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(10000);
            if (connection.getResponseCode() == 200) {
                InputStream inputStream = connection.getInputStream();
                Bitmap bitmap = getCompressBitmap(inputStream);
                cacheImage(inputStream); // 缓存图片
                handler.sendMessage(Message.obtain(handler, GET_DATA_SUCCESS, bitmap));
            }
        } catch (IOException e) {
            handler.sendEmptyMessage(NETWORK_ERROR);
        }
    }).start();
}

// 缓存到本地
private void cacheImage(InputStream input) {
    File cacheDir = getContext().getCacheDir();
    File file = new File(cacheDir, imagePath.hashCode() + "");
    try (FileOutputStream fos = new FileOutputStream(file)) {
        byte[] buffer = new byte[1024];
        int len;
        while ((len = input.read(buffer)) != -1) fos.write(buffer, 0, len);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

三、开源框架:Glide的优雅实现

Glide提供高效图片加载、缓存和生命周期管理,简化开发。

1. 添加依赖

implementation 'com.github.bumptech.glide:glide:4.16.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'

2. 一行代码加载图片

Glide.with(this)
     .load("https://example.com/image.png")
     .into(imageView);

3. 核心API
- with(Context/Activity/Fragment):绑定生命周期
- load(Object model):支持URL、本地路径、资源ID
- into(ImageView):设置目标控件

4. 进阶用法

// 加载占位图与错误图
Glide.with(this)
     .load("https://example.com/image.png")
     .placeholder(R.drawable.loading)
     .error(R.drawable.error)
     .into(imageView);

// 自定义尺寸
RequestOptions options = new RequestOptions()
    .override(500, 500) // 强制尺寸
    .centerCrop(); // 居中裁剪
Glide.with(this).load(url).apply(options).into(imageView);

四、总结

  • 基础方案:自定义ImageView实现联网加载,需处理线程、内存和异常。
  • 优化方案:添加压缩(inSampleSize)和缓存(本地存储)解决性能问题。
  • 开源方案:推荐使用Glide,自动处理生命周期、缓存和优化,减少代码量。

核心思想:从原生实现到框架封装,逐步提升代码健壮性与效率,最终利用开源库简化开发流程。

Xiaoye