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内存区域划分?哪部分是线程公有的,哪部分是私有的?

4.final关键字的用法?

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

5.快排和递归

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

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

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_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 可见

    补充:

  • 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。详细看大图:

15.线程池的参数详解

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不是固定不变的,将会引起各种安全隐患。