源码分析相关面试题

Activity相关面试题

与XMPP相关面试题

与性能优化相关面试题

与登录相关面试题

与开发相关面试题

与人事相关面试题

经常有人问,后台的activity被系统自动回收的话,怎么回到界面的时候恢复数据,通过一个真实案例给大家讲讲如何保存状态,然后带着大家分析onSaveInstanceState的源码。

img

当前页面侧滑菜单指向专题,用户做了如下操作:

1)当用户按下HOME键时。 2)长按HOME键,选择运行其他的程序时。 3)按下电源按键(关闭屏幕显示)时。 4)从activity A中启动一个新的activity时。 5)屏幕方向切换时,例如从竖屏切换到横屏时。

失去焦点,activity很可能被进程终止!被KILL掉了,这时候就需要能保存当前的状态,不然下次用户再次进来看到的还是新闻,这样用户体验就不够好,代码有删减,我自己项目就这样使用的,解决方案如下:

  1. @Override
  2. public void onSaveInstanceState(Bundle outState) {
  3. outState.putInt("newsCenter_position", newsCenterPosition);
  4. outState.putInt("smartService_position", smartServicePosition);
  5. outState.putInt("govAffairs_position", govAffairsPosition);
  6. super.onSaveInstanceState(outState);
  7. }

如上代码可知:

1)界面被回收之后调用onSaveInstanceState方法保存当前的状态,每个侧滑菜单选项都有一个位置。

  1. @Override
  2. public void onCreate(Bundle savedInstanceState) {
  3. if (savedInstanceState != null
  4. && savedInstanceState.containsKey("newsCenter_position")) {
  5. newsCenterPosition = savedInstanceState
  6. .getInt("newsCenter_position");
  7. smartServicePosition = savedInstanceState
  8. .getInt("smartService_position");
  9. govAffairsPosition = savedInstanceState
  10. .getInt("govAffairs_position");
  11. }
  12. super.onCreate(savedInstanceState);
  13. }

由以上代码可知:

1)判断当前Bundle 是否有刚刚我们保存的位置,如果不为空,从当前的Bundle取出来,给每一个位置赋值。

  1. public void switchMenu(int type) {
  2. switch (type) {
  3. case NEWS_CENTER:
  4. if (newsCenterAdapter == null) {
  5. newsCenterAdapter = new MenuAdapter(ct, newsCenterMenu);
  6. newsCenterclassifyLv.setAdapter(newsCenterAdapter);
  7. } else {
  8. newsCenterAdapter.notifyDataSetChanged();
  9. }
  10. newsCenterAdapter.setSelectedPosition(newsCenterPosition);
  11. break;
  12. case SMART_SERVICE:
  13. if (smartServiceAdapter == null) {
  14. smartServiceAdapter = new MenuAdapter(ct, smartServiceMenu);
  15. smartServiceclassifyLv.setAdapter(smartServiceAdapter);
  16. } else {
  17. smartServiceAdapter.notifyDataSetChanged();
  18. }
  19. smartServiceAdapter.setSelectedPosition(smartServicePosition);
  20. break;
  21. case GOV_AFFAIRS:
  22. if (govAffairsAdapter == null) {
  23. govAffairsAdapter = new MenuAdapter(ct, govAffairsMenu);
  24. govAffairsclassifyLv.setAdapter(govAffairsAdapter);
  25. } else {
  26. govAffairsAdapter.notifyDataSetChanged();
  27. }
  28. govAffairsAdapter.setSelectedPosition(govAffairsPosition);
  29. break;
  30. }

以上代码可知:

1)根据当前的位置设置到adapter当中,这样下次用户进来就还是专题了。

总结下savedInstanceState的使用,代码如下:

  1. public class MainActivity extends Activity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. if(savedInstanceState != null)
  7. System.out.println("onCreate() : " + savedInstanceState.getString("octopus"));
  8. }
  9. @Override
  10. protected void onRestoreInstanceState(Bundle savedInstanceState) {
  11. super.onRestoreInstanceState(savedInstanceState);
  12. System.out.println("onRestoreInstanceState() : " + savedInstanceState.getString("octopus"));
  13. }
  14. @Override
  15. protected void onSaveInstanceState(Bundle outState) {
  16. super.onSaveInstanceState(outState);
  17. outState.putString("octopus", "www.baidu.com");
  18. System.out.println("onSaveInstanceState() : save date www.baidu.com");
  19. }
  20. }

横竖屏切换,打印结果如下:

  1. I/System.out( 8167): onSaveInstanceState() : save date www.baidu.com
  2. I/System.out( 8167): onCreate() : www.baidu.com
  3. I/System.out( 8167): onRestoreInstanceState() : www.baidu.com

