写点什么

android 工程师面试题,大厂面试题汇总

发布于: 刚刚

Android 面试题含答案

1、Activity 生命周期?

onCreate() -> onStart() -> onResume() -> onPause() -> onStop() -> onDetroy()

2、Service 生命周期?

service 启动方式有两种,一种是通过 startService()方式进行启动,另一种是通过 bindService()方式进行启动。不同的启动方式他们的生命周期是不一样.


通过 startService()这种方式启动的 service,生命周期是这样:调用 startService() → onCreate()→ onStartConmon()→ onDestroy()。这种方式启动的话,需要注意一下几个问题,第一:当我们通过 startService 被调用以后,多次在调用 startService(),onCreate()方法也只会被调用一次,而 onStartConmon()会被多次调用当我们调用 stopService()的时候,onDestroy()就会被调用,从而销毁服务。第二:当我们通过 startService 启动时候,通过 intent 传值,在 onStartConmon()方法中获取值的时候,一定要先判断 intent 是否为 null。


通过 bindService()方式进行绑定,这种方式绑定 service,生命周期走法:bindService→onCreate()→onBind()→unBind()→onDestroy() bingservice 这种方式进行启动 service 好处是更加便利 activity 中操作 service,比如加入 service 中有几个方法,a,b ,如果要在 activity 中调用,在需要在 activity 获取 ServiceConnection 对象,通过 ServiceConnection 来获取 service 中内部类的类对象,然后通过这个类对象就可以调用类中的方法,当然这个类需要继承 Binder 对象

3、Activity 的启动过程(不要回答生命周期)

app 启动的过程有两种情况,第一种是从桌面 launcher 上点击相应的应用图标,第二种是在 activity 中通过调用 startActivity 来启动一个新的 activity。


我们创建一个新的项目,默认的根 activity 都是 MainActivity,而所有的 activity 都是保存在堆栈中的,我们启动一个新的 activity 就会放在上一个 activity 上面,而我们从桌面点击应用图标的时候,由于 launcher 本身也是一个应用,当我们点击图标的时候,系统就会调用 startActivitySately(),一般情况下,我们所启动的 activity 的相关信息都会保存在 intent 中,比如 action,category 等等。我们在安装这个应用的时候,系统也会启动一个 PackaManagerService 的管理服务,这个管理服务会对 AndroidManifest.xml 文件进行解析,从而得到应用程序中的相关信息,比如 service,activity,Broadcast 等等,然后获得相关组件的信息。当我们点击应用图标的时候,就会调用 startActivitySately()方法,而这个方法内部则是调用 startActivty(),而 startActivity()方法最终还是会调用 startActivityForResult()这个方法。而在 startActivityForResult()这个方法。因为 startActivityForResult()方法是有返回结果的,所以系统就直接给一个-1,就表示不需要结果返回了。而 startActivityForResult()这个方法实际是通过 Instrumentation 类中的 execStartActivity()方法来启动 activity,Instrumentation 这个类主要作用就是监控程序和系统之间的交互。而在这个 execStartActivity()方法中会获取 ActivityManagerService 的代理对象,通过这个代理对象进行启动 activity。启动会就会调用一个 checkStartActivityResult()方法,如果说没有在配置清单中配置有这个组件,就会在这个方法中抛出异常了。当然最后是调用的是 Application.scheduleLaunchActivity()进行启动 activity,而这个方法中通过获取得到一个 ActivityClientRecord 对象,而这个 ActivityClientRecord 通过 handler 来进行消息的发送,系统内部会将每一个 activity 组件使用 ActivityClientRecord 对象来进行描述,而 ActivityClientRecord 对象中保存有一个 LoaderApk 对象,通过这个对象调用 handleLaunchActivity 来启动 activity 组件,而页面的生命周期方法也就是在这个方法中进行调用。

4、Broadcast 注册方式与区别

此处延伸:什么情况下用动态注册


Broadcast 广播,注册方式主要有两种.


