注册

一些之前遇到过但没答上来的Android面试题

这段时间面了几家公司,也跟不同的面试官切磋了一些面试题,有的没啥难度,有的则是问到了我的知识盲区,没办法,Android能问的东西太多了,要全覆盖到太难了,既然没法全覆盖,那么只好亡羊补牢,将这些没答上来的题目做下记录,让自己如果下次遇到了可以答上来


TCP与UDP有哪些差异


这道题回答的不全,仅仅只是将两个协议的概念说了一下,但是真正的差异却没有真正答上来,后来查询了一下资料,两者的差异如下



  • TCP是传输控制协议,是面向连接的协议,发送数据前需要建立连接,TCP传输的数据不会丢失,不会重复,会按照顺序到达
  • 与TCP相对的,UDP是无连接的协议,发送数据前不需要建立连接,数据没有可靠性
  • TCP的通信类似于打电话,需要确认身份后才可以通话,而UDP更像是广播,不关心对方是不是接收,只需要播报出去即可
  • TCP支持点对点通信,而UDP支持一对一,一对多,多对一,多对多
  • TCP传输的是字节流,而UDP传输的是报文
  • TCP首部开销为20个字节,而UDP首部开销是8个字节
  • UDP主机不需要维持复杂的连接状态表

TCP的三次握手


这道题以及下面那道虽然说上来了,但是也没有说的很对,仅仅只是说了下每次握手或者挥手的目的,中间的过程没有说出来,以下是三次握手以及四次挥手的详细过程



  • 第一次握手:客户端将SYN置为1,随机生成一个初始序列号seq发送给服务端,客户端进入SYN_SENT状态
  • 第二次握手:服务端收到客户端的SYN=1之后,知道客户端请求建立连接,将自己的SYN置1,ACK置1,产生一个ack=seq+1,并随机产生一个自己的初始序列号,发送给客户端,服务端进入SYN_RCVD状态
  • 第三次握手:客户端检查ack是否为序列号+1,ACK是否为1,检查正确之后将自己的ACK置为1,产生一个ack=服务器的seq+1,发送给服务器;进入ESTABLISHED状态;服务器检查ACK为1和ack为序列号+1之后,也进入ESTABLISHED状态;完成三次握手,连接建立

TCP的四次挥手



  • 第一次挥手:客户端将FIN设置为1,发送一个序列号seq给服务端,客户端进入FIN_WAIT_1状态
  • 第二次挥手:服务端收到FIN之后,发送一个ACK为1,ack为收到的序列号加一,服务端进入CLOSE_WAIT状态,这个时候客户端已经不会再向服务端发送数据了
  • 第三次挥手:服务端将FIN置1,发送一个序列号给客户端,服务端进入LAST_ACK状态
  • 第四次挥手:客户端收到服务器的FIN后,进入TIME_WAIT状态,接着将ACK置1,发送一个ack=序列号+1给服务器,服务器收到后,确认ack后,变为CLOSED状态,不再向客户端发送数据。客户端等待2* MSL(报文段最长寿命)时间后,也进入CLOSED状态。完成四次挥手

从浏览器输入地址到最终显示页面的整个过程


这个真的知识盲区了,谁会平时没事在用浏览器的时候去思考这个问题呢,结果一查居然还是某大厂的面试题,算了也了解下吧



  1. 第一步,浏览器查询DNS,获取域名对应的ip地址
  2. 第二步,获取ip地址后,浏览器向服务器建立连接请求,发起三次握手请求
  3. 第三步,连接建立好之后,浏览器向服务器发起http请求
  4. 第四步,服务器收到请求之后,根据路径的参数映射到特定的请求处理器进行处理,并将处理结果以及相应的视图返回给浏览器
  5. 第五步,浏览器解析并渲染视图,若遇到js,css以及图片等静态资源,则重复向服务器请求相应资源
  6. 第六步,浏览器根据请求到的数据,资源渲染页面,最终将完整的页面呈现在浏览器上

为什么Zygote进程使用socket通信而不是binder