从打印结果可以看出来,当前Activity被系统回收之后,会调用onSaveInstanceState()保存状态,然后在activity判断bundler是否有当前状态,如果只是到这,估计你们就会吐槽没啥含金量,没办法硬着头皮上,接着咱们来分onSaveInstanceState()源码,请看如下代码:

  1. @Override
  2. public void onSaveInstanceState(Bundle outState) {
  3. super.onSaveInstanceState(outState);
  4. }

以上代码可知

1)调用父类Activity源码里面的onSaveInstanceState方法,代码如下:

  1. protected void onSaveInstanceState(Bundle outState) {
  2. outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
  3. Parcelable p = mFragments.saveAllState();
  4. if (p != null) {
  5. outState.putParcelable(FRAGMENTS_TAG, p);
  6. }
  7. ......
  8. }

以上代码可知

1)outState.put一个tag调用了mWindow里面的saveHierarchyState方法,继续分析Window源代码。

2)window是抽象类调用子类PhoneWindow里面的saveHierarchyState方法代码如下:

  1. @Override
  2. public Bundle saveHierarchyState() {
  3. Bundle outState = new Bundle();
  4. if (mContentParent == null) {
  5. return outState;
  6. }
  7. SparseArray<Parcelable> states = new SparseArray<Parcelable>();
  8. mContentParent.saveHierarchyState(states);
  9. outState.putSparseParcelableArray(VIEWS_TAG, states);
  10. ......
  11. return outState;
  12. }

以上代码可知

1 ) Bundle outState = new Bundle()初始化Bundle对象,Bundle实现了Parcelable接口。

2)states = new SparseArray()并且把自己放到outState当中。

3)mContentParent.saveHierarchyState(states),整个View树的顶层视图保存了层级状态代码如下:

  1. public void saveHierarchyState(SparseArray<Parcelable> container) {
  2. dispatchSaveInstanceState(container);
  3. }

以上代码可知:

1)调相应的dispatchSaveInstanceState方法,代码如下:

  1. protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
  2. if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
  3. mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
  4. Parcelable state = onSaveInstanceState();
  5. if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
  6. throw new IllegalStateException(
  7. "Derived class did not call super.onSaveInstanceState()");
  8. }
  9. if (state != null) {
  10. // Log.i("View", "Freezing #" + Integer.toHexString(mID)
  11. // + ": " + state);
  12. container.put(mID, state);
  13. }
  14. }
  15. }

以上代码可知:

1) mID != NO_ID 判断一个View必须有一个id,也就是说你要么在xml里通过android:id指定要么在代码里通过setId,但是你从如上代码压根是看不出来谷歌想干啥,必须全局搜索NO_ID 和 mID ,一般在源码里面都会有谷歌工程师的注释方便我们理解,搜索NO_ID 可知代码如下:

  1. /**
  2. * Used to mark a View that has no ID.
  3. */
  4. public static final int NO_ID = -1;

原来NO_ID用来标记没有id的View,搜索mID可知原来在如下代码赋值

  1. public void setId(@IdRes int id) {
  2. mID = id;
  3. if (mID == View.NO_ID && mLabelForId != View.NO_ID) {
  4. mID = generateViewId();
  5. }
  6. }

经常当我们看不懂谷歌源码的时候,可以通过曲线救国的方式,看看英文注释,看看源码哪个地方用到当前的类或者方法或者变量,这样就好理解了,好了扯远了,继续分析代码;

2)通过if判断,检测子类是否调用父类的onSaveInstanceState()方法,否则会抛异常,突然看到这才明白,还记得刚刚开始学Android的时候,经常一不小心就把代码里面的super.onCreate(savedInstanceState);这行代码删掉,报了错误还看不懂,原来系统在这里检测了,都怪自己曾经太年轻。

3)container.put(mID, state)这行代码,将state放进SparseArray中,以view自身的id为key,并且从注释来看打印mID的Hex值用来保证每页的id必须是唯一的,难怪每当我给view取id的时候,一个页面有重复的id就会报错,谷歌大婶在这里做判断了,腻害了word哥,总是百思不得其姐,凭啥不让我共用id(因为取名字太难了),原来是想把id做为key来使用。

4)走到这onSaveInstanceState(),调用如下代码:

  1. @CallSuper
  2. protected Parcelable onSaveInstanceState() {
  3. mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
  4. ......
  5. return BaseSavedState.EMPTY_STATE;
  6. }

以上代码可知:

1)设置位标志, 默认不save任何东西,状态为空,这就是为啥我们每次随便写个类继承activity实现onCreate方法的时候可以使用参数savedInstanceState保存状态,因为默认为null,代码如下:

  1. protected void onCreate(Bundle savedInstanceState) {
  2. super.onCreate(savedInstanceState);
  3. savedInstanceState.putString("key","value");
  4. }

至此整个savedInstanceState保存状态源码分析完毕。

  • 欢迎关注微信公众号,长期推荐技术文章和技术视频
  • 微信公众号名称:Android干货程序员

img