第一种是静态注册,也可成为常驻型广播,这种广播需要在 Androidmanifest.xml 中进行注册,这中方式注册的广播,不受页面生命周期的影响,即使退出了页面,也可以收到广播这种广播一般用于想开机自启动啊等等,由于这种注册的方式的广播是常驻型广播,所以会占用 CPU 的资源。


第二种是动态注册,而动态注册的话,是在代码中注册的,这种注册方式也叫非常驻型广播,收到生命周期的影响,退出页面后,就不会收到广播,我们通常运用在更新 UI 方面。这种注册方式优先级较高。最后需要解绑,否会会内存泄露


广播是分为有序广播和无序广播。

5、HttpClient 与 HttpUrlConnection 的区别

此处延伸:Volley 里用的哪种请求方式(2.3 前 HttpClient,2.3 后 HttpUrlConnection)


首先 HttpClient 和 HttpUrlConnection 这两种方式都支持 Https 协议,都是以流的形式进行上传或者下载数据,也可以说是以流的形式进行数据的传输,还有 ipv6,以及连接池等功能。HttpClient 这个拥有非常多的 API,所以如果想要进行扩展的话,并且不破坏它的兼容性的话,很难进行扩展,也就是这个原因,Google 在 Android6.0 的时候,直接就弃用了这个 HttpClient.


而 HttpUrlConnection 相对来说就是比较轻量级了,API 比较少,容易扩展,并且能够满足 Android 大部分的数据传输。比较经典的一个框架 volley,在 2.3 版本以前都是使用 HttpClient,在 2.3 以后就使用了 HttpUrlConnection。

6、java 虚拟机和 Dalvik 虚拟机的区别

Java 虚拟机:


1、java 虚拟机基于栈。 基于栈的机器必须使用指令来载入和操作栈上数据,所需指令更多更多。


2、java 虚拟机运行的是 java 字节码。(java 类会被编译成一个或多个字节码.class 文件)


Dalvik 虚拟机


  • 1、dalvik 虚拟机是基于寄存器的

  • 2、Dalvik 运行的是自定义的.dex 字节码格式。(java 类被编译成.class 文件后,会通过一个 dx 工具将所有的.class 文件转换成一个.dex 文件,然后 dalvik 虚拟机会从其中读取指令和数据

  • 3、常量池已被修改为只使用 32 位的索引,以 简化解释器。

  • 4、一个应用,一个虚拟机实例,一个进程(所有 android 应用的线程都是对应一个 linux 线程,都运行在自己的沙盒中,不同的应用在不同的进程中运行。每个 android dalvik 应用程序都被赋予了一个独立的 linux PID(app_*))

7、进程保活(不死进程)

  • 此处延伸:进程的优先级是什么


当前业界的 Android 进程保活手段主要分为 黑、白、灰 三种,其大致的实现思路如下:


  • 黑色保活 :不同的 app 进程,用广播相互唤醒(包括利用系统提供的广播进行唤醒)

  • 白色保活 :启动前台 Service

  • 灰色保活 :利用系统的漏洞启动前台 Service

  • 黑色保活


所谓黑色保活,就是利用不同的 app 进程使用广播来进行相互唤醒。举个 3 个比较常见的场景:


场景 1 :开机,网络切换、拍照、拍视频时候,利用系统产生的广播唤醒 app


场景 2 :接入第三方 SDK 也会唤醒相应的 app 进程,如微信 sdk 会唤醒微信,支付宝 sdk 会唤醒支付宝。由此发散开去,就会直接触发了下面的 场景 3


场景 3 :假如你手机里装了支付宝、淘宝、天猫、UC 等阿里系的 app,那么你打开任意一个阿里系的 app 后,有可能就顺便把其他阿里系的 app 给唤醒了。(只是拿阿里打个比方,其实 BAT 系都差不多)


白色保活


白色保活手段非常简单,就是调用系统 api 启动一个前台的 Service 进程,这样会在系统的通知栏生成一个 Notification,用来让用户知道有这样一个 app 在运行着,哪怕当前的 app 退到了后台。如下方的 LBE 和 QQ 音乐这样:


灰色保活


灰色保活,这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的 Service 进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个 Notification,看起来就如同运行着一个后台 Service 进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到 Notification),但你的进程优先级又是高于普通后台进程的。那么如何利用系统的漏洞呢,大致的实现思路和代码如下:


