Android高频面试题汇总(一)
1.Leakcanary原理?
- 利用 application.registerActivityLifecycleCallbacks(lifecycleCallbacks) 来监听整个生命周期内的 Activity onDestoryed 事件
- 某个 Activity 被 destory 后,将它传给 RefWatcher 去做观测,确保其后续会被正常回收;
- RefWatcher 首先把 Activity 使用 KeyedWeakReference 引用起来,并使用一个 ReferenceQueue 来记录该 KeyedWeakReference 指向的对象是否已被回收;
- AndroidWatchExecutor 会在 5s 后,开始检查这个弱引用内的 Activity 是否被正常回收。判断条件是:若 Activity 被正常回收,那么引用它的 KeyedWeakReference 会被自动放入 ReferenceQueue 中。
- 判断方式是:先看 Activity 对应的 KeyedWeakReference 是否已经放入 ReferenceQueue 中;如果没有,则手动 GC:gcTrigger.runGc();;然后再一次判断 ReferenceQueue 是否已经含有对应的 KeyedWeakReference。若还未被回收,则认为可能发生内存泄漏
2.如何理解Java的多态?其中,重载和重写有什么区别?
多态是同一个行为具有多个不同表现形式或形态的能力,多态是同一个接口,使用不同的实例而执行不同操作,多态就是程序运行期间才确定,一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法。 多态存在的三个必要条件是:继承,重写,父类引用指向子类引用。 多态的三个实现方式是:重写,接口,抽象类和抽象方法。
| 区别点 | 重载 | 重写 |
|---|---|---|
| 参数列表 | 必须修改 | 不能修改 |
| 返回类型 | 可以修改 | 不能修改 |
| 异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
| 访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
3.谈一下JVM内存区域划分?哪部分是线程公有的,哪部分是私有的?

4.final关键字的用法?
final 可以修饰类、变量和方法。修饰类代表这个类不可被继承。修饰变量代表此变量不可被改变。修饰方法表示此方法不可被重写 (override)。
5.快排和递归
快速排序使用分治法将序列分成两个较大和较小的子序列,然后递归的排序两个子序列。
- 挑选基准值:从数列中挑出一个元素,称为“基准”(pivot),
- 分割:所有比基准值小的元素放在基准值的左边,所有比基准值大的元素放在基准值的右边,分割成两个子序列
- 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。

