热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[置顶]【Android面试】(二):你不能不知道的view---加id和不加id的区别?

请尊重原创劳动成果,转载请注明出处:http:blog.csdn.netcyp331203articledetails45313125,非允许请勿用于商业或盈利用途。上次面

请尊重原创劳动成果,转载请注明出处:http://blog.csdn.net/cyp331203/article/details/45313125,非允许请勿用于商业或盈利用途。


        上次面试,Android开发,被问到:你知道android中,布局文件中加id和不加id有什么区别?这个我真的不知道,蒙了,只能硬着头皮说:加了id会在R文件中生成对应id的数值,然后扯了点view树,总之答非所问。。。虽然最后面试也过了,但是这个问题一直萦绕在心头,挥之不去。刚好今天复习Activity生命周期的时候,看到了相关知识点。


有关Activity的onSaveInstantceState(Bundle outState)方法的一些基础知识在上篇文章中有提到过,大家可以去看看:Android面试】(一):Android中activity保存状态和数据到底该在哪个方法中进行,必须承认,上篇文章中调侃的语气太重,如果有冒犯,提前说句抱歉,本人还是很尊重面试官的,毕竟肯定要比我强才来面试我。


Activity中的onSaveInstantceState


这回还是要从activity中的onSaveInstantceState(Bundle outState)方法说起,下面快速的过一下onSaveInstantceState(Bundle outState)的几个要点:


1、onSaveInstantceState(Bundle outState)会在activity能够被销毁之前被调用,也就是所谓的(killble)状态,这个在上篇中有提到


2、onSaveInstantceState(Bundle outState)会在onStop()方法之前被调用,但不保证会在onPause()方法之前还是之后被调用。


3、重点!!!onSaveInstantceState(Bundle outState)不是一定会被调用的,什么时候会被调用呢?简单一句话:当Activity要进入这么一种状态:“系统可能会以非应用行为退出Activity方式干掉Activity”之前,系统就会调用onSaveInstantceState(Bundle outState)方法。


4、非应用行为退出

什么是非应用行为退出?应用行为退出Activity:比如主动调用finish()方法,或者主动按Back键,让Activity结束。非应用行为退出:比如一个Activity在后台,过了很长时间也没有被重新调用显示出来;又或者系统当前资源非常紧张,主动kill掉当前activity,释放资源以供其他应用使用。


这样设计的逻辑是很清晰的:当系统不确定会不会什么时候在未经“允许”的突发情况下结束掉Activity,在进入这种状态之前,肯定需要保存一下我们想要的数据,比如Activity中有控件有状态值,可以通过onSaveInstantceState(Bundle outState)进行保存,但是就像上一篇文章中说的,onSaveInstantceState(Bundle outState)不保证一定会被调用,因为它不是Activity生命周期中的方法。


5、假设onSaveInstantceState(Bundle outState)方法被调用了,且也保存了数据到Bundle对象,那么什么时候会将其取出来?

上面的第3点中提到过,在系统要进入可以使用“非应用行为”杀死Activity状态之前,会调用onSaveInstantceState(Bundle outState)方法,而Bundle对象可能被取到的条件,就是系统确实使用“非应用行为”杀死了Activity,而在要重建Activity时,会首先将Bundle对象传给onCreate,然后再传给onRestoreInstanceState(Bundle savedInstanceState)方法。如果onSaveInstantceState(Bundle outState)方法调用之后,Activity没有“意外杀死”,那么再次启动Activity时,只会调用onStart--onResume,而不会调用onRestoreInstanceState(Bundle savedInstanceState)方法。


onSaveInstantceState例子


下面把一个Activity在启动到被旋屏之后重新创建的过程打印结果展示出来,这里在onSaveInstantceState方法中往bundle中存一个当前时间,然后在onCreate方法和onRestoreInstanceState方法中将其取出,onCreate方法中会对Bundle进行判空:


启动:




旋屏之后:




下面给Activit中加两个按钮,让它点击跳转到第二个activity,不同的是,一个按钮会在点击时调用finish方法,而另一个则不会:




跳转调用finish()方法:




跳转不调用finish()方法:




发现在onCreate第一次调用时,Bundle为null,而在旋屏之后,onCreate和onRestoreInstanceState方法中都拿到了传过来的时间。