思路一:API < 18,启动前台 Service 时直接传入 new Notification();


思路二:API >= 18,同时启动两个 id 相同的前台 Service,然后再将后启动的 Service 做 stop 处理


熟悉 Android 系统的童鞋都知道,系统出于体验和性能上的考虑,app 在退到后台时系统并不会真正的 kill 掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要 kill 掉哪些进程,以腾出内存来供给需要的 app。这套杀进程回收内存的机制就叫 Low Memory Killer ,它是基于 Linux 内核的 OOM Killer(Out-Of-Memory killer)机制诞生。


  • 进程的重要性,划分 5 级:-

  • 前台进程 (Foreground process)

  • 可见进程 (Visible process)

  • 服务进程 (Service process)

  • 后台进程 (Background process)

  • 空进程 (Empty process)


了解完 Low Memory Killer,再科普一下 oom_adj。什么是 oom_adj?它是 linux 内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。对于 oom_adj 的作用,你只需要记住以下几点即可:


进程的 oom_adj 越大,表示此进程优先级越低,越容易被杀回收;越小,表示进程优先级越高,越不容易被杀回收


普通 app 进程的 oom_adj>=0,系统进程的 oom_adj 才可能<0


有些手机厂商把这些知名的 app 放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、陌陌都在小米的白名单中)。如果从白名单中移除,他们终究还是和普通 app 一样躲避不了被杀的命运,为了尽量避免被杀,还是老老实实去做好优化工作吧。


所以,进程保活的根本方案终究还是回到了性能优化上,进程永生不死终究是个彻头彻尾的伪命题!

8、讲解一下 Context

Context 是一个抽象基类。在翻译为上下文,也可以理解为环境,是提供一些程序的运行环境基础信息。Context 下有两个子类,ContextWrapper 是上下文功能的封装类,而 ContextImpl 则是上下文功能的实现类。而 ContextWrapper 又有三个直接的子类, ContextThemeWrapper、Service 和 Application。其中,ContextThemeWrapper 是一个带主题的封装类,而它有一个直接子类就是 Activity,所以 Activity 和 Service 以及 Application 的 Context 是不一样的,只有 Activity 需要主题,Service 不需要主题。Context 一共有三种类型,分别是 Application、Activity 和 Service。这三个类虽然分别各种承担着不同的作用,但它们都属于 Context 的一种,而它们具体 Context 的功能则是由 ContextImpl 类去实现的,因此在绝大多数场景下,Activity、Service 和 Application 这三种类型的 Context 都是可以通用的。不过有几种场景比较特殊,比如启动 Activity,还有弹出 Dialog。出于安全原因的考虑,Android 是不允许 Activity 或 Dialog 凭空出现的,一个 Activity 的启动必须要建立在另一个 Activity 的基础之上,也就是以此形成的返回栈。而 Dialog 则必须在一个 Activity 上面弹出(除非是 System Alert 类型的 Dialog),因此在这种场景下,我们只能使用 Activity 类型的 Context,否则将会出错。


getApplicationContext()和 getApplication()方法得到的对象都是同一个 application 对象,只是对象的类型不一样。


Context 数量 = Activity 数量 + Service 数量 + 1 (1 为 Application)

9、理解 Activity,View,Window 三者关系

