420 lines
13 KiB
Plaintext
420 lines
13 KiB
Plaintext
|
|
# Glide 图片加载错误处理方案
|
|||
|
|
|
|||
|
|
## 错误信息
|
|||
|
|
```
|
|||
|
|
at com.bumptech.glide.load.engine.DecodeJob.runWrapped(DecodeJob.java:279)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 问题分析
|
|||
|
|
|
|||
|
|
### 可能原因
|
|||
|
|
1. **图片格式不支持或损坏**
|
|||
|
|
- 图片文件损坏
|
|||
|
|
- 不支持的图片格式
|
|||
|
|
- 图片编码异常
|
|||
|
|
|
|||
|
|
2. **内存不足**
|
|||
|
|
- 加载大图片时内存溢出
|
|||
|
|
- 同时加载多张图片导致内存压力
|
|||
|
|
|
|||
|
|
3. **网络问题**
|
|||
|
|
- 网络图片下载不完整
|
|||
|
|
- 响应数据格式错误
|
|||
|
|
|
|||
|
|
4. **Glide配置问题**
|
|||
|
|
- 缺少ProGuard规则
|
|||
|
|
- Glide版本兼容性问题
|
|||
|
|
|
|||
|
|
5. **Context问题**
|
|||
|
|
- Activity/Fragment已销毁但仍在加载
|
|||
|
|
- Context引用导致内存泄漏
|
|||
|
|
|
|||
|
|
## 解决方案
|
|||
|
|
|
|||
|
|
### 方案一:添加错误处理和占位图(推荐)
|
|||
|
|
|
|||
|
|
#### 1. 优化 GlideEngine.java
|
|||
|
|
|
|||
|
|
在 `app/src/main/java/tools/GlideEngine.java` 中添加错误处理:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Override
|
|||
|
|
public void loadImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
|
|||
|
|
Glide.with(context)
|
|||
|
|
.load(url)
|
|||
|
|
.placeholder(R.drawable.picture_image_placeholder) // 添加占位图
|
|||
|
|
.error(R.drawable.picture_image_placeholder) // 添加错误占位图
|
|||
|
|
.fallback(R.drawable.picture_image_placeholder) // 添加空URL占位图
|
|||
|
|
.skipMemoryCache(false) // 使用内存缓存
|
|||
|
|
.diskCacheStrategy(DiskCacheStrategy.ALL) // 使用磁盘缓存
|
|||
|
|
.listener(new RequestListener<Drawable>() {
|
|||
|
|
@Override
|
|||
|
|
public boolean onLoadFailed(@Nullable GlideException e, Object model,
|
|||
|
|
Target<Drawable> target, boolean isFirstResource) {
|
|||
|
|
// 记录错误日志
|
|||
|
|
if (e != null) {
|
|||
|
|
Log.e("Glide", "图片加载失败: " + url, e);
|
|||
|
|
}
|
|||
|
|
return false; // 返回false让Glide显示error占位图
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public boolean onResourceReady(Drawable resource, Object model,
|
|||
|
|
Target<Drawable> target, DataSource dataSource,
|
|||
|
|
boolean isFirstResource) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.into(imageView);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. 优化 NineImageLoader.java
|
|||
|
|
|
|||
|
|
在 `app/src/main/java/utils/NineImageLoader.java` 中:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Override
|
|||
|
|
public void onDisplayImage(Context context, ImageView imageView, String url) {
|
|||
|
|
RequestOptions requestOptions = new RequestOptions();
|
|||
|
|
requestOptions.placeholder(R.mipmap.icon_default_rectangle);
|
|||
|
|
requestOptions.error(R.mipmap.icon_default_rectangle);
|
|||
|
|
requestOptions.skipMemoryCache(false);
|
|||
|
|
requestOptions.diskCacheStrategy(DiskCacheStrategy.ALL);
|
|||
|
|
|
|||
|
|
// 添加错误处理
|
|||
|
|
Glide.with(context)
|
|||
|
|
.load(url)
|
|||
|
|
.apply(requestOptions)
|
|||
|
|
.listener(new RequestListener<Drawable>() {
|
|||
|
|
@Override
|
|||
|
|
public boolean onLoadFailed(@Nullable GlideException e, Object model,
|
|||
|
|
Target<Drawable> target, boolean isFirstResource) {
|
|||
|
|
if (e != null) {
|
|||
|
|
Log.e("NineImageLoader", "九宫格图片加载失败: " + url, e);
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public boolean onResourceReady(Drawable resource, Object model,
|
|||
|
|
Target<Drawable> target, DataSource dataSource,
|
|||
|
|
boolean isFirstResource) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.into(imageView);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3. 优化 DiscoveryFragment.java
|
|||
|
|
|
|||
|
|
在 `app/src/main/java/com/sl/house_property/discovery/DiscoveryFragment.java` 中:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
RequestOptions requestOptions = new RequestOptions();
|
|||
|
|
requestOptions.placeholder(R.mipmap.icon_default_rectangle);
|
|||
|
|
requestOptions.error(R.mipmap.icon_default_rectangle);
|
|||
|
|
requestOptions.skipMemoryCache(false);
|
|||
|
|
requestOptions.diskCacheStrategy(DiskCacheStrategy.ALL);
|
|||
|
|
|
|||
|
|
Glide.with(getContext())
|
|||
|
|
.load(discoveryListEntity.getAvatar())
|
|||
|
|
.apply(requestOptions)
|
|||
|
|
.listener(new RequestListener<Drawable>() {
|
|||
|
|
@Override
|
|||
|
|
public boolean onLoadFailed(@Nullable GlideException e, Object model,
|
|||
|
|
Target<Drawable> target, boolean isFirstResource) {
|
|||
|
|
if (e != null) {
|
|||
|
|
Log.e("DiscoveryFragment", "头像加载失败: " + discoveryListEntity.getAvatar(), e);
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public boolean onResourceReady(Drawable resource, Object model,
|
|||
|
|
Target<Drawable> target, DataSource dataSource,
|
|||
|
|
boolean isFirstResource) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.into(homeGridAdapterItemBinding.imageHead);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 方案二:添加ProGuard规则
|
|||
|
|
|
|||
|
|
在 `app/proguard-rules.pro` 中添加:
|
|||
|
|
|
|||
|
|
```proguard
|
|||
|
|
# Glide 图片加载库
|
|||
|
|
-keep public class * implements com.bumptech.glide.module.GlideModule
|
|||
|
|
-keep class * extends com.bumptech.glide.module.AppGlideModule {
|
|||
|
|
<init>(...);
|
|||
|
|
}
|
|||
|
|
-keep public class * implements com.bumptech.glide.module.RegisterGlideModule
|
|||
|
|
-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder {
|
|||
|
|
*** rewind();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Glide 注解
|
|||
|
|
-keep class com.bumptech.glide.load.engine.DecodeJob {
|
|||
|
|
*;
|
|||
|
|
}
|
|||
|
|
-keep class com.bumptech.glide.load.engine.bitmap_recycle.** {
|
|||
|
|
*;
|
|||
|
|
}
|
|||
|
|
-keep class com.bumptech.glide.load.resource.bitmap.** {
|
|||
|
|
*;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Glide 错误处理
|
|||
|
|
-keep class com.bumptech.glide.load.engine.GlideException {
|
|||
|
|
*;
|
|||
|
|
}
|
|||
|
|
-keep class com.bumptech.glide.load.engine.GlideException$** {
|
|||
|
|
*;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 方案三:配置Glide内存和缓存
|
|||
|
|
|
|||
|
|
在 `MyApplication.java` 中添加Glide配置:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Override
|
|||
|
|
public void onCreate() {
|
|||
|
|
super.onCreate();
|
|||
|
|
|
|||
|
|
// ... 其他初始化代码 ...
|
|||
|
|
|
|||
|
|
// 配置Glide
|
|||
|
|
configureGlide();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void configureGlide() {
|
|||
|
|
// 设置内存缓存大小(可选)
|
|||
|
|
// Glide默认会根据设备内存自动调整
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 方案四:添加图片URL验证
|
|||
|
|
|
|||
|
|
在使用Glide加载图片前,先验证URL:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
public static boolean isValidImageUrl(String url) {
|
|||
|
|
if (TextUtils.isEmpty(url)) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查URL格式
|
|||
|
|
if (!url.startsWith("http://") && !url.startsWith("https://")
|
|||
|
|
&& !url.startsWith("file://") && !url.startsWith("content://")) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查图片格式
|
|||
|
|
String lowerUrl = url.toLowerCase();
|
|||
|
|
return lowerUrl.endsWith(".jpg") || lowerUrl.endsWith(".jpeg")
|
|||
|
|
|| lowerUrl.endsWith(".png") || lowerUrl.endsWith(".gif")
|
|||
|
|
|| lowerUrl.endsWith(".webp") || lowerUrl.contains("image");
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 完整优化示例
|
|||
|
|
|
|||
|
|
### 创建统一的Glide工具类
|
|||
|
|
|
|||
|
|
创建文件:`app/src/main/java/utils/GlideUtils.java`
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package utils;
|
|||
|
|
|
|||
|
|
import android.content.Context;
|
|||
|
|
import android.graphics.drawable.Drawable;
|
|||
|
|
import android.util.Log;
|
|||
|
|
import android.widget.ImageView;
|
|||
|
|
|
|||
|
|
import androidx.annotation.NonNull;
|
|||
|
|
import androidx.annotation.Nullable;
|
|||
|
|
|
|||
|
|
import com.bumptech.glide.Glide;
|
|||
|
|
import com.bumptech.glide.load.DataSource;
|
|||
|
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
|||
|
|
import com.bumptech.glide.load.engine.GlideException;
|
|||
|
|
import com.bumptech.glide.request.RequestListener;
|
|||
|
|
import com.bumptech.glide.request.RequestOptions;
|
|||
|
|
import com.bumptech.glide.request.target.Target;
|
|||
|
|
|
|||
|
|
import com.sl.house_property.R;
|
|||
|
|
|
|||
|
|
public class GlideUtils {
|
|||
|
|
|
|||
|
|
private static final String TAG = "GlideUtils";
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 加载图片(带错误处理)
|
|||
|
|
*/
|
|||
|
|
public static void loadImage(Context context, String url, ImageView imageView) {
|
|||
|
|
loadImage(context, url, imageView, R.drawable.picture_image_placeholder);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 加载图片(带错误处理和占位图)
|
|||
|
|
*/
|
|||
|
|
public static void loadImage(Context context, String url, ImageView imageView, int placeholderResId) {
|
|||
|
|
if (context == null || imageView == null) {
|
|||
|
|
Log.w(TAG, "Context或ImageView为空,无法加载图片");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!isValidImageUrl(url)) {
|
|||
|
|
Log.w(TAG, "无效的图片URL: " + url);
|
|||
|
|
imageView.setImageResource(placeholderResId);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
RequestOptions options = new RequestOptions()
|
|||
|
|
.placeholder(placeholderResId)
|
|||
|
|
.error(placeholderResId)
|
|||
|
|
.fallback(placeholderResId)
|
|||
|
|
.skipMemoryCache(false)
|
|||
|
|
.diskCacheStrategy(DiskCacheStrategy.ALL);
|
|||
|
|
|
|||
|
|
Glide.with(context)
|
|||
|
|
.load(url)
|
|||
|
|
.apply(options)
|
|||
|
|
.listener(new RequestListener<Drawable>() {
|
|||
|
|
@Override
|
|||
|
|
public boolean onLoadFailed(@Nullable GlideException e, Object model,
|
|||
|
|
Target<Drawable> target, boolean isFirstResource) {
|
|||
|
|
if (e != null) {
|
|||
|
|
Log.e(TAG, "图片加载失败: " + url, e);
|
|||
|
|
// 可以在这里添加错误上报
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public boolean onResourceReady(Drawable resource, Object model,
|
|||
|
|
Target<Drawable> target, DataSource dataSource,
|
|||
|
|
boolean isFirstResource) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.into(imageView);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 验证图片URL是否有效
|
|||
|
|
*/
|
|||
|
|
private static boolean isValidImageUrl(String url) {
|
|||
|
|
if (url == null || url.trim().isEmpty()) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
String lowerUrl = url.toLowerCase().trim();
|
|||
|
|
return lowerUrl.startsWith("http://")
|
|||
|
|
|| lowerUrl.startsWith("https://")
|
|||
|
|
|| lowerUrl.startsWith("file://")
|
|||
|
|
|| lowerUrl.startsWith("content://")
|
|||
|
|
|| lowerUrl.startsWith("drawable://");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 加载圆形图片
|
|||
|
|
*/
|
|||
|
|
public static void loadCircleImage(Context context, String url, ImageView imageView) {
|
|||
|
|
RequestOptions options = new RequestOptions()
|
|||
|
|
.circleCrop()
|
|||
|
|
.placeholder(R.drawable.picture_image_placeholder)
|
|||
|
|
.error(R.drawable.picture_image_placeholder);
|
|||
|
|
|
|||
|
|
Glide.with(context)
|
|||
|
|
.load(url)
|
|||
|
|
.apply(options)
|
|||
|
|
.into(imageView);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清除Glide缓存
|
|||
|
|
*/
|
|||
|
|
public static void clearCache(Context context) {
|
|||
|
|
Glide.get(context).clearMemory();
|
|||
|
|
new Thread(new Runnable() {
|
|||
|
|
@Override
|
|||
|
|
public void run() {
|
|||
|
|
Glide.get(context).clearDiskCache();
|
|||
|
|
}
|
|||
|
|
}).start();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 使用建议
|
|||
|
|
|
|||
|
|
### 1. 统一使用工具类
|
|||
|
|
将所有Glide调用改为使用 `GlideUtils`:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 替换前
|
|||
|
|
Glide.with(context).load(url).into(imageView);
|
|||
|
|
|
|||
|
|
// 替换后
|
|||
|
|
GlideUtils.loadImage(context, url, imageView);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 添加必要的导入
|
|||
|
|
在使用RequestListener时需要添加:
|
|||
|
|
```java
|
|||
|
|
import com.bumptech.glide.load.DataSource;
|
|||
|
|
import com.bumptech.glide.load.engine.GlideException;
|
|||
|
|
import com.bumptech.glide.request.RequestListener;
|
|||
|
|
import com.bumptech.glide.request.target.Target;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 检查Context生命周期
|
|||
|
|
确保在Activity/Fragment销毁时取消Glide请求:
|
|||
|
|
```java
|
|||
|
|
@Override
|
|||
|
|
protected void onDestroy() {
|
|||
|
|
super.onDestroy();
|
|||
|
|
Glide.with(this).clear(imageView);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 常见错误处理
|
|||
|
|
|
|||
|
|
### 错误1:DecodeJob异常
|
|||
|
|
- **原因**:图片解码失败
|
|||
|
|
- **解决**:添加错误占位图,验证URL有效性
|
|||
|
|
|
|||
|
|
### 错误2:内存溢出
|
|||
|
|
- **原因**:加载过大图片
|
|||
|
|
- **解决**:使用override()限制图片尺寸
|
|||
|
|
|
|||
|
|
### 错误3:网络图片加载失败
|
|||
|
|
- **原因**:网络问题或URL无效
|
|||
|
|
- **解决**:添加网络检查,使用错误占位图
|
|||
|
|
|
|||
|
|
## 测试验证
|
|||
|
|
|
|||
|
|
1. **测试正常图片加载**
|
|||
|
|
- 验证占位图显示
|
|||
|
|
- 验证图片加载成功
|
|||
|
|
|
|||
|
|
2. **测试错误情况**
|
|||
|
|
- 无效URL
|
|||
|
|
- 网络断开
|
|||
|
|
- 损坏图片
|
|||
|
|
|
|||
|
|
3. **测试内存**
|
|||
|
|
- 加载大量图片
|
|||
|
|
- 检查内存使用
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**Glide版本**: 4.9.0
|
|||
|
|
**建议升级**: 考虑升级到Glide 4.16.0+(如果兼容)
|
|||
|
|
|