Android----onSaveInstanceState 的数据存在哪里?为什么限制了大小?

一.问题来源

对于 Activity 的 onSaveInstanceState 方法大家都不会陌生,当 Activity 在不正常销毁的情况下,就会调用 onSaveInstanceState 方法,并将 Activity 中需要保存的数据(比如 View 状态 或者我们自己的数据)保存到这个方法的参数 Bundle 中。

但是在实际使用的时候你可能会发现当保存的数据过大的时候就会看到如下的 log 日志

1
javabinder !!! FAILED BINDER TRANSACTION !!!

甚至可能发生异常(在高版本下会抛出异常,低版本直接打印日志)

1
android.os.TransactionTooLargeException

上面的信息都是表示 Bundle 传输的数据过大,那么问题来了, onSaveInstanceState 中 Bundle 的数据是存放在哪里,为什么又限制?

二.解决问题的过程

1.官网

在官网 有关于 Parcelables and Bundles 的一段介绍,其中就有提到 Bundles 的数据大小问题。

image.png

翻译一下: Binder 传输缓冲区是一个限制的大小的区域,大小为 1MB,这块缓冲区用于所有进程间的通信,也就是 Binder 通信。这些传输包括 onSaveInstanceState , startActivity 和其他与系统的交互,当传输的数据超过这个大小的时候就会抛出异常。

特别是 onSaveInstanceState 方法,因其需要在 Activity 返回的时候提供数据,官网建议是数据大小不大于 50K.

关于 startActivity 和其他系统交互需要使用 Binder 进行跨进程通信我们知道,但是你可能就有疑问 onSaveInstanceState 不是在自己进程中做 Activity 某些状态的保存,为什么需要 Binder 呢?

关于 Binder 机制中 Binder 缓冲区的限制,网上有很多文章,这里就不进行说明了。

2.系统源码追踪
ActivityThread

在追 onSaveInstanceState 的方法源码的时候,想要找到关于 Binder 方面的内容,就顺着Activity 的方法追,很多人都是到 Application 的 接口中就迷路了.

1
2
3
4
5
6
7
8
9
public interface ActivityLifecycleCallbacks {
void onActivityCreated(Activity activity, Bundle savedInstanceState);
void onActivityStarted(Activity activity);
void onActivityResumed(Activity activity);
void onActivityPaused(Activity activity);
void onActivityStopped(Activity activity);
void onActivitySaveInstanceState(Activity activity, Bundle outState);
void onActivityDestroyed(Activity activity);
}

其实这是一个错误的方向,其实只要想到 onSaveInstanceState 有点类似于 Activity 中其他生命周期的方法,就可以发现 onSaveInstanceState 最开始也是有 ActivityThread 做统一管理的,那么 onSaveInstanceState 的调用 也就和 ActivityThread 有关。

Bundle 的创建

在 ActivityThread 的源码中可以看到有这么个方法

1
2
3
4
5
6
7
8
9
10
11
private void callCallActivityOnSaveInstanceState(ActivityClientRecord r) {
r.state = new Bundle();
r.state.setAllowFds(false);
if (r.isPersistable()) {
r.persistentState = new PersistableBundle();
mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state,
r.persistentState);
} else {
mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
}
}

其中最后调用了 mInstrumentation.callActivityOnSaveInstanceState
那么就到 mInstrumentation 类中查找

1
2
3
4
public void callActivityOnSaveInstanceState(Activity activity, Bundle outState,
PersistableBundle outPersistentState) {
activity.performSaveInstanceState(outState, outPersistentState);
}

这个方法中又调用了 activity.performSaveInstanceState 的方法

1
2
3
4
5
6
7
8
9
10
11
12
final void performSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
onSaveInstanceState(outState, outPersistentState);
saveManagedDialogs(outState);
storeHasCurrentPermissionRequest(outState);
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState +
", " + outPersistentState);
}

...
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
onSaveInstanceState(outState);
}

最后调用的是 Activity onSaveInstanceState 方法 ,显然 callCallActivityOnSaveInstanceState 的 r.state 就是 onSaveInstanceState 的方法的中的参数 Bundle .而 r 就是 ActivityThread 的内部类 ActivityClientRecord

1
2
3
4
5
6
7
8
9
static final class ActivityClientRecord {
IBinder token;
int ident;
Intent intent;
String referrer;
IVoiceInteractor voiceInteractor;
Bundle state; // 这个就是 r.state 也就是 onSaveInstanceState 的 Bundle

}

到这里我们知道 Bundle 是怎样创建的,但是关于 Binder 传输的问题,这里也没有体现,那么究竟是在哪里涉及的 Binder 传输的呢?

onSaveInstanceState 调用时机

这里先补充一个点就是 onSaveInstanceState 的方法的调用时机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
* <p>If called, this method will occur before {@link #onStop}.  There are
* no guarantees about whether it will occur before or after {@link #onPause}.
*
* @param outState Bundle in which to place your saved state.
*
* @see #onCreate
* @see #onRestoreInstanceState
* @see #onPause
*/
protected void onSaveInstanceState(Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());

outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
if (mAutoFillResetNeeded) {
outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
getAutofillManager().onSaveInstanceState(outState);
}
getApplication().dispatchActivitySaveInstanceState(this, outState);
}

从注释中可以看到这个方法的调用会在 onStop 前,那么我们就去 ActivityTread 中看看再 onStop 前发生了什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) {

ActivityClientRecord r = mActivities.get(token);
....
StopInfo info = new StopInfo();
//
performStopActivityInner(r, info, show, true, "handleStopActivity");

...
info.activity = r;
//注意这里 将 r.state 赋值给 info.state
info.state = r.state;
info.persistentState = r.persistentState;

//然后调用 Handler 的 post 方法将这个 StopInfo 类传递给消息队列
mH.post(info);
mSomeActivitiesChanged = true;
}

StopInfo 是一个实现了 Runnable 接口的类,且内部也有Bundle 的引用 所以可以 将 ActivityClientRecord 的 Bundle 赋值给 StopInfo 的 Bundle

1
2
3
4
5
6
7
private static class StopInfo implements Runnable {
ActivityClientRecord activity;
Bundle state;
....


}

最后因为 StopInfo 被 Handle post 之后,那么就会执行其相应的 run 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static class StopInfo implements Runnable {
ActivityClientRecord activity;
Bundle state;
PersistableBundle persistentState;
CharSequence description;

@Override public void run() {
// Tell activity manager we have been stopped.
try {
//注意到这里将 state 作为参数调用了 ActivityManager.getService() 方法。
ActivityManager.getService().activityStopped(
activity.token, state, persistentState, description);
} catch (RemoteException ex) {

if (ex instanceof TransactionTooLargeException
&& activity.loadedApk.getTargetSdkVersion() < Build.VERSION_CODES.N) {
Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex);
return;
}
throw ex.rethrowFromSystemServer();
}
}
}

到这里就可以明白了 onSaveInstanceState 中保存的 Bundle 信息是存在内存中的,且因为是涉及到 Activity 的状态的保存,就需要交由 ActivityManager 进程去做一个管理,所以就需要 Binder 传输做一个跨进程的通信将 Bundle 的数据传递给 ActivityManager。因此 onSaveInstanceState 也涉及到了 Binder 传输,自然而然就受到 Binder 缓冲区大小的限制,到这里问题就解决了。

0%