Android录制更新Notification时触发TransactionTooLargeException求助
我太懂你这个困扰了——明明已经把要更新的数据压到最小,用CountDownTimer更新录制时长的Notification还是触发了TransactionTooLargeException。其实问题核心不是你传递的文本数据,而是每次更新都重新构建整个Notification对象,导致Binder传输的序列化数据累积过载,尤其是频繁调用notify()的时候。
下面结合你的代码,给几个针对性的解决方案:
1. 复用已构建的Notification,避免重复序列化全量数据
你现在每次调用mNotificationManager.notify()时,都通过mBuilder.build()重新生成全新的Notification,这会把所有PendingIntent、布局资源等都重新序列化一遍,哪怕你只改了时间文本。
修改步骤:
- 先新增一个成员变量保存已构建的Notification:
private Notification mNotification; // 新增成员变量
- 在
showNotificationRecording()中构建并保存这个对象:
public void showNotificationRecording() { // ... 原有逻辑代码 ... mNotification = mBuilder.build(); // 保存构建好的Notification startForeground(NOTIFICATION_ID, mNotification); }
- 在
showTimeRecording()里,只更新RemoteView内容,直接复用已有的Notification:
private void showTimeRecording(int convertToSecond) { int hours = convertToSecond / 3600; int minutes = (convertToSecond % 3600) / 60; int seconds = convertToSecond % 60; mRecordTime = String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds); mRecordTimeNotification = mRecordTime; mRemoteViews.setTextViewText(R.id.tv_notify_time, mRecordTimeNotification); // 只有状态真的变化时才更新,减少不必要的RemoteView操作 String targetState = mContext.getString(R.string.recording_state); if (!mState.equals(targetState)) { mState = targetState; mRemoteViews.setTextViewText(R.id.tv_notify_status, mState); } // 直接复用已有的mNotification,无需重新build mNotificationManager.notify(NOTIFICATION_ID, mNotification); }
2. 提前创建PendingIntent,避免重复生成
你现在每次调用showNotificationRecording()都会重新创建多个PendingIntent,这些Intent的序列化数据会增加Notification的体积。可以把PendingIntent的创建移到Service初始化阶段,复用同一个实例:
private PendingIntent mMainPendingIntent; private PendingIntent mPauseAndResumePending; private PendingIntent mSavePendingIntent; private PendingIntent mDeletePendingIntent; @Override public void onCreate() { super.onCreate(); // 提前创建所有需要的PendingIntent Intent notificationIntent = new Intent(this, MainActivity.class); mMainPendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); Intent pauseIntent = new Intent(this, RecordAudioService.class); pauseIntent.setAction(ACTION_PAUSE_AND_RESUME); mPauseAndResumePending = PendingIntent.getService(this, 0, pauseIntent, 0); Intent saveIntent = new Intent(this, RecordAudioService.class); saveIntent.setAction(ACTION_STOP_RECORD); mSavePendingIntent = PendingIntent.getService(this, 0, saveIntent, 0); Intent deleteIntent = new Intent(this, RecordAudioService.class); deleteIntent.setAction(ACTION_NOTIFICATION_DISCARD); mDeletePendingIntent = PendingIntent.getService(this, 0, deleteIntent, 0); }
之后在showNotificationRecording()里直接使用这些提前创建好的PendingIntent即可。
3. 调整CountDownTimer间隔,减少更新频率
你的CountDownTimer设置250ms触发一次onTick,但实际每4次(也就是1秒)才更新一次时间。完全可以把间隔改成1000ms,减少不必要的回调和notify()调用:
public CountDownTimer countDownRecordingTime() { return new CountDownTimer(Long.MAX_VALUE, 1000) { // 把250改成1000 @Override public void onTick(long millisUntilFinished) { mTime++; // 直接计数秒数,简化逻辑 mHandler.post(() -> showTimeRecording(mTime)); // 检查定时结束条件 if (mTimerToEnd != 0 && mTimerToEnd == mTime) { onStopRecording(); } } @Override public void onFinish() {} }; }
4. 优化RemoteView创建逻辑,避免重复实例化
在showNotificationRecording()的分支中,你多次重新创建mRemoteViews,可以优化成只创建一次,再根据模式调整内容:
public void showNotificationRecording() { isSecretModeEnable = mSharePreferencesSettings.getBoolean(RecordSettings.SECRET_MODE, false); Log.d(TAG, "isSecretModeEnable " + isSecretModeEnable); // 根据模式选择布局,只创建一次RemoteView int layoutId = isSecretModeEnable ? R.layout.notify_secret_mode_layout : R.layout.notify_recording_layout; mRemoteViews = new RemoteViews(getPackageName(), layoutId); mBuilder = new NotificationCompat.Builder(mContext); if (isSecretModeEnable) { mBuilder.setPriority(Notification.PRIORITY_MIN) .setSmallIcon(R.drawable.ic_notify_secret) .setContent(mRemoteViews); mRemoteViews.setOnClickPendingIntent(R.id.iv_notify_save, mSavePendingIntent); } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mBuilder.setVisibility(Notification.VISIBILITY_PUBLIC); } mBuilder.setSmallIcon(R.drawable.record_circle) .setPriority(Notification.PRIORITY_MAX) .setDefaults(Notification.DEFAULT_LIGHTS) .setContent(mRemoteViews) .setContentIntent(mMainPendingIntent); mRemoteViews.setTextViewText(R.id.tv_notify_time, mRecordTimeNotification); mRemoteViews.setOnClickPendingIntent(R.id.iv_notify_pause_and_resume, mPauseAndResumePending); mRemoteViews.setOnClickPendingIntent(R.id.iv_notify_save, mSavePendingIntent); mRemoteViews.setOnClickPendingIntent(R.id.iv_notify_closed, mDeletePendingIntent); if (isRecording) { mRemoteViews.setImageViewResource(R.id.iv_notify_pause_and_resume, R.drawable.ic_notify_pause); mState = mContext.getString(R.string.recording_state); } else { mRemoteViews.setImageViewResource(R.id.iv_notify_pause_and_resume, R.drawable.ic_notify_record); mState = mContext.getString(R.string.pause_state); } mRemoteViews.setTextViewText(R.id.tv_notify_status, mState); } isShowNotification = true; mNotificationManager = (NotificationManager) mContext.getSystemService(NOTIFICATION_SERVICE); mNotification = mBuilder.build(); startForeground(NOTIFICATION_ID, mNotification); }
这些优化结合起来,能大幅减少每次Notification更新时传输的数据量,彻底避免触发TransactionTooLargeException。
内容的提问来源于stack exchange,提问作者Công Hải

