UI开发中所遇到的各种坑

303 篇文章 5 订阅
251 篇文章 3 订阅

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里的FragmentManagerImpl对象来控制恢复工作,我们接着看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设为无动画。

结束

以上就是我遇到的坑,希望小伙伴积极补充,共同写出好代码~

引用:

1、https://blog.csdn.net/u012028250/article/details/120481898

2、https://blog.csdn.net/android_zhengyongbo/article/details/70676142

3、https://www.jianshu.com/p/b2a083e88925

4、https://blog.csdn.net/qq_21467035/article/details/115176508

 

转自:那些年Android UI开发中所遇到的各种坑

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值