这个问题真的很不好回答。所以这里先来个算是比较恰当的比喻来形容下它们的关系吧。Activity 像一个工匠(控制单元),Window 像窗户(承载模型),View 像窗花(显示视图)LayoutInflater 像剪刀,Xml 配置像窗花图纸。


  • 1:Activity 构造的时候会初始化一个 Window,准确的说是 PhoneWindow。

  • 2:这个 PhoneWindow 有一个“ViewRoot”,这个“ViewRoot”是一个 View 或者说 ViewGroup,是最初始的根视图。

  • 3:“ViewRoot”通过 addView 方法来一个个的添加 View。比如 TextView,Button 等

  • 4:这些 View 的事件监听,是由 WindowManagerService 来接受消息,并且回调 Activity 函数。比如 onClickListener,onKeyDown 等。

10、四种 LaunchMode 及其使用场景

此处延伸:栈(First In Last Out)与队列(First In First Out)的区别


栈与队列的区别:


  1. 队列先进先出,栈先进后出

  2. \2. 对插入和删除操作的”限定”。 栈是限定只能在表的一端进行插入和删除操作的线性表。 队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。


\3. 遍历数据速度不同


standard 模式


这是默认模式,每次激活 Activity 时都会创建 Activity 实例,并放入任务栈中。使用场景:大多数 Activity。


  • singleTop 模式


如果在任务的栈顶正好存在该 Activity 的实例,就重用该实例( 会调用实例的 onNewIntent() ),否则就会创建新的实例并放入栈顶,即使栈中已经存在该 Activity 的实例,只要不在栈顶,都会创建新的实例。使用场景如新闻类或者阅读类 App 的内容页面。


  • singleTask 模式


如果在栈中已经有该 Activity 的实例,就重用该实例(会调用实例的 onNewIntent() )。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移出栈。如果栈中不存在该实例,将会创建新的实例放入栈中。使用场景如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走 onNewIntent,并且会清空主界面上面的其他页面。


  • singleInstance 模式


在一个新栈中创建该 Activity 的实例,并让多个应用共享该栈中的该 Activity 实例。一旦该模式的 Activity 实例已经存在于某个栈中,任何应用再激活该 Activity 时都会重用该栈中的实例( 会调用实例的 onNewIntent() )。其效果相当于多个应用共享一个应用,不管谁激活该 Activity 都会进入同一个应用中。使用场景如闹铃提醒,将闹铃提醒与闹铃设置分离。singleInstance 不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A -> B (singleInstance) -> C,完全退出后,在此启动,首先打开的是 B。

11、View 的绘制流程

自定义控件


1、组合控件。这种自定义控件不需要我们自己绘制,而是使用原生控件组合成的新控件。如标题栏。


2、继承原有的控件。这种自定义控件在原生控件提供的方法外,可以自己添加一些方法。如制作圆角,圆形图片。


3、完全自定义控件:这个 View 上所展现的内容全部都是我们自己绘制出来的。比如说制作水波纹进度条。


View 的绘制流程:OnMeasure()——>OnLayout()——>OnDraw()


第一步:OnMeasure():测量视图大小。从顶层父 View 到子 View 递归调用 measure 方法,measure 方法又回调 OnMeasure。


第二步:OnLayout():确定 View 位置,进行页面布局。从顶层父 View 向子 View 的递归调用 view.layout 方法的过程,即父 View 根据上一步 measure 子 View 所得到的布局大小和布局参数,将子 View 放在合适的位置上。


第三步:OnDraw():绘制视图。ViewRoot 创建一个 Canvas 对象,然后调用 OnDraw()。六个步骤:①、绘制视图的背景;②、保存画布的图层(Layer);③、绘制 View 的内容;④、绘制 View 子视图,如果没有就不用;


⑤、还原图层(Layer);⑥、绘制滚动条。

12、View,ViewGroup 事件分发

\1. Touch 事件分发中只有两个主角:ViewGroup 和 View。ViewGroup 包含 onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent 三个相关事件。View 包含 dispatchTouchEvent、onTouchEvent 两个相关事件。其中 ViewGroup 又继承于 View。


2.ViewGroup 和 View 组成了一个树状结构,根节点为 Activity 内部包含的一个 ViwGroup。


