Android面试技术点总结

前言

  年末一周不忙了,总结了下在Android开发面试中易问的一些Android相关基础知识与面试题,不太全,大家查缺补漏吧。开始Review!
  

Android组件

Android四大组件

  • Activity:一个Activity通常就是一个单独的屏幕,它上面可以显示一些控件也可以监听并处理用户的事件做出响应。
  • Service:服务,一段长生命周期的,没有用户界面的程序,可以用来开发如监控类程序。
    • bindingService():调用者与服务绑定在一起,不同生,但同死。
    • startService():调用者与服务无关联,调用者退出,服务依然继续。
  • BroadcastReceiver:广播接收器,对外部事件进行过滤只对感兴趣的外部事件进行接收并做出响应。
  • ContentProvider:内容提供者,使一个应用程序的指定数据集提供给其他应用程序。

Activity启动模式

  • Standard:每次都创建新实例加入启动它的Activity的栈中。注意不可使用ApplicationContext来启动,由于非Activity的Context没有任务栈,所以会报错,可通过设置FLAG_ACTIVITY_NEW_TASK标志从而创建新的任务栈将其压入栈中。
  • SingleTop:若新实例已位于栈顶,则复用并调用其onNewIntent,不调用onCreate与onStart。否则新建。
  • SingleTask:若新实例已位于其需要的栈中,则复用并将其栈上的Activity都弹出。否则新建。
  • SingleInstance:具有SingleTask的特性,每个不同类型的Activity都只能独占一个栈,遵循栈内复用。

Activity的TaskAffinity属性

  • TaskAffinity:指Activity的归属,控制Activity所属的任务栈。每个Activity都有TaskAffinity属性,指出了它希望进入的Task。若没有显性指定它的属性等于Application指明的TaskAffinity,若Application也没有指明,那么该TaskAffinity的值等于其应用包名。只对启动模式为SingleTask/SingleInstance的Activity起作用。
  • 与allowTaskReparenting属性配合使用:当把Activity的allowTaskReparenting属性设置成true时,Activity就可以从启动的任务栈移动到TaskAffinity指定的任务栈。例:在TaskA中启动了TaskB的ActivityC,若ActivityC的allowTaskReparenting为true,且ActivityC与TaskB的TaskAffinity相同,那么当TaskB被启动时,ActivityC会直接从TaskA转移到TaskB中。
  • 与SingleTask启动模式配合使用:当指定Activity启动模式为SingleTask或设置FLAG_ACTIVITY_NEW_TASK标识时,系统会先检查当前Task与Activity的TaskAffinity是否相同,相同则放入当前Task,不同则寻找有无与其TaskAffinity相同的Task,有则放入那个Task中,无则新建一个与其TaskAffinity值相同的Task并加入其中,加入的Task会被调至前台。

Activity的Flags

  • FLAG_ACTIVITY_NEW_TASK:指定Activity的启动模式为“singleTask”。优先级大于清单中配置。
  • FLAG_ACTIVITY_SINGLE_TOP:指定Activity的启动模式为“singleTop”。优先级大于清单中配置。
  • FLAG_ACTIVITY_CLEAR_TOP:具有此标记且为“standard”模式的Activity启动时,同一栈中它连同它之上的Activity都出栈,再创建新的实例压入栈中。
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有此标记的Activity不会出现在Activity的历史列表中。等同于在清单中指定Activity的属性android:excludeFromRecents = "true"

Intent七大属性

  • ComponentName:主要用于跨应用访问Activity,进行显式意图访问。(显式 setAction(Intent.xxx))
  • Action:表示想要启动的Activity要完成什么动作,例如: 调用系统的app打电话, 发短信等程序。(隐式 setComponent(ComponentName cn))
  • Category:为Action增加附加类别信息,它是Action大动作的子动作。(隐式 addCategory(Intent.CATEGORY_XXX))
  • Extras:保存需要传递的额外数据,比如邮件标题、正文、收件人、抄送人、短信内容等。(隐式、显式 putExtra(String key,String value);)
  • Data:保存需要传递的数据Uri格式(Uri:统一资源标识符),比如 tel: mailto: smsto等。(隐式 setData(Uri.parse(“tel:xxxxx”)))
  • Type:区分文件类型(MIME大类型/子类型),例如audio/mp3等。(隐式 setType(“XXX/xxx”) )
  • Flags:动态配置Activity的启动模式。(addFlags(int)或setFlags(int))

