分享一波2021自己的Android面试题

1,064 阅读15分钟
  1. RecyclerView原理?和ListView区别,recyclebin机制
  2. 事件分发原理
  3. 屏幕刷新机制(关键字vsync Cherorghapher SurfaceFlinger等)
  4. 为什么不推荐new Thread()方式创建线程,线程池优点
  5. 平时怎么创建线程,rxjava内部怎么实现的
    • 线程池+Handler
  6. 绘制优化怎么做,如何实现recyclerview item懒加载(比如插入一个广告item)
  7. 如何直接定位ANR,堆栈信息都无法直接定位,怎么解决呢(看下systrace吧)
  8. ASM动态化技术说一下,AOP,aspect j实现原理
    • ASM:用于字节码插桩
    //整体流程简介
    //1. 通过asm提供classReader读取java源文件
    //2. 通过classReader.accept函数实现字节码插桩
    //  2.1 自定义类访问者,如`MyClassVisitor extends ClassVisitor`,在visitMethod方法中返回我们自定义的方法访问者
    //  2.2 自定义方法访问者,如`MyMethodVisitor extends AdviceAdapter`,在onMethodEnter和onMehodExit中编写要添加的代码
            //2.2.1 如下 这个函数调用等价于java代码 `System.currentTimeMillis();`
    //  ```
    //      invokeStatic(Type.getType("Ljava/lang/System;"),new Method("currentTimeMillis", "()J"));//
    //  ```
    //
    //3. 通过asm提供的classWriter修改java源文件,如果输出目录等相同,则会覆盖原来源文件,实现字节码修改的目的
    
  9. gradle自定义task怎么做
  10. kotlin scope干什么 怎么用,协程原理知道么?
  11. 为什么阿里开发规约不推荐线程池
  12. 单例平时怎么写,静态内部类单例如何保证线程安全
  13. 常用开源框架原理知道么(必须有几个特别熟的框架,说得出原理,经得住追问)
  14. AIDL是什么
  15. 自定义view开发中怎么用
  16. Handler机制整体描述一下,为什么looper中for循环不会卡死
  17. 说说项目整体架构和难点,如何解决这些难点
  18. MVP怎么实现的,为什么不用MVVM,MVVM比MVP强在哪里
  19. 谈谈JVM ? 说下类加载过程?方法内基本类型和对象类型怎么存储?
  20. HashMap结构、原理说一下?是直接用hashcode算的数组索引么?
  21. HTTP和https区别,端口号(80和443)(没追问tsl过程和tcp握手过程,不过最好准备下)
  22. webSocket如何保证连接稳定,比如锁屏唤醒之后怎么做
  23. String equals 和 ==
    • 值比较和内存地址比较
  24. hook技术用过么(面试官说插件化技术生命周期很难管理)
    • weishu.me/2016/01/28/…
    • Activity启动
      • Activity启动时,AMS会在Activity启动时检查清单文件中activity是否注册,如果没注册就会抛异常
      • 如果AMS不抛出异常,就可以不注册也能启动了,所以需要hook
    • Hook技术:
      • Hook是什么?Hook中文意思就是钩子。简单说,它的作用就是改变代码的正常执行流程
      • 实现hook的技术:反射和动态代理
    • Hook点:钩子挂的地方就是Hook点。
    • 查找Hook点的原则:
      • 1.尽量静态变量或者单例对象。原因是静态变量一般不会修改
      • 2.尽量Hook publit的对象和方法。原因是public一般是提供对外调用的方法,谷歌工程师一般不会去修改
  25. 显示intent和隐式intent
  26. ThreadLocal怎么实现的
    • 每一个线程中都有一个成员变量ThreadLocalMap
    • ThreadLocal存放在ThreadLocalMap中
    • 所以每个线程在访问ThreadLocal时就从内部的ThreadLocalMap中去访问,不用去跟别的线程竞争
  27. IntentService会有ANR问题么
    • 理论上IntentService里执行耗时任务不会ANR
    • 一个IntentService,内部就创建了一个线程,通过Android提供的 Handler Message Looper,这些消息处理的类 构成了一个消息处理的模型。所以IntentService 的onHandleIntent 这个方法其实是在IntentService 中开辟的一个子线程中处理的。
  28. view测量模式
    • UNSPECIFIED:父容器不对View有任何限制,要多大有多大。常用于系统内部。
    • EXACTLY(精确模式):父视图为子视图指定一个确切的尺寸SpecSize。对应LyaoutParams中的match_parent或具体数值。
    • AT_MOST(最大模式): 大小不可超过某数值,如matchparent,最大不能超过父容器。父容器为子视图指定一个最大尺寸SpecSize,View的大小不能大于这个值。对应LayoutParams中的wrap_content。
  29. String可以被继承么?String中有哪些final方法?拼接操作生成了新的字符串,为什么?
    • 不可以 是final类
    • 待补充
    • 内部是建立临时的StringBuilder对象,调用append方法实现(通过字节码可以验证)
    • StringBuffer和StringBuilder: StringBuilder 的方法不是线程安全的,但是速度相对于StringBuffer有优势
  30. 8种基本类型说一下
    • byte(1个字节)
    • boolean(1个字节)
    • char(字符,2个字节)
    • short(短整数,2个字节)
    • int(整数,4个字节)
    • long(长整数,8个字节)
    • float(浮点数,单精度,4个字节)
    • double(双精度,8个字节)
  31. java多态指的是什么?
    • 多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
    • 对面向对象来说,多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法。通过编译之后会变成两个不同的方法,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是大家通常所说的多态性。
    • Java 实现多态有 3 个必要条件:继承、重写和向上转型。只有满足这 3 个条件,开发人员才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为。
      • 继承:在多态中必须存在有继承关系的子类和父类。
      • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
      • 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法。
  32. java泛型
    • 作用:①帮助检查代码中的类型,提前报错;②自动强制转型。
    • 上界:?通配符 + extends关键字,? 规定了泛型的上界,可以用子类的泛型实例去声明父类
      ArrayList<? extends Fruit> fruits2 = new ArrayList<Apple>();
      
    • 下界:在泛型中,用 ?+ super关键字声明,就可以用父类的泛型实例去声明子类
      List<? super Apple> apples2 = new ArrayList<Fruit>();
      
  33. Glide源码说一下?
  34. 子线程向主线程发消息的几种方式?
    • Handler
    • activity.runOnUiThread(runnable)
    • view.post
    • view.postDelay
  35. Dalvik与ART
    • 4.4之前:Dalvik虚拟机执行的是dex字节码,解释执行。从Android 2.2版本开始,支持JIT即时编译(JustInTime) 在程序运行的过程中进行选择热点代码(经常执行的代码)进行编译或者优化。
    • 5.0-7.0:而ART (Android Runtime)是在Android 4.4中引入的一个开发者选项,也是Android 5.0及更高版本的默认Android运行时。ART虚拟机执行的是本地机器码。Android的运行时从Dalvik虚拟机替换成ART虚拟机,并不要求开发者将自己的应用直接编译成目标机器码,APK仍然是一个包含dex字节码的文件。
      • Dalvik下应用在安装的过程,会执行一次优化,将dex字节码进行优化生成odex文件。而Art下将应用的dex字节码翻译成本地机器码的最恰当AOT时机也就发生在应用安装的时候。ART引入了预先编译机制(Ahead OfTime),在安装时,ART使用设备自带的dex2oat工具来编译应用,dex中的字节码将被编译成本地机器码
    • 7.0以后:ART使用预先(AOT)编译,并且从Android N混合使用AOT编译,解释和JIT。
      • 1、最初安装应用时不进行任何AOT编译(安装又快了),运行过程中解释执行,对经常执行的方法进行JIT,经过T编译的方法将会记录到Profile配置文件中。
      • 2、当设备闲置和充电时,编译守护进程会运行,根据Profile文件对常用代码进行AOT编译。待下次运行时直接使用。
  36. 安卓类加载器
    • Activity被PathClassLoader加载
  37. OKHTTP相关:
    • 分发器总结:
      • 对于同步请求,分发器只记录请求,用于判断IdleRunnable是否需要执行
      • 对于异步请求,向分发器中提交请求:
      • Q:如何决定将请求放入ready还是running?
        • A:如果当前正在请求数大于等于64放入ready;如果小于64,但是已经存在同一域名主机的请求小于5个放入ready!
      • Q:从running移动ready的条件是什么?
        • A:每个请求执行完成就会从running移除,同时进行第一步相同逻辑的判断,决定是否移动!
      • Q:分发器线程池的工作行为?
        • A:无等待,最大并发
    • 拦截器:
      • 1、重试拦截器在交出(交给下一个拦截器)之前,负责判断用户是否取消了请求,在获得了结果之后,会根据响应码判断是否需要重定向,如果满足条件那么就会重启执行所有拦截器。
      • 2、桥接拦截器在交出之前,负责将HTTP协议必备的请求头加入其中(如:Host)并添加一些默认的行为(如:GZIP压缩);在获得了结果后,调用保存cookie接口并解析GZIP数据。
      • 3、缓存拦截器顾名思义,交出之前读取并判断是否使用缓存;获得结果后判断是否缓存。
      • 4、连接拦截器在交出之前,负责找到或者新建一个连接,并获得对应的socket流;在获得结果后不进行额外的处理。
      • 5、请求服务器拦截器进行真正的与服务器的通信,向服务器发送数据,解析读取的响应数据。
  38. CAS原子性举例:
    • 内存中某个位置有个值count
    • 多个线程操作count++,假设某一线程将count值由0变为1
    • cpu提供了一个原子指令 compare and swap,这个原子指令接收三个参数,①count内存地址②期望的值(旧的值)③新值
    • 所以cpu会①先比较内存中count的值是不是期望的值,②如果是将新值与旧值进行交换
    • 现代cpu通过一条原子指令将两个操作合二为一,保证这两个操作要不全部完成,要不全部不完成
    • 如果不是期望的值,比如count值不是0而是2,于是cpu就会采用一个自旋的操作(就是一个循环),把2+1,重新调用CAS指令,把旧值重新交换回3,即通过一个循环不停检测
  39. JDK中原子变量的实现机制
    • 如上题,AtomicXxx就是通过一个循环不停地取值计算比较内存中变量值和旧值,直到成功为止
  40. 阻塞队列
    • ①当队列为空的时候,想从队列中取数据时,会被阻塞
    • ②当队列满了的时候,想向队列中存数据时,也会阻塞
    • 在jdk中,有一个阻塞队列接口BlockingQueue
      • 非阻塞方法add和remove
        • add方法用来存元素,当队列已满时,会抛出异常
        • remove方法用来取元素,当队列为空时,会抛出异常
      • 非阻塞方法offer和poll
        • offer用来添加元素,如果队列已满时,会返回false
        • poll用来取元素,当队列为空时,会返回null
      • 真正体现阻塞的是take和put方法
        • take用来取元素,如果队列为空,take方法就会阻塞在那里
        • put用来存元素,如果队列已满,put方法就会阻塞在那里
    • 阻塞队列用来解决生产者消费者问题
      • 作用:
        • 生产者和消费者解耦
        • 平衡生产者和消费者性能均衡问题
      • 常用阻塞队列
        • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
        • LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列
        • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
        • DelayQueue:一个使用优先级队列实现的无界阻塞队列。
        • SynchronousQueue:一个不存储元素的阻塞队列。
        • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
        • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
  41. 线程的状态,wait和notify
  42. 线程池分类及应用场景
  43. synchronized对象和类的区别
  44. volatile关键字作用 会发生指令重拍么(不会)
  45. zygote启动应用为何用socket而不是binder
  46. RelativeLayout为何比LinearLayout绘制次数多
  47. 谁向AMS注册服务
  48. Surface原理,像素如何被渲染到屏幕,Framework层怎么做的
  49. 泛型作用(编译检查),还有哪些其他的作用
  50. mvc mvp mvvm 画图讲清楚他们的区别
  51. handler屏障消息,那么异步消息怎么取的
  52. 组件化通信原理
  53. linkedHashmap为什么用双向链表
  54. postlayout细节与流程
  55. http2.0 多路复用说一下
  56. 如何在子线程中更新UI
  57. Retrofit -对Okhttp的封装: - OkhttpClient封装 - Request请求封装 - Call的封装 - 请求队列的封装
    • Retrofit优点:
      • 对网络请求参数的适配 -> retrofit 23个注解,协议表单,RestFul
      • 封装okhttp,call
      • 数据结果封装
  58. 冷启动优化:
    • adb命令:am start -S -W com.dsh.demos/.MainActivity打印启动耗时
    • AS自带cpu profiler 分析启动耗时函数调用流程
      • cpu profiler只能在8.0以上使用
      • 8.0以下使用DebugTracing,SDK卡中会生成xxapp.trace文件
        • Application构造方法中开启 Debug.startMethodTracing("xxapp");
        • 启动Activity onWindowFocusChanged回调中关闭Debug.stopMethodTracing();
    • AsyncLayoutInflater异步加载xml布局
    • 开启严格模式StrictMode检查代码中的违规操作,如IO操作在主线程中执行是不被严格模式允许的
    • 启动黑白屏问题:
      • 启动页添加含背景图片的主题
      • 在Activity onCreate时改回主题
    • 启动优化相关
      • 1).合理的使用异步初始化、延迟初始化、懒加载机制。
      • 2).启动过程避免耗时操作,如数据库I/O操作不要放在主线程执行。
      • 3).类加载优化:提前异步执行类加载。
      • 4).合理使用IdleHandler进行延迟初始化。
      • 5).简化布局
  59. 卡顿与布局优化
    • cpu profiler与DebugTracing都会消耗额外的性能,所以使用DebugTracing采样的api来跟踪启动过程,可以检查代码中的耗时操作
      • Debug.startMethodTracingSampling(...)
    • 开启gpu过度绘制调试
    • AS Layout Inspector检查布局层级
    • Systrace分析程序卡顿,常用分析滑动卡顿,可以查看线程及cpu执行过程及耗时统计
    • 代码检测方式:
      • 利用UI线程的Looper打印的日志匹配 -> BlockCanary原理
      • 使用Choreographer.FrameCallback回调检测
  60. ANR分析
    • 通过logcat日志,traces文件确认anr发生时间点traces文件和CPU使用率
    • /data/anr/traces.txt
    • 主线程状态
    • 其他线程状态
    • ANR线上监控
      • FileObserver:监控某个目录/文件 状态发生改变、创建、删除文件,如果有anr文件生成,就 上传到服务器
        • 实现:继承FileObserver,在onEvent回调中处理上传等逻辑
      • WatchDog:监控系统死锁等情况,开发者可以采用类似思路监控ANR
  61. charles https抓包原理
  62. apk签名 keystore原理 v1 v2 v3原理
  63. HTTPS TLS握手过程
  64. webSocket是什么,为什么不用http的keep-alive
    • keep-alive
      • 若串行发送请求,可以一直复用一个连接,但速度很慢,每个请求都要等待上个请求完成再进行发送。
      • 若并行发送请求,那么只能每个请求都要进行tcp三次握手建立新的连接。
  65. 组件化依赖如何解决,比如A依赖B,可是我们不需要B,如何剔除对B的依赖
  66. 滑动冲突如何解决,NestedScrollView原理
  67. Viewpager原理
    • 缓存:用ArrayList缓存,至少缓存一个
    • 懒加载setOffscreenPageLimit、 viewpager+fragment
      • Fragment 生命周期按先后顺序:
      onAttach -> onCreate -> onCreatedView -> onActivityCreated -> onStart -> onResume -> onPause -> onStop -> onDestroyView -> onDestroy -> onDetach
      
      • ViewPager + Fragment 的实现我们需要关注的几个生命周期有:
      onCreatedView + onActivityCreated + onResume + onPause + onDestroyView
      
      • 非生命周期函数:
      setUserVisibleHint + onHiddenChanged
      
  68. Dagger2
    • 是一个IOC依赖注入框架,主要使用了apt编译时注解技术
    • 单例问题:假设在ActivityA和ActivityB中同时注入一个单例SingleTon,A跳转到B,那么A中的singleton和B中的singleTon将不是同一个对象,这是由于provider不一致引起的
      • 解决办法是在Application中进行注入,并且提供单例的get方法getAppComonent(),然后在A和B中注入((MyApplication)getApplication().getAppComponent().injectAActivity(this);)即可