6.ANR出现的情况有几种? 怎么分析解决ANR问题?
ANR(Application Not responding)。Android中,主线程(UI线程)如果在规定时内没有处理完相应工作,就会出现ANR。具体来说,ANR会在以下几种情况中出现:
(1) 输入事件(按键和触摸事件)5s内没被处理
(2) BroadcastReceiver的事件(onRecieve方法)在规定时间内没处理完(前台广播为10s,后台广播为60s)
(3) service 前台20s后台200s未完成启动
(4) ContentProvider的publish在10s内没进行完
分析ANR问题,需要结合Log以及trace文件。
7.常用的设计模式有哪些?是否了解责任链模式?
单例模式,观察者模式,工厂模式,建造者模式,构造者模式,中间者模式,桥接模式,适配器模式等等。
8.Android UI优化方案?
1.通过合并多个要显示的数据到一个View来实现减少View个数
2.采用ConstraintLayout减少布局嵌套
3.LinearLayout关闭baselineAligned(减少测量次数)
4.ViewStub延迟初始化
5.include布局时善用merge标签,减少嵌套
6.减少Overdraw过度绘制(开发者选项里面的“调试GPU过度绘制”提供了相应的工具让我们可以很直观的观察到这种现象)
7.Canvas.clipRect/quickReject
- Canvas.clipRect()可以定义绘制的边界,边界以外的部分不会进行绘制。
- Canvas.quickReject()可以用来测试指定区域是否在裁剪范围之外,如果要绘制的元素位于裁剪范围之外,就可以直接跳过绘制步骤。
8.占位背景图优化
根据不同的状态显示不同的占位修饰图。当未加载完成是显示带背景的占位图,当加载完成后我们显示透明背景的占位图 这样被目标图遮盖的区域就不会多绘制一层无用的背景了。
避免Overdraw的核心原则始终只有一个,就是避免绘制看不见的元素
9.Alpha blending透明度合成优化
10.重写hasOverlappingRendering方法
11.Use Hardware Layer
当给一个View.setLayerType(View.LAYER_TYPE_HARDWARE,null)后,就定义了此View采用Hardware Layer(背后采用一个硬件相关纹理)来加速渲染。
9.okhttp中使用到的设计模式?
建造者(OkHttpClient),工厂模式(WebSocket.Factory),观察者模式(EventListener和WebSocketListener),单例模式(Platfrom),策略模式(CookieJar),责任链模式(整个okhttp的核心设计)
拦截器非常强大的机制,它可以监视、重写、和重连请求
10.进程间通信的方式
| 名称 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Intent | 简单易用 | 只能传输Bundle所支持的数据类型 | 四大组件间的进程间通信 |
| 文件共享 | 简单易用 | 不适合高并发 | 简单的数据共享,无高并发场景 |
| AIDL | 功能强大,支持一对多并发实时通信 | 使用稍微复杂,需要注意线程同步 | 复杂的进程间调用,Android中最常用 |
| Messenger | 比AIDL稍微简单易用些 | 比AIDL功能弱,只支持一对多串行实时通信 | 简单的进程间通信 |
| ContentProvider | 强大的数据共享能力,可通过call方法扩展 | 受约束的AIDL,主要对外提供数据线的CRUD操作 | 进程间的大量数据共享 |
| RemoteViews | 在跨进程访问UI方面有奇效 | 比较小众的通信方式 | 某些特殊的场景 |
| Socket | 跨主机,通信范围广 | 只能传输原始的字节流 | 常用于网络通信中 |
| BroadcastReceiver | 简单易用 | 只能单向通信,接收者只能被动的接收消息 | 简单的进程间通信 |
11.App启动流程(Activity的冷启动流程)
点击应用图标后会去启动应用的Launcher Activity,如果Launcer Activity所在的进程没有创建,还会创建新进程,整体的流程就是一个Activity的启动流程。
整个流程涉及的主要角色有:
Instrumentation: 监控应用与系统相关的交互行为。
AMS:组件管理调度中心,什么都不干,但是什么都管。
ActivityStarter:Activity启动的控制器,处理Intent与Flag对Activity启动的影响,具体说来有:
- 1 寻找符合启动条件的Activity,如果有多个,让用户选择;
- 2 校验启动参数的合法性;
- 3 返回int参数,代表Activity是否启动成功。
ActivityStackSupervisior:这个类的作用你从它的名字就可以看出来,它用来管理任务栈。
ActivityStack:用来管理任务栈里的Activity。
ActivityThread:最终干活的人,Activity、Service、BroadcastReceiver的启动、切换、调度等各种操作都在这个类里完成。
注:这里单独提一下ActivityStackSupervisior,这是高版本才有的类,它用来管理多个ActivityStack,早期的版本只有一个ActivityStack对应着手机屏幕,后来高版本支持多屏以后,就有了多个ActivityStack,于是就引入了ActivityStackSupervisior用来管理多个ActivityStack。
整个流程主要涉及四个进程:
调用者进程,如果是在桌面启动应用就是Launcher应用进程。 ActivityManagerService等待所在的System Server进程,该进程主要运行着系统服务组件。 Zygote进程,该进程主要用来fork新进程。 新启动的应用进程,该进程就是用来承载应用运行的进程了,它也是应用的主线程(新创建的进程就是主线程),处理组件生命周期、界面绘制等相关事情。 有了以上的理解,整个流程可以概括如下:
1、点击桌面应用图标,Launcher进程将启动Activity(MainActivity)的请求以Binder的方式发送给了AMS。
2、AMS接收到启动请求后,交付ActivityStarter处理Intent和Flag等信息,然后再交给ActivityStackSupervisior/ActivityStack 处理Activity进栈相关流程。同时以Socket方式请求Zygote进程fork新进程。
3、Zygote接收到新进程创建请求后fork出新进程。
4、在新进程里创建ActivityThread对象,新创建的进程就是应用的主线程,在主线程里开启Looper消息循环,开始处理创建Activity。
5、ActivityThread利用ClassLoader去加载Activity、创建Activity实例,并回调Activity的onCreate()方法,这样便完成了Activity的启动。
最后,再看看另一幅启动流程图来加深理解:

