2020Android面试——个人回想(一)
题一:Activity的四种启动模式及应用
1.Standard:标准模式
该模式下每次打开Activity都将创建新的实例,不论之前是否已经创建该Activity实例。默认Activity的启动模式为该模式。
2.SingleTop:单一栈顶模式
该模式下每次打开Activity时,如果当前任务栈的栈顶已存在该Activity的实例则直接对其进行复用,不会重新创建新的Activity实例。
应用场景:适用于比如接收到多个通知,这个几个通知都是要打开同一个页面,只是页面的内容不同,这个时候可以每次打开时对其进行复用。
3.SingleTask:单一任务模式
该模式下如果当前要打开的Activity在当前任务栈内已存在,则会将已存在实例之上的Activity全部出栈,该Activity成为新的栈顶对其进行复用,不会重新创建新的Activity实例。
应用场景:一般可以用在像我们app的主页、浏览器的首页这样类似于一个主的入口的地方,保证在整个栈中只有一个该实例的存在,回到该页面时将之前的Activity都移除掉,这样我们比方点返回按钮式就不会出现页面错乱这样的现象了。
4.SingleInstance:单一实例模式
该模式下创建的Activity将独自占用一个新的任务栈,栈内只有该Activity一个实例。然后一旦该实例存在后每次打开该Activity都会重用该栈中的这个实例,调用onNewIntent方法。
应用场景:适合一些需要与应用分离的页面,比如说闹铃提醒啊、地图导航啊,当应用退出的时候,进入地图还是刚才的页面。
题二:Activity的生命周期有哪些?
onCreate→onStart→onResume→onPause→onStop→onDestroy onRestart
补充:息屏时,onPause()和onStop()会依次被调用
亮屏时,onRestart(),onStart(),onResume()会依次被调用。
四种启动模式生命周期执行顺序:
Standard:
打开MainActivity后跳转TestOneActivity时,MainActivity执行onCreate→onStart→onResume;
此时跳转TestOneActivity,MainActivity执行onPause,TestOneActivity执行onCreate→onStart→onResume;
此时MainActivity不可见,TestOneActivity可见,接下来MainActivity执行onStop方法。
如果此时点击返回按钮TestOneActivity执行onPause,MainActivity执行onRestart→onStart→onResume,测试MainActivity可见,TestOneActivity不可见你,TestOneActivity执行onStop→onDestroy。
SingleTop:
我们这里将TestOneActivity设置为SingleTop模式。打开MainActivity后跳转到TestOneActivity,此时的生命周期执行过程和Standard模式下输出相同,接下来我们在TestOneActivity中再次打开TestOneActivity时,TestOneActivity会执行onPause→onNewIntent→onResume
SingleTask:
我们这里将MainActivity设置为SingleTask模式,前面的操作依然是从MainActivity跳转至TestOneActivity,接下来我们跳到一个默认模式下的TestTwoActivity,此时栈顶的Activity就是TestTwoActivity,此时我们再跳转一个默认模式下的TestThreeActivity,此时的栈顶是TestThreeActivity。之前的生命周期还是和Standard模式一样;接下来就是不一样的地方,此时我们从TestThreeActivity跳转到MainActivity,这个时候会一次执行如下方法:
TestOneActivity执行onDestroy,TestTwoActivity执行onDestroy,TestThreeActivity执行onPause,MainActivity执行onRestart→onStart→onNewIntent→onResume,此时,MainActivity可见,TestThreeActivity执行onStop→onDestroy
SingleInstance:
我们这里将TestOneActivity设为SingleInstance模式,其他Activity都为Standard模式。
首先打开MainActivity后再打开TestOneActivity页面然后再打开TestTwoActivity页面,到这里所显示的日志前三步是和前面没有区别的,而接下来所要做的操作是,点击返回键,此时我们可以看到我们先回到的是MainActivity页面,TestTwoActivity页面之后被销毁了,并不是我们想的会回到TestOneActivity页面,这也就看出了SIngleInstance的端倪,因为我们的SIngleInstance下的TestOneActivity并不和其他两个Activity在同一个任务栈内,和TestTwoActivity同一任务栈的只有MainActivity,所以在退出时理所应当的退回了MainActivity页面而不是TestOneActivity页面,当我们回到MainActivity页面再次点击返回键时,这时也就彻底关闭了之前所包含MainActivity和TestTwoActivity两个页面的任务栈,接下来只剩独自创建并占用了一个任务栈的TestOneActivity页面,再次点击返回键时也就正常无二了。
特殊生命周期
①页面跳转Dialog:
下面这个生命周期乍一看log感觉像是MainActivity跳转至TestOneActivity再返回MainActivity·,没错确实是这样,但是有一点不同的是可以看到,跳转至TestOneActivity后MainActivity并没有执行onStop方法,这是为什么呢,其实是因为这里我们将TestOneActivity的主题设置成了Dialog的形式,在这样的情况下,MainActivity始终是可见的,只是无法交互而已,所以并不会执行onStop方法。
②横竖屏切换
以下便是进行横竖屏切换时MainActivity所执行的生命周期方法。可以看到在打开MainActivity后进行横竖屏切换时,MainActivity执行了销毁的过程onPause→onStop,此时页面即将被销毁,在销毁之前,执行了onSaveInstanceState方法,之后便销毁了执行了onDestroy方法。在这之后MainActivity重新由onCreate→onStart方法,在界面即将可交互时,执行了onRestoreInstanceState方法,最后onResume时页面恢复可以与用户交互,只不过此时页面已经有竖屏变为了横屏模式。
这里可看到两个特殊的方法onSaveInstanceState和onRestoreInstanceState。不用多说,这两个方法一个onSaveInstanceState是用来在页面即将销毁时保存一些重要数据用的,而对应的onRestoreInstanceState则是在页面即将恢复时将之前onSaveInstanceState所保存的数据进行一个恢复。
③Android activity 下拉通知栏的过程 Activity都执行了哪些方法
答:下拉通知栏对Activity的生命周期没有影响。理解了onPause()和Activity的奇妙联系,就不难理解之前为什么没有被调用的问题了。
查看AlertDialog和Toast的源码,可以发现它们显示的原理,都是通过WindowManager.addView()来显示的。也就是说,AlertDialog和Toast可以看做是当前Activity的一部分View,当然也不会对Activity的生命周期构成影响。
因此,onPause()是否调用的关键就是,是否有另一个Activity参与进来了。
而网上流传甚广的onPause()和onStop()调用中提到的“遮挡”,应该修正为“被Activity遮挡”
当下拉状态栏是Activity会被回调的方法是onWindowFocusChanged(boolean hasFous)方法,该方法的参数表示当前Activity是否有获取焦点,当状态栏被下拉后hasFocus会变为false,恢复后变为true。
题三:Fragment相关问题
Fragment的生命周期有哪些?
Fragment俗称碎片,它必须依附于Activity,一个Activity可以有多个Fragment。它和Activity一样有自己的生命周期。
onAttach→onCreate→onCreateView→onActivityCreated→onStart→onResume→onPause→onStop→onDestroyView→onDestroy→onDetach
onAttach是Fragment与Activity发生关联时调用,对应的onDetach就是Fragment和Activity解除关联时调用的;
onCreateView是创建Fragment视图时调用的,对应的onDestroyView则是Fragment视图被移除时调用的;
onActivityCreated是Activity的onCreate方法返回时调用的。
Fragment与Activity间如何传值?
可以通过Bundle对象传值,通过setArguments传入,通过getArguments获取;
也可以通过getActivity调用Activity这种的方法或者接口回调的方式。
还有SQLite、Sp等间接传递。
FragmentPagerAdapter与FragmentStatePagerAdapter的区别?
FragmentStatePagerAdapter在destoryItem时调用的是remove方法,会回收内存,而FragmentPagerAdapter在destoryItem时调用的是detach方法,只是将其与Activity分离并未销毁。所以,FragmentPagerAdapter适用于页面较少的情况,页面较多时用FragmentStatePagerAdapter合适。
FragmentTransaction的add和replace的区别?
add添加的Fragment在切换时生命周期不会重走,这个时候是有多层FrameLayout的,只会调用onHiddennChanged,hide和show只是把其他界面隐藏;而replace会替换掉源于的,只有一层FrameLayout,生命周期也会重走。
题四:View的事件分发机制
前提:
我们大致了解我们的点击事件都是从Activity开始的,而每个Activity创建时都会执行难onCreate方法,在onCreate方法里又会调用父类的setContentView这个方法,方法传入的参数就是我们的布局文件。我们进入底层查看这个setContentView方法,该房里执行了一行代码getWindow().setContenView()。getWindow所返回的一个Window类的实例mWindow。mWindow是在attach方法中完成实例化的。可以在attach方法张中看到实例化的是一个PhoneWindow的实例,然后看PhoneWindow源码,可以看到它是继承自抽象类Window类的。所以Activity的setContentView方法实际上最终是调用了PhoneWindow的setContentView方法。那在PhoneWindow中的setContentView方法调用了installDecor方法,这个方法里generateDecor会创建一个DecorView实例。众所周知,DecorView是我们Activity的根View。而DecorView实际是PhoneWindow的一个内部类,并且继承了FrameLayout,在执行完generateDecor方法后有了我们的根View,接下来会执行generateLayout方法,那这里呢也就是要加载我们所真正实现的布局文件中的内容了。
所以呢在我们进行一个物理操作触摸点击屏幕这样的操作后,事件也是从Activity先开始处理的。
View事件分发机制
事件首先会传递给当前的Activity,然后调用它的dispatchTouchEvent方法,然后事件处理依次交由PhoneWindow→DecorView→我们的根ViewGroup来处理。
所以我们开始从ViewGroup的dispatchTouchEvent方法开始分析,首先判断是否是DOWN事件,如果是则开始初始化整个事件,因为所有的事件都是DOWN开始,UP结束的。然后判断我们当前的ViewGroup是否对本次事件进行了拦截,如果拦截了,这个时候触发DOWN事件,则会执行onInterceptTouchEvent方法。如果没有就会继续往下执行,相应的子View可与通过requestDisallowIInterceptTouchEvent方法来设置是否允许ViewGroup进行事件拦截,而且onInterceptTouchEvent默认是不进行拦截的,如果需要拦截需要我们重写该方法。当我们的ViewGroup没有拦截事件继续往下执行的时候会通过一个for循环来遍历ViewGroup的子元素,这个遍历是倒序的,从我们的最上层的子View开始往内下层遍历。如果子View能接受到点击事件则交由子元素来处理,调用子View的child.dispatchTouchEvent方法,如果没有则调用super.dispatchTouchEvent方法,也就是ViewGroup的。ViewGroup也是继承自View的,看View的dispatchTouchEvent方法如果有点击事件的监听并且onTouch方法返回true,则表示事件被消费,就不会执行onTouchEvent方法了;否则就会执行onTouchEvent方法。可以看出OnTouchListener中的onTouch方法优先级要高于onTouchEvent方法。
接下来看View的onTouchEvent方法,只要可点击或者可以长按那么该方法就会返回true消耗这个事件,UP的时候调用performClick。从performClick源码看如果View设置了点击事件OnClickListener,那么他的onClick方法就会执行。
点击事件分发的传递规则
onInterceptTouchEvent方法和onTouchEvent方法都在dispatchTouchEvent方法中调用。
首先当点击事件产生后由Activity来处理传递给PhoneWindow再传递给DecorView,最后再传递给顶层的ViewGroup。
一般在事件传递中只考虑ViewGroup的onInterceptTouchEvent方法,,因为一般情况下我们不会重写dispatchTouchEvent方法。对于根ViewGroup,点击事件首先传递给它的dispatchTouchEvent方法,如果该ViewGroup的onInterceptTouchEvent方法返回true,则表示它要拦截这个事件,这个事件就会交给它的onTouchEvent方法处理;如果onInterceptTouchEvent方法放回false,则表示它不拦截这个事件,则这个事件会交给它的子元素的dispatchTouchEvent来处理,如此反复下去。如果传给底层的View,View是没有子View的,就会调用View的dispatchTouchEvent方法,一般情况下最终会调用View的onTouchEvent方法。
如果onTouchEvent方法返回true,则事件由底层的View消耗并处理;如果返回false则表示该View不做处理,则传递给父View的onTouchEvent处理;如果父View的onTouchEvent仍旧返回false,则继续传递给该父View的父View处理,如此反复下去,直到找到事件处的处理者。
题五:View的工作流程
View的工作流程指的就是measure、layout和draw。其中measure用来测量View的宽和高,layout用来指定View的位置,draw则用来绘制View。
理解MeasureSpec
MeasureSpec是View的一个内部类,其封装了一个View的规格尺寸,包括View的宽和高的信息。
它代表了32位的int值,其中高2位代表了SpecMode,低30位则代表SpecSize。
SpecMode指的是测量模式,有以下三种:
UNSPECIFIED:未指定模式,View想多大就多大,父容器不做限制,一般用于系统内部的测量。
AT_MOST:最大模式,对应于wrap_content属性,子View的最终大小是父View指定的SpecSize值,并且子View的大小不能大于这个值。
EXACTLY:精确模式,对应于match_parent属性和具体的数值,父容器测量出View所需要的大小,也就是SpecSize值。
每一个View都有一个MeasureSpec,里面保存了该View的尺寸规格,通过makeMeasureSpec来保存宽和高的信息。通过getMode或getSize得到模式和宽、高。MeasureSpec受自身LayoutParams和父容器MeasureSpec的共同影响。
View的measure流程
measure用来测量View的宽和高,它的流程分为View的measure流程和ViewGroup的measure流程,只不过ViewGroup的measure流程除了要完成自己的测量,还要遍历地调用子元素的measure方法。
①View的measure流程
首先View的onMeasure方法调用了setMeasuredDimension方法用来设置View的宽高,该方法传入的是值是通过getDefaultSize方法获取的,在该方法中根据对MeasureSpec的解析获取了View的模式以及宽高。在该方法源码中可以看出在AT_MOST和EXACTLY模式下返回的值是一样的,即View在这两种模式下的测量宽和高直接取决于SpecSize,也就是说对于一个直接继承自View的自定义View来说,它的wrap_content和match_parent属性的效果是一样的。因此如果要实现自定义View的wrap_content,则要重写onMeasure方法,并对自定义View的wrap_content属性进行处理。
②ViewGroup的measure流程
它不止要测量自身,还要遍历地调用子元素的measure方法。ViewGroup中没有定义onMeasure方法,但定义了measureChildren方法,该方法遍历子元素并调用measureChild方法。
measureChild方法内调用child.getLayoutParams方法来获得子元素的LayoutParams属性,获取子元素的宽和高的MeasureSpec并调用子元素的measure方法进行测量。在该方法中根据父容器的MeasureSpec模式再结合子元素的LayoutParams属性来得出MeasureSpec。
ViewGroup并没有提供onMeasure方法,而是让其子类来各自实现测量的方法,因为ViewGroup有不同布局的需要,很难统一。
下面举个LinearLayout的例子,在它的onMeasure方法中会根据我们的布局方向,如果是垂直方向则调用measureVertical方法,否则调用measureHorizontal方法。
里面定义了mTotalLength存储LinearLayout垂直方向的高度,然后遍历子元素,根据子元素模式计算每个子元素的高度。如果是WRAP_CONTENT,则将每个子元素的高度和margin垂直高度等值相加并赋值给mTotalLength。最后还要加上padding。如果高度设置为MATCH_PARENT或具体数值,则和View的测量方法是一样的。
简单点来讲就是根据宽高属性来测量View的宽高,然后通过setMeasuredDimension方法进行设置存储自身的尺寸。然后ViewGroup要根据其布局特性来测量设置自身相应的尺寸。
View的layout流程
ViewGroup中的layout方法用来确定子元素的位置。View中的layout方法用来确定自身的位置。
layout方法的四个参数l、t、r、b分别是View从左、上、右、下相对于其父容器的距离,里面调用了setFrame方法,该方法将layout传进来的l、t、r、b这四个参数分别初始化了mLeft、mTop、mRight、mBottom四个值,这样就确定了View在父容器中的位置。在调用setFrame方法后会调用onLayout方法。
onLayout方法是一个空方法,和onMeasure类似。确定位置时根据不同控件有不同的实现,所以在View和ViewGroup中均没有实现onLayout方法。
下面举例LinearLayout,也是根据方向来调用不同的方法,比如垂直方向layoutVertical方法,这个方法会遍历子元素并调用setChildFrame方法。其中childTop值是不断累加的,这样子元素才会一次按照垂直方向一个接一个排列下去不会是重叠的。在setChildFrame方法中调用子元素的layout方法来确定自己的位置。
简单点来讲就是根据所得出的测量传入的相应的值,并且根据布局模式,然后去逐个确定每个View相对于父容器的位置。ViewGroup何时被布局的?它当然也是由它的父控件来完成对他的布局,一层一层往上,直到所有控件的顶层节点ViewRoot。在它的布局里会调用一个layout函数,不能被重载也就是View的layout函数
View的draw流程
①如果需要,绘制背景
调用了View的drawBackground方法。并在绘制背景时考虑了偏移参数scrollX和scrollY,如果有偏移值不为0,则会在偏移后的canvas绘制背景。
②保存当前canvas层
③绘制View的内容
调用View的onDraw方法。这个方法是一个空实现,因为不同的View有着不同的内容,需要自己去实现,即在自定义View中重写该方法来实现;
④绘制子View
调用了dispatchDraw方法,这个方法也是一个空实现。
ViewGroup重写了这个方法,看这个方法的实现可以看到ViewGroup的dispatchDraw方法中对子类View进行遍历,并调用drawChild方法,drawChild方法又调用了View的draw方法。View的draw方法源码很长,主要是看是否有缓存,如果没有则正常绘制,有则利用缓存显示。
⑤如果有需要,则绘制View的褪色边缘,这类似于阴影效果
⑥绘制装饰,比如滚动条
绘制装饰的方法为View的onDrawForeground方法,这个方法用于绘制ScrollBar以及其他的装饰,并将它们绘制在视图内容的上层。
简单点来讲在该方法中就是确定我们的View具体的一些样式,然后最终进行绘制。
本文地址:https://blog.csdn.net/wangzhongITger/article/details/108029477
上一篇: Flutter环境搭建(适合Android开发者)
下一篇: 美团Robust热修复工具使用记录