数据存储方式

  • SharePreferences:使用模式Context.MODE_PRIVATE,故不可被其他应用访问,但可获取到目标App的Context即可实现跨进程使用。
  • ContentProvider
  • SQLite数据库
  • File文件
  • 网络存储

Android布局方式及特点

  • FrameLayout:帧布局,所有元素都被放入左上区域,且无法指定确切位置,下一个元素覆盖上一个元素。
  • LinearLayout:线性布局,每个子元素都以垂直或水平的方式来线性排布。
  • AbsoluteLayout:绝对布局,采用坐标位置的方式排列元素。
  • RelativeLayout:相对布局,以某元素为参照物,来定位下一个元素位置。
  • TableLayout:表格布局,以表格的形式排列子元素。
  • 线性与相对加载效率
    1. RelativeLayout会对子View进行两次measure(一横一纵),LinearLayout只对子View进行一次measure(横或纵),若设置weight属性也会进行两次(带与不带weight属性的View各一次),一般情况LinearLayout性能高于RelativeLayout。
    2. 视图层级简单时,用LinearLayout效率更高,避免使用weight属性。
    3. 视图层级复杂时,用RelativeLayout可减少层级嵌套,提高效率。

生命周期

Activity生命周期

  • 打开A,切到B:A:onCreate()—>A:onStart()—>A:onResume()—>A:onPause()—>B:onCreate()—>B:onStart()—> B:onResume()—>A:onStop()
  • Back返回A:B:onPause()—>A:onRestart()—>A:onStart()—>A:onResume()—>B:onStop()—>B:onDestroy()

Fragment生命周期

  • 打开A:onAttach()—>onCreate()—>onCreateView()—>onViewCreated()—>onActivityCreated()—>onStart()—>onResume()—>onPause()—>onStop()—>onDestroyView()—>onDestroy()—>onDetach()
  • 切到B:A:onPause()—>B:onAttach()—>……—>B:onResume()—>A:onStop()
  • Back返回A:B:onPause()—>A:onStart()—>A:onResume()—>B:onStop()—>B:onDestroyView()—>B:onDestroy()—>B:onDetach()

Service生命周期

  • Started Service:startService()—>onCreate()—>onStartCommand()—>onDestroy()
  • Bound Service:bindService()—>onCreate()—>onBind()—>onUnbind()—>onDestroy()
  • 注意:若Service已经启动,则只执行onStart()。Bound Service的生命周期与其绑定的组件同生死,Started Service不受限制。

横竖屏切换生命周期

  • configChanges设置为orientation或者orientation|keyboardHidden:onCreate()—>onStart()—>onResume()—>onPause()—>onStop()—>onDestroy()—>onCreate()—>onStart()—>onResume()
  • configChanges设置为orientation|screenSize或者orientation|screenSize|keyboardHidden:onCreate()—>onStart()—>onResume()—>onConfigurationChanged()
  • 注意:targetSdkVersion<12的话,设置为orientation|keyboardhidden,也不重新创建activity,只调用onconfigurationchanged()。而targetsdkversion>=12的话,activity还是会重新创建(需要加上screenSize才有效果)。

异步多线程

AsyncTask执行方法

  • onPreExecute():初始化操作。
  • doInBackground():后台耗时操作。
  • onProgressUpdate():doInBackground()中调用publishProgress(Progress…),可进行进度条进度更新
  • onPostExecute():耗时操作完成,可刷新UI。
  • 完全可使用RxJava异步数据流代替

