1. 软键盘隐藏问题
问题描述:Activity按下返回调用finish()
方法后,界面已经销毁,但是软键盘依然还留在屏幕上,这让当前正在显示的Activity没有输入框的完全没法看,非常严重的视觉影响。
尝试方案:寻找各种方法去隐藏软键盘,网上各种找。思路是在活动退出时,会调用onDestroy()
方法。
销毁界面,在这个方法里面想办法隐藏界面即可。找到下面这种方法,但还是不行。还尝试过用基类找到所有edittext然后让它们失去焦点,隐藏软键盘,结果发现无用,赶紧调试解决!
解决方案:一开始在onDestroy()
里调隐藏软键盘的思路就是错的,因为onDestroy()之前还有两个生命周期方法,像上述隐藏软键盘的方法有个getCurrentFocus()
,在onDestroy()之前肯定得不到正确的获取当前焦点的那个控件了。所以在onPasue()方法里隐藏软键盘就有效,在onDestroy()
方法里不管用任何方法都是无效的。
注意点:用这个隐藏软键盘的方法,最好做为空判断,否则有可能会出现空指针的异常,如当前界面没有控件获取焦点时,则getCurrentFocus()
这个方法得到的是一个null
。
软键盘占用布局问题,软键盘有时会把一些控件覆盖掉,这时如何把整个界面向上顶起,让任何控件都不会被覆盖呢?有两步,第一在activity里设置一个属性,如下。第二步,布局里加一个scrollview将你要被顶起的视图放进这里,然后当软键盘显示的时候,就会在scrollview里滚动以获得空间进行显示软键盘。
2. merge标签注意点
merge
标签只有在根布局是FrameLayout
时才有用,因为安卓所有界面的根布局都是FrameLayout,所以可以用merge标签进行融合。
merge标签使用后,布局里即使有EditText
也无法自动获得焦点,只能手动设置焦点, 调用requestFocus()
方法。或者是用requestFocus在XML布局文件里。使用后要注意如果在根布局中,则不能用LayoutInflater
来生成一个view,否则会报如下错误,由于我在listview
的getview()
里用了这个带有merge
的布局,所以崩溃了。补充一点,inflater()
方法里可以设置attach root
为true
则可以解析出来,不会出现崩溃。
查看了下源码,是在这个地方抛出了异常。
3. LinearLayout注意点
线性布局默认是水平的,要善用weight
权重这个属性。非常重要的点,如果方向设为水平,则layout_gravity
的top
与bottom
标签是没有效果的。如果方向设为垂直,则left与right是没有效果的,这时如果想放在靠右的地方,则可以使用space
标签,将宽度设为0dp,将layoutweight
设为1放在控件前边即可。
4. Fragment(这是一个大坑,很多人都遇到过的)
虽然Fragment是可以让你的app纵享丝滑的设计,如果你的app想在现在基础上性能大幅度提高,并且占用内存降低,同样的界面Activity占用内存比Fragment要多,响应速度Fragment比Activty在中低端手机上快了很多,甚至能达到好几倍!如果你的app当前或以后有移植平板等平台时,可以让你节省大量时间和精力。
但是Fragment嵌套时或者单Activity+多Fragment架构时遇到很多的坑!!
在这之前为了方便后面文章的介绍,先规定一个“术语”,安卓app有一种特殊情况,就是 app运行在后台的时候,系统资源紧张的时候导致把app的资源全部回收(杀死app的进程),这时把app再从后台返回到前台时,app会重启。这种情况下文简称为:“内存重启”。(屏幕旋转等配置变化也会造成当前Activity重启,本质与“内存重启”类似)。
在系统要把app回收之前,系统会把Activity的状态保存下来,Activity的FragmentManager
负责把Activity中的Fragment保存起来。在“内存重启”后,Activity的恢复是从栈顶逐步恢复,Fragment会在宿主Activity的onCreate
方法调用后紧接着恢复(从onAttach
生命周期开始)。
问题1、getActivity()空指针。
可能你遇到过getActivity()
返回null,或者平时运行完好的代码,在“内存重启”之后,调用getActivity()的地方却返回null,报了空指针异常。大多数情况下的原因:你在调用了getActivity()时,当前的Fragment已经onDetach()
了宿主Activity。比如:你在pop了Fragment之后,该Fragment的异步任务仍然在执行,并且在执行完成后调用了getActivity()方法,这样就会空指针。
解决办法:
更"安全"的方法:(对于Fragment已经onDetach这种情况,我们应该避免在这之后再去调用宿主Activity对象,比如取消这些异步任务,但我们的团队可能会有粗心大意的情况,所以下面给出的这个方案会保证安全)。
在Fragment基类里设置一个Activity mActivity
的全局变量,在onAttach(Activity activity)
里赋值,使用mActivity
代替getActivity()
,保证Fragment即使在onDetach后,仍持有Activity的引用(有引起内存泄露的风险,但是异步任务没停止的情况下,本身就可能已内存泄漏,相比Crash,这种做法“安全”些),即:
问题2、异常:Can not perform this action after onSaveInstanceState。
有很多小伙伴遇到这个异常,这个异常产生的原因是:在你离开当前Activity等情况下,系统会调用onSaveInstanceState()
帮你保存当前Activity的状态、数据等,直到再回到该Activity之前(onResume()
之前),你执行Fragment事务,就会抛出该异常!(一般是其他Activity的回调让当前页面执行事务的情况,会引发该问题)
解决方法:
1、该事务使用commitAllowingStateLoss()
方法提交,但是有可能导致该次提交无效!(宿主Activity被强杀时),对于popBackStack()
没有对应的popBackStackAllowingStateLoss()
方法,所以可以在下次可见时提交事务。
2、利用onActivityForResult()/onNewIntent()
,可以做到事务的完整性,不会丢失事务。
一个简单的示例代码 :
问题3、Fragment重叠异常—–如何正确使用hide、show?
一般满足下面2个条件才可能会发生重叠:
1、发生了页面重启(旋转屏幕、内存不足等情况被强杀重启)。
2、重复replace|add Fragment
或者 使用show
, hide
控制Fragment。
为什么会发生Fragment重叠?
从源码角度分析,为什么发生页面重启后会导致重叠?(在以add方式加载Fragment的时候)。
我们知道Activity中有个onSaveInstanceState()
方法,该方法会在Activity将要被kill的时候回调(例如进入后台、屏幕旋转前、跳转下一个Activity等情况下会被调用)。当Activity只执行onPause
方法时(透明Activity),这时候如果App设置的targetVersion
大于11则不会执行onSaveInstanceState
方法,此时系统帮我们保存一个Bundle
类型的数据,我们可以根据自己的需求,手动保存一些例如播放进度等数据,而后如果发生了页面重启,我们可以在onRestoreInstanceState()或onCreate()里get该数据,从而恢复播放进度等状态,而产生Fragment重叠的原因就与这个保存状态的机制有关,大致原因就是系统在页面重启前,帮我们保存了Fragment的状态,但是在重启后恢复时,视图的可见状态没帮我们保存,而Fragment默认的是show
状态,所以产生了Fragment重叠现象。
分析:
我们先看FragmentActivity
的相关源码:
从上面源码可以看出,FragmentActivity确实是帮我们保存了Fragment的状态,并且在页面重启后会帮我们恢复!其中的mFragments是FragmentController,它是一个Controller,内部通过FragmentHostCallback间接控制FragmentManagerImpl,相关代码如下:
通过上面代码可以看出FragmentController
通过FragmentHostCallback
里的FragmentManagerImp
l对象来控制恢复工作,我们接着看FragmentManagerImpl
到底做了什么:
我们通过saveAllState()
看到了关键的保存代码,原来是是通过FragmentManagerState
来保存Fragment的状态、所处Fragment栈下标、回退栈状态等。而在restoreAllState()恢复时,通过FragmentManagerState里的FragmentState
的instantiate()
方法恢复了Fragment(见下面的分析就明白啦),我们看下FragmentManagerState:
我们只看FragmentState,它也实现了Parcelable
,保存了Fragment的类名、下标、id、Tag、ContainerId以及Arguments
等数据:
至此,我们就明白了系统帮我们保存的Fragment其实最终是以FragmentState形式存在的,此时我们再思考下为什么在页面重启后会发生Fragment的重叠?其实答案已经很明显了,根据上面的源码分析,我们会发现FragmentState里没有Hidden状态的字段!而Hidden状态对应Fragment中的mHidden,该值默认false
!我想你应该明白了,在以add方式加载Fragment的场景下,系统在恢复Fragment时,mHidden=false
,即show状态,这样在页面重启后,Activity内的Fragment都是以show状态显示的,而如果你不进行处理,那么就会发生Fragment重叠现象!
这里给出解决方案:是大家比较熟悉的 findFragmentByTag
:
即在add()
或者replace()
时绑定一个tag,一般我们是用fragment的类名作为tag,然后在发生“内存重启”时,通过findFragmentByTag
找到对应的Fragment,并hide()
需要隐藏的fragment。
下面是个标准恢复写法:
如果你想恢复到用户离开时的那个Fragment的界面,你还需要在onSaveInstanceState(Bundle outState)
里保存离开时的那个可见的tag
或下标,在onCreate
“内存重启”代码块中,取出tag/下标,进行恢复。
问题4、Fragment嵌套的那些坑?
其实一些小伙伴遇到的很多嵌套的坑,大部分都是由于对嵌套的栈视图产生混乱,只要理清栈视图关系,做好恢复相关工作以及正确选择是使用getFragmentManager()
还是getChildFragmentManager()
就可以避免这些问题。
在support 23.2.0
以下的支持库中,对于在嵌套子Fragment的startActivityForResult ()
,会发现无论如何都不能在onActivityResult()中接收到返回值,只有最顶层的父Fragment才能接收到,这是一个support v4
库的一个BUG,不过在support 23.2.0
库中,已经修复了该问题,嵌套的子Fragment也能正常接收到返回数据了!
问题5、不靠谱的出栈方法remove()
。
如果你想让某一个Fragment出栈,使用remove()在加入回退栈时并不靠谱。如果你在add的同时将Fragment加入回退栈:addToBackStack(name)
的情况下,它并不能真正将Fragment从栈内移除,如果你在2秒后(确保Fragment事务已经完成)打印getSupportFragmentManager().getFragments()
,会发现该Fragment依然存在,并且依然可以返回到被remove的Fragment,而且是空白页面。如果你没有将Fragment加入回退栈,remove方法可以正常出栈。如果你加入了回退栈,popBackStack()
系列方法才能真正出栈。
问题6、超级深坑 Fragment转场动画。
如果想让出栈动画运作正常的话,需要使用Fragment的onCreateAnimation中控制动画,但是用代价也是有的,你需要解决出栈动画带来的几个坑。
1、pop多个Fragment时转场动画 带来的问题。
在使用 pop(tag/id)
出栈多个Fragment的这种情况下,将转场动画临时取消或者延迟一个动画的时间再去执行其他事务,原因在于这种情景下,可能会导致栈内顺序错乱(上文有提到),同时如果发生“内存重启”后,因为Fragment转场动画没结束时再执行其他方法,会导致Fragment状态不会被FragmentManager
正常保存下来。
2、进入新的Fragment并立刻关闭当前Fragment 时的一些问题。
如果你想从当前Fragment进入一个新的Fragment,并且同时要关闭当前Fragment。由于数据结构是栈,所以正确做法是先pop
,再add
,但是转场动画会有覆盖的不正常现象,你需要特殊处理,不然会闪屏!
Tip:
如果你遇到Fragment的
mNextAnim
空指针的异常(通常是在你的Fragment被重启的情况下),那么你首先需要检查是否操作的Fragment是否为null;其次在你的Fragment转场动画还没结束时,你是否就执行了其他事务等方法;解决思路就是延迟一个动画时间再执行事务,或者临时将该Fragment设为无动画
。
标签:动画,遇到,Fragment,重启,软键盘,UI,Activity,Android,方法 From: https://blog.51cto.com/u_16163480/6534898