Android程序员面试笔试宝典
上QQ阅读APP看书,第一时间看更新

3.3 常见面试笔试真题

1)说一下你对RecycleView的认识(使用、原理、优化等)。

思路:

作为最经常使用的View之一,就是一道问RecycleView的综合题,把RecycleView所有的知识点给一起描述一遍即可。

解答:

①RecycleView的概述。RecycleView是一个滚动控件,可以看作是ListView的升级版,因为RecycleView不仅能轻松简便实现ListView同样的效果,还封装优化了ListView的缺点,例如ViewHolder的复用。

②RecycleView的优点。每一块的功能RecycleView都能轻松实现,因为它都封装好专门的类去实现。想改变布局的显示方式(例如横向排列、九宫格排列等),就可以通过LayoutManager去指定,例如GridLayoutManager和LinearLayoutManager等。如果要改变子项之间的分割线样式则可以通过ItemDecoration去设置。想给子项添加动画则可以通过ItemAnimator去实现。而最基本的ViewHolder也是可以由开发者根据需求去自行创建与绑定,从而实现Adapter。最后,RecycleView的点击事件也是由开发者根据需求去自行注册,这样就不会出现像ListView那样只能识别子项,而子项中的控件不能实现点击事件的情况。

③RecycleView的使用。下面以实现一个普通的滚动列表为例子,先设计子项布局recycle_item.xml:

然后Activity的布局文件引入RecyclerView:

接下来就是创建RecyclerView的适配器Adapter了:

最后一步就是在Activity里使用RecyclerView了:

由此可见,使用RecyclerView的思路大致可按照以上这个步骤去使用。

2)知道SurfaceView吗?谈一谈你对它的认识。

思路:

作为经常使用的View之一,考察对SurfaceView的认识,所以按照它是什么,有什么用,使用方法或者步骤是怎样的思路去描述它即可。

解答:

①SurfaveView概述。一般的View控件的UI是在主线程里进行绘制的,通过刷新来进行重绘,刷新的时间为16ms。所以在刷新的过程中如果需要执行的逻辑处理过多,会容易出现卡顿现象,而且也会造成主线程阻塞,引起ANR问题。

所以Google就推出了SurfaceView去解决这些问题。SurfaceView继承View,所以拥有View的特性。SurfaceView有两个子类,分别是GLSurfaceView和VideoView。它们有自己独立的、区别于普通View的绘图表面Surface,通过在Surface上绘制图像,然后将产生的视图数据交给SurfaceFlinger,SurfaceFlinger就负责将这些视图数据进行合成,最终发送到显示设备上,从而显示到屏幕上。

现在假设Activity窗口中有DecorView,DecorView里有TextView、Button和SurfaceView。DecorView及其TextView、Button的UI都是在SurfaceFlinger中的同一个Layer上绘制的,而SurfaceView的UI则在SurfaceFlinger的另一个Layer或LayerBuffer上绘制的,如图3.4所示。

图3.4 SurfaceView原理

②SurfaceView的特点。

●具有独立的绘图表面,所以它的UI绘制在独立的线程中进行,不会造成主线程阻塞;

●View主要用于主动更新,而SurfaceView用于被动更新;

●当自定义View需要频繁刷新或者刷新时需要处理比较复杂逻辑时,可以用SurfaceView;

●SurfaceView在绘图时使用了双缓冲机制。

③SurfaceView的使用。

首先创建自定义SurfaceView并继承SurfaceView类,然后分别实现SurfaceHolder.Callback和Runnable及其他的方法:

这里的SurfaceHolder的作用是用它来保存Surface对象的引用,这样就可以操作Surface,从而进行绘制工作。接下来就是初始化工作:

接着就是通过SurfaceHolder的lockCanvans()方法获得Canvas对象,从而在子线程中进行绘制工作,所以整个MySurfaceHolder代码如下:

最后,就可以使用这个自定义SurfaceView去实现想要的需求效果。例如现在想实现一个随着用户手指滑动的轨迹来进行绘图的功能,就先通过Path对象记录用户手指的滑动路径,所以在onTouchEvent()方法中进行:

