@TOC
(仅适合面试前复习回顾知识点)
一:JAVA基础
1.四大特性及其含义
- 抽象:对现实世界的事物进行概括,抽象为在计算机虚拟世界中有意义的实体
- 封装:将某事物的属性和行为包装到对象中,构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,并且尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系
- 继承:子类继承父类,不仅可以有父类原有的方法和属性,也可以增加自己的或者重写父类的方法及属性
- 多态:允许不同类的对象对同一消息做出各自的响应
2.JVM和JVM 内存模型
- JVM是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域
- Java 多线程之间是通过共享内存来通信的,每个线程都有自己的本地内存
- 共享变量存放于主内存中,线程会拷贝一份共享变量到本地内存
- volatile 关键字就是给内存模型服务的,用来保证内存可见性和顺序性
3.JVM 内存结构
私有数据区包含:
- 程序计数器: 是当前线程所执行的字节码的行号指示器
- 虚拟机栈: 是Java方法执行的内存模型
- 本地方法栈: 是虚拟机使用到的Native方法服务
共享数据区包含:
1.Java堆:
- 用于存放几乎所有的对象实例和数组;
- 是垃圾收集器管理的主要区域,也被称做“GC;
- 是垃圾收集器管理的主要区域,也被称做“GC堆”;
2.方法区
- 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;
- 用于存放编译期生成的各种字面量和符号引用
4.JVM之GC
- 回收区域:只针对堆、方法区;线程私有区域数据会随线程结束销毁,不用回收
- 分代收集 GC 方法会吧堆划分为新生代、老年代 新生代:
新生代:新建小对象会进入新生代;通过复制算法回收对象
老年代:新建大对象及老对象会进入老年代;通过标记-清除算法回收对象 -
判断一个对象是否可被回收:
引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
可达性分析法:通过一系列被称为『GC Roots』的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。 -
回收算法
复制算法:把可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用尽后,把还存活着的对象『复制』到另外一块上面,再将这一块内存空间一次清理掉。
标记-清除算法:首先『标记』出所有需要回收的对象,然后统一『清除』所有被标记的对象。
标记-整理算法:首先『标记』出所有需要回收的对象,然后进行『整理』,使得存活的对象都向一端移动,最后直接清理掉端边界以外的内存。 -
四种引用
强引用:不会被回收
软引用:内存不足时会被回收
弱引用:gc 时会被回收
虚引用:无法通过虚引用得到对象,可以监听对象的回收~
5.类加载过程,类加载时机,类加载器,双亲委托模型
类加载过程
- 加载:获取类的二进制字节流;生成方法区的运行时存储结构;在内存中生成 Class 对象;
- 验证:确保该 Class 字节流符合虚拟机要求;
- 准备:初始化静态变量;
- 解析:将常量池的符号引用替换为直接引用;
- 初始化:执行静态块代码、类变量赋值;
类加载时机
- 实例化对象
- 调用类的静态方法
- 调用类的静态变量(放入常量池的常量除外)
类加载器
- 引导类加载器 - 没有父类加载器
- 拓展类加载器 - 继承自引导类加载器
- 系统类加载器 - 继承自拓展类加载器
双亲委托模型
- 双亲委派的意思是如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。
- 优点:防止重复加载,父加载器加载过了就没必要加载了;安全,防止篡改核心库类
6.Java中堆和栈
- 栈:主要用来存放基本数据类型和局部变量;当在代码块定义一个变量时会在栈中为这个变量分配内存空间,当超过变量的作用域后这块空间就会被自动释放掉。
- 堆:用来存放运行时创建的对象,比如通过new关键字创建出来的对象和数组;需要由Java虚拟机的自动垃圾回收器来管理。
7.重载和重写
- 重写:重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
- 重载:重载是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同.
8.volatile 关键字
- 只能用来修饰变量,适用修饰可能被多线程同时访问的变量
- 相当于轻量级的 synchronized,volatitle 能保证有序性(禁用指令重排序)、可见性;后者还能保证原子性
- 变量位于主内存中,每个线程还有自己的工作内存,变量在自己线程的工作内存中有份拷贝,线程直接操作的是这个拷贝
- 被 volatile 修饰的变量改变后会立即同步到主内存,保持变量的可见性。
9.内部类
内部类就是定义在另外一个类里面的类。它隐藏在外部类中,封装性更强,不允许除外部类外的其他类访问它;但它可直接访问外部类的成员。
- 静态内部类是指被声明为static的内部类,可不依赖外部类实例化;而非静态内部类需要通过生成外部类来间接生成
- 静态内部类只能访问外部类的静态成员变量和静态方法,而非静态内部类由于持有对外部类的引用,可以访问外部类的所用成员
10.Java集合
- Set:代表无序、不可重复的集合,常见的类如HashSet、TreeSet;
- List:代表有序、可重复的集合,常见的类如动态数组ArrayList、双向链表LinkedList、可变数组Vector;
- Map:代表具有映射关系的集合,常见的类如HashMap、LinkedHashMap、TreeMap;
- Queue:代表一种队列集合;
-
ArrayList的底层结构是数组,可用索引实现快速查找;是动态数组,相比于数组容量可实现动态增长;
ArrayList非线程安全,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者,默认初始容量为10,每次扩容为原来的1.5倍; - LinkedList底层结构是链表,增删速度快;是一个双向循环链表,也可以被当作堆栈、队列或双端队列
- Vector使用了synchronized关键字,是线程安全的,比ArrayList开销更大,访问更慢;默认初始容量为10,默认每次扩容为原来的2倍,可通过capacityIncrement属性设置;
- HashSet不能保证元素的排列顺序;使用Hash算法来存储集合中的元素,有良好的存取和查找性能;
- TreeSet是SortedSet接口的实现类,根据元素实际值的大小进行排序;采用红黑树的数据结构来存储集合元素;支持两种排序方法:自然排序(默认情况)和定制排序;
11.HashMap是什么,怎么扩容,put,get元素的过程
- HashMap是使用hash算法,然后基于数组+链表+红黑树来实现的,或许还知道HashMap内部数组的初始长度为16,并且还能自动扩容。当链表长度超过8时,将链表转换为红黑树,这样大大减少了查找时间。
- HashMap是无序的,而LinkedHashMap是有序的HashMap,默认为插入顺序,还可以是访问顺序,基本原理是其内部通过Entry维护了一个双向链表,负责维护Map的迭代顺序
- HashMap几个默认值,初始容量为16、填充因子默认为0.75、扩容时容量翻倍。也就是说当HashMap中元素个数超过160.75=12时会把数组的大小扩展为216=32,然后重新计算每个元素在数组中的位置
- 向Hashmap中put元素时,首先判断key是否为空,为空则直接调用putForNullKey(),不为空则计算key的hash值得到该元素在数组中的下标值;如果数组在该位置处没有元素,就直接保存;如果有,还要比较是否存在相同的key,存在的话就覆盖原来key的value,否则将该元素保存在链头,先保存的在链尾
- 从Hashmap中get元素时,计算key的hash值找到在数组中的对应的下标值,返回该key对应的value即可,如果有冲突就遍历该位置链表寻找key相同的元素并返回对应的value
12.各种锁
- 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
- 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。比如Java里面的同步原语synchronized关键字的实现就是悲观锁。
- 独享锁:是指该锁一次只能被一个线程所持有。
- 共享锁:是指该锁可被多个线程所持有。
- 可重入锁:又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
- 公平锁:等待时间最久的线程会优先获得锁,非公平锁无法保证哪个线程获取到锁,synchronized 就是非公平锁
二:计算机网络
1.网络协议模型
- 物理层:通过媒介传输比特,确定机械及电气规范(比特Bit)
- 数据链路层:将比特组装成帧和点到点的传递(帧Frame)
- 网络层:负责数据包从源到宿的传递和网际互连(包PackeT)
- 传输层:提供端到端的可靠报文传递和错误恢复(段Segment)
- 会话层:建立、管理和终止会话(会话协议数据单元SPDU)
- 表示层:对数据进行翻译、加密和压缩(表示协议数据单元PPDU)
- 应用层:允许访问OSI环境的手段(应用协议数据单元APDU)
2.TCP 和 UDP 区别
- TCP 连接;可靠;有序;面向字节流;速度慢;较重量;全双工;适用于文件传输、浏览器等
- UDP 无连接;不可靠;无序;面向报文;速度快;轻量;适用于即时通讯、视频通话等
3.TCP如何实现可靠性传输,udp如何实现可靠性传输
- TCP使用确认机制、重传机制、滑动窗口。
- UDP传输层无法保证数据的可靠传输,只能通过应用层来实现了。实现的方式可以参照tcp可靠性传输的方式,只是实现不在传输层,实现转移到了应用层。
4.三次握手,四次挥手
三次握手:
- A:请求
- B:收到,Over
- A:Over
- A 和 B 两方都要能确保连接,所以需要三次握手。
- 第三次握手可以避免由于客户端延迟的请求连接的请求,使得服务端无故再次建立连接,所以不能两次握手
四次挥手:
- A:结束
- B:稍等片刻,A完成B未必完成
- B:Over
- A:Over
- 由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭。
- 先关读,后关写”,一共需要四个阶段:服务器读通道关闭->客户机写通道关闭->客户机读通道关闭->服务器写通道关闭。
5.MAC和IP地址
- 每台主机在出厂时都有一个唯一的MAC地址,但是IP地址的分配是根据网络的拓朴结构,得以保证路由选择方案建立在网络所处的拓扑位置基础而不是设备制造商的基础上
- 使用IP地址更方便数据传输。数据包在这些节点之间的移动都是由ARP协议负责将IP地址映射到MAC地址上来完成的。
6.拥塞控制
对网络中的路由和链路传输进行速度限制,避免网络过载,包含四个过程:慢开始、拥塞避免、快重传和快恢复
- 慢开始:假设当前发送方拥塞窗口cwnd的值为1,而发送窗口swnd等于拥塞窗口cwnd,因此发送方当前只能发送一个数据报文段(拥塞窗口cwnd的值是几,就能发送几个数据报文段),接收方收到该数据报文段后,给发送方回复一个确认报文段,发送方收到该确认报文后,将拥塞窗口的值变为2,当前的拥塞窗口cwnd的值已经等于慢开始门限值,之后改用拥塞避免算法。
- 拥塞避免:也就是每个传输轮次,拥塞窗口cwnd只能线性加一,而不是像慢开始算法时,每个传输轮次,拥塞窗口cwnd按指数增长。同理,16+1……直至到达24,假设24个报文段在传输过程中丢失4个,接收方只收到20个报文段,给发送方依次回复20个确认报文段,一段时间后,丢失的4个报文段的重传计时器超时了,发送发判断可能出现拥塞,更改cwnd和ssthresh.并重新开始慢开始算法
- 快重传:要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时捎带确认。快重传算法规定,发送方只要一连收到三个重复确认ACK就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器RTO超时。
-
快恢复:与快速重传配合使用,有以下两个要点:
①当发送方连续收到三个重复确认时,就执行“乘法减小”算法,即把ssthresh门限减半。但是接下去并不执行慢开始算法。
②考虑到如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。所以此时不执行慢开始算法,而是将cwnd设置为ssthresh的大小,然后执行拥塞避免算法。
7.从输入网址到获得页面的过程
- 浏览器向DNS服务器请求解析该URL中的域名所对应的IP地址
- 解析出IP地址后,根据该IP地址和默认端口80,和服务器建立TCP连接
- 浏览器发出读取文件的HTTP请求,该请求报文作为TCP三次握手的第三个报文的数据发送给服务器
- 服务器对浏览器请求作出响应,并把对应的html文本发送给浏览器
- 释放TCP连接,若connection模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求
- 客户端将服务器响应的html文本解析并显示
三:操作系统
1.进程和线程的区别
- 进程:是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
- 线程:是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
- 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
2.死锁的产生和避免
- 死锁是指多个进程因循环等待资源而造成无法执行的现象,它会造成进程无法执行,同时会造成系统资源的极大浪费。
- 死锁的产生条件:互斥,占有且等待,不可抢占,循环等待
- 死锁的解决策略:1.银行家算法 2.鸵鸟算法
- 死锁的避免:通过合理的资源分配算法来确保永远不会形成环形等待的封闭进程链,即“如果一个进程的当前请求的资源会导致死锁,系统拒绝启动该进程;如果一个资源的分配会导致下一步的死锁,系统就拒绝本次的分配”
3.临界区
- 每个进程中访问临界资源的那段程序称为临界区,每次只准许一个进程进入临界区,进入后不允许其他进程进入
- 如果有若干个进程要求进入空闲的临界区,一次仅允许一个进程进入
- 任何时候,处于临界区的进程不可多于一个
- 如已有进程进入自己的临界区,则其他试图进入临界区的进程必须等待
- 进入临界区的进程要在有限时间内退出,以便其他进程能及时进入自己的临界区
- 如果不能进入自己的临界区,就应该让出CPU,避免进程出现忙等等现象
4.进程间的通信方式
- 管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在有血缘关系的进程间使用,进程的血缘关系通常是指父子进程关系。
- 命名管道(named pipe):也是半双工的通信方式,但是它允许无亲缘关系关系进程间通信。
- 信号(signal):是一种比较复杂的通信方式,用于通知接收进程某一事件已经发生。
- 信号量(semophere):信号量是一个计数器,可用来控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 消息队列(message queue):消息队列是由消息组成的链表,存放在内核中,并由消息队列标识符标识。消息队列克服了信号传递消息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 共享内存(shared memory):就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的IPC方式,它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量等配合使用,来实现进程间的同步和通信。
- 套接字(socket):套接口也是进程间的通信机制,与其他通信机制不同的是它可用于不同及其间的进程通信。
5.线程间的通信机制
-
锁机制:包括互斥锁、条件变量、读写锁;
互斥锁提供了以排他方式防止数据结构被并发修改的方法。
读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。 - 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
-
信号机制(Signal):****类似进程间的信号处理
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
6.进程间同步与互斥的区别,线程同步的方式
- 互斥:指某一个资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的
-
同步:是指在互斥的基础上(大多数情况下),通过其它机制实现访问者对资源的有序访问。大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
同步:体现的是一种协作性。互斥:体现的是排它性。 -
同步机制遵循的原则:
1.空闲让进;
2.忙则等待;
3.有限等待;
4.让权等待; -
线程同步的方式:
-----临界区:通过对多线程的串行化来访问公共资源或者一段代码,速度快,适合控制数据访问。
-----互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以可以保证公共资源不会同时被多个线程访问。
-----信号量:它允许多个线程同一时刻访问同一资源,但是需要限制同一时刻访问此资源的最大线程数目。信号量对象与其他前面几种方法不同,信号允许多个线程同时使用共享资源。
-----事件(信号):通过通知操作的方式来保持多线程的同步,还可以方便实现多线程的优先级比较操作。
四.Android
1.Android四大组件
- Activity :是用户操作的可视化界面;它为用户提供了一个完成操作指令的窗口。当我们创建完毕Activity之后,需要调用setContentView()方法来完成界面的显示;以此来为用户提供交互的入口。在Android App 中只要能看见的几乎都要依托于Activity,所以Activity是在开发中使用最频繁的一种组件。
- Service:是一个没有界面常驻后台的组件,它通常用作在后台处理耗时的逻辑,与Activity一样,它存在自己的生命周期,也需要在AndroidManifest.xml配置相关信息。
- Content Provider:主要用来接收和发送广播
- Broadcast Receiver:使用很少,一般通过contentprovider访问外部APP的内部数据以及自身数据可以被外部访问,是应用程序间非常通用的共享数据的一种方式
2.Activity的生命周期
- onCreate():Activity 正在创建,常做初始化工作,如setContentView界面资源、初始化数据
- onStart(): Activity 正在启动,这时Activity 可见但不在前台,无法和用户交互
- onResume():Activity 获得焦点,此时Activity 可见且在前台并开始活动
- onPause(): Activity 正在停止,可做 数据存储、停止动画等操作
- onStop(): ctivity 即将停止,可做稍微重量级回收工作,如取消网络连接、注销广播接收器等
- onDestroy(): Activity 即将销毁,常做回收工作、资源释放
- onRestart():Activity 重新启动,
3.Activity 的四种启动模式、应用场景
- standard 标准模式: 每次启动一个 Activity 都会重新创建一个新的实例,不管这个实例是否已经存在,此模式的 Activity 默认会进入启动它的 Activity 所属的任务栈中;
- singleTop 栈顶复用模式: 如果新 Activity 已经位于任务栈的栈顶,那么此 Activity 不会被重新创建,同时会回调 onNewIntent方法,如果新 Activity 实例已经存在但不在栈顶,那么Activity 依然会被重新创建;
- singleTask 栈内复用模式: 只要 Activity 在一个任务栈中存在,那么多次启动此 Activity 都不会重新创建实例,并回调onNewIntent 方法,此模式启动 Activity A,系统首先会寻找是否存在 A 想要的任务栈,如果不存在,就会重新创建一个任务栈,然后把创建好 A 的实例放到栈中;
- singleInstance单实例模式: 这是一种加强的 singleTask 模式,具有此种模式的 Activity 只能单独地位于一个任务栈中,且此任务栈中只有唯一一个实例;
4.两个Activity 之间跳转
- 首先定义两个Activity,分别为A和B。
- 当我们在A中激活B时,A调用onPause()方法,此时B出现在屏幕时,B调用onCreate()、onStart()、onResume()。
- 这个时候B【B不是一个透明的窗体或对话框的形式】已经覆盖了A的窗体,A会调用onStop()方法
5.启动其他应用的Activity
- 在保证有权限访问的情况下,通过隐式Intent进行目标Activity的IntentFilter匹配
- 一个intent只有同时匹配某个Activity的intent-filter中的action、category、data才算完全匹配,才能启动该Activity。
- 一个Activity可以有多个 intent-filter,一个 intent只要成功匹配任意一组 intent-filter,就可以启动该Activity。
6.ActivityManagerService
- ActivityManagerService是Android中最核心的服务 , 主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块类似。
7.Service的生命周期
- onCreate():服务第一次被创建时调用
- onStartComand():服务启动时调用
- onBind():服务被绑定时调用
- onUnBind():服务被解绑时调用
- onDestroy():服务停止时调用
8.Service的两种启动方式
-
Context.startService()方式启动:**通过startService启动后,service会一直无限期运行下去,只有外部调用了stopService()或stopSelf()方法时,该Service才会停止运行并销毁。
生命周期如下所示,启动时,startService->onCreate()->onStart(),停止时,stopService->onDestroy() -
Context.bindService()方式启动:bindService启动的服务和调用者之间是典型的client-server模式。调用者是client,service则是server端。service只有一个,但绑定到service上面的client可以有一个或很多个。当多个client都解除绑定之后,系统才会销毁service。
生命周期如下所示,绑定时,bindService -> onCreate() –> onBind(),调用者退出即解绑定时,Srevice就会unbindService –>onUnbind() –> onDestory()。
9.IntentService与普通Service的区别
- 相同点:IntentService继承自Service,因而两个都是服务
- 不同点: IntentService内部开启了一个HandlerThread线程,然后使用此线程的Looper构造了一个Handler对象,在这个线程中执行Handler对象发送的消息。IntentService可以执行耗时任务。普通的Service如果不开启子线程的话是不能执行耗时任务的,会造成ANR。
10.Service保活
- 在Service的onStartCommand()中设置flages值为START_STICKY,使得Service被杀死后尝试再次启动Service
- 提升Service优先级,比如设置为一个前台服务
- 在Activity的onDestroy()通过发送广播,并在广播接收器的onReceive()中启动Service
11.Broadcast的分类
- Normal Broadcast:一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们接收的先后是随机的。
- Ordered Broadcast:一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递,所以此时的广播接收器是有先后顺序的,且优先级(priority)高的广播接收器会先收到广播消息。有序广播可以被接收器截断使得后面的接收器无法收到它。
- System Broadcast:Android系统内置了多个广播,涉及到手机的基本操作,基本上都会发出相应的系统广播。
- Local Broadcast:发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收本应用程序发出的广播。
- Sticky Broadcast:这种广播会一直滞留,当有匹配该广播的接收器被注册后,该接收器就会收到此条广播。
12.Broadcast的两种注册形式
- 代码动态注册:动态注册的接收器必须要在程序启动之后才能接收到广播
- 配置文件里静态注册:态注册的接收器即便程序未启动也能接收到广播,比如想接收到手机开机完成后系统发出的广播
13.ContentProvider(内容提供商)
- ContentProvider的作用是为不同的应用之间数据共享,提供统一的接口,我们知道安卓系统中应用内部的数据是对外隔离的,要想让其它应用能使用自己的数据(例如通讯录)。
- 封装。对数据进行封装,提供统一的接口,使用者完全不必关心这些数据是在 DB ,XML 、Preferences 或者网络请求来的。当项目需求要改变数据来源时,使用我们的地方完全不需要修改。
- 提供一种跨进程数据共享的方式。
- 数据更新通知机制,数据是在多个应用程序中共享的,当其中一个应用改变了这些共享数据的时候,它有责任通知其它应用程序,让它们知道共享数据被修改了,这样它们就可以作相应的处理。
14.View工作原理
ViewRoot 的 performTraversals 方法调用触发开始 View 的绘制,然后会依次调用
- 先measure测量,用于确定View的测量宽高,再 layout布局,用于确定View的最终宽高和四个顶点的位置,最后 draw绘制,用于将View 绘制到屏幕上
- performMeasure:遍历 View 的 measure 测量尺寸
- performLayout:遍历 View 的 layout 确定位置
- performDraw:遍历 View 的 draw 绘制
14.Android中的事件传递机制
- 一个MotionEvent产生了以后,系统需要将这个点击事件传递到一个具体的View上。
-
事件的传递顺序:Activity(Window) -> ViewGroup -> View
- 用户触摸该位置时候,最先调用最外层父控件ViewGroupA的 dispatchTouchEvent(),然后调用ViewGroupA的onInterceptTouchEvent(),如果返回true,表示拦截事件,此时直接调用 ViewGroupA的 onTouchEvent(),如果返回false,就把事件继续给父控件 ViewGroupB分发;
- 首先调用 父控件ViewGroupB的 dispatchTouchEvent(),然后调用 ViewGroupB的 onInterceptTouchEvent(),如果返回true,表示拦截事件,直接调用 ViewGroupB的 onTouchEvent(),如果返回false,表示不拦截,继续把事件分发给下一级View;
15.ANR问题
ANR(Application Not responding)。Android中,主线程(UI线程)如果在规定时内没有处理完相应工作,就会出现ANR。
- 在5秒内没有响应输入的事件(例如,按键按下,屏幕触摸
- BroadcastReceiver在10秒内没有执行完毕
- service 前台20s后台200s未完成启动 Timeout executing service
- app自身进程主线程阻塞, 挂起, 死锁导致
- 机器本身的cpu, 内存, io繁忙, 无法及时响应
ANR异常是经常遇到的问题,主要的解决办法最常用的就是不要在主线程中做耗时的操作,而应放在子线程中来实现
16.子线程禁止访问UI
- UI控件非线程安全,在多线程中并发访问可能会导致UI控件处于不可预期的状态
- UI是属于主线程的,子线程要想用,不好意思资源只有一份,子线程抢不过主线程(也就是所谓的避免死锁)
- Android是单线程模型
为什么不加锁?
- 锁机制会让UI访问的逻辑变得复杂
- 锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行
17.Handler的实现原理
- 当我们需要在子线程处理耗时的操作(例如访问网络,数据库的操作),而当耗时的操作完成后,需要更新UI,这就需要使用Handler来处理,因为子线程不能做更新UI的操作,Handler能帮我们很容易的把任务(在子线程处理)切换回它所在的线程。简单理解,Handler就是解决线程和线程之间的通信的。
-
Handler有几个核心的类:Message,Handler,Message Queue,Looper和ThreadLocal
- 一个Thread只能有一个Looper,可以有多个Handler
- Looper有一个MessageQueue,可以处理来自多个Handler的Message;MessageQueue有一组待处理的Message,这些Message可来自不同的Handler;Message中记录了负责发送和处理消息的Handler;Handler中有Looper和MessageQueue
- 主线程直接new一个Handler,子线程的Looper需要手动去创建`
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();//为子线程创建Looper
new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//子线程消息处理
}
};
Looper.loop();
}
}).start();
18.ThreadLocal(本地线程)
- ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
- 解决并发问题:使用 ThreadLocal 代替 Synchronized 来保证线程安全,同步机制采用空间换时间 -> 仅仅先提供一份变量,各个线程轮流访问,后者每个线程都持有一份变量,访问时互不影响。
- 解决数据存储问题: ThreadLocal 为变量在每个线程中创建了一个副本,所以每个线程可以访问自己内部的副本变量。
19.线程池
通过ThreadPoolExecutor并通过一系列参数来配置各种各样的线程池,线程池可以减少创建和销毁线程的次数,从而减少系统资源的消耗
当一个任务提交到线程池时
- 首先判断核心线程池中的线程是否已经满了,如果没满,则创建一个核心线程执行任务,否则进入下一步
- 判断工作队列是否已满,没有满则加入工作队列,否则执行下一步
- 判断线程数是否达到了最大值,如果不是,则创建非核心线程执行任务,否则执行饱和策略,默认抛出异常
线程池的好处:
- 对多个线程进行统一地管理,避免资源竞争中出现的问题。
- 对线程进行复用,线程在执行完任务后不会立刻销毁,而会等待另外的任务,这样就不会频繁地创建、销毁线程和调用GC。
- JAVA提供了一套完整的ExecutorService线程池创建的api,可创建多种功能不一的线程池,使用起来很方便。
线程池分类
- FixThreadPool:线程数量固定的线程池,所有线程都是核心线程,当线程空闲时不会被回收;能快速响应外界请求。
- CachedThreadPool:线程数量不定的线程池(最大线程数为Integer.MAX_VALUE),只有非核心线程,空闲线程有超时机制,超时回收;适合于执行大量的耗时较少的任务
- ScheduledThreadPool:核心线程数量固定,非核心线程数量不定;可进行定时任务和固定周期的任务。
- SingleThreadExecutor:只有一个核心线程,可确保所有的任务都在同一个线程中按顺序执行;好处是无需处理线程同步问题。
20内存泄漏和内存溢出
- 内存泄漏:是指程序在申请内存后,无法释放已申请的内存空间。是造成应用程序OOM的主要原因之一
- 内存溢出:是指程序在申请内存时,没有足够的内存空间供其使用。
21.内存泄漏,java是否存在内存泄漏
内存泄漏(Memory Leak)是指程序在申请内存后,无法释放已申请的内存空间。
- 单例模式导致的内存泄漏:单例传入参数this来自Activity,使得持有对Activity的引用。
- Handler导致的内存泄漏:Message持有对Handler的引用,而非静态内部类的Handler又隐式持有对外部类Activity的引用,使得引用关系会保持至消息得到处理,从而阻止了Activity的回收。
- 线程导致的内存泄漏:AsyncTask/Runnable以匿名内部类的方式存在,会隐式持有对所在Activity的引用。
- 资源未关闭导致的内存泄漏:未及时注销资源导致内存泄漏,如BraodcastReceiver、File、Cursor、Stream、Bitmap等。
22.Android中线程
- AsyncTask:内部是Handler和两个线程池实现的,Handler用于将线程切换到主线程,两个线程池一个用于任务的排队,一个用于执行任务,当AsyncTask执行execute方法时会封装出一个FutureTask对象,将这个对象加入队列中,如果此时没有正在执行的任务,就执行它,执行完成之后继续执行队列中下一个任务,执行完成通过Handler将事件发送到主线程。AsyncTask必须在主线程初始化,因为内部的Handler是一个静态对象,在AsyncTask类加载的时候他就已经被初始化了。
- HandlerThread:继承自 Thread,start开启线程后,会在其run方法中会通过Looper 创建消息队列并开启消息循环,这个消息队列运行在子线程中,所以可以将HandlerThread 中的 Looper 实例传递给一个 Handler,从而保证这个 Handler 的 handleMessage 方法运行在子线程中,Android 中使用 HandlerThread的一个场景就是 IntentService。
- IntentService:继承自Service,它的内部封装了 HandlerThread 和Handler,可以执行耗时任务,同时因为它是一个服务,优先级比普通线程高很多,所以更适合执行一些高优先级的后台任务,HandlerThread底层通过Looper消息队列实现的,所以它是顺序的执行每一个任务。可以通过Intent的方式开启IntentService,IntentService通过handler将每一个intent加入HandlerThread子线程中的消息队列,通过looper按顺序一个个的取出并执行,执行完成后自动结束自己,不需要开发者手动关闭。
23.冷启动与热启动
- 冷启动: 当应用启动时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用, 这个启动方式就叫做冷启动(后台不存在该应用进程)。冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。
- 热启动: 当应用已经被打开, 但是被按下返回键、Home键等按键时回到桌面或者是其他程序的时候,再重新打开该app时, 这个方式叫做热启动(后台已经存在该应用进程)。热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application。
24.Android 系统启动流程
- 按电源键
- 加载引导程序 BootLoader 到 RAM
- 执行 BootLoader 程序启动内核
- 启动 init 进程
- 启动 Zygote 和各种守护进程
- 启动 System Server 服务进程开启 AMS、WMS 等
- 启动 Launcher 应用进程
困了,未完待续。。。