Contents

Android高频面试题汇总(一)

1.Leakcanary原理?

  1. 利用 application.registerActivityLifecycleCallbacks(lifecycleCallbacks) 来监听整个生命周期内的 Activity onDestoryed 事件
  2. 某个 Activity 被 destory 后,将它传给 RefWatcher 去做观测,确保其后续会被正常回收;
  3. RefWatcher 首先把 Activity 使用 KeyedWeakReference 引用起来,并使用一个 ReferenceQueue 来记录该 KeyedWeakReference 指向的对象是否已被回收;
  4. AndroidWatchExecutor 会在 5s 后,开始检查这个弱引用内的 Activity 是否被正常回收。判断条件是:若 Activity 被正常回收,那么引用它的 KeyedWeakReference 会被自动放入 ReferenceQueue 中。
  5. 判断方式是:先看 Activity 对应的 KeyedWeakReference 是否已经放入 ReferenceQueue 中;如果没有,则手动 GC:gcTrigger.runGc();;然后再一次判断 ReferenceQueue 是否已经含有对应的 KeyedWeakReference。若还未被回收,则认为可能发生内存泄漏

2.如何理解Java的多态?其中,重载和重写有什么区别?

多态是同一个行为具有多个不同表现形式或形态的能力,多态是同一个接口,使用不同的实例而执行不同操作,多态就是程序运行期间才确定,一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法。 多态存在的三个必要条件是:继承,重写,父类引用指向子类引用。 多态的三个实现方式是:重写,接口,抽象类和抽象方法。

区别点重载重写
参数列表必须修改不能修改
返回类型可以修改不能修改
异常可以修改可以减少或删除,一定不能抛出新的或者更广的异常
访问可以修改一定不能做更严格的限制(可以降低限制)

3.谈一下JVM内存区域划分?哪部分是线程公有的,哪部分是私有的?

https://img.ibook8.club/mweb/16044872296914.jpg

4.final关键字的用法?

final 可以修饰类、变量和方法。修饰类代表这个类不可被继承。修饰变量代表此变量不可被改变。修饰方法表示此方法不可被重写 (override)。

5.快排和递归

快速排序使用分治法将序列分成两个较大和较小的子序列,然后递归的排序两个子序列。

  1. 挑选基准值:从数列中挑出一个元素,称为“基准”(pivot),
  2. 分割:所有比基准值小的元素放在基准值的左边,所有比基准值大的元素放在基准值的右边,分割成两个子序列
  3. 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。 https://img.ibook8.club/mweb/16032522440181.jpg

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的启动流程。 https://img.ibook8.club/mweb/16032862574130.jpg 整个流程涉及的主要角色有:

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的启动。
最后,再看看另一幅启动流程图来加深理解: https://img.ibook8.club/mweb/16032866250813.jpg

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_serverAMS 发起 startActivity 请求

  • system_server 进程收到请求后,向 Zygote 进程发送创建进程的请求;

  • Zygote 进程 fork 出新的子进程,即 App 进程

  • App 进程创建即初始化 ActivityThread,然后通过 Binder IPCsystem_server 进程的 AMS 发起 attachApplication 请求

  • system_server 进程的 AMS 在收到 attachApplication 请求后,做一系列操作后,通知 ApplicationThread bindApplication,然后发送 H.BIND_APPLICATION 消息

  • 主线程收到 H.BIND_APPLICATION 消息,调用 handleBindApplication 处理后做一系列的初始化操作,初始化 Application

  • system_server 进程的 AMSbindApplication 后,会调用 ActivityStackSupervisor.attachApplicationLocked,之后经过一系列操作,在 realStartActivityLocked 方法通过 Binder IPCApp 进程发送 scheduleLaunchActivity 请求;

  • App进程的 binder 线程(ApplicationThread)在收到请求后,通过 handler 向主线程发送 LAUNCH_ACTIVITY 消息;

  • 主线程收到 message 后经过 handleLaunchActivityperformLaunchActivity 方法,然后通过反射机制创建目标 Activity

  • 通过 Activity attach 方法创建 window 并且和 Activity 关联,然后设置 WindowManager 用来管理 window,然后通知 Activity 已创建,即调用 onCreate

  • 然后调用 handleResumeActivityActivity 可见 https://img.ibook8.club/mweb/16033681253492.jpg 补充:

  • ActivityManagerService 是一个注册到 SystemServer 进程并实现了 IActivityManagerBinder,可以通过 ActivityManagergetService 方法获取 AMS 的代理对象,进而调用 AMS 方法

  • ApplicationThreadActivityThread 的内部类,是一个实现了 IApplicationThreadBinderAMS通过 Binder IPCApplicationThread 对应用进行控制

  • 普通的 Activity 启动和本流程差不多,至少不需要再创建 App 进程了

  • Activity A 启动 Activity B,A 先 pause 然后 B 才能 resume,因此在 onPause 中不能做耗时操作,不然会影响下一个 Activity 的启动