与此同时,别忘了在doDrawing()方法中进行绘制:

这样就实现了一个简单版的绘图板功能了,其他的功能需求也是按这样的思路去实现。

3)整个自定义View流程里有很多方法,例如requestLayout()、onLayout()、onDraw()和onDrawChild(),请你谈一谈它们之间的联系与区别。

解答:

requestLayout():调用onMeasure()和onLayout()的过程,然后根据标记位来判断是否调用onDraw()。

onLayout():通常该方法是针对ViewGroup,因为调用onLayout()是让ViewGroup的子控件分布好位置。

onDraw():绘制控件本身(背景、canvas图层、内容、子view、fading边缘和装饰)。

onDrawChild():回调每个子控件的onDraw()方法。

4)说一下invalidate()与postInvalidate()区别。

解答:

这两个方法都是用于控件刷新的,invalidate()是主动刷新,在主线程中使用的,所以当在子线程中调用时就要使用异步机制(Handler等),而postInvalidate()则是在子线程中直接调用即可。

5)描述一下Activity、Window、View三者的关系与联系。

思路:

该题就是考查对Android的UI界面架构的认识,所以得先想起架构图,然后根据图中的层层架构去描述Activity、Window和View。

解答:

为了便于理解,下面首先了解它们之间的关系,如图3.5所示。

图3.5 UI架构

Activity有一个Window对象,由Phone Window来实现。而PhoneWindow对象下有一个DecorView对象,它就是整个应用界面的根View了,也就是顶层视图。DecorView包含了布局内容,就是用户看到的UI界面。PhoneWindow对其进行管理,监听它所有View的事件。DecorView之所以包含了全部布局内容,是因为它将屏幕分成两部分,一部分是TitleView,另一部分是ContentView。而平时Activity的布局文件xml就是设置在ContentView里。

所以,当执行setContentView()方法后,ActivityManagerService会调用onResume()方法,然后系统就把DecorView添加到PhoneWindow中让其管理并显示出布局内容出来。

6)你是怎么优化自定义View的?

思路:

可以把一般的思路给说出来,然后再结合具体的例子去讲解会更好。

解答:

①减少冗余的代码,尤其是onDraw()里的代码,尽量减少调用刷新方法的频率;

②避免在onDraw()里处理内存分配的逻辑;

③因为每次执行requestLayout()方法都是非常耗时,所以最好自定义ViewGroup来执行,因为ViewGroup有onLayout()方法直接可以对子控件进行布局;

④减少View的层级,避免冗余复杂。

7)对于自定义View你有什么好的适配方案?

思路:

既然是适配,那就是要根据屏幕大小来定出View的宽高,那这里其实可以设计一个类去根据缩放比例(使用的设备大小/设计稿上定好的大小)去更改View的大小。

解答:

可以通过自定义一个工具类来实现,示例代码如下:

工具类定义好后,就可以在自定义View里使用它:

8)在自定义View时怎么知道View的大小?

思路:

其实就是对View的测量,在onMeasure()里进行测量。分别以View和ViewGroup的两个角度去阐述。

解答:

①如果测量的是View,那么使用getDefaultSize()。getDefaultSize()有两个参数,第一个参数是getSuggestedMinimumWidth()或者getSuggestedMinimumHeight(),就是将获取最小宽度或最小高度作为默认值,没有就默认为0;第二个参数则是widthMeasureSpec或者heightMeasureSpec。最终getDefaultSize()返回的是MeasureSpec的模式和大小、getSuggested MinimumWidth()/getSuggestedMinimumHeight()进行测量后的最终大小。

②如果测量的是ViewGroup,那么需要先遍历它的所有子控件,然后重写onMeasure()方法。调用measureChildren()遍历ViewGroup里所有子控件,当View的状态是可见时就调用measureChild(),将视图宽高和边距计算好后传给getChildMeasureSpec()里,然后结合ViewGroup的MeasureSpec与子View的LayoutParams信息去获取最终测量值。最后将结果传入child的measure()方法,完成最终的测量。

9)谈一谈你对View的刷新机制的认识。

