前言
下面跟大家分享的这些面试题都是互联网大厂真实流出的面试内容,每个问题都附带完整详细的答案,不像网上的那些资料三教九流有的甚至还没答案,这些面试题我也是经过日积月累才整理出来的精品资料。
这些面试题主要是针对1-5年左右的Android开发程序员提升的,不管是传统行业还是互联网行业,掌握这些技术基本都能拿到一个不错的薪资,希望对大家有所帮助。
面试题展示如下:
Java篇
一、抽象类与接口的区别?
大体区别如下:
- 抽象类可以提供成员方法的实现细节,而接口中只能存在 public 抽象方法;
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的;
- 接口中不能含有构造器、静态代码块以及静态方法,而抽象类可以有构造器、静态代码块和静态方法;
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口;
- 抽象类访问速度比接口速度要快,因为接口需要时间去寻找在类中具体实现的方法;
- 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。如果你往接口中添加方法,那么你必须改变实现该接口的类。
- 接口更多的为了约束类的行为,可用于解耦,而抽象类更加侧重于代码复用。
二、谈谈List,Set,Map的区别?
List中存储的数据是有顺序的,并且值允许重复;Map中存 储的数据是无序的,它的键是不允许重复的,但是值是允
许重复的;Set中存储的数据是无顺序的,并且不允许重 复,但元素在集合中的位置是由元素的hashcode决定,即
位置是固定的(Set集合是根据hashcode来进行数据存储 的,所以位置是固定的,但是这个位置不是用户可以控制
的,所以对于用户来说set中的元素还是无序的)
三、说一下线程的几种状态?
- 第一是创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
- 第二是就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
- 第三是运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
- 第四是阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
- 第五是死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。
四、如何实现多线程中的同步?
多线程同步和异步不是一回事。 几种情况,
- 就是大家说的synchronized 他可以保证原子性,保证多个线程在操作同一方法时只有一个线程可以持有锁,并且操作该方法,
- 就是手动调用读写锁,
- 手动操作线程的wait和notify
- volatile我记得是没有原子性的,他可以保证内存可见性,在多线程的情况下保证每个线程的数据都是最新的
Android篇
一、Activity 与 Fragment 之间常见的几种通信方式?
viewModel 做数据管理,activity 和 fragment 公用同个viewModel 实现数据传递
二、BroadcastReceiver 与LocalBroadcastReceiver 有什么区别?
- BroadcastReceiver 是跨应用广播,利用Binder机制实现,支持动态和静态两种方式注册方式。
- LocalBroadcastReceiver 是应用内广播,利用Handler 实现,利用了IntentFilter的match功能,提供消息的发布与接收功能,实现应用内通信,效率和安全性比较高,仅支持动态注册。
三、子线程能否更新UI?为什么?
子线程是不能直接更新UI的
注意这句话,是不能直接更新,不是不能更新(极端情况下可更新)绘制过程要保持同步(否则页面不流畅),而我们的主线程负责绘制ui,极端情况就是,在Activity的onResume(含)之前的生命周期中子线程都可以进行更新ui,也就是onCreate,onStart和onResume,此时主线程的绘制还没开始。
四、 谈谈 Handler 机制和原理?
首先在UI线程我们创建了一个Handler实例对象,无论是匿名内部类还是自定义类生成的Handler实例对象,我们都需要对handleMessage方法进行重写,在handleMessage方法中我们可以通过参数msg来写接受消息过后UIi线程的逻辑处理,接着我们创建子线程,在子线程中需要更新UI的时候,新建一个Message对象,并且将消息的数据记录在这个消息对象Message的内部,比如arg1,arg2,obj等,然后通过前面的Handler实例对象调用sendMessge方法把这个Message实例对象发送出去,之后这个消息会被存放于MessageQueue中等待被处理,此时MessageQueue的管家Looper正在不停的把MessageQueue存在的消息取出来,通过回调dispatchMessage方法将消息传递给Handler的handleMessage方法,最终前面提到的消息会被Looper
从MessageQueue中取出来传递给handleMessage方法。
五、谈谈Android的事件分发机制?
当点击的时候,会先调用顶级viewgroup的dispatchTouchEvent,如果顶级的viewgroup拦截了此事件(onInterceptTouchEvent返回true),则此事件序列
由顶级viewgroup处理。如果顶级viewgroup设置setOnTouchListener,则会回调接口中的onTouch,此时顶级的viewgroup中的onTouchEvent不再回调,如果不设
置setOnTouchListener则onTouchEvent会回调。如果顶级viewgroup设置setOnClickListener,则会回调接口中的onClick。如果顶级viewgroup不拦截事件,事件就会向下传递给他的子view,然后子view就会调用它的dispatchTouchEvent方法。
Android Framework篇
一、你了解 Android 系统启动流程吗?
当按电源键触发开机,首先会从ROM中预定义的地方加载引导程序BootLoader 到 RAM中,并执行BootLoader程序启动Linux
Kernel,然后启动用户级别的第一个进程: init 进程。init
进程会解析init.rc脚本做一些初始化工作,包括挂载文件系统创建工作目录以及启动系统服务进程等,其中系统服务进程包括
Zygote、service manager、media 等。在 Zygote中会进一步去启动 system_ server进程,然后在
system_server 进程中会启动AMS、WMS、PMS等服务,等这些服务启动之后,AMS中就会打开Launcher应用的home
Activity,最终就看到了手机的“桌面”。
二、能具体说说是怎么导致的死锁的吗?
在POSIX 标准中,fork的行为是这样的:复制整个用户空间的数据(通常使用 copy-on-write
的策略,所以可以实现的速度快)以及所有系统对象,然后仅复制当前线程到子进程。这里:所有父进程中别的线程,到了子进程中都是突然蒸发掉的对于锁来说,从
OS 看,每个锁有一个所有者,即最后一次 lock 它的线程。假设这么一个环境,在 fork 之前,有一个子线程 lock
了某个锁,获得了对锁的所有权。fork
以后,在子进程中,所有的额外线程都人间蒸发了。而锁却被正常复制了,在子进程看来,这个锁没有主人,所以没有任何人可以对它解锁。 当子进程想
lock 这个锁时,不再有任何手段可以解开了。
三、Zygote 为什么不采用 Binder 机制进行 IPC 通信?
Binder机制中存在Binder线程池,是多线程的,如果Zygote采用Binder的话就存在上面说的fork()与多线程的问题了。其实严格来说,Binder机制不一定要多线程,所谓的Binder线程只不过是在循环读取Binder驱动的消息而已,只注册一个Binder线程也是可以工作的,比如service
manager
就是这样的。实际上Zygote尽管没有采取Binder机制,它也不是单线程的,但它在fork()前主动停止了其他线程,fork()后重新启动了。
四、Binder是如何做到一次拷贝的
主要是因为Linux是使用的虚拟内存寻址方式,它有如下特性:
- 用户空间的虚拟内存地址是映射到物理内存中的。
- 对虚拟内存的读写实际上是对物理内存的读写,这个过程就是内存映射。
- 这个内存映射过程是通过系统调用mmap()来实现的。
- Binder借助了内存映射的方法,在内核空间的接收方用户控件的数据缓存区之间做了一层内存映射,就相当于直接拷贝到了接收方用户空间的数据缓存区,从而减少了一次数据拷贝。
五、Binder机制是如何跨进程的
- Binder驱动
- 在内核空间创建一块接收缓存区,
- 实现地址映射:将内核缓存区、接收进程用户空间映射到同一接收缓存区
- 发送进程通过系统调用 (copy_from_user) 将数据发送到内核缓存区。由于内核缓存区和 接收进程用户空间存在映射关系,故相当于也发送了接收进程的用户空间,实现了跨进程通信
Flutter篇
一、Flutter 中的生命周期
initState()表示当前 State 将和一个 BuildContext 产生关联,但是此时BuildContext
没有完全装载完成,如果你需要在该方法中获取 BuildContext ,可以 new Future.delayed(const
Duration(seconds: 0, (){//context}); 一下。didChangeDependencies()在 initState()之后调用,当 State
对象的依赖关系发生变化时,该方法被调用,初始化时也会调用。deactivate()当 State
被暂时从视图树中移除时,会调用这个方法,同时页面切换时,也会调用。dispose() Widget 销毁了,在调用这个方法之前,总会先调用
deactivate()。didUpdateWidge 当 widget 状态发生变化时,会调用。通过 StreamBuilder 和 FutureBuilder 我们可以快速使用 Stream 和 Future
快速构建我们的异步控件,Flutter 中 runApp 启动入口其实是一个 WidgetsFlutterBinding
,它主要是通过BindingBase 的子类 GestureBinding 、ServicesBinding
、SchedulerBinding 、PaintingBinding 、SemanticsBinding 、 RendererBinding
、WidgetsBinding 等,通过 mixins 的组合而成的。 Flutter 中的 Dart
的线程是以事件循环和消息队列的形式存在,包含两个任务队列,一个是 microtask 内部队列,一个是event 外部队列,而
microtask 的优先级又高于event。因为 microtask 的优先级又高于 event,同时会阻塞event
队列,所以如果microtask 太多就可能会对触摸、绘制等外部事件造成阻塞卡顿哦。Flutter 中存在四大线程,分别为 UI Runner、GPU Runner、IO Runner, Platform Runner
(原生主线程),同时在 Flutter 中可以通过 isolate 或者compute 执行真正的跨线程异步操作。
二、Widget 和 element 和 RenderObject 之间的关系?
- Widget是用户界面的一部分,并且是不可变的。
- Element是在树中特定位置Widget的实例。
- RenderObject是渲染树中的一个对象,它的层次结构是渲染库的核心。
Widget会被inflate(填充)到Element,并由Element管理底层渲染树。Widget并不会直接管理状态及渲染,
而是通过State这个对象来管理状态。Flutter创建Element的可见树,相对于Widget来说,是可变的,通常界面开发中,我们不用直接操作Element,而是由框架层实现内部逻辑。就如一个UI视图树中,可能包含有多个TextWidget(Widget被使用多次),但是放在内部视图树的视角,这些TextWidget都是填充到一个个独立的Element中。Element会持有renderObject和widget的实例。记住,Widget
只是一个配置,RenderObject 负责管理布局、绘制等操作。在第一次创建 Widget 的时候,会对应创建一个 Element,然后将该元素插入树中。如果之后 Widget 发生了变化,则将其与旧的
Widget 进行比较,并且相应地更新 Element。重要的是,Element 不会被重建,只是更新而已。
三、mixin extends implement 之间的关系?
继承(关键字 extends)、混入 mixins (关键字 with)、接口实现(关键字
implements)。这三者可以同时存在,前后顺序是extends -> mixins -> implements。
Flutter中的继承是单继承,子类重写超类的方法要用@Override,子类调用超类的方法要用super。
在Flutter中,Mixins是一种在多个类层次结构中复用类代码的方法。mixins的对象是类,mixins绝不是继承,也不是接口,而是一种全新的特性,可以mixins多个类,mixins的使用需要满足一定条件
就举例这么多了,面试题也不是几个就能全部覆盖的,毕竟面试官不是吃素的,他会换着花样问你有需要面试题的朋友可以关注一下哇哇,以上都可以分享!!!