而在主动调用finish结束activity时,没有调用onSaveInstantceState方法;而如果不finish掉activity1,直接跳转activity2,则会在activity1的onStop之前调用onSaveInstantceState方法。


View中的onSaveInstantceState和id的关系


好了,说了一大堆,貌似还没有进入本文关注的焦点。。。下面就来了:

上面说了onSaveInstantceState方法,下面来看看这个方法里到底干了什么:(你mei的,怎么还是onSaveInstantceState方法?!汗Σ( ° △ °|||)︴,就快到了)


来看看Activity中的源码:

 protected void onSaveInstanceState(Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
getApplication().dispatchActivitySaveInstanceState(this, outState);
}



主要做了这么几件事:


1、mWindow.saveHierarchyState()中的数据,放入到Bundle对象中


2、将Fragments中的state数据存放到Bundle对象中


3、将Bundle对象通过Application的dispatchActivitySaveInstanceState进行分发。


今天本文关注第一个问题:mWindow.saveHierarchyState()


发现返回的是一个mWindow,而这个mWindow是一个Activity类中 Window类型的成员变量:


private Window mWindow;


可能你已经在猜测这个window和PhoneWindow的关系了,Window是一个抽象类,其中的setContentView方法也是一个抽象方法,并没有实现。来看看Window类的注释:


The only existing implementation of this abstract class is android.policy.PhoneWindow, which you should instantiate when needing a Window. 


意思是说:Window类只有一个实现类,那就是PhoneWindow。


这下明白了,我们再去看看PhoneWindow类的源码,这个类我们并不能直接使用,它位于:Android源码目录/frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java


找到saveHierarchyState()方法,我们来看看它干了些什么事:


 /** {@inheritDoc} */
@Override
public Bundle saveHierarchyState() {
Bundle outState = new Bundle();//新建一个Bundle对象,用于存放状态
if (mCOntentParent== null) {
return outState;
}

SparseArray states = new SparseArray();//新建了一个SparseArray,里面维护了一个key数组和一个value数组,类似于Map
mContentParent.saveHierarchyState(states);//调用view里面的saveHierarchyState方法,将状态值保存到
outState.putSparseParcelableArray(VIEWS_TAG, states);将SparseArray中的数据存储到Bundle对象中

// save the focused view id
View focusedView = mContentParent.findFocus();
if (focusedView != null) {
if (focusedView.getId() != View.NO_ID) {//注意这里,如果当前焦点view有设置id,才会进入到下面
outState.putInt(FOCUSED_ID_TAG, focusedView.getId());//特别存储当前焦点view的id值
} else {
if (false) {
Log.d(TAG, "couldn't save which view has focus because the focused view "
+ focusedView + " has no id.");
}
}
}

// save the panels 保存panel状态
SparseArray panelStates = new SparseArray();
savePanelState(panelStates);
if (panelStates.size() > 0) {
outState.putSparseParcelableArray(PANELS_TAG, panelStates);
}

if (mActionBar != null) {//保存actionBar状态
SparseArray actiOnBarStates= new SparseArray();
mActionBar.saveHierarchyState(actionBarStates);
outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
}

return outState;
}


PhoneWindow类的成员变量mContentParent用来描述一个类型为DecorView的视图对象,或者这个类型为DecorView的视图对象的一个子视图对象,用作UI容器.当它的值等于null的时候,就说明正在处理的应用程序窗口的视图对象还没有创建.


简而言之,只要我们给Activity设置了显示内容,不管是布局文件还是view,就会挂载在这个mContentParent之下。所以一般情况下,mContentParent不会为null


好了,上面其实已经看出来了一点东西,如果一个焦点view的id没有设置的话,那么就无法向Bundle对象中保存当前焦点view的id,焦点标识是使用的:FOCUSED_ID_TAG这个常亮。


我们再来看看mContentParent.saveHierarchyState(states)干了些什么:

由于:private ViewGroup mContentParent;是一个viewgroup,而viewGroup里没有saveHierarchyState方法,于是实际上调用的view中的saveHierarchyState方法:


    public void saveHierarchyState(SparseArray container) {
dispatchSaveInstanceState(container);
}



