1 核心问题
为什么在Activity的onSaveInstanceState中捕获了异常,仍然会发生TransactionTooLargeException崩溃?
答案:因为崩溃不是发生在onSaveInstanceState()方法内部,而是发生在之后的Binder事务提交阶段!
2 Android状态保存完整流程
2.1 整体流程图
scss
Activity生命周期变化(如按Home键)
↓
[步骤1] Activity.onPause()
↓
[步骤2] Activity.onStop() ← 我们在这里
↓
[步骤3] Activity.onSaveInstanceState(Bundle outState)
├─ super.onSaveInstanceState(outState)
│ ├─ 保存View hierarchy状态
│ ├─ FragmentManager.saveAllState() ← Fragment状态保存
│ │ ├─ Fragment1.onSaveInstanceState()
│ │ ├─ Fragment2.onSaveInstanceState()
│ │ └─ 将所有Fragment状态序列化到Bundle
│ └─ SavedStateRegistry保存状态
└─ 应用自定义保存逻辑
↓
[步骤4] Bundle序列化完成,数据已在内存中
↓
[步骤5] PendingTransactionActions$StopInfo.run() ← ⚠️ 崩溃发生在这里!
↓
[步骤6] ActivityClient.activityStopped(token, state, persistentState)
↓
[步骤7] IActivityClientController.activityStopped() ← Binder调用
↓
[步骤8] BinderProxy.transact() ← 准备跨进程传输
↓
[步骤9] BinderProxy.transactNative() ← Native方法,检查数据大小
↓
❌ 如果数据 > 1MB,抛出TransactionTooLargeException
↓
[步骤10] SystemServer进程接收状态数据
3 关键源码分析
3.1 Activity.onSaveInstanceState() - 步骤3
less
// frameworks/base/core/java/android/app/Activity.java
protected void onSaveInstanceState(@NonNull Bundle outState) {
// 保存View hierarchy状态
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
// 保存Fragment状态 ← 大数据的主要来源
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
// 保存SavedStateRegistry状态
mActivityLifecycleCallbacks.onActivitySaveInstanceState(this, outState);
// ⚠️ 注意:这里只是把数据放到Bundle中,还没有传输!
}
关键点:
- ✅ 这个方法中的try-catch可以捕获这个方法内部的异常
- ❌ 但无法捕获方法返回后的异常
- Bundle数据此时还在应用进程内存中,还没有跨进程传输
3.2 FragmentManager.saveAllState() - Fragment状态序列化
ini
// androidx/fragment/app/FragmentManager.java
Parcelable saveAllState() {
// 收集所有Fragment的状态
ArrayList<FragmentState> active = new ArrayList<>();
for (Fragment f : mActive.values()) {
if (f != null) {
// ⚠️ 调用每个Fragment的onSaveInstanceState()
FragmentState fs = new FragmentState(f);
active.add(fs);
// 保存Fragment的Arguments(可能很大)
fs.mArguments = f.mArguments;
// 保存Fragment的SavedState(可能很大)
if (f.mSavedFragmentState != null) {
fs.mSavedFragmentState = f.mSavedFragmentState;
} else {
Bundle savedState = new Bundle();
f.onSaveInstanceState(savedState); // ← 调用Fragment的方法
fs.mSavedFragmentState = savedState;
}
}
}
// 创建FragmentManagerState,包含所有Fragment数据
FragmentManagerState fms = new FragmentManagerState();
fms.mActive = active; // ← 这里可能包含大量数据
return fms;
}
关键点:
- 所有Fragment的状态都会被收集
- Fragment的netData、mAdapter等如果被序列化,会非常大
- 这些数据最终都打包到Activity的outState中
3.3 PendingTransactionActions$StopInfo.run() - 步骤5 ⚠️
崩溃堆栈:
php
at android.app.servertransaction.PendingTransactionActions$StopInfo.run(PendingTransactionActions.java:146)
源码分析:
typescript
// frameworks/base/core/java/android/app/servertransaction/PendingTransactionActions.java
public class PendingTransactionActions {
static class StopInfo implements Runnable {
private ActivityClientRecord mActivityClientRecord;
private Bundle mState; // ← 这里存储了Activity的状态
private PersistableBundle mPersistentState;
@Override
public void run() {
try {
// ⚠️ 这里调用ActivityClient.activityStopped()
// 将状态数据通过Binder传输到SystemServer
ActivityClient.getInstance().activityStopped(
mActivityClientRecord.token,
mState, // ← 包含1.4MB的数据
mPersistentState,
mActivityClientRecord.getDescription()
);
} catch (RemoteException e) {
// ❌ 这个catch无法捕获TransactionTooLargeException
// 因为异常是在更深层的transactNative()中抛出的
}
}
}
}
关键点:
- 这个方法在onSaveInstanceState()返回之后执行
- 它负责将Bundle通过Binder传输到SystemServer进程
- 这里的异常无法被应用代码的try-catch捕获!
3.4 BinderProxy.transact() - 步骤8-9
java
// frameworks/base/core/java/android/os/BinderProxy.java
public boolean transact(int code, Parcel data, Parcel reply, int flags) {
// 将Bundle序列化到Parcel
data.writeStrongBinder(token);
data.writeBundle(mState); // ← 1.4MB数据写入Parcel
// 调用Native方法进行跨进程传输
return transactNative(code, data, reply, flags);
// ↑ 如果Parcel大小超过限制,这里会抛出异常!
}
// Native方法(C++实现)
private native boolean transactNative(int code, Parcel data, Parcel reply, int flags);
Native层检查(C++) :
arduino
// frameworks/native/libs/binder/Parcel.cpp
status_t Parcel::writeToParcel(const Parcel* parcel) const {
size_t dataSize = parcel->dataSize();
// ⚠️ 检查数据大小,限制约1MB
if (dataSize > IBinder::MAX_IPC_SIZE) {
// 抛出TransactionTooLargeException
return BAD_VALUE; // ← 这里!
}
// 继续传输...
}
// IBinder::MAX_IPC_SIZE 通常是 1MB - 8KB
关键点:
- 异常是在Native层抛出的
- 发生在Binder事务提交时,不是在Java代码中
- 应用代码无法捕获这个异常!
4 为什么Try-Catch无效?
4.1 原始代码分析
kotlin
// 修复前的代码
override fun onSaveInstanceState(outState: Bundle) {
try {
super.onSaveInstanceState(outState) // ← 步骤3:保存状态到Bundle
// 此时数据还在内存中,没有传输,不会抛异常
} catch (e: Throwable) {
// ❌ 这个catch只能捕获super.onSaveInstanceState()内部的异常
// ❌ 无法捕获之后Binder传输时的异常
PictorialLog.e(TAG, "[onSaveInstanceState] error: ${e.message}")
}
}
// ← onSaveInstanceState()方法返回,Bundle数据已准备好
// ⏬ 之后的流程(应用代码无法控制)
// PendingTransactionActions$StopInfo.run()
// → ActivityClient.activityStopped(mState) // mState包含1.4MB数据
// → BinderProxy.transact()
// → transactNative() ← ⚠️ 异常在这里抛出!应用代码无法捕获
4.2 异常传播路径
scss
[应用进程 - Java层]
onSaveInstanceState(outState) {
try {
super.onSaveInstanceState(outState) ← 步骤1:数据放入Bundle
✅ 成功返回(数据还在内存)
} catch (e) {
❌ 不会执行到这里
}
} ← 方法返回,try-catch作用域结束
↓ [方法返回后,应用代码失去控制]
PendingTransactionActions$StopInfo.run() { ← 步骤2:准备传输
ActivityClient.activityStopped(mState) ← 步骤3:调用Binder
}
[应用进程 - Native层]
BinderProxy.transactNative(data) { ← 步骤4:Native方法
Parcel.writeToParcel() {
if (size > 1MB) {
❌ 抛出TransactionTooLargeException ← 步骤5:异常!
// 应用的try-catch已经不在调用栈上,无法捕获
}
}
}
↓ 异常向上传播
[Android Framework]
异常未被捕获,传播到UncaughtExceptionHandler
→ 应用崩溃!
5 正确的解决方案
5.1 方案对比
方案 | 代码 | 效果 | 原理 |
---|---|---|---|
❌ 捕获异常 | try { super.onSaveInstanceState() } catch {} |
无效 | 异常发生在方法返回后 |
✅ 清空数据 | outState.remove("android:support:fragments") |
有效 | 从源头减少数据量 |
✅ 禁止保存 | Fragment.onSaveInstanceState() { outState.clear() } |
有效 | 阻止Fragment保存大数据 |
5.2 修复后的代码
kotlin
override fun onSaveInstanceState(outState: Bundle) {
try {
super.onSaveInstanceState(outState) // ← 步骤1:保存状态
// ✅ 步骤2:立即清空大数据,在传输之前!
outState.remove("android:support:fragments")
outState.getBundle("androidx.lifecycle.BundlableSavedStateRegistry.key")?.apply {
remove("android:support:fragments")
remove("android:support:activity-result")
}
// 测量Bundle大小
measureBundleSize(outState)
} catch (e: Throwable) {
PictorialLog.e(TAG, "[onSaveInstanceState] error: ${e.message}")
}
}
// ← 方法返回,Bundle大小已被控制在安全范围
// → 之后的Binder传输不会超过限制,不会抛异常 ✅
关键改进:
- ✅ 在onSaveInstanceState()内部清空大数据
- ✅ 在Bundle传输之前就减少数据量
- ✅ 从源头解决问题,而不是捕获异常
6 执行时序对比
6.1 修复前(会崩溃)
css
时间轴:
T1: onSaveInstanceState() 开始
└─ super.onSaveInstanceState(outState)
└─ Fragment状态保存 (1.4MB) ✅ 成功
T2: onSaveInstanceState() 结束
└─ outState 包含 1.4MB 数据 ⚠️
T3: PendingTransactionActions$StopInfo.run() 开始
└─ ActivityClient.activityStopped(mState)
└─ BinderProxy.transact()
└─ transactNative() 检查数据大小
└─ 1.4MB > 1MB ❌
└─ 抛出 TransactionTooLargeException
└─ 应用崩溃!
6.2 修复后(不会崩溃)
css
时间轴:
T1: onSaveInstanceState() 开始
└─ super.onSaveInstanceState(outState)
└─ Fragment状态保存 (1.4MB) ✅ 成功
└─ outState.remove("android:support:fragments") ✅ 关键!
└─ Fragment状态被删除
T2: onSaveInstanceState() 结束
└─ outState 只包含 8KB 数据 ✅
T3: PendingTransactionActions$StopInfo.run() 开始
└─ ActivityClient.activityStopped(mState)
└─ BinderProxy.transact()
└─ transactNative() 检查数据大小
└─ 8KB < 1MB ✅
└─ 传输成功
└─ 应用正常运行 ✅
7 Fragment的onSaveInstanceState()原理
7.1 Fragment修复
kotlin
// 修复前:没有重写,使用默认实现
// 默认实现会保存所有成员变量(如果它们是可序列化的)
// 修复后:
override fun onSaveInstanceState(outState: Bundle) {
// 不调用super,完全跳过状态保存
// super.onSaveInstanceState(outState)
// 清空可能被父类保存的状态
outState.clear()
PictorialLog.i(TAG, "[onSaveInstanceState] 跳过状态保存,避免数据过大")
}
执行时机:
scss
Activity.onSaveInstanceState(outState)
→ super.onSaveInstanceState(outState)
→ FragmentManager.saveAllState()
→ Fragment.onSaveInstanceState(savedState) ← 这里!
→ savedState包含Fragment状态
→ savedState打包到outState
为什么有效:
- Fragment.onSaveInstanceState()在Activity.onSaveInstanceState()内部被调用
- 如果Fragment不保存状态,savedState就是空的
- 最终outState中的Fragment数据就很小
8 Bundle大小计算
8.1 崩溃时的数据分布
python
Bundle总大小: 1,419,084 bytes (1.35 MB)
├─ android:viewHierarchyState: 4,780 bytes (0.5%)
│ └─ android:views: 4,732 bytes
└─ androidx.lifecycle.BundlableSavedStateRegistry.key: 1,413,508 bytes (99.5%)
├─ android:support:activity-result: 1,680 bytes (0.1%)
└─ android:support:fragments: 1,411,568 bytes (99.4%) ← 主要问题!
└─ android:support:fragments: 1,411,496 bytes
└─ ***Fragment状态
├─ netData: List<***ItemInfo> (约800KB)
├─ mAdapter数据 (约400KB)
├─ slideViewFirstData (约100KB)
└─ 其他状态变量 (约100KB)
8.2 修复后的数据分布(预期)
python
Bundle总大小: 8,192 bytes (8 KB)
├─ android:viewHierarchyState: 4,780 bytes (58%)
│ └─ android:views: 4,732 bytes
└─ androidx.lifecycle.BundlableSavedStateRegistry.key: 3,412 bytes (42%)
├─ android:support:activity-result: 1,680 bytes
└─ android:support:fragments: 0 bytes ← 已清空!
└─ EXTRA_IMAGE_ID: "6564140" (50 bytes)
减少量 : 1,419,084 - 8,192 = 1,410,892 bytes (99.4%减少)
9 总结
9.1 核心要点
-
异常发生的真实位置:
- ❌ 不是在Activity.onSaveInstanceState()内部
- ✅ 是在之后的Binder.transactNative()中
-
为什么Try-Catch无效:
- onSaveInstanceState()方法返回后,try-catch作用域结束
- 异常在Native层抛出,应用代码无法捕获
- 异常传播到UncaughtExceptionHandler,导致崩溃
-
正确的解决思路:
- ❌ 不是捕获异常(无法捕获)
- ✅ 是从源头减少数据量
- ✅ 在Bundle传输之前就控制大小
-
修复的技术原理:
- 在onSaveInstanceState()内部清空Fragment状态
- 在Fragment.onSaveInstanceState()中禁止保存大数据
- 确保Bundle大小 < 1MB,避免触发异常
9.2 类比理解
这就像快递包裹超重:
错误做法(Try-Catch):
你:把100kg的包裹交给快递员
快递员:好的(接收包裹)
你:离开现场
快递员:称重发现超重,拒绝发货
你:已经离开,无法处理这个问题
结果:快递失败 ❌
正确做法(清空数据):
你:准备100kg的包裹
你:意识到超重,减少到5kg
你:把5kg的包裹交给快递员
快递员:称重通过,发货成功
结果:快递送达 ✅
10 相关源码路径
组件 | 源码路径 |
---|---|
Activity | frameworks/base/core/java/android/app/Activity.java |
FragmentManager | androidx/fragment/app/FragmentManager.java |
PendingTransactionActions | frameworks/base/core/java/android/app/servertransaction/PendingTransactionActions.java |
ActivityClient | frameworks/base/core/java/android/app/ActivityClient.java |
BinderProxy | frameworks/base/core/java/android/os/BinderProxy.java |
Parcel (Native) | frameworks/native/libs/binder/Parcel.cpp |