3.触摸事件由 Action_Down、Action_Move、Aciton_UP 组成,其中一次完整的触摸事件中,Down 和 Up 都只有一个,Move 有若干个,可以为 0 个。


4.当 Acitivty 接收到 Touch 事件时,将遍历子 View 进行 Down 事件的分发。ViewGroup 的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的 View,这个 View 会在 onTouchuEvent 结果返回 true。


5.当某个子 View 返回 true 时,会中止 Down 事件的分发,同时在 ViewGroup 中记录该子 View。接下去的 Move 和 Up 事件将由该子 View 直接进行处理。由于子 View 是保存在 ViewGroup 中的,多层 ViewGroup 的节点结构时,上级 ViewGroup 保存的会是真实处理事件的 View 所在的 ViewGroup 对象:如 ViewGroup0-ViewGroup1-TextView 的结构中,TextView 返回了 true,它将被保存在 ViewGroup1 中,而 ViewGroup1 也会返回 true,被保存在 ViewGroup0 中。当 Move 和 UP 事件来时,会先从 ViewGroup0 传递至 ViewGroup1,再由 ViewGroup1 传递至 TextView。


6.当 ViewGroup 中所有子 View 都不捕获 Down 事件时,将触发 ViewGroup 自身的 onTouch 事件。触发的方式是调用 super.dispatchTouchEvent 函数,即父类 View 的 dispatchTouchEvent 方法。在所有子 View 都不处理的情况下,触发 Acitivity 的 onTouchEvent 方法。


7.onInterceptTouchEvent 有两个作用:1.拦截 Down 事件的分发。2.中止 Up 和 Move 事件向目标 View 传递,使得目标 View 所在的 ViewGroup 捕获 Up 和 Move 事件。

13、保存 Activity 状态

onSaveInstanceState(Bundle)会在 activity 转入后台状态之前被调用,也就是 onStop()方法之前,onPause 方法之后被调用;

14、Android 中的几种动画

帧动画:指通过指定每一帧的图片和播放时间,有序的进行播放而形成动画效果,比如想听的律动条。


补间动画:指通过指定 View 的初始状态、变化时间、方式,通过一系列的算法去进行图形变换,从而形成动画效果,主要有 Alpha、Scale、Translate、Rotate 四种效果。注意:只是在视图层实现了动画效果,并没有真正改变 View 的属性,比如滑动列表,改变标题栏的透明度。


属性动画:在 Android3.0 的时候才支持,通过不断的改变 View 的属性,不断的重绘而形成动画效果。相比于视图动画,View 的属性是真正改变了。比如 view 的旋转,放大,缩小。

15、Android 中跨进程通讯的几种方式

Android 跨进程通信,像 intent,contentProvider,广播,service 都可以跨进程通信。


intent:这种跨进程方式并不是访问内存的形式,它需要传递一个 uri,比如说打电话。


contentProvider:这种形式,是使用数据共享的形式进行数据共享。


service:远程服务,aidl


广播

16、AIDL 理解

此处延伸:简述 Binder


AIDL: 每一个进程都有自己的 Dalvik VM 实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行着自己的操作,都在自己的那片狭小的空间里过完自己的一生。而 aidl 就类似与两个进程之间的桥梁,使得两个进程之间可以进行数据的传输,跨进程通信有多种选择,比如 BroadcastReceiver , Messenger 等,但是 BroadcastReceiver 占用的系统资源比较多,如果是频繁的跨进程通信的话显然是不可取的;Messenger 进行跨进程通信时请求队列是同步进行的,无法并发执行。


Binde 机制简单理解:


在 Android 系统的 Binder 机制中,是有 Client,Service,ServiceManager,Binder 驱动程序组成的,其中 Client,service,Service Manager 运行在用户空间,Binder 驱动程序是运行在内核空间的。而 Binder 就是把这 4 种组件粘合在一块的粘合剂,其中核心的组件就是 Binder 驱动程序,Service Manager 提供辅助管理的功能,而 Client 和 Service 正是在 Binder 驱动程序和 Service Manager 提供的基础设施上实现 C/S 之间的通信。其中 Binder 驱动程序提供设备文件/dev/binder 与用户控件进行交互,


