加载网络图片:从基础实现到开源框架¶
一、自定义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,自动处理生命周期、缓存和优化,减少代码量。
核心思想:从原生实现到框架封装,逐步提升代码健壮性与效率,最终利用开源库简化开发流程。