其实简单概括来讲,就是 startActivity - Instrumetaton BinderAMS 通信,AMS 检验各种信息,如果没问题,ApplicationThread 发送信息给 ActivityThreadActivityThreadHandler 启动 activity。详细看大图: https://img.ibook8.club/mweb/16033683104394.jpg

15.线程池的参数详解

1
2
3
4
5
6
7
8
public ThreadPoolExecutor(int corePoolSize,  // 线程池的核心线程数
                          int maximumPoolSize, // 线程池的最大线程数
                          long keepAliveTime, // 当线程数大于核心时,多余的空闲线程等待新任务的存活时间。
                          TimeUnit unit, // keepAliveTime的时间单位
                          ThreadFactory threadFactory, // 线程工厂
                          BlockingQueue<Runnable> workQueue,// 用来储存等待执行任务的队列
                          RejectedExecutionHandler handler // 拒绝策略
                          ) 
  1. corePoolSize 线程池保留的最小线程数。如果线程池中的线程少于此数目,则在执行execut()时创建。
  2. maximumPoolSize 线程池中允许拥有的最大线程数。 如果对于和核心线程和最大线程依然有疑惑,不用急,后面会有详细的说明
  3. keepAliveTime 当线程闲置时,保持线程存活的时间。 默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
  4. unit keepAliveTime的时间单位,时分秒毫秒等。
  5. threadFactory 使用默认的即可
  6. workQueue 工作队列,存放提交的等待任务,其中有队列大小的限制。
  • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
  • LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,而在未指明容量时,容量默认为Integer.MAX_VALUE
  • LinkedBlockingDeque: 使用双向队列实现的双端阻塞队列,双端意味着可以像普通队列一样FIFO(先进先出),可以以像栈一样FILO(先进后出)
  • PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现Comparable接口也可以提供Comparator来对队列中的元素进行比较,跟时间没有任何关系,仅仅是按照优先级取任务。
  • DelayQueue:同PriorityBlockingQueue,也是二叉堆实现的优先级阻塞队列。要求元素都实现Delayed接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
  • SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用take()方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用put()方法的时候就会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。

16.String, StringBufferStringBuilder的区别?String的两种创建方式,在JVM的存储方式相同吗?

String是不可变类,每当我们对String进行操作的时候,总是会创建新的字符串。所以操作String很耗资源,因此Java提供了两个工具类来操作String - StringBufferStringBuilderStringBufferStringBuilder都是可变类,StringBuffer是线程安全的,StringBuilder则不是线程安全的。 所以在多线程对同一个字符串操作的时候,我们应该选择用StringBuffer。 非多线程的情况下,StringBuilder的效率比StringBuffer高。 一般来说,这三者的执行效率是StringBuilder>StringBuffer>String

既然如此,我们为什么还要用String? 因为String的设计是不可变的,为什么这样设计?

  • 字符串常量池(String pool, String intern pool, String保留池) 是Java堆内存中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。这样能节省大量的Java堆内存
  • 允许String对象缓存HashCodeJavaString对象的哈希码被频繁地使用, 比如在hashMap等容器中。字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存.这也是一种性能优化手段,意味着不必每次都去计算新的哈希码.
  • 安全性,String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。