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;unit
keepAliveTime
的时间单位,时分秒毫秒等。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不是固定不变的,将会引起各种安全隐患。