Android应用报错:Could not copy bitmap to parcel blob 求助
java.lang.RuntimeException: Could not copy bitmap to parcel blob 开发仓库管理Android(Java)应用,通过Volley的HTTP请求将员工签名图片上传至服务器,数据和图片已成功存储,但应用随后崩溃,报错java.lang.RuntimeException: Could not copy bitmap to parcel blob。未通过Intent传递图片,尝试过不传递idUsuario/nombreUsuario参数、上传后删除生成的图片,仍未解决。
相关代码
FirmaActivity代码
import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Color; import android.os.Bundle; import android.util.Base64; import android.util.Log; import android.view.View; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.toolbox.StringRequest; import com.android.volley.toolbox.Volley; import com.ctc.controlgarita.data.Empleado; import com.github.gcacace.signaturepad.views.SignaturePad; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; public class FirmaActivity extends AppCompatActivity { String firmaEncoded; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_firma); Intent intent = getIntent(); Button buttonBorrarFirma = findViewById(R.id.buttonBorrarFirma); Button buttonEnviarRegistro = findViewById(R.id.buttonEnviarRegistro); SignaturePad signaturePad = findViewById(R.id.signature_pad); signaturePad.setBackgroundColor(Color.WHITE); signaturePad.setOnSignedListener(new SignaturePad.OnSignedListener() { @Override public void onStartSigning() { // No se necesita } @Override public void onSigned() { buttonBorrarFirma.setEnabled(true); buttonEnviarRegistro.setEnabled(true); } @Override public void onClear() { buttonBorrarFirma.setEnabled(false); buttonEnviarRegistro.setEnabled(false); } }); buttonBorrarFirma.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { signaturePad.clear(); } }); String nombreUsuario = intent.getStringExtra("nombreUsuario"); String idUsuario = intent.getStringExtra("idUsuario"); String tipoRegistro = intent.getStringExtra("tipoRegistro"); String llave = intent.getStringExtra("llave"); String corpo = intent.getStringExtra("corpo"); String numeroMovil = intent.getStringExtra("numeroMovil"); String numeroCorpo = intent.getStringExtra("numeroCorpo"); String idEmpleado = intent.getStringExtra("idEmpleado"); buttonEnviarRegistro.setOnClickListener(view -> { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); final Bitmap[] signatureBitmap = {signaturePad.getSignatureBitmap()}; signatureBitmap[0].compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); byte[] byteArray = byteArrayOutputStream.toByteArray(); firmaEncoded = Base64.encodeToString(byteArray, Base64.DEFAULT); String url = "http://SOME_IP_ADDRESS/wsGarita/ingresarRegistro.php"; StringRequest stringRequest = new StringRequest(Request.Method.POST, url, response -> { try { JSONObject jsonObject = new JSONObject(response); boolean success = jsonObject.getBoolean("success"); if (success) { Intent nextIntent = new Intent(FirmaActivity.this, FinRegistroActivity.class); nextIntent.putExtra("nombreUsuario", nombreUsuario); nextIntent.putExtra("idUsuario", idUsuario); startActivity(nextIntent); } else { Toast.makeText(FirmaActivity.this, "ERROR al ingresar registro", Toast.LENGTH_LONG).show(); } } catch (JSONException e) { e.printStackTrace(); Toast.makeText(FirmaActivity.this, "ERROR al procesar JSON", Toast.LENGTH_LONG).show(); } }, error -> { Log.e("ErrorVolley", error.toString()); }) { @Override protected Map<String, String> getParams() { // Parámetros para la solicitud POST Map<String, String> params = new HashMap<>(); params.put("tipo_registro", tipoRegistro); params.put("empleado_id", idEmpleado); params.put("registro_llave", llave); params.put("registro_corpo", corpo); params.put("numero_movil", numeroMovil); params.put("numero_corpo", numeroCorpo); params.put("usuario_id", idUsuario); params.put("firma", firmaEncoded); return params; } }; RequestQueue requestQueue = Volley.newRequestQueue(getApplicationContext()); requestQueue.add(stringRequest); }); } }
FinRegistroActivity代码
public class FinRegistroActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fin_registro); Toolbar toolbar = findViewById(R.id.toolbar); TextView toolbarTitle = findViewById(R.id.toolbar_title); Intent intent = getIntent(); String nombreUsuario = intent.getStringExtra("nombreUsuario"); String idUsuario = intent.getStringExtra("idUsuario"); if (toolbar != null) { setSupportActionBar(toolbar); if (getSupportActionBar() != null) { getSupportActionBar().setDisplayShowTitleEnabled(false); // Deshabilitar el título predeterminado if (nombreUsuario != null) { toolbarTitle.setText(getString(R.string.toolbar_title, nombreUsuario)); } } } // end if toolbar Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { Intent nextIntent = new Intent(FinRegistroActivity.this, RegistroActivity.class); nextIntent.putExtra("idUsuario", idUsuario); nextIntent.putExtra("nombreUsuario", nombreUsuario); startActivity(nextIntent); } }, 2500); } }
堆栈跟踪
Reducing the number of considered missed Gc histogram windows from 407 to 100 2023-12-18 09:39:30.177 5179-5207 Parcel com.ctc.controlgarita W Expecting binder but got null! 2023-12-18 09:39:30.716 5179-5179 ActivityStopInfo com.ctc.controlgarita W Bundle stats: 2023-12-18 09:39:30.719 5179-5179 ActivityStopInfo com.ctc.controlgarita W android:viewHierarchyState [size=1428] 2023-12-18 09:39:30.722 5179-5179 ActivityStopInfo com.ctc.controlgarita W android:views [size=1380] 2023-12-18 09:39:30.726 5179-5179 ActivityStopInfo com.ctc.controlgarita W androidx.lifecycle.BundlableSavedStateRegistry.key [size=1228] 2023-12-18 09:39:30.726 5179-5179 ActivityStopInfo com.ctc.controlgarita W PersistableBundle stats: 2023-12-18 09:39:30.726 5179-5179 ActivityStopInfo com.ctc.controlgarita W [null] 2023-12-18 09:39:30.727 5179-5179 AndroidRuntime com.ctc.controlgarita D Shutting down VM 2023-12-18 09:39:30.732 5179-5179 AndroidRuntime com.ctc.controlgarita E FATAL EXCEPTION: main Process: com.ctc.controlgarita, PID: 5179 java.lang.RuntimeException: Could not copy bitmap to parcel blob. at android.graphics.Bitmap.nativeWriteToParcel(Native Method) at android.graphics.Bitmap.writeToParcel(Bitmap.java:2140) at android.os.Parcel.writeParcelable(Parcel.java:2538) at android.os.Parcel.writeValue(Parcel.java:2439) at android.os.Parcel.writeValue(Parcel.java:2316) at android.os.Parcel.writeArrayMapInternal(Parcel.java:1265) at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1802) at android.os.Bundle.writeToParcel(Bundle.java:1389) at android.os.Parcel.writeBundle(Parcel.java:1334) at android.os.Parcel.writeValue(Parcel.java:2433) at android.os.Parcel.writeValue(Parcel.java:2323) at android.os.Parcel.writeSparseArray(Parcel.java:1422) at android.os.Parcel.writeValue(Parcel.java:2463) at android.os.Parcel.writeValue(Parcel.java:2316) at android.os.Parcel.writeArrayMapInternal(Parcel.java:1265) at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1802) at android.os.Bundle.writeToParcel(Bundle.java:1389) at android.os.Parcel.writeBundle(Parcel.java:1334) at android.os.Parcel.writeValue(Parcel.java:2433) at android.os.Parcel.writeValue(Parcel.java:2323) at android.os.Parcel.writeArrayMapInternal(Parcel.java:1265) at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1802) at android.os.Bundle.writeToParcel(Bundle.java:1389) at android.os.Parcel.writeTypedObject(Parcel.java:2157) at android.app.IActivityClientController$Stub$Proxy.activityStopped(IActivityClientController.java:1277) 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:942) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7872) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) 2023-12-18 09:39:30.750 5179-5179 Process com.ctc.controlgarita I Sending signal. PID: 5179 SIG: 9 ---------------------------- PROCESS ENDED (5179) for package com.ctc.controlgarita ---------------------------- ---------------------------- PROCESS STARTED (9849) for package com.ctc.controlgarita ----------------------------
解决方法
这个错误并非主动传递Bitmap导致的,而是系统在Activity销毁时自动保存视图状态(viewHierarchyState)时,尝试将SignaturePad中的Bitmap写入Parcel失败。从堆栈跟踪能看到,崩溃发生在activityStopped阶段,系统在保存Activity的状态Bundle,其中包含了视图层级的状态,而SignaturePad内部的Bitmap因为某种原因无法被序列化写入Parcel。
1. 禁止SignaturePad保存状态
在布局文件中给SignaturePad添加android:saveEnabled="false"属性,阻止系统自动保存它的状态:
<com.github.gcacace.signaturepad.views.SignaturePad android:id="@+id/signature_pad" android:layout_width="match_parent" android:layout_height="match_parent" android:saveEnabled="false" />
2. 在Activity停止时清空签名
在FirmaActivity中重写onStop()方法,在Activity停止前清空SignaturePad的内容,避免系统尝试保存Bitmap:
@Override protected void onStop() { super.onStop(); SignaturePad signaturePad = findViewById(R.id.signature_pad); if (signaturePad != null) { signaturePad.clear(); } }
3. 压缩签名Bitmap后再处理(可选优化)
虽然已经做了Base64编码,但获取签名Bitmap时可以先压缩尺寸,减少内存占用,也能降低系统保存状态时的压力:
// 替换原获取Bitmap的代码 Bitmap originalBitmap = signaturePad.getSignatureBitmap(); // 压缩到合适尺寸,比如宽高不超过500px int maxSize = 500; int width = originalBitmap.getWidth(); int height = originalBitmap.getHeight(); float scale = Math.min((float)maxSize/width, (float)maxSize/height); Bitmap scaledBitmap = Bitmap.createScaledBitmap(originalBitmap, (int)(width*scale), (int)(height*scale), true); // 后续用scaledBitmap进行压缩编码 scaledBitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); // 用完后回收原Bitmap originalBitmap.recycle();
为什么之前的尝试没用?
之前尝试不传递参数、删除生成的图片,但这些操作都没触及问题核心——系统自动保存Activity视图状态时处理SignaturePad的Bitmap失败,所以需要从阻止状态保存或清空Bitmap入手。
内容的提问来源于stack exchange,提问作者pmpato