12.synchronized和lock的区别
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
- Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
- 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
- Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)
- 性能上来说,在资源竞争不激烈的情形下,Lock性能稍微比synchronized差点(编译程序通常会尽可能的进行优化synchronized)。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
13.Android源码中有哪些设计模式?
- 单例模式(WMS,AMS,LayoutInflater)
- Builder模式(AlertDialog.Builder)
- 原型模式(Intent)
- 策略模式(动画插值器TimeInterpolator)
- 状态模式(WifiManager)
- 责任链模式(ViewGroup)
- 解释器模式(PackageParser)
- 适配器模式(Adapter)
- 观察者模式(notifyDataSetChanged)
- 备忘录模式(onSaveInstanceState和onRestoreInstanceState)
- 模板方法模式(AsyncTask)
- 代理模式(ActivityManagerProxy)
- 组合模式(View和ViewGroup)
- 装饰模式(Context)
14.App 启动流程(基于Android8.0)
-
点击桌面
App图标,Launcher进程采用Binder IPC(具体为ActivityManager.getService获取AMS实例) 向system_server的AMS发起startActivity请求 -
system_server进程收到请求后,向Zygote进程发送创建进程的请求; -
Zygote进程fork出新的子进程,即App进程 -
App进程创建即初始化ActivityThread,然后通过Binder IPC向system_server进程的AMS发起attachApplication请求 -
system_server进程的AMS在收到attachApplication请求后,做一系列操作后,通知ApplicationThread bindApplication,然后发送H.BIND_APPLICATION消息 -
主线程收到
H.BIND_APPLICATION消息,调用handleBindApplication处理后做一系列的初始化操作,初始化Application等 -
system_server进程的AMS在bindApplication后,会调用ActivityStackSupervisor.attachApplicationLocked,之后经过一系列操作,在realStartActivityLocked方法通过Binder IPC向App进程发送scheduleLaunchActivity请求; -
App进程的
binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息; -
主线程收到
message后经过handleLaunchActivity,performLaunchActivity方法,然后通过反射机制创建目标Activity; -
通过
Activity attach方法创建window并且和Activity关联,然后设置WindowManager用来管理window,然后通知Activity已创建,即调用onCreate -
然后调用
handleResumeActivity,Activity可见
补充: -
ActivityManagerService是一个注册到SystemServer进程并实现了IActivityManager的Binder,可以通过ActivityManager的getService方法获取AMS的代理对象,进而调用AMS方法 -
ApplicationThread是ActivityThread的内部类,是一个实现了IApplicationThread的Binder。AMS通过Binder IPC经ApplicationThread对应用进行控制 -
普通的
Activity启动和本流程差不多,至少不需要再创建App进程了 -
Activity A启动Activity B,A 先pause然后 B 才能resume,因此在onPause中不能做耗时操作,不然会影响下一个Activity的启动
其实简单概括来讲,就是 startActivity - Instrumetaton Binder 与 AMS 通信,AMS 检验各种信息,如果没问题,ApplicationThread 发送信息给 ActivityThread ,ActivityThread 的Handler 启动 activity。详细看大图:

15.线程池的参数详解
public ThreadPoolExecutor(int corePoolSize, // 线程池的核心线程数
int maximumPoolSize, // 线程池的最大线程数
long keepAliveTime, // 当线程数大于核心时,多余的空闲线程等待新任务的存活时间。
TimeUnit unit, // keepAliveTime的时间单位
ThreadFactory threadFactory, // 线程工厂
BlockingQueue<Runnable> workQueue,// 用来储存等待执行任务的队列
RejectedExecutionHandler handler // 拒绝策略
)
corePoolSize线程池保留的最小线程数。如果线程池中的线程少于此数目,则在执行execut()时创建。maximumPoolSize线程池中允许拥有的最大线程数。 如果对于和核心线程和最大线程依然有疑惑,不用急,后面会有详细的说明keepAliveTime当线程闲置时,保持线程存活的时间。 默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;unitkeepAliveTime的时间单位,时分秒毫秒等。threadFactory使用默认的即可workQueue工作队列,存放提交的等待任务,其中有队列大小的限制。
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,而在未指明容量时,容量默认为Integer.MAX_VALUE。LinkedBlockingDeque: 使用双向队列实现的双端阻塞队列,双端意味着可以像普通队列一样FIFO(先进先出),可以以像栈一样FILO(先进后出)PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现Comparable接口也可以提供Comparator来对队列中的元素进行比较,跟时间没有任何关系,仅仅是按照优先级取任务。DelayQueue:同PriorityBlockingQueue,也是二叉堆实现的优先级阻塞队列。要求元素都实现Delayed接口,通过执行时延从队列中提取任务,时间没到任务取不出来。SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用take()方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用put()方法的时候就会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
16.String, StringBuffer,StringBuilder的区别?String的两种创建方式,在JVM的存储方式相同吗?
String是不可变类,每当我们对String进行操作的时候,总是会创建新的字符串。所以操作String很耗资源,因此Java提供了两个工具类来操作String - StringBuffer和StringBuilder。
StringBuffer和StringBuilder都是可变类,StringBuffer是线程安全的,StringBuilder则不是线程安全的。
所以在多线程对同一个字符串操作的时候,我们应该选择用StringBuffer。
非多线程的情况下,StringBuilder的效率比StringBuffer高。
一般来说,这三者的执行效率是StringBuilder>StringBuffer>String
既然如此,我们为什么还要用String?
因为String的设计是不可变的,为什么这样设计?
- 字符串常量池(String pool, String intern pool, String保留池) 是
Java堆内存中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。这样能节省大量的Java堆内存 - 允许
String对象缓存HashCode,Java中String对象的哈希码被频繁地使用, 比如在hashMap等容器中。字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存.这也是一种性能优化手段,意味着不必每次都去计算新的哈希码. - 安全性,
String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。

补充: