首页 > 其他分享 >那些年Android UI开发中所遇到的各种坑

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

时间:2023-06-22 15:01:41浏览次数:42  
标签:动画 遇到 Fragment 重启 软键盘 UI Activity Android 方法


1. 软键盘隐藏问题

问题描述:Activity按下返回调用finish()方法后,界面已经销毁,但是软键盘依然还留在屏幕上,这让当前正在显示的Activity没有输入框的完全没法看,非常严重的视觉影响。

尝试方案:寻找各种方法去隐藏软键盘,网上各种找。思路是在活动退出时,会调用onDestroy()方法。

销毁界面,在这个方法里面想办法隐藏界面即可。找到下面这种方法,但还是不行。还尝试过用基类找到所有edittext然后让它们失去焦点,隐藏软键盘,结果发现无用,赶紧调试解决!

那些年Android UI开发中所遇到的各种坑_出栈

解决方案:一开始在onDestroy()里调隐藏软键盘的思路就是错的,因为onDestroy()之前还有两个生命周期方法,像上述隐藏软键盘的方法有个getCurrentFocus(),在onDestroy()之前肯定得不到正确的获取当前焦点的那个控件了。所以在onPasue()方法里隐藏软键盘就有效,在onDestroy()方法里不管用任何方法都是无效的。

注意点:用这个隐藏软键盘的方法,最好做为空判断,否则有可能会出现空指针的异常,如当前界面没有控件获取焦点时,则getCurrentFocus()这个方法得到的是一个null

软键盘占用布局问题,软键盘有时会把一些控件覆盖掉,这时如何把整个界面向上顶起,让任何控件都不会被覆盖呢?有两步,第一在activity里设置一个属性,如下。第二步,布局里加一个scrollview将你要被顶起的视图放进这里,然后当软键盘显示的时候,就会在scrollview里滚动以获得空间进行显示软键盘。

那些年Android UI开发中所遇到的各种坑_android_02

2. merge标签注意点

merge标签只有在根布局是FrameLayout时才有用,因为安卓所有界面的根布局都是FrameLayout,所以可以用merge标签进行融合。

merge标签使用后,布局里即使有EditText也无法自动获得焦点,只能手动设置焦点, 调用requestFocus()方法。或者是用requestFocus在XML布局文件里。使用后要注意如果在根布局中,则不能用LayoutInflater来生成一个view,否则会报如下错误,由于我在listviewgetview()里用了这个带有merge的布局,所以崩溃了。补充一点,inflater()方法里可以设置attach roottrue则可以解析出来,不会出现崩溃。

那些年Android UI开发中所遇到的各种坑_android_03

查看了下源码,是在这个地方抛出了异常。

那些年Android UI开发中所遇到的各种坑_ui_04

3. LinearLayout注意点

线性布局默认是水平的,要善用weight权重这个属性。非常重要的点,如果方向设为水平,则layout_gravitytopbottom标签是没有效果的。如果方向设为垂直,则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,这种做法“安全”些),即:

那些年Android UI开发中所遇到的各种坑_软键盘_05

问题2、异常:Can not perform this action after onSaveInstanceState。

有很多小伙伴遇到这个异常,这个异常产生的原因是:在你离开当前Activity等情况下,系统会调用onSaveInstanceState()帮你保存当前Activity的状态、数据等,直到再回到该Activity之前(onResume()之前),你执行Fragment事务,就会抛出该异常!(一般是其他Activity的回调让当前页面执行事务的情况,会引发该问题)

解决方法:

1、该事务使用commitAllowingStateLoss()方法提交,但是有可能导致该次提交无效!(宿主Activity被强杀时),对于popBackStack()没有对应的popBackStackAllowingStateLoss()方法,所以可以在下次可见时提交事务。

2、利用onActivityForResult()/onNewIntent(),可以做到事务的完整性,不会丢失事务。

一个简单的示例代码 :

那些年Android UI开发中所遇到的各种坑_android_06

问题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的相关源码:

那些年Android UI开发中所遇到的各种坑_ui_07

从上面源码可以看出,FragmentActivity确实是帮我们保存了Fragment的状态,并且在页面重启后会帮我们恢复!其中的mFragments是FragmentController,它是一个Controller,内部通过FragmentHostCallback间接控制FragmentManagerImpl,相关代码如下:

那些年Android UI开发中所遇到的各种坑_软键盘_08

通过上面代码可以看出FragmentController通过FragmentHostCallback里的FragmentManagerImpl对象来控制恢复工作,我们接着看FragmentManagerImpl到底做了什么:

那些年Android UI开发中所遇到的各种坑_重启_09

我们通过saveAllState()看到了关键的保存代码,原来是是通过FragmentManagerState来保存Fragment的状态、所处Fragment栈下标、回退栈状态等。而在restoreAllState()恢复时,通过FragmentManagerState里的FragmentStateinstantiate()方法恢复了Fragment(见下面的分析就明白啦),我们看下FragmentManagerState:

那些年Android UI开发中所遇到的各种坑_重启_10

我们只看FragmentState,它也实现了Parcelable,保存了Fragment的类名、下标、id、Tag、ContainerId以及Arguments等数据:

那些年Android UI开发中所遇到的各种坑_android_11

至此,我们就明白了系统帮我们保存的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。

下面是个标准恢复写法:

那些年Android UI开发中所遇到的各种坑_出栈_12

如果你想恢复到用户离开时的那个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

相关文章

  • Android系统服务 AMS 启动流程
    背景当SystemServer启动的时候,从Zygote进程fork()出SystemServer进程,经过初始化后,会通过反射调用SystemServer.java的mian()方法,其中会启动一系列系统服务。AMS就是其中的一个。一、缘起SystemServer进程SystemServer的main():/***Themainentrypointfromzygote......
  • Android 启动优化实践:将启动时间降低 50%
    前言作为APP体验的重要环节,启动速度是各个技术团队关注的重点。几百毫秒启动耗时的增减都会影响用户的体验,并直接反应在留存上。心遇APP作为一款用于满足中青年市场用户社交诉求的应用,对各个性能层次的手机型号,都要求有良好的启动体验。因此,随着用户量快速增长,启动优化作为一个......
  • Android - View框架的layout机制
    系统为什么要有layout过程?view框架经过measure之后,可以算出每一个view的尺寸大小,但是如果想要将view绘制的屏幕上,还需要知道view对应的位置信息。除此之外,对一个ViewGroup而言,还需要根据自己特定的layout规则,来正确的计算出子View的绘制位置,已达到正确的layout目的。位置是View相对......
  • 一天被艾特@48次!35岁Android程序员处境堪比生产队的驴!
    缘起随着互联网和移动互联网的快速发展,各类应用软件(app)如雨后春笋般涌现,许多应用程序甚至成为超级app,一些活跃用户过亿的应用程序成为国民app,这些app的兴起与程序员这个群体密不可分。快速发展的行业、互联网巨头的光环、国民级的应用程序带来的成就感、远超出普通行业的薪水,每年......
  • Android 开发之Activity的启动模式-SingleTop
    接下来,介绍下Activity的另一种启动模式-栈顶复用模式(SingleTop)SingleTopsingleTop模式,它的表现几乎和standard模式一模一样,一个singleTopActivity的实例可以无限多,唯一的区别是如果在栈顶已经有一个相同类型的Activity实例,Intent不会再创建一个Activity,而是通过onNewIntent()被......
  • Android:克服这些困难,让你直达阿里P7!
    写在前面;Android开发前几年火爆一时,市场饱和后Android程序员每一名程序员都想进阶,甚至成为架构师,但这期间,需要付出的辛苦和努力远超过我们的想象。就我这几年对所接触的Android工程师调研:97%的Android开发技术人都会面临这些困境(可能也是你的困惑)主要困境;**外包公司/小型团队技术......
  • Android:大厂技术面试过不了怎么办?别急!这些知识体系让你的面试稳如泰山!
    前言年年寒冬,年年也挡不住一个安卓程序员追求大厂的决心。想要进入大厂,我们需要掌握哪些知识点呢?这里,我为大家梳理了一个整体的知识架构。整体包括Java、Android、算法、计算机基础等等,相应的知识点的面试题都整理出来了。希望大家阅读之后,能帮助大家完善与整理自己的知识体系。祝......
  • Android-Kotlin-单例模式
    先看一个案例,非单例模式的案例:描述Dog对象:packagecn.kotlin.kotlin_oop08classDog(varname:String,varcolor:String){/***显示狗狗的名字*/funshowDogName(){println("狗狗的名字是:${this.name}")}/***显示狗狗的颜......
  • Android-Kotlin-枚举ENUM
    为什么要用枚举?枚举的好处有:1.使程序更容易编写和维护2.防止用户乱输入,是一种约束来看两个案例案例一星期:星期的枚举:enumclass类名{}packagecn.kotlin.kotlin_oop09/***定义星期的枚举类*/enumclassMyEnumerateWeek{星期一,星期二,星期三,星......
  • Android-Kotlin-函数表达式&String与int转换$异常处理
    Kotlin的函数表达式:packagecn.kotlin.kotlin_base03/***函数第一种写法*/funaddMethod1(number1:Int,number2:Int):Int{returnnumber1+number2}/***函数第二个种写法*/funaddMethod2(number1:Int,number2:Int)=number1+number2/***......