1. TCP和UDP的区别
1)TCP是面向连接的,在正式开始传输数据前,要建立可靠的信道,而UDP是无连接的,也就是不建立传输通道,直接发送数据。
2)TCP的传输是一对一的,而UDP可以一对多、一对一、多对一、多对多传输。
3)TCP的数据传输是可靠的,有无差错、不重复、不丢失、且按序到达的特点,而UDP不能保证数据安全到达。
4)TCP对系统资源要求较多,UDP对系统资源要求较少,且UDP具有较好的实时性。
2. HTTP和HTTPS的区别
首先就是https需要申请证书,会产生一定的花费,然后http基于明文传输,而https是由SSL+HTTP构建的可进行加密传输、身份验证的网络协议,比http协议更安全,最后,http和https默认的连接端口不同,http默认的端口号是80,https是443端口。
3. 什么是DNS?
DNS是域名解析系统,是万维网上的存储IP地址和域名映射关系的一个分布式数据库,通过DNS,用户不用记忆难记的IP地址,而是使用域名替代。
4. 为什么选用Binder作为进程间通信的方式
1)从性能的角度考虑:Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要两次。
2)从稳定性的角度考虑:Binder是基于C/S架构的,Client端有什么需求,直接发送给Server端去完成,架构清晰明了,Client端与Server端相互独立,稳定性强。
3)从安全的角度考虑:传统的IPC方式的接收方都无法获得发送进程可靠的UID/PID,从而无法判断对方的身份,而Android为每个应用程序都分配好了自己的UID,在Binder中,Server端根据权限访问控制策略,可以清楚的判断UID是否有访问权限。
5. 为什么Looper.loop()的死循环不会造成ANP?
首先,为了保证主线程在执行完所有代码之后,不会直接结束,所以在Looper.loop()中设置了死循环,在某种意义上讲,此死循环是与应用进程的生命周期是一致的,因为一旦退出死循环,应用也就退出了。并且,Android是依靠事件驱动的,Looper从MessageQueue中获取Message,然后将消息发送给对应的Handler进行处理,loop()仅仅起到了一个控制中枢的功能,如果此时主线程阻塞,只能说是事件处理造成阻塞。
6. 集合和数组的区别
1)数组是定长的,初始化之后长度就不可改变;而集合的长度是可变的,可以随着元素的增长而增长。
2)集合只能存储引用类型的变量,而数组既可以存储引用变量,也可以存储基本数据类型。
7. LinkedList和ArrayList的区别
ArrayList是基于数组实现的,LinkedList是基于双向链表的,因此若对List进行get、set操作,ArrayList的效率要优于Linked,同样,若是执行add和remove操作,LinkedList的效率要更高一些。
8. ArrayList的扩容机制
当使用ArrayList的无参构造方法初始化一个ArrayList时,此ArrayList的size = 0,capacity = 0;当执行第一次add操作时,size = 1,capacity初始化为10;此后执行add操作时,ArrayList会判断新添加数据后,size是否超过capacity,如果超过,则新建一个是原数组1.5倍的新数组,然后将原数组中的数据拷贝到新数组中。
9. HashMap的实现原理
1)put方法:首先将K,V封装到node对象中,然后调用K的hashCode()得出k的hash值,然后将hash值转化为数组下标,如何此数组下标上没有任何元素,就把node添加到这个位置上,如果此数组下标有元素,就会拿着k和链表上每个节点的k进行equals 。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。
10. 操作系统的四个特征
1)并发:并发是指两个或多个事件在同一时间间隔内发生,宏观上是并行执行的,微观上是交替运行的。
2)共享:即资源共享,是指系统中的资源可供内存中的多个并发执行的进程共享。
- 互斥共享方式:系统中的某些资源,虽然可以由多个进程共享,但在某一段时间内,只允许一个进程访问资源。
- 同时共享方式:系统中的某些资源,允许多个线程在同一时间内访问,所谓的同时,往往是宏观上的,而在微观角度,则是交替执行的。
3)虚拟:虚拟是一种管理技术,将物理上的实体转换为用户可以感受到的多个逻辑物。
4)异步:在多道程序环境下,多个程序可以同时运行,但由于系统资源有限,进程的执行并不是连贯的,而是走走停停的这样一种方式去运行。
11. 同步和异步
1)同步是指程序1调用程序2,程序1必须等到程序2执行完毕之后,再继续运行。
2)异步是指程序1调用程序2,程序1可以在程序2运行过程中继续运行,程序2在执行完毕后通知程序1,程序1再来处理。
12. 进程和线程的区别
1)进程是资源分配的基本单位,线程是程序执行的基本单位。
2)系统为进程分配独立的地址空间和系统资源,而进程中的线程共享进程中的系统资源和地址空间。
3)线程的上下文切换比进程的上下文切换要快的多。
13. TCP是如何保证可靠性的
TCP通过校验和、序列号、超时重传、滑动窗口、拥塞控制、流量控制来实现可靠性。
- 校验和:通过校验和的方式,接收端可以检测数据是否有差错和异常,如果有则直接丢弃数据段,重新发送。
- 序列号:接收端可以根据序列号来判断是否有未接收到的数据段,并且去掉序列号重复的数据段。
- 超时重传:发送端发送数据之后,若未在规定时间收到接收方的确认,则认为数据段已丢失,重新发送。
- 滑动窗口:滑动窗口即提高的数据的传送速率,也避免了发送方发送数据过多而导致接收方无法处理的异常。
- 流量控制:如果主机A 一直向主机B发送数据,不考虑主机B的接受能力,则可能导致主机B的接受缓冲区满了而无法再接受数据,从而会导致大量的数据丢包,引发重传机制。而在重传的过程中,若主机B的接收缓冲区情况仍未好转,则会将大量的时间浪费在重传数据上,降低传送数据的效率。所以引入流量控制机制,主机B通过告诉主机A自己接收缓冲区的大小,来使主机A控制发送的数据量。流量控制与TCP协议报头中的窗口大小有关。
- 拥塞控制:在数据传输过程中,可能由于网络状态的问题,造成网络拥堵,此时引入拥塞控制机制,在保证TCP可靠性的同时,提高性能。
14. 解释一下HTTP的长连接和短连接
- 短连接:HTTP1.0默认短连接,短连接就是说,浏览器每次和服务器进行一次交互,都要建立一次连接,如果浏览器接收到的的web资源需要其他web资源,则需要重新建立连接。
- 长连接:如果使用长连接,则当客户端和服务器通信时,用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问服务端上的网页,会继续使用这一条已经建立的连接,直到超过限定的时间。
15. 在浏览器输入一个url的过程
首先是浏览器发送请求,利用DNS域名解析器将域名转换为相应的IP地址;浏览器获得域名对应的IP地址后,会向服务器发送连接请求,通过三次握手建立TCP连接之后浏览器会向服务器发送HTTP请求;服务器收到这个请求,会根据路径参数映射到特定的处理器上进行处理,最后将处理结果及相应视图返回给浏览器;浏览器解析并渲染视图,如果遇到js、css文件等,则重复上述步骤向服务器发送请求。
16. http2.0和http1.1的区别
1)新的二进制格式:http1.1的解析是基于文本,而文本的表现形式多样,用于数据传输就造成了不必要的系统开销,http2.0是基于二进制,二进制只是0、1的组合,更加方便健壮的实现了数据传输。
2)头部压缩:http1.1的header带有大量信息,而且每次都要重新发送,http2.0通过压缩算法对header进行压缩,大大减少了数据传输的压力。
3)多路复用:在http1.1之中,client向服务器发送请求后,必须收到服务器的确认信息后,才会继续发送下一个请求,但在http2.0之中,多个请求可以同时以帧的方式向服务端发送,最后再在服务端根据序列进行合并。
4)服务器推送:服务器除了对最初请求的响应外,服务器还可以额外的向客户端推送资源,而无需客户端明确的请求。
17. Handler原理
在使用Handler进行消息的收发之前,首先我们要先运行Looper.parpare()方法,判断当前线程是否存在Looper,如果存在,抛出异常,如果不存在那就创建一个,并同时创建唯一与之对应的MessageQueue对象,交给Looper持有。接着我们要实例化一个新的Handler,在Handler的构造方法中,Handler持有当前线程的Looper与MessageQueue对象,并为其设置回调方法。接着,我们要执行Looper.loop()方法,此方法中有一个死循环,Looper不断地从MessageQueue中取出Message,在dispatchMessage()方法中调用Handler的handlerMessage()方法对Message进行分发。
18. View的绘制过程
当Activity创建完成后,会将一个DecorView添加到Window中,同时创建ViewRoot的实现类ViewRootImpl对象将之相关联。ViewRootImpl会调用perfromTraVersals()方法,performTraversals方法会依次调用performMeasure、performLayout、performDraw三个方法,分别完成view的测量、布局和绘制。
以view的测量为例,在performTraversals方法中,会通过getRootMeasureSpec方法来获取到根布局的MeasureSpec,作为参数传递给performMeasure方法,在performMeasure方法中,将父布局的MeasureSpec传递给measure方法中的onmeasure方法,再加上子View自己的layoutParams,得到自己的MeasureSpec,如果子View是ViewGroup,则继续调用Measure方法来遍历计算其中每个子View的大小;如此反复完成整个view树的遍历,performLayout和performDraw也都是如此。
20. JVM的内存结构中,哪些是线程私有的?
程序计数器、java虚拟机栈、本地方法栈
21. 分代的垃圾回收策略
分为年轻代和老年代,年轻代又分为一个Eden区和两个Survivor区,绝大多数刚刚被创建的对象会存放在 Eden 区。
当 Eden 区第一次满的时候,会进行垃圾回收。首先将 Eden 区的垃圾对象回收清除,并将存活的对象复制到 S0,此时 S1 是空的。
下一次 Eden 区满时,再执行一次垃圾回收。此次会将 Eden 和 S0 区中所有垃圾对象清除,并将存活对象复制到 S1,此时 S0 变为空。
如此反复在 S0 和 S1 之间切换几次(默认 15 次)之后,如果还有存活对象。说明这些对象的生命周期较长,则将它们转移到老年代中。
22. 可达性分析算法
在Java中,是通过可达性分析算法来判断对象是否存活,该算法将GC Roots 对象作为起点,开始向下搜索,搜索走过的路径被称为引用链,当一个对象与引用链上的任一个对象都无关联时,则证明该对象是不可用的。
23. 可作为GC Roots的对象
虚拟机栈和本地方法栈中引用的对象、方法区中的静态变量和常量引用的对象
24. 通过finalize()判定对象是否存活
当然,即便被判断为不可达的对象,也不一定是非死不可的,这个时候他们处于缓刑阶段,如果要真正宣告其死亡,则必须再经过两次标记。
第一次标记并进行筛选:对象没有finalize()方法,或者该方法已被虚拟机调用过 ,虚拟机将这两种情况都视为没有必要继续执行,对象被回收。
第二次标记:如果这个对象被判定为有必要执行finalize()方法,那么这个对象就会被放在一个F-Queue的队列中,并由稍后虚拟机建立的一条低优先级的Finalize()线程去执行,所谓的执行是指虚拟机会触发这个方法,但并不承诺会等待它运行结束,这么做的原因是,如果一个finalize()方法执行缓慢,甚至发生死循环,就会导致其他F-Queue队列中的其他对象永远处于等待状态,甚至导致整个内存回收系统崩溃。若对象在执行finalize()方法时,成功与引用链上任意一个对象相关联,譬如把自己赋值给某个成员变量,那么在第二次标记时,它将被移除“即将回收”的集合,否则标记后被回收。
25. 简单介绍一下抽象类
简单来说,抽象类就是用abstract修饰的类,其中包含一个或多个抽象方法,抽象方法用也用abstract修饰,它只有声明,而没有具体的实现。 抽象类不能用来创建对象,只能用来被继承,并且若有子类要继承抽象类,则必须重写其中所有的抽象方法,如果不重写,则必须将子类也定义为abstract
26. 简单介绍一下接口
接口和抽象类差不多,也是一种特殊的类,由全局常量和抽象方法所组成,如果子类实现它,则必须重写接口的方法。它之所以被设计出来为其他类指定规范。
27. 抽象和接口的区别
1)抽象类有构造方法,接口不存在。
2)抽象类中可以有实现方法,接口中必须全部是抽象方法。
3)实现抽象类需要用extends关键字,接口的子类需要implements来实现,并且接口可以实现多继承。
28. 什么是死锁?
多个线程同时被阻塞,他们中的一个或多个都在等在其他资源的释放,比如说:线程A持有资源2,线程B持有资源1,他们都想获取对方的资源,所以这两个线程就会因为互相等待而进入死锁状态。
29. 产生死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:获得资源的进程如果阻塞,也不会主动放弃资源
- 不剥夺条件:进程已经获得的资源,如果未使用完毕,不能强行剥夺
- 循环与等待条件:两个或多个进程之间形成一种头尾相接的循环等待资源关系
30. wait()和sleep()方法的共同点和不同点
1)相同点:两者都会暂停线程的执行,使线程进入等待状态。
2)不同点:
- sleep()方法没有释放锁,而wait()方法释放了锁
- 执行sleep()方法后,可以通过超时或者调用interupt()方法来唤醒休眠中的线程,执行wait()方法后,通过notify()方法或notifyAll()方法来唤醒等待线程。
31. 如何保证线程安全
1)尽量不适用共享变量,将不必要的共享变量转换为局部变量来使用。
2)使用synchronized关键字同步代码块,或者使用lock为操作进行加锁。
3)使用ThreadLocal为每一个线程建立一个变量的副本,各个线程间独立操作,互不影响。
32. synchronized和lock的区别
1)lock是一个接口,而synchronized是java中的一个关键字
2)synchronized在发生异常时,会主动释放锁,而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象。
3)通过Lock可以知道有没有成功获取锁(tryLock()方法:如果获取锁成功,则返回true),而synchronized却无法办到。
4)当竞争资源非常激烈时(即有大量线程同时竞争),Lock的性能要远远优于synchronized。
33. volitail关键字
Java提供了volatile关键字来保证可见性,当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去主存中读取新值。
34. synchronized 关键字和 volatile 关键字的区别
- volatile关键字是线程同步的轻量级实现,所以volatile性能比synchronized关键字要好。
- volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。
- volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。
35. 使用线程池的好处
1)降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。
2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
3)提高线程的可管理性。使用线程池对线程进行的统一分配、调优和监控。
36. 说一说几种常见的线程池
- FixedThreadPool:该线程池中的线程数量始终不变,当有新的任务提交时,若有空闲线程,则立即执行。若没有,则新任务会被暂存在一个任务队列中,等到有线程空闲时再处理。
- SingleThreadExecutor:只会创建一个线程执行任务,若有多余的任务被提交到该线程池,会被保存在一个任务队列中,等到线程空闲,再按顺序执行任务。
- CachedThreadPool:线程池的线程数量不确定,若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
37. 讲一讲https的原理
- 采用 HTTPS 协议的服务器必须要有一套数字证书。颁发证书的同时会产生一个私钥和公钥。私钥由服务端自己保存,不可泄漏。公钥则是附带在证书的信息中,可以公开。证书本身也附带一个电子签名,这个签名用来验证证书的完整性和真实性,可以防止证书被篡改。
- 客户端向服务器发起https请求,然后连接到 server 的 443 端口
- 服务器响应客户端请求,将证书传递给客户端。
- 客户端解析证书并对其进行验证。如果证书中的信息有问题,就会向访问者显示一个警告,由其选择是否还要继续通信。
- 如果证书没有问题,客户端就会从服务器证书中取出服务器的公钥A。然后客户端还会生成一个随机码 KEY,并使用公钥将其加密。
- 客户端把加密后的随机码 KEY 发送给服务器,服务器使用私钥B将其解密。之后就可以进行对称加密进行通信。
- 发送方使用密钥 (随机码 KEY)对数据进行对称加密并发送给接收方,接收方使用相同的密钥 (随机码 KEY)解密数据。
38. APP的启动过程
1)点击桌面应用图标后,Launcher的startActivity()方法,会通过Binder通信,调用system_server进程中AMS服务的startActivity方法,向Zygote进程发送创建进程的请求。
2)Zygote进程fork出App进程,开始执行应用的main方法,创建ActivityThread线程,并初始化ApplicationThread。
3)App进程发送请求,通过Binder调用system_sever进程中AMS服务的attachApplication方法,将ApplicationThread对象与AMS绑定
4)system_server进程通过binder向App进程发送handleBindApplication请求和scheduleLaunchActivity请求。
5)App进程的ApplicationThread在收到请求后,通过handler向主线程发送BIND_APPLICATION和LAUNCH_ACTIVITY消息。
6)主线程在收到Message后,创建Application,再通过反射机制创建目标Activity。
7)到此,App便正式启动,开始进入Activity生命周期。
39. 如何避免配置发生改变时Activity发生重建
要避免配置改变的时候,Activity被杀死后被重建,可以通过改变主配置文件中相应Activity的configChanges属性,例如,我们要避免在横竖屏转换的时候,Activity的重建,可以为相应Activity添加ConfigChanges=“orientation|screenSize”,此时,即便是发生横竖屏转换,Activity也不会发生重建。
40. 优先级低的Activity在内存不足被回收后怎样做可以恢复到销毁前状态?
当优先级低的Activity在内存不足被杀死后,系统会自动调用onSaveInstanceState()方法,并且将Activity的一些临时性状态保存到Bundle类型的对象中,并将其作为参数传递给onRestoreInstanceState()和onCreate()方法,我们只需要在这两个方法中取出相应参数对Activity进行处理,便可以恢复到销毁前的状态。
41. 简单介绍一下AMS
AMS是Android中最核心的服务,主要负责四大组件的启动、切换、调度,以及应用进程的管理和调度等工作。比如说,我们在APP的启动过程中,就是通过AMS中的startActivity()方法来请求创建进程,接着,也是在AMS中向ApplicationThread传递handleBindApplication请求和scheduleLaunchActivity请求,来通知ActivityThread创建Application和Activity。
42. 如何启动其他应用的Activity
我们可以通过隐式Intent的方式来启动其他应用的Activity,其方法是,首先定义一个隐式Intent,为它添加相应的action属性,然后在另一个应用的主配置文件中,为相应的Activity添加intent-filter标签,并在其中添加与创建的隐式Intent对应的action属性,不过要注意的是,如果该Activity是程序入口,则应在intent-filter中设置category的模式为LAUNCHER,如果不是的话,category的模式设置为Default。
43. Fragment和Activity的关系
1)Fragment依赖于Activity存在,不能独立存在,Activity是Fragment的一个容器
2)一个Activity中可以有多个Fragment,一个Fragment也可以在多个Activity中出现
3)Fragment的生命周期方法是由宿主Activity调用的,并且他的生命周期方法比Activity多。
4)Activity在运行时可以动态的删除或添加Fragment
44. 如何在Activity和Service之间通信
要在Activity和Service之间进行通信,可以通过广播的形式。比如说,我们在播放音乐的时候,可以显示音乐播放的进度。首先定义相关的Activity和Service,之后在Activity中创建一个BroadcastReceiver的实现类,在其onReceiver中实现改变进度条的功能,接着实例化BroadcastReceiver,并为其添加intentFilter,使用registerReceiver注册广播,之后在Service中发送广播。
45. 能否在Service中执行耗时操作,如果非要可以怎么做?
因为Service同样执行在主线程中,所以我们不能直接在Service中执行耗时操作,如果非要执行耗时操作,可以通过创建线程,让耗时操作在子线程中执行,否则极有可能造成ANR。
46. 广播的几种形式
- 普通广播:在广播发出之后,所有的广播接收器几乎会同时接收到这条广播消息。
- 有序广播:一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。
- 本地广播:发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收本应用程序发出的广播。
- 粘性广播:这种广播会一直滞留,当有匹配该广播的接收器被注册后,该接收器就会收到此条广播。
47. 广播的两种注册形式?区别在哪?
广播的注册有两种方法:一种在活动里通过代码动态注册,另一种在配置文件里静态注册。不同点是动态注册的接收器必须要在程序启动之后才能接收到广播,而静态注册的接收器即便程序未启动也能接收到广播,比如想接收到手机开机完成后系统发出的广播就只能用静态注册了。
49. Binder通信原理
首先,使用Binder进行通信时,有四个重要的角色,分别是Client、Service、Binder驱动和ServiceManager。首先:Server进程向ServiceManager注册,ServiceManager会存储Service对象的引用, 之后Client进程向ServiceManager请求Service服务,ServiceManager查询完毕后,在通过Binder驱动的时候Binder驱动会将Service对象转换为其代理对象。之后,Client调用对象的相关方法,这个消息会先发送给Binder驱动,Binder驱动再通知对应Service调用相关方法,Service对象经过相应运算后返回处理结果给Binder驱动,Binder驱动再将处理结果返回给Client。
50. 如何保证service不会被杀死?
1)在onStartCommand方法中,返回START_STICKY,该返回值的含义是:当service因系统内存不足被kill后,等到内存空闲时,再尝试重新创建service,一旦创建成功则回调onStartCommand。
2)在主配置文件中,通过设置priority属性来提升service的优先级。
3)就是当Service调用onDestory的时候,发送一个自定义的广播,当收到广播的时候,重新启动service。
4)将service设置成前台服务。
51. Android中四种数据存储的方式
1)文件存储:假如我们想要实现图片加载的网络缓存,可以文件存储来将已加载的图片添加到硬盘中,之后若应用还要访问资源,可直接通过文件访问。
2)SharedPreferences:它是一种轻量型的存储方式,适合用于存储一些简单的参数配置。比如说在游戏中是否打开声音、上一次登录的账号等。
3)SQLite数据库存储:一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,在存储大量复杂的关系型数据的时可以使用。
4)ContentProvider:四大组件之一,用于数据的存储和共享,不仅可以让不同应用程序之间进行数据共享,还可以选择只对哪一部分数据进行共享,可保证程序中的隐私数据不会有泄漏风险。
52. 什么是序列化?
序列化表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。如果在Android中,需要通过Intent传输自定义对象类型的数据,必须进行序列化。
53. Serializable接口和Parcelable接口的区别?
1)Serializable是java提供的,而Parcelable是android系统特有的。
2)Parcelable效率比实现Serializable接口高效,可用于Intent数据传递,也可以用于进程间通信(IPC)
3)Serializable的实现更简单,但进行的I/O操作更多,会产生更多的系统开销。
54. MeasureSpec是什么?
MeasureSpec代表View的测量值,是一个32位的int值,前两位表示SpecMode(测量模式),后30位表示SpecSize(某种测量模式下的规格大小)。
55. SpecMode的三种类型
- UNSPECIFIED:父容器不对View有任何限制,要多大有多大。(一般不常用)
- EXACTLY(精确模式):父视图为子视图指定一个确切的尺寸SpecSize。对应LyaoutParams中的match_parent或具体数值。
- AT_MOST(最大模式):父容器为子视图指定一个最大尺寸SpecSize,View的大小不能大于这个值。对应LayoutParams中的wrap_content。
56. View的事件分发机制
在View的分发机制中,有三个重要的方法,分别是dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()方法。其中,dispatchTouchEvent负责将事件分发到其子View或当前View中,onInterceptTouchEvent方法仅存在与ViewGroup中,用于拦截点击事件,onTouchEvent中完成对点击事件的处理。在整个事件分发过程中,如果没有中断,那么整个的事件流向应该是一个类U型图,由Activity的dispatchTouchEvent()方法开始,到ViewGroup的dispatchTouchEvent(),然后是ViewGroup的onInterceptTouchEvent()方法,接着是View的dispatchTouchEvent(),然后是View、ViewGroup、Activity的onTouchEvent方法。如果,dispatchTouchEvent 和 onTouchEvent return false,事件都回传给父控件的onTouchEvent处理。onInterceptTouchEvent如果返回true,事件交给ViewGroup的onTouchEvent来处理。如果dispatchTouchEvent和onTouchEvent返回true,则代表事件被消费,终止传递。
57. onTouch()、onTouchEvent()和onClick()关系?
他们的优先级是onTouch()>onTouchEvent()>onClick()。当事件分发执行dispatchTouchEvent()方法时,会首先判断是否有onTouchListener监听事件,如果有的话,就执行onTouch()方法,如果没有的话,才会继续执行onTouchEvent()方法,然后在其中判断是否执行performClick()方法调用onClick()。
58. 简单介绍一下AsyncTask
AsyncTask是Android已经封装好的轻量级的异步类,是一个抽象类,用来执行异步的耗时任务,它有几个比较重要的抽象方法:doInBackground()方法用于执行耗时操作,onProgressUpdate()用于在主线程显示任务执行的进度,可以通过在doInBackground()中调用publishProgress()来实现。onPostExecute()方法用来在线程任务结束后,将执行结果显示到UI组件。onPreExecute()用于执行线程任务前的操作,其中doInBackground和onPostExecute是必须复写的,其他方法根据需求复写。定义好AsyncTask的子类后,就可以在主线程调用它的execute()方法来启动。
59. AsyncTask和Handler的区别
1)AsyncTask是一个轻量级的异步类,实现方式比较简单快捷,但有多个异步操作进行UI变更时,就会变得复杂。
2)Handler的优点是结构清晰,功能定义明确,但在只执行单个异步任务时,结构不如AsynvTask简单。
60. 简单介绍一下HandlerThread
HandlerThread是封装了Handler和Thread的一个类,用来执行多个耗时操作,而不需要多次开启线程,减少了对性能的消耗;缺点是不能同时进行多个任务的处理,一次只能处理一个任务,处理效率低,可以当做一个轻量级的线程池来使用。
61. 简单介绍一下IntentService
IntentService是Android中的一个封装类,继承自Service,本身是一个抽象类,可以用来执行耗时的异步任务,任务结束后会自动停止,并且它拥有较高的优先级,不易被系统杀死,使用的时候,只需在子类中实现onHandlerInent()方法和构造方法。启动方法和正常的Service相同。
62. String、StringBuffer、StringBuilder的区别
1)String定义的是字符串常量,StringBuffer和StringBuilder定义的是字符串变量,也就是说,String定义的对象一经创建就不能修改,而后两者可变。
2)StringBuffer是线程安全的,Stringbuilder是非线程安全的。
3)String更适用于少量的字符串操作的情况,StringBuilder适用于单线程下在字符缓冲区进行大量操作的情况,StringBuffer适用于多线程下在字符缓冲区进行大量操作的情况
63. String a="“和String a=new String(”")的的关系和异同?
通过String a="“直接赋值的方式得到的是一个字符串常量,存在于常量池;通过new String(”")创建的字符串不是常量是实例对象,会在堆内存开辟空间并存放数据。
64. 什么是反射?
反射就是,在运行状态中,对于任意一个类都能知道它的所有属性和方法,对于任何一个对象都能够调用它的任何一个方法和属性。
65. 讲一讲面向对象的基本特征
面向对象有三大特征,分别是:封装、继承、多态
1)封装:将对象的实现细节隐藏,只提供一些共有方法来作为其他对象访问的接口。
2)继承:在需要定义和实现一个类时,可以从一个已存在的类中派生,这个过程被称为继承。
3)多态:简单来说,就是同一个东西表现出多种状态,比如说重写和重载,都是多态性的一个表现。
66. java常见的访问修饰符
用public修饰的类、类属变量及方法,包内及包外的任何类均可以访问; 用protected修饰的类、类属变量及方法,包内的任何类及包外那些继承了该类的子类才能访问;用private修饰的类、类属变量及方法,只有本类可以访问,而包内包外的任何类均不能访问它。
67. 堆和栈的区别
1)栈由系统自动分配,而堆是人为申请开辟;
2)栈获得的空间较小,而堆获得的空间较大;
3)栈内存的更新速度要快于堆内存;
4)栈是连续的空间,而堆是不连续的空间。
68. 介绍一下TCP的三次握手
客户端向服务器发起连接请求,将报文段的标志位置为SYN并设置seq值,然后将报文段发送给服务端,此时客户端进入SYN_SEND状态;服务器收到客户端的报文段后,设置报文段的标志位、seq值和ACK确认号,然后将报文段发送给客户端,此时服务器进入SYN_RECV状态;客户端收到服务器的报文段,向服务器发送ACK报文段,服务器接收到后,双方都进入ESTABLISHED状态,完成TCP三次握手。
69. 为什么不是两次握手或者四次握手
之所以不是两次握手,是因为,如果客户端的第一次连接请求,在网络节点中滞留,直到客户端放弃连接请求,这时请求才到达服务端,如果是两次握手,那么服务器在收到请求后,就直接向客户端发送连接请求,并且认为已经建立连接,服务端就会等待客户端发送数据,造成了不必要的系统开销,三次握手可以很好的解决这一个问题。
至于为什么不是四次握手,因为三次握手已经可以建立可靠的TCP连接,无需浪费资源反复进行确认。
70. 简单介绍一下四次挥手
首先是主机1向主机2发送一个FIN报文段,主机1进入FIN_WAIT_1状态;主机2收到报文段,向主机1回一个ACK报文段,告诉主机1,我收到了你关闭连接的请求,主机1进入FIN_WAIT_2状态;接着主机2向主机1再次发送FIN报文段,表示自己已经没有数据要发送,同时主机2进入LAST_ACK状态;主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL(报文段最大生存时间)后依然没有收到回复,则证明主机2的连接已经正常关闭,主机1关闭连接。
71. TCP的流量控制
如果发送方把数据发送得过快,接收方可能会来不及接收,这就会造成数据的丢失。TCP通过滑动窗口机制实现了流量控制。在连接建立时,接收方会告诉发送方自己的接收窗口大小。发送方的发送窗口必须小于此数值。如果接收窗口减为0,则发送方停止发送数据,此时发送方会启动一个持续计时器。若持续计时器设置的时间到期,发送方就发送一个检测报文段给接收方。如果接收方可以接收数据,就重新开始发送数据;如果接收方不能接收数据,就重新设置持续计时器。
72. TCP的拥塞控制
TCP通过四种拥塞控制算法来进行拥塞控制:在TCP连接中,发送方维持一个拥塞窗口的变量,其值取决于网络拥塞的程度,只要是没有出现拥塞,拥塞窗口就大一些,如果出现了拥塞,拥塞窗口就小一些。并维护一个慢开始门限值,当拥塞窗口小于慢开始门限值,执行慢开始算法,当拥塞窗口大于慢开始门限值,执行拥塞避免方法。
1)慢开始算法:在慢开始算法中,拥塞窗口的初始值为1,发送方只能发送一个数据报文段,接收方收到该数据报文段后,给发送方回复一个确认报文段,发送方收到该确认报文后,将拥塞窗口的值乘2。
2)拥塞避免算法:在拥塞避免方法中,发送方收到确认报文后,会将拥塞窗口的值加1,当网络出现拥塞时,将慢开始门限值更新为拥塞窗口值的一半,并将拥塞窗口的值置为1,重新开始慢开始算法。
3)快重传:当某个报文段丢失,接收方接收到其他报文段后,发现不是按序到达的报文段,就向发送方发送丢失报文段的重复确认,当发送方接收到某个报文段3次重复确认后,就会立即重传丢失报文段。
4)快恢复:发送方一旦收到3个重复确认,就知道只是丢失个别报文段,并没有发生网络拥塞,就不会执行慢开始算法,而是将慢开始门限值和拥塞窗口的值更新为当前拥塞窗口值的一半,开始执行拥塞避免方法。
73. 基本数据类型和引用数据类型的区别
1)存储位置不同:基本变量类型的具体内容是存放在栈中,引用数据类型的具体内容都是存放在堆中的,而栈中存放的只是其具体内容所在的地址。
2)传递方式不同:基本数据类型的传参是值传递,引用数据类型的传参是传递的变量的引用。
74. Java中创建对象的几种方式
1)使用new关键字
2)调用类名的newInstance()方法创建对象
3)调用已经实现对象的clone()方法(此方式需要类实现Cloneable接口,并重写clone方法)
4)对已经序列化的对象进行反序列化
75. invalidate()和postInvalidate()的区别?
invalidate()是用来刷新View的,必须是在UI线程中进行工作。比如在修改某个view的显示时,调用invalidate()才能看到重新绘制的界面。但是invalidate无法在子线程中调用,所以系统提供了 postInvalidate(),它可以在子线程中调用,直接刷新页面。
76. res目录和assets目录的区别?
res/中的文件会被映射到R.java文件中,访问时可以使用资源Id ,它不可以有目录结构
assets文件夹下的文件不会被映射到R.java中,访问时需要AssetManager类,可以创建子文件夹
77. 强引用,软引用,弱引用,幻象引用有什么区别?
所谓强引用 (Strong Reference) 就是我们常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还活着,垃圾回收不回收这种对象。
软引用,是一种相对强引用弱化一些的引用,只有当 JVM 认为内存不足时,才会试图回收软引用指向的对象。
弱引用,比软引用拥有更短的生命周期,一旦发现了具有弱引用的对象,不管当前内存空间是否足够,都会回收它的内存。
虚引用,形同虚设 ,虚引用不会决定对象的生命周期,如果一个对象仅持有虚引用,其实就和没有任何引用一样。在任何时候都可能被垃圾回收器回收。 虚引用和软引用的一个区别是,虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
78. SharedPreference的apply和commit的区别?
SharedPreference 相关修改使用 apply 方法进行提交会先写入内存,然后异步写入磁盘,commit方法是直接写入磁盘。如果频繁操作的话 apply 的性能会优于 commit,apply会将最后修改内容写入磁盘。但是如果希望立刻获取存储操作的结果,并据此做相应的其他操作,应当使用 commit。
79. Vector和ArrayList的区别?
1)Vector是线程安全的,而ArrayList是线程不安全的,由于线程的同步必然要影响性能,所以ArrayList的性能比Vector要好。
2)当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,如果在集合中使用数据量比较大的数据,用vector有一定的优势。
80. Vector的实现原理?
Vector的实现原理和ArrayList类似,当我们初始化一个Vector时,指定容量增长参数,则默认构建一个初始长度为10的Vector,Vector在每次添加元素时,都要确认是否有足够的容量,如果有,则直接添加,如果没有的话,就根据指定容量增长参数,创建一个长度为原长度+容量增长参数的新数组,然后将数组中的元素拷贝到新数组中去,另外,Vector在很多方法中都用synchronized修饰了代码块,可以保证线程安全。
81. Android中的几种布局方式?
1)LinearLayout:线性布局,可以指定其中的控件是水平排列还是垂直排列,如果是水平排列,则布局中的控件都在一行,如果是垂直排列,则一行只有一个元素,多个元素依次垂直往下。
2)FrameLayout:最简单的一个布局,它指定屏幕上的一块空白区域,所有的元素都会被固定在该区域的左上角,后一个子元素将会直接在前一个子元素之上进行覆盖填充,把它们部份或全部挡住。
3)AbsoluteLayout:绝对布局,它通过layout_x和layout_y来为控件指定具体的坐标,一般不建议使用,因为做屏幕适配比较复杂。
4)TableLayout:表格布局,它类似Html里面的Table。每一个TableLayout里面有表格行TableRow标签,TableRow里面可以具体定义每一个元素。
5)RelativeLayout:相对布局,它指定某一个元素为参照物,来进行定位,此布局中存在诸如:layout_below、layout_alignParentRight等属性,用来为控件设置相对位置。
82. 简单介绍一下ANR
在Android里,应用程序的响应性是由Activity Manager和WindowManager系统服务监视的 。当它监测到以下情况中的一个时,Android就会针对特定的应用程序显示ANR:
1)主线程对输入事件在5秒内没有处理完毕 (前提是要有输入事件)
2)主线程在执行BroadcastReceiver的onReceive函数时10秒内没有执行完毕
3)主线程在执行Service的各个生命周期函数时20秒内没有执行完毕
造成以上几点的原因主要有两种,一种是,在主线程中进行了耗时操作,导致主线程阻塞。另一种是,其他进程一直占用CPU,导致当前进程无法抢占到时间片。
83. String可以被继承吗?
不可以,String类是用final修饰的,不可被继承。
84. final修饰符的作用?
1)当用final修饰一个类时,表明这个类不能被继承。
2)如果一个类用final修饰,类中所有的成员方法都会被隐式的指定为final,final修饰的方法不可被重写。
3)用final修饰的成员变量表示常量,只能被赋值一次,赋值后值无法改变。
85. 线程同步的方式
1)使用synchronized关键字修饰方法或代码块
2)使用volite关键字修饰变量
3)使用lock为操作加锁
86. 为什么要进行四次挥手,而不是三次
当被动关闭端在收到FIN报文段之后,需要发送两次报文段。如果被动关闭方在该连接上没有数据要发送,那么三次挥手是可以的,但是如果被动关闭方还需要等待一段时间才可以关闭连接,那么这样的三次挥手就不能满足需求。
87.当客户端进入TIME-WAIT状态的时候,为什么要等待2MSL才关闭连接?
因为最后客户端发送的ACK报文段有可能丢失,从而导致服务器接收不到确认报文。那么这时服务器可以重传这个FIN报文段,使客户端再重传一次确认报文,最后客户端和服务器都能正常的关闭。假设客户端不等待2MSL,而是在发送完ACK之后直接释放关闭,一但这个ACK丢失的话,服务器就无法正常的进入关闭连接状态。另外,客户端在发送最后一个ACK之后,再经过经过2MSL,就可以使本链接所产生的所有报文段都从网络中消失。从保证在关闭连接后不会有还在网络中滞留的报文段去骚扰服务器。
88. 介绍一下内部类
java中的内部类主要分为:成员内部类、局部内部类、匿名内部类和静态内部类。
- 成员内部类:是定义在外部类中,来作为外部类的成员,成员内部类可以无条件访问外部类所有的属性和方法,但是当外部类想要访问成员内部类的属性和方法时,则必须创建内部类的实例来进行访问。
- 局部内部类:局部内部类存在于外部类的方法中,它的访问权限仅限于方法内部,局部内部类和局部变量一样,前面不能加访问修饰符以及static关键字。
- 匿名内部类:如果一个对象我们只需要使用一次,那么便可以不把它保存到类型变量中,而是直接通过new 类名.方法名的方法去使用。
- 静态内部类:通过static修饰的内部类,可以称之为静态内部类,静态内部类不能直接访问外部类的非静态成员,但可以通过new 外部类().成员的方式访问,在外部类的静态成员与内部类的静态成员相同的情况下, 可以通过"类名.静态成员"的方式来访问外部类的静态成员;如果不同,可以直接调用外部类的静态成员名,并且创建静态内部类的对象时,不需要外部类的对象,可以直接创建。
89. 简单介绍一下static
static一般用来修饰方法、变量和代码块,使其在类加载的时候便将其初始化。然后就可以通过 “类名.” 的方式去访问。
用static修饰的静态方法,其不能在内部访问非静态成员方法和变量,反过来则可以。
静态变量被所有的对象共享,在内存中只有一个副本,在类加载的时候初始化。而非静态变量在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
此外,static也可以修饰代码块,来优化程序性能,使需要实例化的对象仅在类加载的时候被初始化一次,从而优化性能。
90. 简单介绍一下Callable接口
我们可以使用Callable接口来创建一个线程,和继承Thread类,实现Runable接口的方式不同,使用Callable接口,当线程任务结束后,我们可以接收一个返回值。当使用的时候,我们只需要继承Callable接口,并重写其call方法。然后在主线程中实例化实现了Callback接口的类,将其作为参数实例化一个FutureTask对象,然后将FutureTask对象作为参数实例化一个Thread类,然后启动线程。等到线程执行结束后,便可以在FutureTask对象中获取返回值。
91. 简单介绍一下线程池
简单来说,所谓线程池就是存储线程的容器,在Java中提供了ThreadPoolExecutor类,来进行有关线程的操作,它有几个重要的参数,分别是:corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler,其中:
- corePoolSize:代表核心线程数,当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize
- maximumPoolSize:线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。
- keepAliveTime:线程存活保持时间,当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
- workQueue:任务队列,用于传输和保存等待执行任务的阻塞队列。
- threadFactory(线程工厂):用于创建新线程。
- handler:线程饱和策略,当线程池和队列都满了,再加入线程会执行此策略。
它的执行流程一般是:
- 当一个任务到来的时候如何它的核心线程数即corePoolSize没满的话,就会创建一个核心线程去执行该任务
- 如何核心线程数满了,但是阻塞队列没有满的话,就会将该线程先放入阻塞队列中
- 如果核心线程和阻塞队列都满了,但是最大线程数没有满的话,就会新建一个非核心线程去执行该任务
- 如果核心线程数、阻塞队列、最大线程数都满了的话,就会执行线程池的拒绝策略
92. 为什么要使用同步
当多个线程同时操作一个可共享的资源变量时,将会导致数据不准确,相互之间产生冲突,因此要加入同步,以避免在该线程没有完成操作之前,被其他线程调用, 从而保证了该变量的唯一性和准确性。
93. 实现线程同步的几种方式
- 使用synchronize关键字:当synchronize修饰的是代码块或方法时,锁住的是当前对象,此时如果另外一个线程实例化此类,那么这个新new出来的对象依旧可以对synchronize修饰的方法或者代码块进行访问,当synchronize修饰对象的时候,锁住的是对象,其他线程访问该对象时都会阻塞。当线程修饰静态方法或类时,锁住的是此类的所有对象,此时即便是其他线程想要访问当前类的其他对象,也会产生阻塞。
- 使用volatile关键字,Java提供了volatile关键字来保证可见性,当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去主存中读取新值。
- 使用lock为操作加锁