再来看看dispatchSaveInstanceState方法:

    protected void dispatchSaveInstanceState(SparseArray container) {
if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {//只有有id的情况下才能进入到里面,添加view的状态
mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
Parcelable state = onSaveInstanceState();//调用view自己的onSaveInstanceState方法
if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
throw new IllegalStateException(
"Derived class did not call super.onSaveInstanceState()");
}
if (state != null) {
// Log.i("View", "Freezing #" + Integer.toHexString(mID)
// + ": " + state);
container.put(mID, state);
}
}
}


看到这里基本上真相大白了,如果不给一个view设置一个id,那么在Activity调用onSaveInstantceState(Bundle outState)方法时,就没办法保存它的状态,而且即使它当前是焦点view,也没办法将其焦点状态记录在Bundle对象中,这会导致在需要取出Bundle状态对象时,出现问题。


上面还可以看到,view的状态,是由onSaveInstanceState()方法来获取的:


   protected Parcelable onSaveInstanceState() {
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
return BaseSavedState.EMPTY_STATE;
}


总结


1、实际上view默认的onSaveInstanceState方法中什么都没做,返回的是一个空状态,这个方法是一个protected方法,于是在view的各个子类中,可能会有不同的实现,因为毕竟不是每个view都需要保存状态,而且不同类型的view要保存的状态值也不近相同,比如textview可能需要保存文字状态,而scrollview就需要保存滚动到的位置值等等。


2、我们可以通过自定义View,重写onSaveInstanceState和onRestoreInstanceState方法,来保存和取出一些应对特定环境时比较重要的状态值。


3、值得注意的是,container.put(mID, state);状态值是由id的值来作为key值来存储的,所以如果同类的view,在使用相同的id时,在取状态值的时候,就可能会出现问题,来看看scrollview中的onSaveInstanceState方法:


@Override
protected Parcelable onSaveInstanceState() {
if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
// Some old apps reused IDs in ways they shouldn't have.
// Don't break them, but they don't get scroll state restoration.
return super.onSaveInstanceState();
}
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.scrollPosition = mScrollY;
return ss;
}

源码中写的很清楚: Some old apps reused IDs in ways they shouldn't have.  Don't break them, but they don't get scroll state restoration.


如果你在应用中使用两个ScrollView,且都指定一样的id,那么在onSaveInstanceState时,后调用的那个则会覆盖掉之前的那个ScrollView的Scroll的值,导致在之后取出的时候,会让两个ScrollView的滑动进度总是一样。


而且看上面的判断条件:if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2),表示在4.3(包括)以前ScrollView的scroll state是不会保存的,所以在这之前要实现对应的功能,只能自定义一个view继承ScrollView,然后重写onSaveInstanceState相关方法了。


好了,今天就到这里,谢谢大家!



请尊重原创劳动成果,转载请注明出处:http://blog.csdn.net/cyp331203/article/details/45313125,非允许请勿用于商业或盈利用途。

















推荐阅读
  • vue使用
    关键词: ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 标题: ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • Oracle seg,V$TEMPSEG_USAGE与Oracle排序的关系及使用方法
    本文介绍了Oracle seg,V$TEMPSEG_USAGE与Oracle排序之间的关系,V$TEMPSEG_USAGE是V_$SORT_USAGE的同义词,通过查询dba_objects和dba_synonyms视图可以了解到它们的详细信息。同时,还探讨了V$TEMPSEG_USAGE的使用方法。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 基于dlib的人脸68特征点提取(眨眼张嘴检测)python版本
    文章目录引言开发环境和库流程设计张嘴和闭眼的检测引言(1)利用Dlib官方训练好的模型“shape_predictor_68_face_landmarks.dat”进行68个点标定 ... [详细]
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • 本文分析了Wince程序内存和存储内存的分布及作用。Wince内存包括系统内存、对象存储和程序内存,其中系统内存占用了一部分SDRAM,而剩下的30M为程序内存和存储内存。对象存储是嵌入式wince操作系统中的一个新概念,常用于消费电子设备中。此外,文章还介绍了主电源和后备电池在操作系统中的作用。 ... [详细]
author-avatar
SuperBaby蜜
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有