AsyncTask图片压缩报错:FATAL EXCEPTION: AsyncTask #3(待传Firebase Storage)
解决AsyncTask图片压缩时的FATAL EXCEPTION问题
嘿,我帮你排查下这个AsyncTask崩溃的问题,看了你的代码片段,有几个明显的坑需要填上,还有一些潜在的风险会导致崩溃,我来一步步帮你解决:
先揪出代码里的致命问题
- 参数类型完全不匹配:你的
AsyncTask泛型声明是<Uri, Integer, byte[]>,但构造函数却传入了Bitmap,doInBackground接收的是Uri参数,但你实际要处理的是构造里存的Bitmap——这种逻辑混乱很容易导致空指针或者类型转换异常,直接触发崩溃。 - Activity上下文泄漏:在
onPreExecute里直接用NextActivity.this,AsyncTask会持有Activity的强引用,如果Activity在压缩任务完成前被销毁(比如用户按了返回键),就会导致内存泄漏,甚至因为上下文失效触发崩溃。 - 潜在的OOM风险:处理大图时如果没做Bitmap回收,很容易触发内存溢出,这也是AsyncTask崩溃的高频原因。
修正后的完整AsyncTask代码
我重新写了一个更健壮的版本,解决了上面的问题,还加入了回调接口解耦逻辑:
import android.content.Context; import android.graphics.Bitmap; import android.os.AsyncTask; import android.util.Log; import android.widget.Toast; import java.io.ByteArrayOutputStream; import java.lang.ref.WeakReference; public class BackgroundImageResize extends AsyncTask<Void, Integer, byte[]> { private final WeakReference<Context> mContextRef; private final Bitmap mBitmap; private OnCompressListener mListener; // 定义回调接口,把压缩结果传递给上层 public interface OnCompressListener { void onCompressSuccess(byte[] compressedData); void onCompressFailed(Exception e); } public BackgroundImageResize(Context context, Bitmap bitmap, OnCompressListener listener) { mContextRef = new WeakReference<>(context); mBitmap = bitmap; mListener = listener; } @Override protected void onPreExecute() { super.onPreExecute(); Context context = mContextRef.get(); if (context != null) { Toast.makeText(context, "Compressing image...", Toast.LENGTH_SHORT).show(); } } @Override protected byte[] doInBackground(Void... params) { if (mBitmap == null) { return null; } try { // 双重压缩策略:先质量压缩,太大再尺寸压缩 ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 质量压缩(JPEG格式,70%质量,可调整) mBitmap.compress(Bitmap.CompressFormat.JPEG, 70, baos); int fileSize = baos.size(); // 如果超过100KB,循环缩小尺寸(可根据需求调整阈值) while (fileSize > 1024 * 100) { baos.reset(); // 把图片尺寸缩小一半 Bitmap resizedBitmap = Bitmap.createScaledBitmap(mBitmap, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2, true); resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 70, baos); // 回收临时Bitmap,避免OOM if (!resizedBitmap.isRecycled() && resizedBitmap != mBitmap) { resizedBitmap.recycle(); } fileSize = baos.size(); } // 回收原始Bitmap(如果后续不再使用) if (!mBitmap.isRecycled()) { mBitmap.recycle(); } return baos.toByteArray(); } catch (Exception e) { Log.e("BackgroundResize", "Image compression failed", e); return null; } } @Override protected void onPostExecute(byte[] result) { super.onPostExecute(result); Context context = mContextRef.get(); if (context != null && result != null) { Toast.makeText(context, "Compression completed", Toast.LENGTH_SHORT).show(); // 回调给上层处理上传逻辑 if (mListener != null) { mListener.onCompressSuccess(result); } } else { if (mListener != null) { mListener.onCompressFailed(new Exception("Compression failed or context is invalid")); } } } }
如何在Activity中使用
// 假设你已经获取到了要压缩的Bitmap对象 Bitmap originalBitmap = ...; new BackgroundImageResize(this, originalBitmap, new BackgroundImageResize.OnCompressListener() { @Override public void onCompressSuccess(byte[] compressedData) { // 压缩成功,调用上传到Firebase的方法 uploadImageToFirebase(compressedData); } @Override public void onCompressFailed(Exception e) { Toast.makeText(NextActivity.this, "Compression failed: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } }).execute();
上传到Firebase Storage的示例代码
import com.google.firebase.storage.FirebaseStorage; import com.google.firebase.storage.StorageReference; import com.google.firebase.storage.UploadTask; import java.io.ByteArrayInputStream; private void uploadImageToFirebase(byte[] compressedData) { // 获取Firebase Storage引用 StorageReference storageRef = FirebaseStorage.getInstance().getReference(); // 生成唯一的文件名(用时间戳避免重复) StorageReference imageRef = storageRef.child("compressed_images/" + System.currentTimeMillis() + ".jpg"); ByteArrayInputStream inputStream = new ByteArrayInputStream(compressedData); UploadTask uploadTask = imageRef.putStream(inputStream); // 监听上传结果 uploadTask.addOnSuccessListener(taskSnapshot -> { // 上传成功,获取下载URL imageRef.getDownloadUrl().addOnSuccessListener(downloadUri -> { String imageUrl = downloadUri.toString(); Toast.makeText(this, "Upload successful! URL: " + imageUrl, Toast.LENGTH_LONG).show(); // 这里可以把URL保存到Firestore或者SharedPreferences }); }).addOnFailureListener(e -> { Toast.makeText(this, "Upload failed: " + e.getMessage(), Toast.LENGTH_SHORT).show(); Log.e("FirebaseUpload", "Upload failed", e); }); }
额外推荐:用Coroutines替代AsyncTask(更现代的方案)
Android 11之后AsyncTask已经被废弃,推荐用Kotlin Coroutines或者Java的Executor来处理后台任务,代码更简洁且更安全。这里给你一个Kotlin Coroutines的示例:
import android.graphics.Bitmap import android.widget.Toast import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext // 在Activity中调用 lifecycleScope.launch { Toast.makeText(this@NextActivity, "Compressing image...", Toast.LENGTH_SHORT).show() val compressedData = withContext(Dispatchers.IO) { compressImage(originalBitmap) } if (compressedData != null) { Toast.makeText(this@NextActivity, "Compression done", Toast.LENGTH_SHORT).show() uploadImageToFirebase(compressedData) } else { Toast.makeText(this@NextActivity, "Compression failed", Toast.LENGTH_SHORT).show() } } // 压缩函数 private fun compressImage(bitmap: Bitmap): ByteArray? { return try { val baos = ByteArrayOutputStream() bitmap.compress(Bitmap.CompressFormat.JPEG, 70, baos) var fileSize = baos.size() while (fileSize > 1024 * 100) { baos.reset() val resizedBitmap = Bitmap.createScaledBitmap(bitmap, bitmap.width / 2, bitmap.height / 2, true) resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 70, baos) if (!resizedBitmap.isRecycled && resizedBitmap != bitmap) { resizedBitmap.recycle() } fileSize = baos.size() } bitmap.recycle() baos.toByteArray() } catch (e: Exception) { e.printStackTrace() null } }
内容的提问来源于stack exchange,提问作者Aditya Sissodiya
相关产品推荐
相关产品推荐