Client、Service,Service Manager 通过 open 和 ioctl 文件操作相应的方法与 Binder 驱动程序进行通信。而 Client 和 Service 之间的进程间通信是通过 Binder 驱动程序间接实现的。而 Binder Manager 是一个守护进程,用来管理 Service,并向 Client 提供查询 Service 接口的能力。

17、Handler 的原理

Android 中主线程是不能进行耗时操作的,子线程是不能进行更新 UI 的。所以就有了 handler,它的作用就是实现线程之间的通信。


handler 整个流程中,主要有四个对象,handler,Message,MessageQueue,Looper。当应用创建的时候,就会在主线程中创建 handler 对象,


我们通过要传送的消息保存到 Message 中,handler 通过调用 sendMessage 方法将 Message 发送到 MessageQueue 中,Looper 对象就会不断的调用 loop()方法


不断的从 MessageQueue 中取出 Message 交给 handler 进行处理。从而实现线程之间的通信。

18、Binder 机制原理

在 Android 系统的 Binder 机制中,是有 Client,Service,ServiceManager,Binder 驱动程序组成的,其中 Client,service,Service Manager 运行在用户空间,Binder 驱动程序是运行在内核空间的。而 Binder 就是把这 4 种组件粘合在一块的粘合剂,其中核心的组件就是 Binder 驱动程序,Service Manager 提供辅助管理的功能,而 Client 和 Service 正是在 Binder 驱动程序和 Service Manager 提供的基础设施上实现 C/S 之间的通信。其中 Binder 驱动程序提供设备文件/dev/binder 与用户控件进行交互,Client、Service,Service Manager 通过 open 和 ioctl 文件操作相应的方法与 Binder 驱动程序进行通信。而 Client 和 Service 之间的进程间通信是通过 Binder 驱动程序间接实现的。而 Binder Manager 是一个守护进程,用来管理 Service,并向 Client 提供查询 Service 接口的能力。

19、热修复的原理

我们知道 Java 虚拟机 —— JVM 是加载类的 class 文件的,而 Android 虚拟机——Dalvik/ART VM 是加载类的 dex 文件,


而他们加载类的时候都需要 ClassLoader,ClassLoader 有一个子类 BaseDexClassLoader,而 BaseDexClassLoader 下有一个


数组——DexPathList,是用来存放 dex 文件,当 BaseDexClassLoader 通过调用 findClass 方法时,实际上就是遍历数组,


找到相应的 dex 文件,找到,则直接将它 return。而热修复的解决方法就是将新的 dex 添加到该集合中,并且是在旧的 dex 的前面,


所以就会优先被取出来并且 return 返回。

20、Android 内存泄露及管理

  • (1)内存溢出(OOM)和内存泄露(对象无法被回收)的区别。

  • (2)引起内存泄露的原因


(3) 内存泄露检测工具 ——→LeakCanary


内存溢出 out of memory:是指程序在申请内存时,没有足够的内存空间供其使用,出现 out of memory;比如申请了一个 integer,但给它存了 long 才能存下的数,那就是内存溢出。内存溢出通俗的讲就是内存不够用。


内存泄露 memory leak:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光

最后

由于题目很多整理答案的工作量太大,所以仅限于提供知识点,详细的很多问题和参考答案我都整理成了 PDF 文件





**本文已被[CODING 开源项目:《Android 学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](


)



收录**


最后自我介绍一下,小编 13 年上海交大毕业,曾经在小公司待过,也去过华为、OPPO 等大厂,18 年进入阿里一直到现在。

深知大多数初中级 Android 工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此也是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

用户头像

还未添加个人签名 2021.10.19 加入

还未添加个人简介

评论

发布
暂无评论
android工程师面试题,大厂面试题汇总