异步消息处理Handler、Looper、Message关系

  Looper负责创建一个MessageQueue,然后无限循环从中读取由Handler创建发送的一个或多个Message。消息队列为空,线程则会阻塞等待。

  • Looper:构造中创建一个MessageQueue并绑定当前线程,方法主要为prepare()与loop(),prepare将Looper对象放入ThreadLocal初始化,在loop方法中循环从MessageQueue中读取消息,没有则线程阻塞等待。读到消息后调用消息来源Handler中的dispathMessage()方法,其内部最终调用handleMessage()方法。
  • Handler:内部通过Looper.myLooper()获取了当前线程保存的Looper实例并与其MessageQueue进行关联,向其MessageQueue发送Message。
  • Message:存储消息内容。

异常处理

内存溢出与内存泄漏

  • 内存溢出:所需内存超过系统所能提供内存的上限。
  • 案例
    1. 大量使用Bitmap未recycle()回收。
    2. 循环中大量创建对象。
  • 内存泄露:分配出去的内存无法回收。
  • 案例
    1. 静态变量(静态Context持有Activity本身引用,导致其无法被回收)
    2. 非静态内部类和匿名内部类(Handler问题,Handler持有Activity的引用导致其无法被回收,可使用静态内部类加WeakReference弱引用实现)
    3. 既是静态变量又是非静态内部对象(非静态内部对象持有Activity的引用,而其静态变量导致Activity无法被回收)
    4. 单例模式没有解绑(使用EventBus生成单例并注册一个Activity,页面结束时没有解除注册,会导致Activity无法被回收)
    5. 属性动画造成内存泄漏(无限循环的属性动画没有将其停止,View动画则不会)
    6. RxJava 使用不当(没有及时取消订阅)

ANR异常Application Not Responding

  • 5秒内无法响应用户输入时间(键盘输入,触摸屏幕等)
  • BroadcastReceiver10s内无法结束
  • 解决方案:避免在UI主线程进行耗时操作(文件读写、数据库、网络请求),交给子线程去完成。

Runtime Exception

  • 运行时异常:NullPointerException,ClassCastException,IllegalArgumentException,ArithmeticException,ArrayStoreException,IndexOutOfBoundsException,NegativeArraySizeException,NumberFormatException,NoSuchElementException,SecurityException,UnsupportedOperationException,BufferUnderflowException,BufferOverflowException,MissingResourceException

设计模式

MVC,MVP,MVVVM

  • MVC:通过Controller的控制去操作Model层的数据并交给View层展示。xml布局文件相当于View层,JavaBean类相当于Model层,Activity相当于Controller层。Activity会比较臃肿。
  • MVP:MVC升级版,Activity与Fragment只作为View层,Model层处理数据,而数据与View的交互都交给Presenter层,实现View与Model的解耦。给Activity减压,后期维护与测试更为便利。逻辑复杂的界面会导致接口过多。
  • MVVM:与MVP区别不大,Presenter层替换为ViewModel层,View层与ViewModel层是绑定关系,当更新ViewModel层数据时,View层ui会自动更新。绑定可使用DataBinding实现。View层还是会过重。publishProgress(Progress…),可进行进度条进度更新
  • MVP+DataBinding:使用DataBinding节省数据绑定的时间,又使用Presenter将业务逻辑和view层分离。

屏幕适配

屏幕适配

  • 种像素密度机型,做5套图。比例 1:1.5:2:3:4
  • 多用相对布局
  • 尺寸限定符
  • 点九图
  • 不同图片填充类型ScaleType

事件分发与消费

事件分发消费机制

  • Activity->View
  • dispatchTouchEvent:用来分发TouchEvent。
  • onInterceptTouchEvent:用来拦截TouchEvent,ViewGroup特有。
  • onTouchEvent:用来处理TouchEvent。

