进程和线程
进程是拥有资源的基本单元,它使得多个程序阔以并发的执行,以此来提高系统的资源利用率和吞吐量,它拥有独立的地址控件和独立的堆,创建和销毁进程的开销大。
线程的进行资源调度的基本单位,它主要是用来减少程序在并发执行时所需要付出的时间开销,可以提高操作系统的并发性能,同一进程的各个线程之间共享进程的开销,创建和销毁的开销小
区别:
进程比线程更加的健壮,因为他有独立的地址空间,多进程的情况下,一个进程崩溃掉并不会影响其他进程,而多线程的情况下一个线程崩溃,其他的线程都会挂掉。但是多进程的切换开销更大,因为通常伴随着页调度,而线程切换只需要切换线程上下文就可以了
从输入网址到获得页面的过程
1.浏览器查询dns,获取域名对应的ip地址
2.浏览器获得域名对应的ip地址以后,发起三次握手
3.tcp/ip连接建立后,浏览器向服务器发送http请求
4.服务器接收到这个请求后,返回数据给浏览器
5.浏览器根据请求到的资源进行渲染
tcp和udp的区别
1.tcp是面向连接的,udp是无连接的:tcp在传输数据之前必须先建立好连接,而且是一对一的连接,而udp可以一个主机同时向多个主机发送消息
2.tcp可靠,udp不可靠:tcp要通过三次握手、四次挥手来建立和断开连接,并且在传输过程中还有流量控制、拥塞控制、失败重传等机制,而udp是尽最大努力交付,不保证可靠交付数据
3.tcp只支持点对点通信,udp支持一对一,一对多,多对一等多种通信方式
4.tcp面向字节流,udp面向报文:面向字节流的原因是因为tcp连接是可靠的,所以可以多次发送,一次接收,而udp无连接,所以必须一次发送,一次接收,因为udp不能保证发送数据的都是同一个客户端
5.tcp的首部开销更大,有20个字节,而udp首部8个字节并且固定不变
6.tcp由于要进行三次握手、四次挥手等操作,所以tcp的传输速度比udp慢
DNS解析过程
1.当用户通过浏览器访问某域名时,浏览器首先会在自己的缓存中查找是否有该域名对应的ip地址
2.如果浏览器缓存中没有对应ip就会自动检查用户计算机系统host文件的dns缓存是否有该域名对应的ip
3.如果系统中也没有的话就会去检查路由器缓存中是否存在。这三步都是客户端的dns缓存
4.客户端查询不到ip地址时就会进入isp dns缓存中进行查询
5.如果还是查不到的话就会进入根服务器中查询,根域名收到请求后会查看区域文件记录,如果没有就将其管辖范围内顶级域名服务器ip告诉本地dns服务器
6.顶级域名服务器接收到请求后查询自己的缓存,如果没有就进入下一级域名服务器进行查找,并重复此步骤直到找到
7.本地域名服务器把返回的结果保存到缓存,同时将结果返回给客户端,客户端通过这个ip地址与web服务器建立连接
数据库中的索引一般是什么结构
一般使用B+树来做索引。选择B+树而不是B树或红黑树的原因如下:
1.B+树只有叶子结点存放数据,其余的节点都存储索引。B树的节点既要存储索引,又要存储数据
2.B+树的磁盘Io代价更低,查询效率也更加稳定,更有利于数据的范围查找
3.红黑树一般是存储在内存中才会使用的数据结构,而索引是存放在磁盘中的,当存储大量数据时,红黑树由于深度过大的原因导致磁盘IO过于频繁,进而导致效率低下(因为要获取磁盘上的数据,必须先通过磁盘移动臂移动到数据所在的页面,然后找到指定盘面,接着找到数据所在的隧道,最后才进行读写),而红黑树的深度过大导致磁盘io频繁读写,而B+树可以有多个子女就能够降低树的高度以此来减少磁盘IO。它将节点的大小设为等于一个页,这样每个节点只需要一次IO就可以完全载入
数据库中的事务
事务就是一组原子性的操作,这些操作要么全部发生,要么全部不发生,不能只发生一部分
事务具有ACID四种特性:原子性,一致性,隔离性,持久性
原子性:事务是一个不可分割的操作
一致性:事务开始前和结束后,数据库的完整约束不被破坏
隔离性:每个读写事务之间是相互分开的,在事务提交前对其他事务是不可见的
持久性:事务一旦提交,其结果就是永久性的,宕机也能恢复
http和https的区别
http是一个超文本传输协议,用于在浏览器和服务器之间传递信息,是以明文的方式发送内容的,它不提供任何方式的数据加密,所以不是安全的。https就是带有ssl的http,它的出现就是为了解决http的安全问题
http协议运行在tcp上,明文传输,客户端与服务端都都无法验证对方的身份
https是带有ssl的http,运行于ssl上,ssl运行于tcp上,是添加了加密和认证机制的http
http的连接很简单,是无状态的,而https协议由ssl+http协议构成,可以进行加密、身份验证和网络协议
端口不同:http是80,https是443
资源消耗不同:https由于加密解密会消耗更多的cpu和内存资源,它的握手阶段比较耗时,会使界面的加载时间延长接近百分之50
https的安全信道建立过程
首先客户端发起https请求,服务端返回包含公钥的证书,客户端对证书进行验证,抽取公钥,再产生一个随机数并使用公钥进行加密,然后将加密后的信息发送给服务器,服务端根据私钥对随机数进行解密,服务端通过客户端传过来的随机数构造加密算法,对结果进行加密后返回给客户端
注:https中只有服务端保存了私钥
对称加密与非对称加密
对称加密就是加密和解密使用的是同一个密钥,非对称加密使用的是公钥和私钥,公钥可以随意发布,私钥只有自己知道。由于非对称加密不需要发送用来解密的密钥,所以可以保证安全性,但是比对称加密慢,所以一般还是用对称加密来传送消息,只是对称加密使用的密钥可以通过费对称加密的方法发送出去
怎么保证线程安全性
线程安全性体现在原子性、可见性、有序性。所以要保证安全性可以从这三个点入手
原子性:提供互斥访问,同一时刻只能有一个线程操作(这里可以使用原子类,synchronized)
可见性:一个线程对主内存的修改可以及时被其他线程看到(synchronized、volatitle)
有序性:一个线程观察其他线程中的指令执行顺序,happens-before原则
什么情况下volatile能够保证线程安全?
1.运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
2.变量不需要与其他状态变量共同参与不变约束
除了synchronized,还有什么方式能够保证线程安全
lock,原子类
Activity A 启动 Activity B,两个Activity的生命周期顺序?,按back键返回呢?
onPause(A) -> onCreate(B) -> onStart(B) -> onResume(B) -> onStop(A)
B中按back键返回:onPause(B) -> onRestart(A) -> onStart(A) -> onResume(A) -> onStop(B) -> onDestroy(B)
A中按back键返回:onPause(A)-> onStop(A) -> onDestroy(A)
Android三种存储方式的选择和应用场景
文件:最基本的一种数据存储方式,不对存储的内容进行任何的格式化处理,所有的数据都原封不动的保存到文件中,适合存储一些简单的文本数据或者二进制数据
SharedPreferences:用于存储少量数据,数据格式较为简单,使用键值对进行存储。比如应用程序的各种配置信息等。它具有一定的缓存机制,并发读写可能会导致不可预知的结果
SQLite:是Android的内置数据库,是一款轻量级的关系型数据库,运算速度快,占用内存小,支持标准的sql语法,遵循了数据库的ACID事务
类加载的生命周期(JVM的启动过程)
加载、验证、准备、解析、初始化、使用、卸载
验证:为了确保字节流中所包含的信息不回威胁到虚拟机自身的安全,因为有些class并不是由java源码编写而来的,这样java本不能做的事就被其他层面做到了,所以不验证的话可能导致系统崩溃。包括:文件格式验证、元数据验证、字节码验证
jvm的四种引用状态
1.强引用:只要强引用还存在,垃圾收集器就永远不会回收被引用的对象
2.软引用:一些有用但并非必须的对象,在系统将要发生内存溢出之前,会对这些对象实行第二次回收,如果回收了还是没有足够的内存才会抛出内存溢出异常
3.弱引用:非必须对象,强度比软引用更弱,被弱引用关联的对象,只能生存到下一次垃圾收集发生之前
4.虚引用:完全不会对其生存时间造成影响,也无法通过虚引用取得对象实例,只是在回收时回收到一个通知
序列化和反序列化,什么时候需要,什么时候不需要
序列化:把对象转换为字节序列的过程称为对象的序列化
反序列化:把字节序列恢复为对象的过程称为对象的反序列化
当想把内存中的对象状态保存到一个文件中或者数据库中、想用套接字在网络上传送对象、想通过RMI传输对象的时候就要用到的序列化,比如RPC通信时就需要序列化和反序列化
但是有时候并不希望进行序列化,这个时候用上transient关键字就能够禁止序列化了。比如一个用户的某系敏感信息(密码,银行卡号)等等,为了安全起见不希望在网络中被传输,这个可以就要用该关键字禁止序列化了。
为什么要有反射,什么时候用,缺点是什么
当我们的程序在运行时,需要动态地加载一些类,这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载。反射就是用于在运行时需要检测或修改程序行为的地方
它的缺点有3点:
1.性能低:反射包括了一些动态类型,所以jvm无法对这些代码进行优化。所以反射比非反射操作低得多
2.安全限制:反射要求程序必须在一个没有安全限制的环境中运行
3.内部暴露:反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有属性和方法)。反射会降低可移植性,破坏抽象性
hashcode、equals
hashcode主要用于获取哈希码,也就是对象的存储地址。当我们重写equals()方法时,同时也要重写hashcode()方法,主要就是针对映射相关的操作即map或者hashset。hashcode和equals方法都是Object类中的,如果我们只是重写equals方法的话,那么到时候使用hashcode依然是使用的Object的hashcode方法,这样的话哈希码一般都不会相同。而当我们使用hashmap时,当我们调用它的get方法时在内部是根据哈希码和equals共同来获取的,如果不重写hashcode,那么我们可能通过get方法是得不到想要的值的
三次握手、四次挥手以及为什么
第一次握手:客户端将带有syn(syn=j)的数据包发送到服务器,自己进入syn_sent状态,等待服务器确认 syn:同步序列编号
第二次握手:服务器收到数据包后,也就是设置ack为j+1,然后自己的syn设置为k,再将syn和ack发送到客户端
第三次握手:客户端收到服务器的syn和ack后,确认服务端发送过来的ack,再将ack设置为k+1,然后再将ack发送给服务端,之后两者都进入establish状态
原因:1.防止旧的重复连接建立 2.同步双方的初始化序列号 3.避免资源浪费
1.假如是三次握手,这个时候由于网路的原因导致旧的syn报文比新的先到达,此时服务端返回syn+ack给客户端,客户端就能判断这是否是一个历史连接,如果是的话就会发送rst给服务端终止此次连接。而如果是两次握手,就不能判断当前连接是否是历史连接(第三次握手时如果是历史连接发送的是rst报文,不是的话发送的是ack报文)
2.tcp双方都必须维护一个序列号,它可以去除重复的数据,根据序列号按顺序接收,标识发出的数据包哪些已经被对方接收到。而两次握手只能保证一方的初始化序列号被对方成功接收,无法保证双方的初始化序列号都被接收,而服务端可以将ack和syn一起发送给客户端,因为就不要四次握手了
3.如果只有两次握手,当客户端的syn在网络中阻塞时,就收不到服务端的ack报文,就会重新发送syn,由于没有第三次握手,服务端就不清楚客户端是否已经收到了自己发出的ack,所以每收到一个syn都会主动建立一个连接,就会造成资源浪费
四次挥手:
第一次挥手:客户端发送一个FIN,用来关闭客户端和服务端的数据传输,客户端进入FIN_WAIT_1状态
第二次挥手:服务端收到FIN后,发送一个Ack给客户端,确认序号为收到序号+1,服务端进入CLOSE_WAIT状态,客户端收到ack后进入FIN_WAIT_2状态,此时tcp连接处于半关闭状态
第三次挥手:服务端发送一个FIN用来关闭服务端和客户端的数据传输,服务端进入LAST_ACK状态
第四次挥手:客户端收到FIN后,接着发送一个ACK给服务端,确认序号为收到序号+1,进入TIME_WAIT状态,服务端收到ack后进入CLOSE状态,在2msl时间后,客户端也进入close状态,完成四次挥手
原因:因为关闭连接时,客户端向服务端发送fin,仅仅表示客户端不再发送数据但是还能接收数据,服务端收到客户端的fin后,会先回复一个ack报文,表示确认客户端和服务端之间的连接断开,但是服务端和客户端之间还没断开,这个时候服务端可能还有数据需要发送给客户端,等到服务端没有数据发送给客户端后会再发送fin给客户端,用来关闭服务端和客户端之间的通信,因此是4次挥手。
数据结构:10大排序中的两个
这里说下快排和堆排:https://blog.csdn.net/qq_40058686/article/details/104888783
Handler的作用
因为如果在主线程中执行耗时操作,界面可能会出现假死现象,这个时候我们需要将耗时操作放到子线程中执行,而子线程可能会涉及到UI更新,而在子线程中更新UI是十分危险的,Handler就是来解决这个问题的,它运行在UI线程中,与子线程之间可以通过message对象来传递数据,配合主线程进行UI更新
Handler机制的原理
Android提供了Handler和Looper来满足线程间的通信,Handler是先进先出原则,Looper类用来管理特定线程内对象之间的消息交换
Handler主要有创建消息、发送消息、处理消息三个功能
1.创建消息:每一个消息都需要被指定的Handler处理,通过Handler创建消息就可以完成这个功能。Android消息机制中引入了消息池,Handler创建消息时首先会查询消息池中是否有消息存在,如果有则直接从消息池中获取,如果没有则重新初始化一个消息实例。使用消息池的好处就是:消息不被使用时,不会作为垃圾被回收,而是放入消息池,可供下次Handler创建消息时使用。它提高了消息对象的复用,减少了垃圾回收的次数
2.Handler发送消息
UI主线程初始化第一个Handler时会通过ThreadLocal创建一个Looper,该Looper与UI主线程对应。使用ThreadLocal的目的是保证每一个线程只创建唯一一个Looper。之后的其他Handler初始化时都是直接获取第一个Handler创建的Looper。Looper初始化时会创建一个消息队列。
Handler持有对UI主线程消息队列和Looper的引用,子线程可以通过Handler将消息发送给UI线程的消息队列
3.Handler处理消息
UI主线程通过Looper查询消息队列,当发现有消息存在时会将消息取出,首先分析消息,通过消息的参数判断该消息对应的Handler,然后将消息分发到指定的Handler进行处理
怎么避免Handler的内存泄漏
在Java中非静态内部类和匿名内部类都会隐式持有当前类的外部引用,由于handler是非静态内部类所以其持有当前Activity的隐式引用,如果handler没有被释放,其所持有的外部引用就不能被释放,假如此时Activity不需要了,但是Handler还持有对它的引用,则该Activity就不能被回收,就会发生内存泄漏
解决方案:方法1.在Activity被销毁时,清空Handler中未执行或正在执行的Callback以及Message,也就是在Activity中的OnDestroy方法中调用handler的removeCallbacksAndMessages()方法
方法2.创建一个新的类设置为静态内部类并继承自Handler,在里面使用弱引用就能够保证在下次GC时被回收(不太好)
Looper.loop()是否会阻塞线程,为什么?
不会阻塞。
对于一个线程,是一段可执行的代码,当可执行代码执行完成后,线程声明周期就会终止。而对于主线程,我们并不希望运行一段时间自己就退出。而想要保证线程一直存活,做法就是死循环,它能够保证不会被退出。这里虽然有一个死循环,但是线程依然能够去处理其他事务,也就是通过创建新线程的方式。在进入Looper.loop()之前就会通过thread.attach()方法来建立新的binder线程来处理其他事务。虽然主线程中有死循环,但是并不会特别消耗cpu资源,这里利用了epoll机制,也就是在messageQueue没有消息时,就会阻塞在loop的queue.next()中,此时主线程会释放cpu资源进入休眠状态,直到下个消息到达或有事务发生,所以其实主线程很多时候是处于休眠状态,并不会消耗大量cpu资源。而Activity的生命周期的实现是通过handler的消息机制实现的,也就是handler机制用于在同一个进程的线程间通信
接口和抽象类的区别
抽象类支持方法的默认实现,接口不支持,接口是完全抽象的
子类可以继承抽象类,子类要实现一个接口,那么要实现接口的所有方法,或者将该子类声明为抽象类
抽象类有构造函数,接口没有
抽象类可以被实例化,只是抽象类的实例化不是通过new实现的,而是子类继承父类,在子类进行实例化之前,父类先实例化了。而接口是完全不能被实例化的
对MVP的理解
MVP将M与V分隔开来,通过P交互,这样在Android中就可以明确的把Activity当做View,View和Model之间不能直接交互,当View有变动或者Model有变动时,并不会相互影响。这样的话,耦合度低,对后期维护来说就比较友好。因为View与Model分离的缘故,Model可以单独进行单元测试
安卓的生命周期
onCreate、onStart、onResume、onPause、onStop、onDestroy
还有一个onRestart,也就是当当前Activity处于onStop()时假如重新恢复到当前Activity时就会来到onRestart(),然后进入onStart()
ANR原因及排查
ANR即应用无响应,主要有三种类型
1.按键或触摸时间在特定时间无响应(5s)
2.BroadcastReceiver在特定时间无响应(10s)
3.Service在特定的时间无法处理完成(20s)
具体就是在主线程做耗时操作、数据库操作、大量读写操作,或者非主线程持有lock导致主线程等待lock超时,非主线程终止或崩溃导致主线程一直等待
排查:ANR发生时会在log中输出错误信息,从log中可以获得ANR的类型,cpu的使用情况,cpu使用率过高可能是CPU饥饿导致ANR,cpu使用率过低可能是主线程被block了,IOwait高是主线程进行了IO操作
还可以去trace文件中查看调用stack
避免:避免在主线程上进行复杂耗时的操作,如果broadCastReceiver要进行复杂的操作时可以在onReceive()方法中启动一个Service来处理
怎么解决加载大图的OOM问题
1.先将bitmapFactory.option.inJustDecodeBounds设置为true,这样就只会加载图片的宽高等信息,并不会将图片加载到内存
2.根据imageView空间的大小和图片大小计算出比例关系inSampleSize,并设置给options.inSample
3.将options.inJustDecodeBounds设置为false,再重新加载图片,这样加载的就是压缩之后的图片
单例模式:https://blog.csdn.net/qq_40058686/article/details/104193461
HashMap的扩容机制以及为什么是两倍扩容
HashMap使用的是懒加载,构建完hashMap对象后,只要不进行put方法,hashMap并不会去初始化或者扩容table,当首次调用put方法时,hashMap会发现table为空然后调用resize方法初始化,添加完元素后如果hashMap发现size大于threshold,则会调用resize进行扩容。
扩容过程如下:
1.若threshold(阈值)不为空,table首次初始化大小为阈值,否则初始化为16
2.默认的负载因子大小为0.75,当一个map填满了75%时,就会扩容,扩容后的大小为原来的两倍
3.重新调整map的大小,并将原来的对象放入新的bucket数组中
两倍扩容的原因:hashMap是根据key的hash值决定key放入哪个桶中,通过tab = [(n-1)&hash]公式计算出来的。因为n永远是2的次幂,所以n-1通过二进制表示,永远都是尾端以连续1的形式表示,这样做运算时后x位的1就会保留。好处就是:
1.&运算速度比%快
2.能够保证索引值肯定在capacity中,不会超出数组长度
3.(n-1)&hash,当n为2次幂时,满足(n-1)&hash = hash % n这个公式
java垃圾回收机制:https://blog.csdn.net/qq_40058686/article/details/104084172
synchronized修饰代码块,方法,静态代码块的区别
synchronized修饰方法或者代码块,锁的都是对象,而如果修饰的是静态代码块,那么锁住的是类,类的所有对象共用一把锁
get和post的区别
1.get一般从服务器上获取资源,post一般更新服务器上的资源
2.get请求的数据附在url之后,放在报文的请求头中,以?分割url和传输数据,参数之间以&连接,post会把数据放在报文段请求体中
3.post安全性更高
4.get请求的长度受限于url长度的限制,发送的数据量较少
Android的事件分发机制
一般情况下,事件是从用户按下Action_Down那一刻产生的,事件会按照Activity、Viewgroup、View的顺序进行传递,其中有三个比较重要的方法:dispatchTouchEvent()、onTouchEvent()、onInterceptTouchEvent()
Activity的事件分发机制:dispatchTouchEvent()是负责事件分发的,当点击事件产生后,事件首先会传递给当前的Activity,这会调用Activity的dispatchTouchEvent()方法,该方法中首先会判断是否是down事件,如果是的话会调用onUserInteraction()方法,该方法主要是实现屏保功能。然后会尝试去获得PhoneWindow,再调用PhoneWindow的superDispatchTouchEvent()方法,它会调用DecorView的superDispatchTouchEvent()方法。DecorView是继承于FrameLayout的,FrameLayout又是ViewGroup的子类,所以会直接调用ViewGroup的dispatchTouchEvent()方法
ViewGroup的事件分发机制:首先进入ViewGroup的dispatchTouchEvent()方法,该方法判断是否拦截该事件,如果返回true,则表示拦截,也就是在此ViewGroup中进行处理,这个时候需要复写onInterceptTouchEvent(),此时会调用ViewGroup父类的dispatchTouchEvent()方法,然后调用自身的onTouch()、onTouchEvent()、performClick()、onClick()方法。如果返回false则表示不拦截,事件继续向下传递,找到被点击的相应子View空间,调用子View的dispatchTouchEvent()方法
View的事件分发机制:上面的ViewGroup没有拦截事件,接着就会来到View的dispatchTouchEvent(),这时会调用view的onTouch()方法,若返回true,则代表事件被消费,不会再继续往下传递,dispatchTouchEvent()就返回true,这时也不会调用view的onClick()方法了。假如onTouch()返回false,则说明事件没有被消费,继续往下传递,此时dispatchTouchEvent()的返回值就是onTouchEvent(),于是会调用onTouchEvent()方法,接着调用performClick()方法,然后调用onClick()方法,也就是手动回调setOnClickListener()为控件注册点击事件
Java重载、重写(从多态的角度说明)
https://blog.csdn.net/carson_ho/article/details/81502540
内存泄漏和内存溢出的区别及解决方案
内存泄漏:是指程序在申请内存后,无法释放已经申请的内存空间而造成了内存泄漏,一次内存泄漏一般不会有太大影响,但是堆积的后果就是导致内存溢出
内存溢出:指程序申请内存时,没有足够的内存供申请者使用,也就是说需要的空间比拥有的内存大,内存不够用而导致OOM
内存泄漏的解决方法:
1.将类变为静态类,使得它随着活动的结束而结束
2.资源性对象在不使用的时候,应该调用它的close()函数将其关闭掉
3.假如是使用集合导致了内存泄漏,那么在退出程序之前,将集合里面的东西给clear掉
4.假如是WebView造成的内存泄漏,那么在不使用它的时候,调用它的destroy()函数
内存溢出的原因及解决方法:
原因:
1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据
2.集合类中有对象的引用,使用后没有清楚,产生了堆积,导致jvm无法回收
3.代码中存在死循环产生过多的对象实体
4.启动参数内存值设置过小
解决方案:
1.修改jvm的启动参数,直接增加内存(-Xms)
2.检查错误日志,看下是否出现了oom
3.对代码进行分析,看下是否出现了死循环
Android中如何避免内存泄漏?
1.在涉及使用Context时,对于声明周期比Activity长的对象应该使用Application的context,要优秀考虑使用Application的context
2.对于需要在静态内部类中使用非静态外部成员变量(如:Context,View),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏(不推荐)
3.对于不再需要使用的对象,显式地将其赋值为null
内部类的分类及特点
静态内部类:被声明为static的内部类,不能访问外部类的普通成员变量,只能访问外部类中的静态成员变量和静态方法
成员内部类:也就是非静态内部类,可以自由地引用外部类的属性和方法
局部内部类:定义在一个代码块内的类,作用范围为其所在的代码块,不能被public、protected、private、static修饰,只能访问方法中定义为final 的局部变量
匿名内部类:是一种没有类名的内部类,不使用关键词class、extends、implements,它必须继承其他类或实现其他接口
1.匿名内部类不能有构造函数
2.不能定义静态成员、方法和类
3.不能是public、protecte、private、static
4.只能创建匿名内部类的一个实例
注:匿名内部类会导致内存泄漏,它持有外部类的引用,当内部类进行延时操作的时候,如果外部类是Activity买把呢执行destroy后并不会被销毁,因此就会导致内存泄漏
网络的七层模型、五层模型
七层:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
五层:应用层、传输层、网络层、数据链路层、物理层
为什么DNS解析需要先有URL再去获得ip地址而不是直接通过ip地址去获取?
因为ip地址用起来不方便,不好记忆,用域名方便一些
多线程和多进程的区别?
多进程的数据共享复杂,需要用IPC,但是同步简单,占用内存多,切换复杂,cpu利用率低,进程间不会互相影响
多线程共享数据简单,同步复杂,占用内存少,cpu利用率高,一个线程挂掉会导致整个进程挂掉
多进程切换复杂的原因:每个进程都有自己的内存空间,所以进程间切换是内存空间的切换,而多线程共享进程的内存空间,是在一个内存空间里进行切换的。进程切换需要切换页表,而且还伴随着页调度,而线程只需要保存线程上下文信息就好了
recyclerView和listView(缓存相关)
recyclerView和listView的缓存原理大致相同,滑动过程中,离开屏幕的itemView会被回收至缓存,进入屏幕中的itemView则优先从缓存中获取。两者的区别就是缓存的层级不同,listView只有两层,而RecyclerView有四级缓存
listView的两层缓存:mActiveViews、mScrapViews
recyclerView的四级缓存:mAttachedScrap(存储屏幕中显示的ViewHolder)、mCachedViews(存储屏幕外的缓存)、mViewCacheExtension(自定义缓存)、mRecyclerPool(当屏幕外的缓存大小大于2时,会用这个,便于放入mRecyclerPool中缓存)
mActiveViews和mAttachedScrap功能相似,可以快速重用屏幕上可见的列表项ItemView,而不需要重新createView和bindView
mScrapView和mCachedViews+mRecyclerPool功能类似,会缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用
RecyclerView的优势在于:a.mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内也无须bindView快速重用,b.mRecyclerPool可以供多个RecyclerView共同使用
同时,两者的缓存不同,RecyclerView缓存在viewHolder中,可以看做是View+ViewHolder,而ListView缓存在View上
listView获取缓存的方法如下:
listView的mscrapViews是根据pos获取响应的缓存,但并不直接用,是重新getView(这样会重新bindView)。所以listView中通过pos获取的是View
RecyclerView的流程如下:
RecyclerView在获取缓存的时候,是通过匹配pos获得目标的位置的花村,这么做可以不用重新bindView,所以RecyclerView通过pos获取的是viewHolder,再通过对flag判断View是否需要重新bindView
它们两个最大的区别就是数据源改变时的缓存的处理逻辑:listView是将所有的mActiveViews都移到了二级缓存mScrapViews,而RecyclerView会对每一个View修改标志位,区分是否要重新bindView
handler内存泄漏的引用链?
looper -> messageQueue -> message -> handler
如果messageQueue为空就不会出现泄漏