问题背景:为什么传递大图片会崩溃?
在 Android 开发中,通过 Intent
传递数据(如启动 Activity
或 Service
)时,若直接传递大尺寸的 Bitmap
或字节数组,应用会直接崩溃并抛出 TransactionTooLargeException
。例如以下代码:
java
// 错误示例:直接传递 Bitmap
Bitmap bigBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.large_image);
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra("image", bigBitmap); // 此处崩溃!
startActivity(intent);
原因分析:Binder 事务缓冲区限制
Android 的跨进程通信(IPC)底层依赖 Binder 机制 ,而 Binder 的事务缓冲区(Transaction Buffer)有固定大小限制(通常为 1MB )。当通过 Intent
传递的数据超过此限制时,系统会抛出 TransactionTooLargeException
。
- 直接传递 Bitmap 的问题 :
Bitmap
对象默认通过Parcelable
序列化,若图片尺寸过大(如 3000x4000 的 ARGB_8888 图片占内存 48MB),序列化后的数据远超 1MB,直接触发崩溃。
解决方案与完整代码实现
方案 1:通过 URI 传递图片(推荐)
核心思想
将图片保存到文件系统中,通过 FileProvider
生成 content://
URI 传递,而非直接传递数据。
实现步骤
1. 配置 FileProvider
在 AndroidManifest.xml
中添加 FileProvider
声明:
xml
<application>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
2. 定义文件路径
创建 res/xml/file_paths.xml
,指定可访问的目录:
xml
<paths>
<!-- 对应 Context.getExternalFilesDir() -->
<external-files-path name="external_files" path="images/" />
<!-- 或使用缓存目录 -->
<cache-path name="cache" path="temp_images/" />
</paths>
3. 保存图片并生成 URI
在发送方(如 MainActivity
)中保存图片并生成 URI:
java
// 创建临时图片文件
File imagesDir = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "images");
if (!imagesDir.exists()) imagesDir.mkdirs();
File imageFile = new File(imagesDir, "temp_image.jpg");
// 将 Bitmap 保存到文件
try (FileOutputStream fos = new FileOutputStream(imageFile)) {
bitmap.compress(Bitmap.CompressFormat.JPEG, 85, fos);
} catch (IOException e) {
e.printStackTrace();
}
// 生成 Content URI
Uri contentUri = FileProvider.getUriForFile(
this,
getPackageName() + ".fileprovider",
imageFile
);
// 传递 URI
Intent intent = new Intent(this, DetailActivity.class);
intent.setData(contentUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
4. 接收方解析 URI
在目标 Activity
中读取 URI 并加载图片:
java
Uri imageUri = getIntent().getData();
try (InputStream inputStream = getContentResolver().openInputStream(imageUri)) {
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
imageView.setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
方案 2:压缩图片后再传递
适用场景
若必须直接传递 Bitmap
(如简单应用内部跳转),可先压缩图片至合理尺寸。
java
// 计算缩放比例
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.large_image, options);
int scale = Math.max(options.outWidth / targetWidth, options.outHeight / targetHeight);
// 加载缩小后的图片
options.inJustDecodeBounds = false;
options.inSampleSize = scale;
Bitmap scaledBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.large_image, options);
// 传递 Bitmap(仅在确定尺寸足够小时使用!)
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra("image", scaledBitmap);
startActivity(intent);
方案 3:全局缓存 + 标识符传递
实现思路
将 Bitmap
存入全局缓存(如 Application
类或单例),通过 Intent
传递唯一标识符(如文件名或 UUID)。
java
// 全局缓存类
public class ImageCache {
private static Map<String, Bitmap> cache = new HashMap<>();
public static void saveBitmap(String key, Bitmap bitmap) {
cache.put(key, bitmap);
}
public static Bitmap getBitmap(String key) {
return cache.get(key);
}
}
// 发送方
String imageKey = UUID.randomUUID().toString();
ImageCache.saveBitmap(imageKey, bigBitmap);
intent.putExtra("image_key", imageKey);
// 接收方
String key = getIntent().getStringExtra("image_key");
Bitmap bitmap = ImageCache.getBitmap(key);
技术对比与选型建议
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
URI 传递 | 安全、无大小限制、符合规范 | 需文件读写权限、多步操作 | 跨进程、大文件传递 |
压缩图片 | 简单直接 | 可能损失画质、仍有大小风险 | 小图、临时传递 |
全局缓存 | 内存访问快 | 内存占用高、需管理生命周期 | 单进程内频繁使用的图片 |
关键点总结
-
永远不要直接传递大 Bitmap
直接传递
Bitmap
或字节数组必然触发TransactionTooLargeException
,务必使用 URI 或缓存。 -
FileProvider 是最佳实践
content://
URI 既安全又无大小限制,需注意配置file_paths.xml
和权限。 -
压缩图片需权衡质量与尺寸
使用
inSampleSize
或createScaledBitmap
缩小尺寸,但需评估画质要求。 -
全局缓存注意内存管理
避免内存泄漏,可在
onDestroy()
中移除缓存,或使用WeakReference
。