Touch事件的传递流程

  • 一次完整的Touch事件由一个Down、一个Up和若干个Move组成。Down事件通过dispatchTouchEvent进行分发,目的是为了找到真正需要处理Touch请求的View。当某View或ViewGroup的onTouchEvent事件返回true时,便表示它是真正要处理这次请求的View,之后的Aciton_UP和Action_MOVE将由它处理。当所有子View的onTouchEvent都返回false时,这次的Touch请求就由根ViewGroup,即Activity自己处理了。
  • 无拦截
      E/Activity:   dispatchTouchEvent
      E/ViewGroup:  dispatchTouchEvent
      E/ViewGroup:  onInterceptTouchEvent
      E/View:     dispatchTouchEvent
      E/View:     onTouchEvent:返回false不处理
      E/ViewGroup:  onTouchEvent
      E/Activity:   onTouchEvent:ACTION_DOWN
      E/Activity:   dispatchTouchEvent
      E/Activity:   onTouchEvent:ACTION_UP
      分发过程无拦截,事件一直向下分发至View,View在onTouchEvent中返回false表示不处理,则向上传递至Activity处理。由于ACTION_DOWN事件确定了事件交给Activity处理,所以ACTION_UP事件直接分发给Activity处理,不再下传了。
  • Intercept拦截
      E/Activity:   dispatchTouchEvent
      E/ViewGroup:   dispatchTouchEvent
      E/ViewGroup:  onInterceptTouchEvent
      E/View:    dispatchTouchEvent
      E/View:    onTouchEvent
      E/ViewGroup:  onTouchEvent:ACTION_DOWN:返回true消费
      E/Activity:   dispatchTouchEvent
      E/ViewGroup:  dispatchTouchEvent
      E/ViewGroup:  onTouchEvent:ACTION_MOVE:返回true消费
      E/Activity:    dispatchTouchEvent
      E/ViewGroup:  dispatchTouchEvent
      E/ViewGroup:  onTouchEvent:ACTION_UP:返回true消费
      事件在回传过程中被ViewGroup在onTouchEvent中返回true消费了,则不继续向上回传。Up与Move事件原理同上,直接分发到ViewGroup中进行处理。
  • Dispatch拦截
      E/Activity:   dispatchTouchEvent
      E/ViewGroup:  dispatchTouchEvent:返回true拦截
      E/Activity:   dispatchTouchEvent
      E/ViewGroup:  dispatchTouchEvent:返回true拦截
      事件在分发过程中被ViewGroup在dispatchTouchEvent中返回true拦截了,则不继续向下分发,Down事件就此结束,也没有定位到事件的View,所以Move与Up事件还需重新进行分发过程。

自定义View

自定义View生命周期

  • onFinishInflate():初始准备完毕。
  • onAttachedToWindow():将View绑定到Activity所在Window上。
  • onMeasure():计算当前View的宽高大小。
  • onLayout():计算当前View的位置。
  • onDraw():绘制View。
  • onWindowFocusChanged():View焦点变化。
  • onDetachedFromWindow():Activity销毁后,View会从Window上。剥离

三方框架

ButterKnife原理

  根据注解生成一个Java类,里面动态的去做一些FindViewById的事。   

Dragger2好处

  • 避免重复去new,及单例之类的工作,增加开发效率。
  • 更好的管理类实例。
  • 解耦。

Retrofit

  • Retrofit使用注解+java接口来定义后台服务API接口。
  • 使用Retrofit.create函数创建接口动态代理的示例。
  • 可将http请求解耦,配合RxJava会更加的灵活。

OkHttp

  • Request:每一个HTTP请求包含一个URL、一个方法(GET或POST或其他)、一些HTTP头。请求还可能包含一个特定内容类型的数据类的主体部分。
  • Response:响应是对请求的回复,包含状态码、HTTP头和主体部分。
  • Call:OkHttp使用Call抽象出一个满足请求的模型,尽管中间可能会有多个请求或响应。执行Call有两种方式,同步或异步。

Volley

xUtils:快速开发框架

  • DbUtils
  • ViewUtils
  • HttpUtils
  • BitmapUtils

