1.对UI布局的了解?
布局种类(7种)
线性布局LinearLayout
:将组件以横排或纵排的方式排列表格布局TableLayout
:表格布局是以行和列的形式来对控件进行管理的,有多少个TableRow对象或view控件就会有多少行,等于含有最多子控件的TableRow的列数。如第一(行)TableRow含2个子控件,第二(行)TableRow含3个,第三(行)TableRow含4个,那么这个表格布局的列数就是4列。网格布局GridLayout
:可以自己设置布局中组件的排列方式
- 可以自定义网格布局有多少行( rowCount),多少列(columnCount)
- 可以直接设置组件位于某行( layout_row)某列( layout_column)
- 可以设置组件横跨几行(layout_rowSpan)或者几列(layout_columnSpan)
帧布局FrameLayout
:都按照层次堆叠在屏幕的左上角。后加进来的控件覆盖前面的控件。相对布局RelativeLayout
:允许组件指定他的父类或者子类的位置进行布局,非常灵活,对于屏幕大小不同的手机适配性好,但是属性之间的冲突难以控制,需要大量的测试对布局进行调整工作绝对布局AbsoluteLayout
: 直接就可以决定子组件的绝对位置,简单直接就可以定位,但是手机的屏幕不一样大,适配性差
可以直接用Android:layout_x和android:layout_y来定义组件的位置(两个属性只能在绝对布局中使用,在相对布局中都不会显示的)约束布局ConstraintLayout
:适合使用可视化的方式来编写界面,辅助线Guildline,帮助定位控件,可以帮助解决布局适配的问题
做业务时布局选型的依据
- 布局中不得不使用 ViewGroup 多重嵌套时,不要使用 LinearLayout 嵌套,改用 RelativeLayout,可以有效降低嵌套数,多重嵌套导致 measure 以及 layout 等步骤耗时过多。推荐使用约束布局,效率更高
- 在 Activity 中显示对话框或弹出浮层时,尽量使用 DialogFragment,而非Dialog/AlertDialog,这样便于随Activity生命周期管理对话框/弹出浮层的生命周期。
- 灵活使用布局,推荐 Merge、ViewStub 来优化布局,尽可能多的减少 UI布局层级,推荐使用 FrameLayout,LinearLayout、RelativeLayout 次之。
- 根据不同的业务场景选择不同的布局,核心就是减少布局层级的嵌套,保证滑动屏幕时 UI 的流畅。
2.屏幕适配的几种解决方案
Android适配最核心的问题有两个:其一,就是适配的效率,即把设计图转化为App界面的过程是否高效,其二如何保证实现UI界面在不同尺寸和分辨率的手机中UI的一致性。
- dp+自适应布局+weight比例布局直接适配
- dimens基于px的适配(宽高限定符适配)
- 今日头条适配(修改手机的设备密度 density):通过修改density值,强行把所有不同尺寸分辨率的手机的宽度dp值改成一个统一的值,这样就解决了所有的适配问题
优化UI性能的方式
ConstraintLayout 或 RelativeLayout布局可以优化,ConstraintLayot 类似 RelativeLayout,但是更灵活更强大。 具有更强的性能优势,简化嵌套深度。更好的屏幕适配,可以使用比例来适配(layout_constraintGuide_percent属性),效果更好。
3.Android事件传递机制
事件的定义:当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件
(Touch事件)。(Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)
被封装成MotionEvent对象)
主要发生的Touch事件的类型有四种:
- MotionEvent.ACTION_DOWN:手指刚接触屏幕
- MotionEvent.ACTION_UP:手指刚从屏幕上松开
- MotionEvent.ACTION_MOVE:手指在屏幕上滑动
- MotionEvent.ACTION_CANCEL:非人为因素取消
事件列:从手指接触屏幕至手指离开屏幕,这个过程产生的一系列事件 任何事
件列都是以DOWN事件开始,UP事件结束,中间有无数的MOVE事件,即当一个MotionEvent 产生后,系统需要把这个事件传递给一个具体的 View 去处理
- 点击屏幕后立刻松开,事件序列为DOWN->UP
- 点击屏幕后滑动一会再松开,事件序列为DOWN->MOVE->(无数个MOVE事件)…->MOVE->UP(非人为因素事件取消除即为CANCEL事件)
事件分发的本质
点击事件(MotionEvent)向某个View进行传递并最终得到处理
即当一个点击事件发生后,系统需要将这个事件传递给一个具体的View去处理。这个事件传递的过程就是分发过程。
事件分发对象
Activity、ViewGroup、View一个点击事件产生后,传递顺序是:Activity(Window) -> ViewGroup ->View
Android的UI界面是由Activity、ViewGroup、View及其派生类组合而成的
- Activity:能够控制生命周期和处理事件
- VIew:View是所有UI组件的基类,一般Button、ImageView、TextView等控件都是继承父类View
- ViewGroup:一组View的集合(含多个子View),其本身也是从View派生的,即ViewGroup是View的子类, 是Android所有布局的父类或间接父类:项目用到的布局
(LinearLayout、RelativeLayout等),都继承自ViewGroup,即属于ViewGroup子类, 与普通View的区别:ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。
事件分发的主要方法
- dispatchTouchEvent(MotionEvent event):用来进行事件分发
- onInterceptTouchEvent(MotionEvent event):判断是否拦截事件(只存在于ViewGroup中)
- boolean onTouchEvent(MotionEvent event):处理点击事件
4.事件分发机制方法&流程介绍
activity事件分发的传递顺序为:
当一个点击事件发生时,事件最先传到Activity的dispatchTouchEvent()进行事件分发,最终是调用了ViewGroup的dispatchTouchEvent()方法这样事件就从 Activity 传递到了 ViewGroup
- 事件最先传到Activity的dispatchTouchEvent()进行事件分发
- 调用Window类实现类PhoneWindow的superDispatchTouchEvent()
- 调用DecorView的superDispatchTouchEvent()
- 最终调用DecorView父类的dispatchTouchEvent(),即ViewGroup的
dispatchTouchEvent()- Activity->Window->DecorView->ViewGroup(dispatchTouchEvent)
ViewGroup的事件分发的传递顺序为
调用自身的onInterceptTouchEvent判断是否拦截(默认不拦截),拦截则调用自身的onTouchEvent方法,不拦截的会调用子View的dispatchTouchEvent方法将事件分发给子VIew
View的事件分发的机制为:
当前View可见,如果设置TouchListener,先响应TouchListener.onTouch方法,如果TouchListener.onTouch方法返回false才执行onTouchEvent
onTouch和onTouchEvent这两个方法都是在View的dispatchTouchEvent中调用,但onTouch优先于onTouchEvent执行。如果在onTouch方法中返回true将事件消费掉,onTouchEvent()将不会再执行。
完整流程是:
点击事件,根ViewGroup调用dispatchTouchEvent来分发,接着判断是否拦截onInterceptTouchEvent,若被拦截调用自身的onTouchEvent分发方法,再判断是否被消费,若被消费事件就结束,没有被消费就调用Activity的onTouchEvent来处理点击事件。
若没有被拦截的会调用子ViewGroup的dispatchTouchEvent方法来分发,接着调用子ViewGroup的onInterceptTouchEvent判断是否被拦截,若被拦截则调用自己的onTouchEvent分发方法,接着判断是否被消费,若被消费,则消费事件就结束,没有被消费就调用父类ViewGroup的onTouchEvent来处理,接着再判断是否被消费,若被消费事件就结束,没有被消费就调用Activity的onTouchEvent来处理点击事件。
若没有被拦截则调用View的dispatchTouchEvent进行事件分发后再调用View的onInterceptTouchEvent判断是否被拦截,若被拦截则调用自己的onTouchEvent分发方法,接着判断是否被消费,若被消费则消费事件就结束,没有被消费就调用父类ViewGroup的onTouchEvent来处理,接着再判断是否被消费,若被消费事件就结束,没有被消费就调用父类ViewGroup的onTouchEvent来处理,接着再判断是否被消费,若被消费事件就结束,没有被消费就调用Activity的onTouchEvent来处理点击事件。
5.View绘制的流程框架
View的绘制是从上往下一层层迭代下来的。 DecorView–>ViewGroup -->View , 按照这个流程从上往下, 依次measure(测量),layout(布局),draw(绘制)。
Measure流程
调用measure()方法, 进行一些逻辑处理, 然后调用onMeasure()方法, 再循环遍历每个子View测量判断是否是ViewGroup,不是的就调用View的measure()方法, 然后调用onMeasure()方法, 在其中调用setMeasuredDimension()设定子View的宽高信息, 完成View的测量操作。若是再重新进行测量。
Layout流程
测量完View大小后, 就需要将View布局在Window中, View的布局主要通过确定上下左右四个点来确定的,ViewGroup先在layout()中调用setFrame确定自己的布局位置, 然后在onLayout()方法中再调用子View的layout()方法, 让子View布局。如果当前View就是一个单一的View, 那么没有子View, 就不需要实现该方法。对于单一View布局就是:先在layout()中调用setFrame确定自己的布局位置,再调用**onLayout()**的空方法,然后布局就结束了。
Draw过程
- 绘制背景 background.draw(canvas)
- 绘制自己( onDraw)(空方法,自己实现)
- 绘制Children(dispatchDraw)(单一View不用实现)
- 绘制装饰( onDrawScrollBars)
总结
自定义View的种类不同, 可能分别要自定义实现不同的方法。 但是这些方法不外乎: onMeasure()方法, onLayout()方法, onDraw()方法。
- onMeasure()方法: 单一View, 一般重写此方法, 针对wrap_content情况, 规定View默认的大小值, 避免于match_parent情况一致。 ViewGroup, 若不重写, 就会执行和单子View中相同逻辑, 不会测量子View。 一般会重写onMeasure()方法, 循环测量子View。
- onLayout()方法:单一View, 不需要实现该方法。 ViewGroup必须实现, 该方法是个抽象方法, 实现该方法, 来对子View进行布局。
- onDraw()方法: 无论单一View, 或者ViewGroup都需要实现该方法, 因其是个空方法
6.Activity的生命周期
在正常情况下, 一个Activity从启动到结束会以如下顺序经历整个生命周期:
onCreate()->onStart()->onResume()->onPause()->onStop()->onDestory()。 包含了六个部分, 还有一个onRestart()没有调用。
onCreate(): 当 Activity 第一次创建时会被调用。 这是生命周期的第一个方法。在这个方法中, 可以做一些初始化工作, 比如调用setContentView去加载界面布局资源, 初始化Activity所需的数据。 当然也可借助onCreate()方法中的Bundle对象来回复异常情况下Activity结束时的状态 。
onRestart(): 表示Activity正在重新启动。 一般情况下, 当当前Activity从不可见重新变为可见状态时, onRestart就会被调用。 这种情形一般是用户行为导致的, 比如用户按Home键切换到桌面或打开了另一个新的Activity, 接着用户又回到了Actvity。
onStart(): 表示Activity正在被启动, 即将开始, 这时Activity已经出现了, 但是还没有出现在前台, 无法与用户交互。 这个时候可以理解为Activity已经显示出来,但是我们还看不到。
onResume():表示Activity已经可见了, 并且出现在前台并开始活动。 需要和onStart()对比, onStart的时候Activity还在后台, onResume的时候Activity才显示到前台。
onPause():表示 Activity正在停止, 仍可见, 正常情况下, 紧接着onStop就会被调用。 在特殊情况下, 如果这个时候快速地回到当前Activity, 那么onResume就会被调用( 极端情况) 。 onPause中不能进行耗时操作, 会影响到新Activity的显示。 因为onPause必须执行完, 新的Activity的onResume才会执行。
onStop():表示Activity即将停止, 不可见, 位于后台。 可以做稍微重量级的回收工作, 同样不能太耗时。
onDestory():表示Activity即将销毁, 这是Activity生命周期的最后一个回调, 可以
做一些回收工作和最终的资源回收。
7.Activity生命周期的几种普通情况
生命周期的几种普通情况
- 针对一个特定的Activity, 第一次启动, 回调如下: onCreate()->onStart()->onResume()
- 用户打开新的Activiy的时候, 上述Activity的回调如下: onPause()->onStop()
- 再次回到原Activity时, 回调如下: onRestart()->onStart()->onResume()
- 按back键回退时, 回调如下: onPause()>onStop()->onDestory()
- 按Home键切换到桌面后又回到该Actitivy, 回调如下: onPause()->onStop()->onRestart()->onStart()->onResume()
- 调用finish()方法后, 回调如下: onDestory()(以在onCreate()方法中调用为例,不同方法中回调不同, 通常都是在onCreate()方法中调用
8.Activity特殊情况下的生命周期
横竖屏切换的生命周期:
- onPause()->onSaveInstanceState()-> onStop()-onDestroy()->onCreate()->onStart()>onRestoreInstanceState->onResume()
资源内存不足导致优先级低的Activity被杀死
- 前台Activity——正在和用户交互的Activity, 优先级最高。
- 可见但非前台Activity——比如Activity中弹出了一个对话框, 导致Activity可见但是位于后台无法和用户交互。
- 后台Activity——已经被暂停的Activity, 比如执行了onStop, 优先级最低。当系统内存不足时, 会按照上述优先级从低到高去杀死目标Activity所在的进程。 我们在平常使用手机时, 能经常感受到这一现象。 这种情况下数组存储和恢复过程和上述情况一致, 生命周期情况也一样。
9.Activity的三种运行状态
Resumed( 活动状态)
又叫Running状态, 这个Activity正在屏幕上显示, 并且有用户焦点。 这个很好理解, 就是用户正在操作的那个界面。
Paused( 暂停状态)
这是一个比较不常见的状态。 这个Activity在屏幕上是可见的, 但是并不是在屏幕最前端的那个Activity。 比如有另一个非全屏或者透明的Activity是Resumed状态, 没有完全遮盖这个Activity。
Stopped( 停止状态)
当Activity完全不可见时, 此时Activity还在后台运行, 仍然在内存中保留Activity的状态, 并不是完全销毁。 这个也很好理解, 当跳转的另外一个界面, 之前的界面还在后台, 按回退按钮还会恢复原来的状态, 大部分软件在打开的时候, 直接按Home键, 并不会关闭它, 此时的Activity就是Stopped状态。
10.Activity的启动模式
标准模式( standard)
启动一次Activity, 就会创建一个新的Activity实例并置于栈顶。 谁启动了这个Activity, 那么这个Activity就运行在启动它的那个Activity所在的栈中。
例如: Activity A启动了Activity B, 则就会在A所在的栈顶压入一个新的Activity。
栈顶复用模式( singleTop)
如果需要新建的Activity位于任务栈栈顶, 那么此Activity的实例就不会重建, 而是重用栈顶的实例。在通知栏点击收到的通知, 然后需要启动一个Activity, 这个Activity就可以用singleTop, 否则每次点击都会新建一个Activity。对于那些及其耗费系统资源的Activity,我们可以考虑将其设为singleTask模式,减少资源耗费。singleTop适合接收通知启动的内容显示页面。例如,某个新闻客户端的新闻内容页面,如果收到10个新闻推送,每次都打开一个新闻内容页面是很烦人的。
栈内复用模式( singleTask)
该模式是一种单例模式, 即一个栈内只有一个该Activity实例。 该模式, 可以通过在AndroidManifest文件的Activity中指定该Activity需要加载到那个栈中, 即singleTask的Activity可以指定想要加载的目标栈。singleTask适合作为程序入口点。
例如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。之前打开过的页面,打开之前的页面就ok,不再新建。
全局唯一模式( singleInstance)
使用singleInstance模式的活动会启用一个新的返回栈来管理这个活动,假设程序中有一个活动是允许其他程序调用的,然后要实现其他程序和当前程序共享这个活动的实例,前面的三种模式做不到,因为每个程序都有自己的返回栈,同一个活动在不同的返回栈中入栈必然是创建了新的实例。而singleInstance模式下会有一个单独的栈来管理这个活动,不管是哪一个应用程序来返问这个活动,都共同的用同一个栈来管理这个活动。singleInstance适合需要与程序分离开的页面。例如闹铃提醒,将闹铃提醒与闹铃设置分离。
11.Fragment生命周期
为Fragment是依附于Activity存在的,因此它的生命周期收到Activity的生命周期影响。
生命周期
onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume()->onPause()->onStop()->onDestoryView()->onDestory()->onDetach()。
- onAttach(Activity):当Fragment与Activity发生关联的时候调用
- onCreateView(LayoutInflater, ViewGroup, Bundle) 创建该Fragment的视图
- onActivityCreated(Bundle) :当Activity的onCreated方法返回时调用
- onDestroyView()与onCreateView() 方法相对应,当该Fragment的视图被移除时调用
- onDetach() 与 onAttach() 方法相对应,当Fragment与Activity取消关联时调用
.
Fragment与Activity之间的通信
Fragment依附于Activity存在,因此与Activity之间的通信可以归纳为以下几点:
- 如果Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法
- 如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作
- Fragment中可以通过getActivity()得到当前绑定的Activity的实例,然后进行操作。
Fragment与Activity通信的优化
- 要考虑Fragment的重复使用,所以必须降低Fragment与Activity的耦合,而且Fragment更不应该直接操作别的Fragment,毕竟Fragment操作应该由它的管理者Activity来决定。
12.Android的动画机制,怎么写的
Android动画可以分为3类:
帧动画
- 帧动画是最容易实现的一种动画, 这种动画更多的依赖于完善的UI资源, 他的原理就是将一张张单独的图片连贯的进行播放, 从而在视觉上产生一种动画的效果; 有点类似于某些软件制作gif动画的方式。帧动画就是顺序播放一组预先定义好的图片,就类似于我们观看视频,就是一张一张的图片连续播放。
- 帧动画的使用:
1、在res/drawable目录下定义一个XML文件,根节点为系统提供的animation-list,然后放入定义更好的图片;
2、使用AnimationDrawable
类的start方法播放第一步定义好的Drawable中的图片,形成动画效果;
android:oneshot=“false”: 表示是否重复播放动画,还是只播放一次;
每个item都有Drawable和duration属性,Drawable表示我们要播放的图片;duration表示这张图播放的时间;
View动画(也称补间动画)
- view动画也称为补间动画,因为我们只需要拿到一个view,设定它开始和结束的位置,中间的view会自动由系统补齐,而不需要帧动画每一幅图都是提前准备好的。补间动画又可以分为四种形式, 分别是 alpha( 淡入淡出) ,scale( 缩放大小) , translate( 位移) ,rotate( 旋转) 。View动画的
四种基本效果
对应了四个Animation
的子类:TranslateAnimation
、ScaleAnimation
、RotateAnimation
、AlphaAnimation
- view动画的使用
1、比如说在res/anim目录下,创建一个平移动画translate_animation.xml,或者创建一个AnimationSet集合动画把View动画的平移、缩放、旋转、渐变都揉在一起
2、也可以用与View动画相对应的四个类创建对应动作动画的对象,或者通过代码实现所有动画效果
Animation translate = new TranslateAnimation(,,,,);
translate.setDuration(10000);
Animation alpha = new AlphaAnimation(1,0);
alpha.setDuration(3000);- View动画的使用场景
1、 LayoutAnimation
LayoutAnimation作用于ViewGroup,为ViewGroup指定一个动画,然后,当它的子元素出场时都会具有这种效果。这种效果常用于ListView,有的ListView的每个item都以一定的动画形式出现,就是用到的LayoutAnimation。LayoutAnimation也是一个View动画, 使用方式有2步:
1、定义LayoutAnimation的xml文件,里面的android:animation属性可以指定具体的动画,也就是引用到View动画中的四种xml文件
2、在ViewGroup指定android:layoutAnimation属性,即引用到第一步中的LayoutAnimation的xml文件,这样,指定的ViewGroup比如ListView的每个item都具有了android:animation属性中指定具体的动画的效果
3、除了用xml文件,也同样可以使用代码实现,使用AnimationUtils.loadAnimation方法加载LayoutAnimation的xml文件后赋值给Animation 对象,如果是ViewGroup,再将Animation 对象赋值给LayoutAnimationController
对象,最后将LayoutAnimationController 对象传入到对应ViewGroup组件listView.setLayoutAnimation()方法中,如果是View,可以直接将Animation 对象赋值给view
.startAnimation
()方法。
2、 Activity的切换效果
Activity有默认的切换效果,但是我们可以定制,主要用到overridePendingTransition(int enterAnima, int exitAnima)这个方法, 这个方法必须在startActivity或者finish方法之后调用才会生效。
3.Fragment的切换效果
可以使用FragmentTransaction的setCustomAnimation(R.anim.animation_set)方法添加切换动画。
属性动画
属性动画与补间动画的不同之处:
- 补间动画只能定义两个关键帧在透明、旋转、位移和倾斜这四个属性的变换,但是属性动画可以定义任何属性的变化。
- 补间动画只能对 UI 组件执行动画,但属性动画可以对任何对象执行动画。
属性动画的属性:
- 动画持续时间android:duration。默认为 300ms
- 动画插值方式android:interploator
- 动画重复次数 android:repeatCount
- 重复行为android:repeatMode
- 动画集。在属性资源文件中通过 <set …/> 来组合。
- 帧刷新率。指定多长时间播放一帧。默认为 10 ms。
属性动画 API
Animator: 提供创建属性动画的基类,基本不会直接使用这个类。
ValueAnimator
:属性动画用到的主要的时间引擎,负责计算各个帧的属性值,基本上其他属性动画都会直接或间接继承它;
ObjectAnimator
: ValueAnimator 的子类,对指定对象的属性执行动画。
AnimatorSet
:Animator 的子类,用于组合多个 Animator。Evaluator ,用来控制属性动画如何计算属性值
- IntEvaluator:计算 int 类型属性值的计算器。
- FloatEvaluator: 用于计算 float 类型属性值的计算器。
- ArgbEvaluator: 用于计算十六进制形式表示的颜色值的计算器。
- TypeEvaluator: 可以自定义计算器。
使用 ValueAnimator
- 调用 ValueAnimator 的 ofInt()、ofFloat() 或者 ofObject() 静态方法创建 ValueAnimator 实例。
- 调用 ValueAnimator 的 setXxx() 等方法设置持续时间,插值方式、重复次数等。
- 调用 ValueAnimator 的 start() 方法启动动画。
- 为 ValueAnimator 注册 AnimatorUpdateListener 监听器,在该监听器中可以监听 ValueAnimator 计算出来的值改变,并将这些值应用到指定对象上。
属性动画的使用
- 使用 ValueAnimator 或者 ObjectAnimator 的静态工厂方法创建动画。
- 创建 ValueAnimator 或 ObjectAnimator 对象 —— 即可以从 XML 资源文件加载该动画也可以直接调用 ValueAnimator 或者 ObjectAnimator 的静态工厂方法创建动画。
- 根据需要为 Animator 对象设置属性。
- 如果需要监听 Animator 的动画开始事件,动画结束事件、动画重复事件、动画值改变事件,并根据事件提供响应处理代码,需要为Animator 对象设置监听器。
- 如果有多个动画需要同时播放,需要使用 AnimatorSet 组合这些动画。
animSet.playTogether(valueAnimator,objectAnimator)
- 调用 AnimatorSet 对象的 start方法 启动动画。
- 使用资源文件来定义动画。
13.传统动画和属性动画对比
- 补间动画中, 虽然使用translate将图片移动了,但是点击原来的位置, 依旧可以发生点击事件, 而属性动画却不是。属性动画是真正的实现了view的移动, 补间动画对view的移动更像是在不同地方绘制了一个影子, 实际的对象还是处于原来的地方。
- 当我们把动画的repeatCount设置为无限循环时, 如果在Activity退出时没有及时将动画停止, 属性动画会导致Activity无法释放而导致内存泄漏, 而补间动画却没有问题。 因此, 使用属性动画时切记在Activity执行 onStop 方法时顺便将动画停止。 ( 对这个怀疑的同学可以自己通过在动画的Update 回调方法打印
日志的方式进行验证) 。- xml 文件实现的补间动画, 复用率极高。 在Activity切换, 窗口弹出时等情景中有着很好的效果。
- 使用帧动画时需要注意,不要使用过多特别大的图, 容易导致内存不足。
14.Android消息机制
消息机制的简介
消息机制主要包含: MessageQueue, Handler和Looper 以及Message这四个部分
Message
要传递的消息, 可以传递数据;
MessageQueue
消息队列, 但是它的内部实现并不是用的队列, 实际上是通过一个单链表的数据结构来维护消息列表, 因为单链表在插入和删除上比较有优势。 主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息 (MessageQueue.next);
handler
Android消息机制的上层接口。 Handler的使用过程很简单, 通过它可以轻松地将一个任务切换到Handler所在的线程中去执行。在子线程中, 进行耗时操作, 执行完操作后, 发送消息, 通知主线程更新UI。
1)消息处理机制,传递消息Message
2)更新UI的一套机制,子线程通知主线程更新UI
Looper
不断循环执行(Looper.loop), 从MessageQueue中读取消息, 按分发机制将消息分发给目标处理者。
handler产生内存泄露的情况
根本原因是因为 Handler 对象持有了外部类 Activity 的引用
。可以使用静态的内部类继承 Handler或者单独写一个类
MessageQueue, Handler和Looper三者之间的关系
每个线程中只能存在一个Looper, Looper是保存在ThreadLocal中的。 主线程( UI线程) 已经创建了一个Looper, 所以在主线程中不需要再创建Looper, 但是在其他线程中需要创建Looper。 每个线程中可以有多个Handler, 即一个Looper可以处理来自多个Handler的消息。 Looper中维护一个MessageQueue, 来维护消息队列, 消息队列中的
Message可以来自不同的Handler。
15.Listview和Recyclerview的区别
- Listview继承重写BaseAdapter类; 自定义ViewHolder与convertView的优化(判断是否为null);recyclerview继承重写RecyclerView.Adapter与RecyclerView.ViewHolder设置LayoutManager,以及layout的布局效果
- 布局上的不同:listview:布局比较单一,只支持竖直方向滑动;recyclerview:三种布局,线性布局,这个和listview相似 ,实现横向/纵向列表方向的item,网格布局,可以指定item的数量,瀑布流布局,可以指定列表方向,也可以指定同方向的item数量
- 布局刷新:listview中通常刷新数据 notifyDataSetChanged() ,这种刷新是全局刷新的,每一个item的数据都会重新加载一次,这样很消耗资源,在一些需要频繁更新数据的场景,比如淘宝实时更新的界面,listview实现会很鸡肋;recyclerview可以通过 notifyItemChanged() 来实现局部刷新;ListView实现局部刷新,依然是可以实现的,当一个item数据刷新时,我们可以在Adapter中,实现一个onItemChanged()方法,在方法里面获取到这个ite-P;后调用getView()方法来刷新这个item的数据
- 空数据处理:ListView 提供了 setEmptyView 这个 API 来让我们处理 Adapter 中数据为空的情况
- 复用item:Recyclerview可复用item,Listview默认每次加载一个新的item创建一个新view
- ViewHolder:Listview需要创建自定义viewHolde,RecycleView继承recyclerView.ViewHolder
- 嵌套滚动机制:ListView 不支持嵌套滚动机制,Recyclerview实现NestedScrollingChild接口支持嵌套滚动机制
16.如何优化RecyclerView
- 尽量将复杂的数据处理操作放到异步中完成。RecyclerView需要展示的数据经常是从远端服务器上请求获取,但是在网络请求拿到数据之后,需要将数据做扁平化操作,尽量将最优质的数据格式返回给UI线程。
- 优化RecyclerView的布局,避免将其与ConstraintLayout使用
- 针对快速滑动事件,可以使用addOnScrollListener添加对快速滑动的监听,当用户快速滑动时,停止加载数据操作。
- 如果ItemView的高度固定,可以使用setHasFixSize(true)。这样RecyclerView在onMeasure阶段可以直接计算出高度,不需要多次计算子ItemView的高度,这种情况对于垂直RecyclerView中嵌套横向RecyclerView效果非常显著。
- 当UI是Tab feed流时,可以考虑使用RecycledViewPool来实现多个RecyclerView的缓存共享
17.Service知识点
1.面试官问:
一定听过 Service 吧,是怎么理解的?
回答:
Service 是一个专门在后台执行长时间操作的类,它并不与用户产生 UI 交互。它提供了两种启动方式。
- started
其它组件调用 startService() 启动一个 Service。一旦启动,Service 将一直运行在后台,即使启动这个 Service 的组件已经被销毁。通常一个被 start 的 Service 会在后台执行单独的操作,也并不需要给启动它的组件返回结果。只有当 Service 自己调用 stopSelf() 或者其它组件调用 stopService() 才会终止。 - bind
其它组件可以调用 bindService() 来绑定一个 Service。这种方式会让 Service 和启动它的组件绑定在一起,当启动它的组件销毁的时候,Service 也会自动进行 unBind 操作。同一个 Service 可以被多个组件绑定,只有所有绑定它的组件都进行了 unBind 操作,这个 Service 才会被销毁。
2.面试官问:
这两种方式对应的生命周期,可以简单讲讲?
回答:
-
onCreate() 和 onDestroy() 均会被回调
-
两条生命周期路径都可以包含两个嵌套的生命周期:
完整生命周期(entire lifetime):从 onCreate() 被调用,到 onDestroy() 返回。和 Activity 类似,一般在 onCreate() 方法中做一些初始化的工作,在 onDestroy() 中做一些资源释放的工作。如,若 Service 在后台播放一个音乐,就需要在 onCreate() 方法中开启一个线程启动音乐,并在 onDestroy() 中结束线程。
活动生命周期(activity lifetime):从 onStartCommand() 或 onBind() 回调开始,由相应的 startService() 或 bindService() 调用。start 方式的活动生命周期结束就意味着完整证明周期的结束,而 bind 方式,当 onUnbind() 返回后,Service 的活动生命周期结束。
3.面试官问:
Service 的 onCreate() 可以执行耗时操作吗?
回答:
Service 运行在主线程中,它并不是一个新的线程,也不是新的进程,onCreate() 并不能执行耗时操作。
4.面试官问:
那如果要在 Service 中执行耗时操作,怎么做?
回答:
-
使用 Thread,开一个线程,然后一阵混沌操作。
-
使用 AysncTask 或 HandlerThread 来替代 Thread 创建线程。
-
IntentService 也是一个不错的选择。
18.网络通信协议
TCP/IP协议,TCP/IP是一个协议组,分为3个层次:
-
网络层:包括IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议。
-
传输层:包括TCP协议和UDP协议。
-
应用层:包括HTTP、FTP、TELNET、SMTP、DNS等协议。
Socket属于传输层的技术, API实现TCP协议后即可用于HTTP通信,实现UDP协议后即可用于FTP通信
1.面试官问:
HTTP网络请求的方式?
回答:
- 有三种方式HttpURLConnectoin、HttpClient、AndroidHttpClient(很少用)
- HttpURLConnectoin提供与联网有关的类,包括流和数据包套接字、Internet协议、常见HTTP处理,通过HttpURLConnection(继承自URLConnection),可用于向指定网站发送GET请求、POST请求。
- HttpClient是Apache提供的HTTP网络访问接口
- AndroidHttpClient:Android特有的网络编程,如:访问WiFi,访问Android联网信息,邮件等功能。
- HttpURLConnection是最佳的选择。它的API简单,体积较小,因而非常适用于Android项目
2.面试官问:
GET请求和POST请求的区别?
回答:
- get提交,提交的信息都显示在地址栏中。post提交,提交的信息不显示在地址栏中
- get提交,对于敏感的数据信息不安全。post提交,对于敏感信息安全。
- get提交,对于大数据不行,因为地址栏存储体积有限post提交,可以提交大体积数据
- get提交,将信息封装到了请求消息的请求行中post提交,将消息封装到了请求体中
3.面试官问:
基于Socket套接字的服务器和Android客户端交互的一个实例?
回答:
- 套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认
- 服务端开启一个子线程来循环的从客户端那里获取消息,获取消息后将这个消息打印出来,并将这个消息再发送给客户端
5.面试官问:
Socket与HTTP区别?
回答:
- 请求—响应方式不同:通常情况下Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开,Socket连接需要通过
轮询
告诉网络,该连接处于活跃状态。HTTP连接使用的是“请求—响应
”的方式,需要数据的时发送一条请求,服务器收到请求后返回相应的数据,请求过后这个连接就不在了,当下次需要数据时再发送请求 - 对数据的即时性要求比较高的情况下一般采用Socket通讯,例如聊天的功能
- 对数据的即时性要求不是特别高的情况下一般采用HTTP通讯, 比如在项目中,我们请求的接口,请求一次就给我们返回数据,很少会去不断的去请求服务器的数据。
19.ViewModel、LiveData
1.面试官问:
谈谈对ViewModel和LiveData的了解?
回答:
ViewModel类的设计目的是以一种关注生命周期的方式存储和管理与UI相关的数据。在同一个Activity中存在多个Fragment时,相互传递数据,一般通过定义接口来实现,Acticity从中协调。使用ViewModel能轻易解决Activity和Fragment之间的通信。Activity不需要做任何事情,不需要干涉这两个Fragment之间的通信。Fragment不需要互相知道,即使一个消失不可见,另一个也能正常的工作。Fragment有自己的生命周期,它们之间互不干扰,即便你用一个FragmentC替代了B,FragmentA也能正常工作,没有任何问题。
LiveData是一个可观察的数据持有者类。LiveData在生命周期状态更改时通知Observer对象,更新这些Observer对象中的UI。观察者可以在每次应用程序数据更改时更新UI,而不是每次发生更改时更新UI(Activity或者Fragment只要在需要观察数据的时候观察数据即可,不需要理会生命周期变化了。这一切都交给LiveData来自动管理)。
ViewModel用来存储和管理与UI相关的数据,LiveData用来通知和接收数据的改变从而更新界面,两者是搭配在一起使用的
。
2.面试官问:
LiveData有没有内存泄漏?
回答:
当页面销毁时他们会自动被移除,不会导致内存溢出。
20.MVC、MVP、MVVM 之间有何区别?
1.面试官问:
谈谈对MVC的了解?
回答:
视图层(View):对应于xml布局文件和java代码动态view部分。
控制层(Controller):控制层是由Activity来承担的,Activity本来主要是作为初始化页面,展示数据的操作,但是因为XML视图功能太弱,所以Activity既要负责视图的显示又要加入控制逻辑,承担的功能过多。
模型层(Model):针对业务模型,建立的数据结构和相关的类,它主要负责网络请求,数据库处理,I/O的操作。
2.面试官问:
谈谈对MVP的了解?
回答:
视图层(View):负责绘制UI,与用户进行交互(在Android中体现为Activity),把Activity中的UI逻辑抽象成View接口
协调者(Presenter):作为View与Model交互的中间纽带,处理与用户交互的负责逻辑,业务逻辑抽象成Presenter接口。
模型层(Model):负责存储,检索以及操纵数据。
3.面试官问:
谈谈对MVVM的了解?
回答:
视图层(View):展示数据,接收到用户的操作传递给viewModel层,通过dataBinding实现数据与view的单向绑定或双向绑定。View层不做任何业务逻辑、不涉及操作数据、不处理数据、UI和数据严格的分开
视图模型层(ViewModel ):调用Model层获取数据,以及业务逻辑的处理。 ViewModel 和Presenter 的作用类似 ,只不过是通过 databinding 将数据与UI进行了绑定。ViewModel 不做和UI相关的事
模型层(Model):获取数据,model层将结果通过接口的形式传递给ViewModel层
4.面试官问:
这些设计模式之间有何区别?
回答:
按照MVC的分层,Activity既要负责视图的显示又要加入控制逻辑,Activity不仅承担了View的角色,还承担了一部分的Controller角色;
MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model。
MVVM中的ViewModel 和Presenter 的作用类似,但是ViewModel 层不会持有任何控件的引用,更不会在ViewModel中通过UI控件的引用去做更新UI的事情