2024年3月27日发(作者:)
Android面试题集包含答案
本文作者
作者:guoxiaoxing
链接:
/guoxiaoxing/android-interview
本文基于作者采用的MIT协议分发。
手画一下Android系统架构图,描述一下各个层次的作用?
Android系统架构图
从上到下依次分为六层:
•
应用框架层
•
进程通信层
•
系统服务层
•
Android运行时层
•
硬件抽象层
•
Linux内核层
Activity如与Service通信?
可以通过bindService的方式,先在Activity里实现一个
ServiceConnection接口,并将该接口传递给bindService()方法,在
ServiceConnection接口的onServiceConnected()方法
里执行相关操作。
Service的生命周期与启动方法有什么区别?
•
startService():开启
•
bindService():开启
Service,调用者退出后Service仍然存在。
Service,调用者退出后Service也随即退
出。
Service生命周期:
•
只是用startService()启动服务:onCreate() ->
onStartCommand() -> onDestory
•
只是用bindService()绑定服务:onCreate() -> onBind() ->
startService()启动服务与bindService()绑定服务:
onUnBind() -> onDestory
•
同时使用
onCreate() -> onStartCommnad() -> onBind() -> onUnBind() ->
onDestory
广播分为哪几种,应用场景是什么?
•
普通广播:调用
•
有序广播:调用
sendBroadcast()发送,最常用的广播。
sendOrderedBroadcast(),发出去的广播会被
广播接受者按照顺序接收,广播接收者按照Priority属性值从大-小排
序,Priority属性相同者,动态注册的广播优先,广播接收者还可以
•
选择对广播进行截断和修改。
广播的两种注册方式有什么区别?
•
静态注册:常驻系统,不受组件生命周期影响,即便应用退出,
广播还是可以被接收,耗电、占内存。
•
动态注册:非常驻,跟随组件的生命变化,组件结束,广播结束。
在组件结束前,需要先移除广播,否则容易造成内存泄漏。
广播发送和接收的原理了解吗?
1. 继承BroadcastReceiver,重写onReceive()方法。
2. 通过Binder机制向ActivityManagerService注册广播。
3. 通过Binder机制向ActivityMangerService发送广播。
4. ActivityManagerService查找符合相应条件的广播
(IntentFilter/Permission)的BroadcastReceiver,将广播发送到
BroadcastReceiver所在的消息队列中。
5. BroadcastReceiver所在消息队列拿到此广播后,回调它的
onReceive()方法。
ContentProvider、ContentResolver与ContentObserver之间
的关系是什么?
•
ContentProvider:管理数据,提供数据的增删改查操作,数据
源可以是数据库、文件、XML、网络等,ContentProvider为这些数
据的访问提供了统一的接口,可以用来做进程间数据共享。
•
ContentResolver:ContentResolver可以不同URI操作不同的
ContentProvider中的数据,外部进程可以通过ContentResolver与
ContentProvider进行交互。
•
ContentObserver:观察ContentProvider中的数据变化,并
将变化通知给外界。
遇到过哪些关于Fragment的问题,如何处理的?
•
getActivity()空指针:这种情况一般发生在在异步任务里调用
getActivity(),而Fragment已经onDetach(),此时就会有空指针,
解决方案是在Fragment里使用
•
一个全局变量mActivity,在onAttach()方法里赋值,这样可能
会引起内存泄漏,但是异步任务没有停止的情况下本身就已经可能内
存泄漏,相比直接crash,这种方式
•
显得更妥当一些。
•
Fragment视图重叠:在类onCreate()的方法加载Fragment,
有判断saveInstanceState==null或并且没
if(findFragmentByTag(mFragmentTag) == null),导致重复加载了
同一个Fragment导致重叠。(PS:replace情况下,如果没有加入回
退栈,则不判断也不会造成重叠,但建议还是统一判断下)
@Override
protected void onCreate(@Nullable Bundle
savedInstanceState) {
// 在页面重启时,Fragment会被保存恢复,而此时再加载
Fragment会重复加载,导致重叠 ;
if(saveInstanceState == null){
// 或者 if(findFragmentByTag(mFragmentTag) == null)
// 正常情况下去 加载根Fragment
}
}
Android里的Intent传递的数据有大小限制吗,如何解决?
Intent传递数据大小的限制大概在1M左右,超过这个限制就会
静默崩溃。处理方式如下:
•
进程内:EventBus,文件缓存、磁盘缓存。
•
进程间:通过ContentProvider进行款进程数据共享和传递。
描述一下Android的事件分发机制?
Android事件分发机制的本质:事件从哪个对象发出,经过哪些
对象,最终由哪个对象处理了该事件。此处对象指的是Activity、
Window与View。
Android事件的分发顺序:Activity(Window) -> ViewGroup
-> View
Android事件的分发主要由三个方法来完成,如下所示:
// 父View调用dispatchTouchEvent()开始分发事件
public boolean dispatchTouchEvent(MotionEvent event){
boolean consume = false;
// 父View决定是否拦截事件
if(onInterceptTouchEvent(event)){
// 父View调用onTouchEvent(event)消费事件,如果该方法返
回true,表示
// 该View消费了该事件,后续该事件序列的事件(Down、
Move、Up)将不会在传递
// 该其他View。
consume = onTouchEvent(event);
}else{
// 调用子View的dispatchTouchEvent(event)方法继续分发事件
consume = chTouchEvent(event);
}
return consume;
}
描述一下View的绘制原理?
View的绘制流程主要分为三步:
1. onMeasure:测量视图的大小,从顶层父View到子View递
归调用measure()方法,measure()调用onMeasure()方法,
onMeasure()方法完成绘制工作。
2. onLayout:确定视图的位置,从顶层父View到子View递归
调用layout()方法,父View将上一步measure()方法得到的子View
的布局大小和布局参数,将子View放在合适的位置上。
3. onDraw:绘制最终的视图,首先ViewRoot创建一个Canvas
对象,然后调用onDraw()方法进行绘制。onDraw()方法的绘制流程
为:① 绘制视图背景。② 绘制画布的图层。 ③ 绘制View内容。
4. ④ 绘制子视图,如果有的话。⑤ 还原图层。⑥ 绘制滚动条。
requestLayout()、invalidate()与postInvalidate()有什么区别?
•
requestLayout():该方法会递归调用父窗口的requestLayout()
方法,直到触发ViewRootImpl的performTraversals()方法,此时
mLayoutRequestede为true,会触发onMesaure()与onLayout()方
法,不一定
•
会触发onDraw()方法。
:该方法递归调用父View的
•
invalidate()
invalidateChildInParent()方法,直到调用ViewRootImpl的
invalidateChildInParent()方法,最终触发ViewRootImpl的
performTraversals()方法,此时mLayoutRequestede为false,不会
•
触发onMesaure()与onLayout()方法,当时会触发onDraw()方
法。
•
postInvalidate():该方法功能和invalidate()一样,只是它可以
在非UI线程中调用。
一般说来需要重新布局就调用requestLayout()方法,需要重新绘
制就调用invalidate()方法。
了解APK的打包流程吗,描述一下?
Android的包文件APK分为两个部分:代码和资源,所以打包方
面也分为资源打包和代码打包两个方面,这篇文章就来分析资源和代
码的编译打包原理。
APK整体的的打包流程如下图所示:
具体说来:
1. 通过AAPT工具进行资源文件(包括、
布局文件、各种xml资源等)的打包,生成文件。
2. 通过AIDL工具处理AIDL文件,生成相应的Java文件。
3. 通过Javac工具编译项目源码,生成Class文件。
4. 通过DX工具将所有的Class文件转换成DEX文件,该过程主
要完成Java字节码转换成Dalvik字节码,压缩常量池以及清除冗余信
息等工作。
5. 通过ApkBuilder工具将资源文件、DEX文件打包生成APK文
件。
6. 利用KeyStore对生成的APK文件进行签名。
7. 如果是正式版的APK,还会利用ZipAlign工具进行对齐处理,
对齐的过程就是将APK文件中所有的资源文件举例文件的起始距离都
偏移4字节的整数倍,这样通过内存映射访问APK文件
8. 的速度会更快。
了解APK的安装流程吗,描述一下?
APK的安装流程如下所示:
复制APK到/data/app目录下,解压并扫描安装包。
1. 资源管理器解析APK里的资源文件。
2. 解析AndroidManifest文件,并在/data/data/目录下创建对
应的应用数据目录。
3. 然后对dex文件进行优化,并保存在dalvik-cache目录下。
4. 将AndroidManifest文件解析出的四大组件信息注册到
PackageManagerService中。
5. 安装完成后,发送广播。
当点击一个应用图标以后,都发生了什么,描述一下这个过程?
点击应用图标后会去启动应用的LauncherActivity,如果
LancerActivity所在的进程没有创建,还会创建新进程,整体的流程
就是一个Activity的启动流程。
Activity的启动流程图(放大可查看)如下所示:
整个流程涉及的主要角色有:
•
Instrumentation: 监控应用与系统相关的交互行为。
•
AMS:组件管理调度中心,什么都不干,但是什么都管。
•
ActivityStarter:Activity启动的控制器,处理Intent与Flag对
Activity启动的影响,具体说来有:1 寻找符合启动条件的Activity,
如果有多个,让用户选择;2 校验启动参数的合法性;3 返回int参数,
代表Activity是否启动成功。
•
ActivityStackSupervisior:这个类的作用你从它的名字就可以
看出来,它用来管理任务栈。
•
ActivityStack:用来管理任务栈里的Activity。
•
ActivityThread:最终干活的人,是ActivityThread的内部类,
Activity、Service、BroadcastReceiver的启动、切换、调度等各种
操作都在这个类里完成。
注:这里单独提一下ActivityStackSupervisior,这是高版本才有
的类,它用来管理多个ActivityStack,早期的版本只有一个
ActivityStack对应着手机屏幕,后来高版本支持多屏以后,就有了多
个ActivityStack,于是就引入了ActivityStackSupervisior用来管理
多个ActivityStack。
整个流程主要涉及四个进程:
•
调用者进程,如果是在桌面启动应用就是
•
ActivityManagerService
Launcher应用进程。
等所在的System Server进程,该进
程主要运行着系统服务组件。
•
Zygote进程,该进程主要用来fork新进程。
•
新启动的应用进程,该进程就是用来承载应用运行的进程了,它
也是应用的主线程(新创建的进程就是主线程),处理组件生命周期、
界面绘制等相关事情。
有了以上的理解,整个流程可以概括如下:
1. 点击桌面应用图标,Launcher进程将启动Activity
(MainActivity)的请求以Binder的方式发送给了AMS。
2. AMS接收到启动请求后,交付ActivityStarter处理Intent和
Flag等信息,然后再交给ActivityStackSupervisior/ActivityStack
3. 处理Activity进栈相关流程。同时以Socket方式请求Zygote
进程fork新进程。
4. Zygote接收到新进程创建请求后fork出新进程。
5. 在新进程里创建ActivityThread对象,新创建的进程就是应用
的主线程,在主线程里开启Looper消息循环,开始处理创建Activity。
6. ActivityThread利用ClassLoader去加载Activity、创建
Activity实例,并回调Activity的onCreate()方法。这样便完成了
Activity的启动。
BroadcastReceiver与LocalBroadcastReceiver有什么区别?
•
BroadcastReceiver 是跨应用广播,利用Binder机制实现。
Handler实现,
•
LocalBroadcastReceiver 是应用内广播,利用
利用了IntentFilter的match功能,提供消息的发布与接收功能,实
现应用内通信,效率比较高。
Android Handler机制是做什么的,原理了解吗?
Android消息循环流程图如下所示:
主要涉及的角色如下所示:
•
Message:消息,分为硬件产生的消息(例如:按钮、触摸)
和软件产生的消息。
•
MessageQueue:消息队列,主要用来向消息池添加消息和取
走消息。
•
Looper:消息循环器,主要用来把消息分发给相应的处理者。
•
Handler:消息处理器,主要向消息队列发送各种消息以及处理
各种消息。
整个消息的循环流程还是比较清晰的,具体说来:
1. Handler通过sendMessage()发送消息Message到消息队列
MessageQueue。
2. Looper通过loop()不断提取触发条件的Message,并将
Message交给对应的target handler来处理。
3. target handler调用自身的handleMessage()方法来处理
Message。
事实上,在整个消息循环的流程中,并不只有Java层参与,很多
重要的工作都是在C 层来完成的。我们来看下这些类的调用关系。
注:虚线表示关联关系,实线表示调用关
系。
在这些类中MessageQueue是Java层与C 层维系的桥梁,
MessageQueue与Looper相关功能都通过MessageQueue的
Native方法来完成,而其他虚线连接的类只有关联关系,并没有直接
调用的关系,它们发生关联的桥梁是MessageQueue。
Android Binder机制是做什么的,为什么选用Binder,原理了解
吗?
Android Binder是用来做进程通信的,Android的各个应用以及
系统服务都运行在独立的进程中,它们的通信都依赖于Binder。
为什么选用Binder,在讨论这个问题之前,我们知道Android也
是基于Linux内核,Linux现有的进程通信手段有以下几种:
1. 管道:在创建时分配一个page大小的内存,缓存区大小比较
有限;
2. 消息队列:信息复制两次,额外的CPU消耗;不合适频繁或信
息量大的通信;
3. 共享内存:无须复制,共享缓冲区直接付附加到进程虚拟地址
空间,速度快;但进程间的同步问题操作系统无法实现,必须各进程
利用同步工具解决;
4. 套接字:作为更通用的接口,传输效率低,主要用于不通机器
或跨网络的通信;
5. 信号量:常作为一种锁机制,防止某进程正在访问共享资源时,
其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同
线程之间的同步手段。6. 信号: 不适用于信息交换,更适用于进程中断
控制,比如非法内存访问,杀死某个进程等;
既然有现有的IPC方式,为什么重新设计一套Binder机制呢。主
要是出于以上三个方面的考量:
•
高性能:从数据拷贝次数来看Binder只需要进行一次内存拷贝,
而管道、消息队列、Socket都需要两次,共享内存不需要拷贝,
Binder的性能仅次于共享内存。
•
稳定性:上面说到共享内存的性能优于Binder,那为什么不适
用共享内存呢,因为共享内存需要处理并发同步问题,控制负责,容
易出现死锁和资源竞争,稳定性较差。而Binder基于C/S架构,客户
端与服务端彼此独立,稳定性较好。
•
安全性:我们知道Android为每个应用分配了UID,用来作为
鉴别进程的重要标志,Android内部也依赖这个UID进行权限管理,
包括6.0以前的固定权限和6.0以后的动态权限,传荣IPC只能由用户
在数据包里填入UID/PID,这个标记完全
•
是在用户空间控制的,没有放在内核空间,因此有被恶意篡改的
可能,因此Binder的安全性更高。
描述一下Activity的生命周期,这些生命周期是如何管理的?
Activity与Fragment生命周期如下所示:
读者可以从上图看出,Activity有很多种状态,状态之间的变化也
比较复杂,在众多状态中,只有三种是常驻状态:
•
Resumed(运行状态):Activity处于前台,用户可以与其交互。
•
Paused(暂停状态):Activity被其他Activity部分遮挡,无法
接受用户的输入。
•
Stopped(停止状态):Activity被完全隐藏,对用户不可见,
进入后台。
其他的状态都是中间状态。
我们再来看看生命周期变化时的整个调度流程,生命周期调度流
程图如下所示:
所以你可以看到,整个流程是这样的:
1. 比方说我们点击跳转一个新Activity,这个时候Activity会入
栈,同时它的生命周期也会从onCreate()到onResume()开始变换,
这个过程是在ActivityStack里完成的,ActivityStack
2. 是运行在Server进程里的,这个时候Server进程就通过
ApplicationThread的代理对象ApplicationThreadProxy向运行在
app进程ApplicationThread发起操作请求。
3. ApplicationThread接收到操作请求后,因为它是运行在app
进程里的其他线程里,所以ApplicationThread需要通过Handler向
主线程ActivityThread发送操作消息。
4. 主线程接收到ApplicationThread发出的消息后,调用主线程
ActivityThread执行响应的操作,并回调Activity相应的周期方法。
注:这里提到了主线程ActivityThread,
更准确来说ActivityThread不是线程,因为
它没有继承Thread类或者实现Runnable接
口,它是运行在应用主线程里的对象,那么应
用的主线程
到底是什么呢?从本质上来讲启动启动时
创建的进程就是主线程,线程和进程处理是否
共享资源外,没有其他的区别,对于Linux来
说,它们都只是一个struct结构体。
Activity的通信方式有哪些?
•
startActivityForResult
•
EventBus
•
LocalBroadcastReceiver
Android应用里有几种Context对象?
发布评论