Mqtt即时通讯协议

  • 使用发布/消息订阅模式。
  • TCP/IP提供网络连接。
  • 提供一对多的消息发布,解除应用程序耦合。
  • 小型传输,开销很小。

Jpush极光推送

RxJava

  • 响应式编程
  • 采用升级版观察者模式的异步事件流
  • 空调(观察者)、遥控器(被观察者)、红外信号(操作符)的例子

GreenDao

Realm

  • Realm 是一个 MVCC (多版本并发控制)数据库,本质上是一个嵌入式数据库,它不是基于SQLite所构建的,它拥有自己的数据库存储引擎,可以高效且快速地完成数据库的构建操作。和SQLite不同,它可在持久层直接和数据对象工作。在它之上是一个函数式风格的查询api,比传统的SQLite 操作更快 。
  • 易用
    • Ream不是在SQLite基础上的ORM,它有自己的数据查询引擎。并且十分容易使用。
  • 快速
    • 由于它是完全重新开始开发的数据库实现,所以它比任何的ORM速度都快很多,甚至比SQLite速度都要快。
  • 跨平台
    • Realm 支持 iOS & OS X (Objective‑C & Swift) & Android。我们可以在这些平台上共享Realm数据库文件,并且上层逻辑可以不用任何改动的情况下实现移植。
  • 高级
    • Ream支持加密,格式化查询,易于移植,支持JSON,流式api,数据变更通知等高级特性
  • 可视化
    • Realm 还提供了一个轻量级的数据库查看工具,在Mac Appstore 可以下载“Realm Browser”这个工具,开发者可以查看数据库当中的内容,执行简单的插入和删除数据的操作。(windows上还不清楚)

面试问题

资源及状态的操作保存,最好保存在生命周期的哪个函数里?

  保存在onPause()中

哪些情况内存会被垃圾回收机制处理掉?

  • 对象引用超过其作用范围。
  • 将对象赋值为null。
  • 实体不再被任何对象所引用。

数据库升级方法及注意事项

  应用版本更新前若数据库结构有改变,则需更改数据库版本号。新版本运行后需在onUpgrade()中根据数据库版本号判断是否要升级数据库,若版本号大于旧版本号则需要升级,一为了保证旧数据的不丢失,二是为了进行数据库结构的更新。

  • 表结构有更改
    1. 首先将旧数据库的表A重名为A_temp。
    2. 创建满足新版本数据库结构的新表A(增加与修改的字段)。
    3. 将表A_temp中数据插入到表A中。
  • 加入新表
    1. 只需在新版本运行后插入新的表,并给相应字段附上默认值即可。
  • 注意:跨版本升级需要先确定相邻版本的差别,从版本1开始依次迭代更新,先执行v1到v2,再v2到v3等等。或根据版本差别,编写对应的升级代码。

ScrollView嵌套ListView问题

  原因:ScrollView拦截了ListView的Move事件。

  • 动态计算ListView高度。
  • 自定义ScrollView重写onInterceptTouchEvent返回false,不拦截ListView的Move事件。
  • 在listView的dispatchTouchEvent方法中调用requestDisallowInterceptTouchEvent(true)申请不被拦截触摸事件。

RecyclerView有几种Manager,如何实现瀑布流

  • LinearLayoutManager: 线性布局管理器,呈现线性布局的RecyclerView,实现ListView效果。
  • GridLayoutManager:网格布局管理器,呈现网格布局的RecyclerView,实现GridView效果。
  • StaggeredGridLayoutManager:交错网格布局管理器,呈现交错网格布局的RecyclerView,它是一种特殊的GridLayoutManager,区别在于,它允许每个Item的长度或高度不一致,实现瀑布流效果。

总结

  目前Android面试技术点总结的还不太全,我会持续更新,也欢迎码友们指出文中写的不对的地方,或者有遗漏的知识点也可以联系我,咱们一起来维护,方便你我他。最后祝各位求职顺利,入职满意的公司。

坚持原创技术分享,您的支持是我前进的动力,谢谢!