Android中读取相册大图片遇TransactionTooLargeException的Java解决方案
问题场景
原本使用以下代码从相册读取图片:
Intent intent = null; intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); intent.setType("image/*"); startActivityForResult(intent, SELECT_PICTURE);
当读取大于1MB的图片时,触发如下异常:
Uncaught exception: Error Message: java.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size 1132804 bytes
at android.app.ActivityClient.activityStopped(ActivityClient.java:86)
at android.app.servertransaction.PendingTransactionActions$StopInfo.run(PendingTransactionActions.java:143)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8751)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
异常根源:
Caused by: android.os.TransactionTooLargeException: data parcel size 1132804 bytes
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(BinderProxy.java:635)
at android.app.IActivityClientController$Stub$Proxy.activityStopped(IActivityClientController.java:1358)
at android.app.ActivityClient.activityStopped(ActivityClient.java:83)
... 9 more
解决思路及代码实现
异常核心原因是:ACTION_PICK返回的Intent会把图片完整数据塞进Parcel,当图片体积超过Binder事务的默认大小限制(约1MB)时,就会触发该异常。解决关键是只获取图片Uri,再通过Uri读取并压缩图片,避免大数据量的Binder传输。
方案1:通过Uri读取并压缩图片(推荐)
保留原启动相册的逻辑,在结果回调中只拿Uri,再通过ContentResolver读取图片并压缩:
启动相册代码
private static final int SELECT_PICTURE = 100; // 启动相册选择 public void pickImageFromGallery() { Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); intent.setType("image/*"); startActivityForResult(intent, SELECT_PICTURE); }
处理返回结果并压缩图片
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == SELECT_PICTURE && resultCode == RESULT_OK && data != null) { Uri imageUri = data.getData(); if (imageUri != null) { // 获取压缩后的Bitmap,避免内存溢出和事务过大问题 Bitmap compressedBitmap = getCompressedBitmapFromUri(imageUri); // 后续使用压缩后的Bitmap进行业务操作 } } } // 从Uri读取并生成压缩后的Bitmap private Bitmap getCompressedBitmapFromUri(Uri uri) { BitmapFactory.Options options = new BitmapFactory.Options(); // 先仅读取图片尺寸,不加载到内存 options.inJustDecodeBounds = true; try (InputStream inputStream = getContentResolver().openInputStream(uri)) { BitmapFactory.decodeStream(inputStream, null, options); } catch (IOException e) { e.printStackTrace(); return null; } // 计算压缩比例,目标尺寸可根据业务需求调整 int targetWidth = 1080; int targetHeight = 1080; int width = options.outWidth; int height = options.outHeight; int inSampleSize = 1; if (height > targetHeight || width > targetWidth) { int halfWidth = width / 2; int halfHeight = height / 2; while ((halfHeight / inSampleSize) >= targetHeight && (halfWidth / inSampleSize) >= targetWidth) { inSampleSize *= 2; } } // 加载压缩后的图片 options.inJustDecodeBounds = false; options.inSampleSize = inSampleSize; options.inPreferredConfig = Bitmap.Config.RGB_565; // 降低内存占用 try (InputStream inputStream = getContentResolver().openInputStream(uri)) { return BitmapFactory.decodeStream(inputStream, null, options); } catch (IOException e) { e.printStackTrace(); return null; } }
方案2:替换为ACTION_GET_CONTENT(可选)
ACTION_GET_CONTENT的返回逻辑更偏向于传递Uri而非直接数据,部分场景下可避免Parcel过大问题,启动代码调整如下:
public void pickImageFromGallery() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/*"); startActivityForResult(intent, SELECT_PICTURE); }
后续的Uri读取和压缩逻辑与方案1完全一致。
注意事项
- Android 10及以上版本需适配Scoped Storage权限,确保正常访问媒体文件;
- 若需上传或保存图片,建议将压缩后的Bitmap保存为文件,避免内存占用过高;
- 若使用AndroidX库,推荐用
ActivityResultContracts.GetContent替代startActivityForResult,适配性更好。
内容的提问来源于stack exchange,提问作者PSP