解答:

子View需要刷新的时候就调用invalidate(),找到自己的父View并通知它刷新自己,它的源码如下:

可以看到,View首先通过成员变量mParent记录自己的父View,然后将AttachInfo中保存的信息告诉父View来刷新自己。

需要注意的是,invalidate()是在主线程中调用的,所以如果要在子线程中使用就要使用Handler机制,而postInvalidate()则可在直接在子线程和主线程中使用来刷新视图。

10)请简单说一下View的绘制流程。

思路:

以View的测量、布局和绘制的思路去阐述。

解答:

①View的测量,计算View的大小。从MeasureSpec中获取测量模式和大小:

说到MeasureSpec的测量模式,它有3种:

●EXACTLY:精确测量模式,当视图的layout_width或者layout_height指定为具体数值或者match_parent时,表示父视图已经决定了子视图的精确大小,此时View的测量值就是SpecSize的值;

●UNSPECIFIED:不指定测量模式,不限制大小,可以是任何大小。开发中很少使用到,用于绘制自定义View;

●AT_MOST:最大值模式,当前视图的layout_width或者layout_height指定为wrap_content时,控件大小随子控件或内容来改变,大小不超过父控件运行的最大尺寸。

当从MeasureSpec中获取到测量模式和大小后,可根据不同的测量模式来给出不同的测量值:

②View的布局,确定View在父控件里的布局位置:

③View的绘制,绘制背景、canvas图层、内容、子view、fading边缘和装饰:

11)如果要创建自定义View,那么如何提供获取View属性的接口?

思路:

其实本质就是问自定义View过程中如果想自定义属性的步骤。

解答:

在自定义View过程中,当需要给View创建属性时,先在values目录下创建attrs.xml文件,定义相关属性:

上面代码中定义了两个属性,titleTextSize和title,它们的类型分别是dimension和string类型,然后在布局文件里使用这两个自定义属性:

可以看到,先定义了命名空间xmlns:myview去引用自定义myview:title属性。最后,在自定义View类里把这些自定义属性的值提取出来:

在构造方法中的AttributeSet就是对应values目录下attrs.xml文件里的declare-styleable标签,使用ontainStyledAttributes()方法将属性集合提取到TypeArray里,然后使用getXXX()方法就能获取属性值。

12)ListView中图片错位的问题是怎样产生的?有什么办法去解决?

解答:

因为ListView使用了缓存机制convertview,而且是异步的,当整个屏幕刚好能显示8个item时,如果此时向上滑,从而显示第9个item,但它是重用第一个item来显示的,而此时又异步进行网络请求第9个item的图片,自然比第一个item的图片加载慢,所以第9个item就显示第1个的图片。而当向下滑动时,因为第9个item的相片此时加载完成,所以第1个item又会复用第9个item的图片了,所以这样就会导致图片错乱了。

解决方法是给ImageView控件设置一个标识,并设置一张默认图片。当向上滑动时,第9个item显示,把第一个item隐藏。因为标识的缘故,即使第1个item的加载图片要比第9个的要快,但因为此时的标识是第9个item的,所以也不会显示第1个item的图片。

13)同样都是滑动控件,说一下RecyclerView和ListView的区别。

思路:

可以从ViewHolder、动画、Adapter、滑动方式和点击事件等方面去阐述。

解答:

①ViewHolder。作为保存控件的工具类,ListView是要开发者自己定义的,而且不需要一定要使用它,当然如果不使用它但是每次都要调用findViewById(id)会导致性能降低。而RecyclerView则已经封装好了ViewHolder,直接使用即可。

②动画。在一般情况下要使用动画框架则会用属性动画或者View动画,而RecyclerView有封装好的ItemAnimator,通过它可以轻松实现RecyclerView在添加、删除和滑动item项时的动画效果。

③Adapter。ListView有3个系统写好的Adapter,分别是ArrayAdapter、CursorAdapter和SimpleCursorAdapter,可以直接使用,也可以自定义,通过getView()方法来绑定控件。而RecyclerView则是自定义Adapter,使用观察者模式,将数据传给定义好的Adapter。