应用层选手遇到偏底层问题就头疼了,但是这个问题还是要知道的,毕竟跟我们app的启动流程相关



  1. 原因一:从初始化时机上,Binder通信需要在Android运行时以及Binder驱动已经初始化之后才能使用,而在这之前,Zygote已经启动了,所以只能使用socket通信
  2. 原因二:从出现的先后顺序上,Zygote相比于Binder机制,更早的被设计以及投入使用,所以在Android的早期版本中,Android就已经使用socket来监听其他进程的请求
  3. 原因三:从使用上,socket通信不依赖于Binder机制,它是一种简单通用的IPC机制,也不需要复杂的接口定义
  4. 原因四:从兼容性上来讲,socket是一种跨平台的IPC机制,可以在不同的操作系统和环境中使用。
  5. 原因五:从性能上来讲,由于使用Zygote通信并不是频繁的操作,所以使用socket通信不会对系统性能造成显著影响
  6. 原因六:从安全性上来讲,使用socket可以确保只有系统中特定的服务如system_server才能与Zygote通信,从而提升一定的安全性

使用Binder的好处有哪些


上面那个问题问好了紧接着就是这道题,我嗯嗯啊啊的零碎说了几个,肯定也是不过关的,回头查了下资料,使用Binder的优势如下



  • 从效率上来讲,Binder比较高效,相比较于其他几种进程的通信方式(管道,消息队列,Socket,共享内存),Binder只需要拷贝一次内存就好了,而除了共享内存,其余都都要拷贝两次内存,共享内存虽然不需要拷贝,但是实现方式复杂,所以综合考虑Binder占优势
  • 使用的是更加便于理解,更简单的面向对象的IPC通信方式
  • Binder既支持同步调用,也支持异步调用
  • Binder使用UID和PID来验证请求的来源,这样可以确保每个Binder事务可以精确到发起者,为进程间的通信提供了保障
  • Binder是基于c/s架构,架构清晰明确,Server端与Client端相对独立
  • Binder有一套易于使用的API供进程间通信,将复杂的内部实现隐藏起来

如果一个线程连续调用两次start,会怎样?


会怎样?谁知道呀,正常人谁会没事去调用两次start呢?但是这个还真有人问了,我只能说没遇到过,后来回去自己试了下才知道


image.png

如上述代码所示,有一个线程,然后连续调用了两次start方法,当我们运行一下这段代码后,得到的结果如下


image.png

可以发现线程有正常运行,但同时也因为多调了一次start而抛出了异常,这个异常在start方法里面就能看到


image.png

有一个状态为started,正常第一次启动线程时候,started为false,所以是不会抛出异常的,started为true的地方是在下面这个位置


image.png

调用了native方法nativeCreated后,started状态位才变成true,这个时候如果再去调用start方法,那么必然会抛出异常


如何处理协程并发的数据安全


之前遇到过这么个问题,并发处理的协程之间是否可以保证数据安全,这个由于之前有实验过,所以想都没想就说可以保证数据安全,但面试官只是呵呵了一下,我捉摸着难道不对吗,后来回去试了一下才发现,不一定就能保证数据安全,看下面这段代码


image.png

这段代码里面在runBlocking中创建了1000个协程,每一个协程都对变量count做自增操作,最后把结果打印出来,我们预期的是打印出的结果就是1000,实际结果如下


image.png

看到的确就是1000,没啥毛病,多试几次也是一样的,但是如果换一种写法试试看呢


image.png

原本都是runBlocking里面的子协程,现在将这些协程变成非runBlocking的子协程,结果是不是还是1000呢,看下结果


image.png

明显不是了,所以并发处理的协程,并不能保证数据安全,那么如何可以让数据安全呢,有以下几个办法


原子类


image.png

这个好理解,同处理线程安全差不多


channel


image.png

receive函数只有等到阻塞队列里面有数据的时候才会执行,没有数据的时候会一直等待,所以这就能保证这些协程可以并发执行,不过要注意的是这里的Channel一定要设置队列大小,不然程序会一直阻塞,receive一直在等待队列里面有数据


mutex


image.png

使用互斥锁的方式,withLock函数内部执行了获取锁跟释放锁逻辑,将变量count保护起来,实现数据安全,除此之外,还可以使用lockunLock函数来实现,代码如下


image.png

总结


总的来讲自己在系统层面,偏底层的那些问题上,还是掌握的不多,这个也跟自己多年徘徊在应用层开发有关,底层知识用到的不多,自然也就忽略了,但是如果面试的话,就算是面的应用层,也是需要知道一些底层方面的知识,不然面试官随便问几个,你不会,别人会,岗位不就被别人拿走了吗


作者:Coffeeee
来源:juejin.cn/post/7402204610978545673

0 个评论

要回复文章请先登录注册