④滑动方式。ListView只可以在垂直方向上进行滑动,而RecyclerView可以直接通过LinearLayoutManager设置水平或者垂直方向的滑动,可以通过StaggeredGridLayoutManager设置瀑布流排列的风格来滑动,也可以通过GridLayoutManager设置成网格排列风格来滑动。

⑤点击事件。ListView因为实现的接口是OnItemClickListener,所以只能监听子项的点击事件。RecycleView的点击事件也是由开发者根据需求去自行注册,不仅仅是子项的各种点击事件,连子项中的其他控件的点击事件也能实现。这样就不会出现像ListView那样只能识别子项,而子项中的控件不能实现点击事件的情况。

14)自定义View或ViewGroup时要注意什么?

解答:

①因为直接继承View或ViewGroup的控件默认不支持wrap_content,所以需要在重写onMeasure()方法中进行设置;

②如果是继承View的控件,则要在onDraw()方法里设置padding,否则控件的padding是不起作用的。而如果是继承ViewGroup的控件,那么也要处理好自身的padding以及它的子控件的margin,否则也是不起作用的;

③因为View内部已经有post一类的方法,所以没必要一定要使用Handler;

④为了防止内存泄漏,要及时在onDetachedFromWindow()或者onAttachedToWindow()里结束动画和线程;

⑤注意避免滑动冲突的情况。

15)ListView和RecyclerView之间在性能上有什么区别?

解答:

①因为RecyclerView有布局管理器,它比ListView支持的布局类型更多;

②RecyclerView有ViewHolder,对于控件的复用会好过ListView,在编写规范上也会清晰明了;

③RecyclerView有封装好的动画机制,例如ItemAnimator,直接使用即可;

④如果要对特定的item项进行刷新,ListView会比RecyclerView实现起来复杂;

⑤RecyclerView可以对自己的item项里的控件实现点击事件监听,而ListView则不能,它只可以对自己的item项监听点击事件;

⑥ListView可以直接调用其封装好的方法处理空数据,而RecyclerView要开发者自己写;

⑦RecyclerView支持嵌套机制。

16)请简单描述一下View的渲染。

解答:

Android系统每隔16ms就会发出VSYNC信号,通知UI进行渲染,当渲染成功,也就是能达到60fps,就能让画面看起来流畅。所以每次渲染的计算操作都必须在16ms内完成。如果渲染超时,也就是这一帧画面渲染时间超过16ms,垂直同步机制就会让显示器等待GPU完成栅格化渲染操作,这样会让这一帧画面多停留了16ms或者更多时间,这样就造成了画面停顿,也就是卡顿现象。

17)View与SurfaceView的区别是什么?

解答:

①View不能在子线程中进行UI操作,而SurfaceView可以在任何线程中更新UI;

②SurfaceView放置在最底层,在其之上可以添加其他层,但不能是透明的层;

③SurfaceView可以控制帧数,执行动画效率要比View的效率高;

④综合来说,SurfaceView的创建和用法要比自定义View的复杂,占用资源也比自定义View要多。

18)你能介绍一下ListView和它的Adapter吗?

解答:

Adapter把列表上的数据映射到ListView中,ListView常用的Adapter有:

①ArrayAdapter:实现简单的数据绑定,绑定每个对象的字符串值到预定的TextView上;

②SimpleAdapter:针对HashMap构成的列表,将其数据通过键值对形式绑定到ListView,从而实现每个item项要实现的布局;

③BaseAdapter:当其他Adapter满足不了需求时,就会自定义Adapter来继承BaseAdapter,然后实现getItem()、getItemId()、getView()和getCount()方法,对ListView列表进行绘制等逻辑处理,从而实现各种开发需求。

19)RecyclerView的缓存机制与ListView的缓存机制有什么区别?

解答:

①ListView是二层缓存,缓存View,每次刷新都需要绑定数据,复用时候全部都要绑定数据,先缓存再复用;

②RecyclerView是四级缓存,缓存ViewHolder,ViewHolder保存的是View,不需要每次刷新都要绑定数据,直接使用即可,先复用再缓存。