java面试题2022

28次阅读

(1)java 面试题(基础 + 进阶)(必须)java 中 == 和 equals 和 hashCode 的区别 == 是运算符,用来比较两个值、两个对象的内存地址是否相等;equals 是 Object 类的方法,默认情况下比较两个对象是否是同一个对象,内部实现是通过“==”来实现的。如果想比较两个对象的其他内容,则可以通过重写 equals 方法,hashCoed 也是 Object 类里面的方法,返回值是一个对象的哈希码,同一个对象哈希码一定相等,但不同对象哈希码也有可能相等。1、如果两个对象 equals,Java 运行时环境会认为他们的 hashcode 一定相等。2、如果两个对象不 equals,他们的 hashcode 有可能相等。3、如果两个对象 hashcode 相等,他们不一定 equals。4、如果两个对象 hashcode 不相等,他们一定不 equals。

int、char、long 各占多少字节数(笔试题多出现)Int:4 字节 chat:2 字节 long\double:8 字节

int 与 integer 的区别(笔试)1、Integer 是 int 的包装类,int 则是 java 的一种基本数据类型 2、Integer 变量必须实例化后才能使用,而 int 变量不需要 3、Integer 实际是对象的引用,当 new 一个 Integer 时,实际上是生成一个指针指向此对象;而 int 则是直接存储数据值 4、Integer 的默认值是 null,int 的默认值是 0

java 面试题 2022

谈谈对 java 多态的理解多态是指:父类引用指向子类对象,在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。(同一消息可以根据发送对象的不同而采用多种不同的行为方式。

多态的作用:消除类型之间的耦合关系。

实现多态的技术称为:动态绑定(dynamicbinding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

实现多态的三要素:继承,重写,父类引用指向子类对象(即,声明是父类,实际指向的是子类的一个对象)

String、StringBuffer、StringBuilder 区别 1、三者在执行速度上:StringBuilder>StringBuffer>String(由于 String 是常量,不可改变,拼接时会重新创建新的对象)。2、StringBuffer 是线程安全的,StringBuilder 是线程不安全的。(由于 StringBuffer 有缓冲区)

什么是内部类?内部类的作用内部类:将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。作用:1. 每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个 (接口的) 实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,2. 方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。3. 方便编写事件驱动程序 4. 方便编写线程代码

抽象类和接口区别相同:1、都能被继承 2、继承的类都必须将未实现的函数实现 3、只关注方法的定义,不关注方法的实现差异:1、一个子类可以继承多个接口,但是只能继承一个父类 2、抽象类在对象中只能表示一种对象,接口可以被很多对象继承

抽象类与接口的应用场景如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。如果你想实现多重继承,那么你必须使用接口。由于 Java 不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。

抽象类是否可以没有方法和属性?抽象类专用于派生出子类,子类必须实现抽象类所声明的抽象方法,否则,子类仍是抽象类。包含抽象方法的类一定是抽象类,但抽象类中的方法不一定是抽象方法。抽象类中可以没有抽象方法,但有抽象方法的一定是抽象类。所以,java 中抽象类里面可以没有抽象方法。抽象类的作用在于子类对其的继承和实现,也就是多态;而没有抽象方法的抽象类的存在价值在于:实例化了没有意义,因为类已经定义好了,不能改变其中的方法体,但是实例化出来的对象却满足不了要求,只有继承并重写了他的子类才能满足要求。所以才把它定义为没有抽象方法的抽象类

泛型中 extends 和 super 的区别 1、限定参数类型的上界:参数类型必须是 T 或 T 的子类型限定参数类型的下界:参数类型必须是 T 或 T 的超类型 2、只能用于方法返回,告诉编译器此返参的类型的最小继承边界为 T,T 和 T 的父类都能接收,但是入参类型无法确定,只能接受 null 的传入只能用于限定方法入参,告诉编译器入参只能是 T 或其子类型,而返参只能用 Object 类接收既不能用于入参也不能用于返参

父类的静态方法能否被子类重写不能,父类的静态方法能够被子类继承,但是不能够被子类重写,即使子类中的静态方法与父类中的静态方法完全一样,也是两个完全不同的方法。

进程和线程的区别(问的蛮多的,回答的时候用口语说出来,不要背书)进程是 cpu 资源分配的最小单位,线程是 cpu 调度的最小单位。进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源。一个进程内可拥有多个线程,进程可开启进程,也可开启线程。一个线程只能属于一个进程,线程可直接使用同进程的资源, 线程依赖于进程而存在。

final,finally,finalize 的区别 final: 修饰类、成员变量和成员方法,类不可被继承,成员变量不可变,成员方法不可重写 finally: 与 try…catch… 共同使用,确保无论是否出现异常都能被调用到 finalize: 类的方法, 垃圾回收之前会调用此方法, 子类可以重写 finalize()方法实现对资源的回收

Serializable 和 Parcelable 的区别 SerializableJava 序列化接口在硬盘上读写读写过程中有大量临时变量的生成,内部执行大量的 i / o 操作,效率很低。ParcelableAndroid 序列化接口效率高使用麻烦在内存中读写(AS 有相关插件一键生成所需方法),对象不能保存到磁盘中

静态属性和静态方法是否可以被继承?是否可以被重写?以及原因?可继承不可重写而是被隐藏如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为 ” 隐藏 ”。如果你想要调用父类的静态方法和属性,直接通过父类名。 方法或变量名完成。

成员内部类、静态内部类、局部内部类和匿名内部类的理解,以及项目中的应用 java 中内部类主要分为成员内部类、局部内部类(嵌套在方法和作用域内)、匿名内部类(没构造方法)、静态内部类(static 修饰的类,不能使用任何外围类的非 static 成员变量和方法,不依赖外围类)使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。因为 Java 不支持多继承,支持实现多个接口。但有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。

string 转换成 integer 的方式及原理 String—>integerIntrger.parseInt(string);Integer—>stringInteger.toString(); 原理:parseInt(Strings)– 内部调用 parseInt(s,10)(默认为 10 进制)正常判断 null,进制范围,length 等判断第一个字符是否是符号位循环遍历确定每个字符的十进制值通过 *= 和 -= 进行计算拼接判断是否为负值返回结果。

哪些情况下的对象会被垃圾回收机制处理掉?1. 所有实例都没有活动线程访问。2. 没有被其他任何实例访问的循环引用实例。3.Java 中有不同的引用类型。判断实例是否符合垃圾收集的条件都依赖于它的引用类型。要判断怎样的对象是没用的对象。这里有 2 种方法:1. 采用标记计数的方法:给内存中的对象给打上标记,对象被引用一次,计数就加 1,引用被释放了,计数就减一,当这个计数为 0 的时候,这个对象就可以被回收了。当然,这也就引发了一个问题:循环引用的对象是无法被识别出来并且被回收的。所以就有了第二种方法:2. 采用根搜索算法:从一个根出发,搜索所有的可达对象,这样剩下的那些对象就是需要被回收的

静态代理和动态代理的区别,什么场景使用?由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的。class 文件就已经存在了。动态代理类:在程序运行时,运用反射机制动态创建而成。场景:著名的 Spring 框架、Hibernate 框架等等都是动态代理的使用例子

Java 的异常体系 Throwable,Error,Exception

谈谈你对解析与分派的认识。解析:Java 中方法调用的目标方法在 Class 文件里面都是常量池中的符号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用。这种解析的前提是:方法在程序真正运行之前就有一个可以确定的调用版本,并且这个方法的调用版本在运行期是不可改变的,即“编译期可知,运行期不可变”,这类目标的方法的调用称为解析(Resolve)。

只要能被 invokestatic 和 invokespecial 指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合条件的有静态方法(invokestatic 指令)、私有方法、实例构造方法、父类方法(这 3 个是 invokespecial 指令),它们在类加载的的解析阶段就会将符号引用解析为该方法的直接引用。分派:分派是多态性的体现,Java 虚拟机底层提供了我们开发中“重载”(Overload)“和重写”(Override)的底层实现。其中重载属于静态分派,而重写则是动态分派的过程。解析调用一定是个静态的过程,在编译期就完全确定,在类加载的解析阶段就将涉及的符号引用全部转变为可以确定的直接引用,不会延迟到运行期再去完成。

Java 中实现多态的机制是什么?答:方法的重写 Overriding 和重载 Overloading 是 Java 多态性的不同表现重写 Overriding 是父类与子类之间多态性的一种表现重载 Overloading 是一个类中多态性的一种表现。

说说你对 Java 反射的理解 JAVA 反射机制是在运行状态中, 对于任意一个类, 都能够知道这个类的所有属性和方法; 对于任意一个对象, 都能够调用它的任意一个方法和属性。从对象出发,通过反射(Class 类)可以取得取得类的完整信息(类名 Class 类型,所在包、具有的所有方法 Method[]类型、某个方法的完整信息(包括修饰符、返回值类型、异常、参数类型)、所有属性 Field[]、某个属性的完整信息、构造器 Constructors),调用类的属性或方法自己的总结:在运行过程中获得类、对象、方法的所有信息。

说说你对 Java 注解的理解元注解元注解的作用就是负责注解其他注解。java5.0 的时候,定义了 4 个标准的 meta-annotation 类型,它们用来提供对其他注解的类型作说明。1.@Target2.@Retention3.@Documented4.@Inherited

Java 中 String 的了解在源码中 string 是用 final 进行修饰,它是不可更改,不可继承的常量。

String 为什么要设计成不可变的?1、字符串池的需求字符串池是方法区(MethodArea)中的一块特殊的存储区域。当一个字符串已经被创建并且该字符串在池中,该字符串的引用会立即返回给变量,而不是重新创建一个字符串再将引用返回给变量。如果字符串不是不可变的,那么改变一个引用(如:string2)的字符串将会导致另一个引用(如:string1)出现脏数据。2、允许字符串缓存哈希码在 java 中常常会用到字符串的哈希码,例如:HashMap。String 的不变性保证哈希码始终一,因此,他可以不用担心变化的出现。这种方法意味着不必每次使用时都重新计算一次哈希码——这样,效率会高很多。3、安全 String 广泛的用于 java 类中的参数,如:网络连接(Networkconnetion),打开文件(openingfiles)等等。如果 String 不是不可变的,网络连接、文件将会被改变——这将会导致一系列的安全威胁。操作的方法本以为连接上了一台机器,但实际上却不是。由于反射中的参数都是字符串,同样,也会引起一系列的安全问题。

Object 类的 equal 和 hashCode 方法重写,为什么?首先 equals 与 hashcode 间的关系是这样的:1、如果两个对象相同(即用 equals 比较返回 true),那么它们的 hashCode 值一定要相同;2、如果两个对象的 hashCode 相同,它们并不一定相同 (即用 equals 比较返回 false) 由于为了提高程序的效率才实现了 hashcode 方法,先进行 hashcode 的比较,如果不同,那没就不必在进行 equals 的比较了,这样就大大减少了 equals 比较的次数,这对比需要比较的数量很大的效率提高是很明显的

java 的集合以及集合之间的继承关系

List,Set,Map 的区别 Set 是最简单的一种集合。集合中的对象不按特定的方式排序,并且没有重复对象。Set 接口主要实现了两个实现类:HashSet:HashSet 类按照哈希算法来存取集合中的对象,存取速度比较快 TreeSet:TreeSet 类实现了 SortedSet 接口,能够对集合中的对象进行排序。List 的特征是其元素以线性方式存储,集合中可以存放重复对象。ArrayList(): 代表长度可以改变得数组。可以对元素进行随机的访问,向 ArrayList()中插入与删除元素的速度慢。LinkedList(): 在实现中采用链表数据结构。插入和删除速度快,访问速度慢。Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。Map 没有继承于 Collection 接口从 Map 集合中检索元素时,只要给出键对象,就会返回对应的值对象。HashMap:Map 基于散列表的实现。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量 capacity 和负载因子 loadfactor,以调整容器的性能。LinkedHashMap:类似于 HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用 (LRU) 的次序。只比 HashMap 慢一点。而在迭代访问时发而更快,因为它使用链表维护内部次序。TreeMap:基于红黑树数据结构的实现。查看“键”或“键值对”时,它们会被排序 (次序由 Comparabel 或 Comparator 决定)。TreeMap 的特点在于,你得到的结果是经过排序的。TreeMap 是唯一的带有 subMap() 方法的 Map,它可以返回一个子树。WeakHashMao:弱键(weakkey)Map,Map 中使用的对象也被允许释放: 这是为解决特殊问题设计的。如果没有 map 之外的引用指向某个“键”,则此“键”可以被垃圾收集器回收。

List 和 Set 和 Map 的实现方式以及存储方式 List:常用实现方式有:ArrayList 和 LinkedListArrayList 的存储方式:数组,查询快 LinkedList 的存储方式:链表,插入,删除快

Set:常用实现方式有:HashSet 和 TreeSetHashSet 的存储方式:哈希码算法,加入的对象需要实现 hashcode()方法,快速查找元素 TreeSet 的存储方式:按序存放,想要有序就要实现 Comparable 接口

附加:集合框架提供了 2 个实用类:collections(排序,复制、查找)和 Arrays 对数组进行(排序,复制、查找)

Map:常用实现方式有:HashMap 和 TreeMapHashMap 的存储方式:哈希码算法,快速查找键值 TreeMap 存储方式:对键按序存放

数组(如 arryList)中数组容量不够了,怎么扩容?在 JDK1.7 中如果通过无参构造的话,初始数组容量是 0,当数组进行 add()添加时,才真正的分配容量,通过位运算,每次按照 1.5 倍的比例扩容。在 JDK1.6 中,初始数组容量为 10,每次通过 copeof 方式扩容 1.5 倍 +1.

HashMap 的实现原理, 如何 put 数据和 get 数据?在 JDK1.6,JDK1.7 中,HashMap 采用数组 + 链表实现,即使用链表处理冲突,同一 hash 值的链表都存储在一个链表里。但是当位于一个链表中的元素较多,即 hash 值相等的元素较多时,通过 key 值依次查找的效率较低。而 JDK1.8 中,HashMap 采用位数组 + 链表 + 红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。当链表数组的容量超过初始容量 * 加载因子(默认 0.75)时,再散列将链表数组扩大 2 倍,把原链表数组的搬移到新的数组中。为什么需要使用加载因子?为什么需要扩容呢?因为如果填充比很大,说明利用的空间很多,如果一直不进行扩容的话,链表就会越来越长,这样查找的效率很低,扩容之后,将原来链表数组的每一个链表分成奇偶两个子链表分别挂在新链表数组的散列位置,这样就减少了每个链表的长度,增加查找效率。

HashMap 在 put 时候,底层源码可以看出,当程序试图将一个 key-value 对象放入到 HashMap 中,首先根据该 key 的 hashCode()返回值决定该 Entry 的存储位置,如果两个 Entry 的 key 的 hashCode()方法返回值相同,那他们的存储位置相同,如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加的 Entry 的 value 将会覆盖原来的 Entry 的 value,但是 key 不会被覆盖,反之,如果返回 false,新添加的 Entry 将与集合中原有的 Entry 形成 Entry 链,新添加的位于头部,旧的位于尾部。存:

取:

ArrayMap 和 HashMap 的对比 1、存储方式不同 HashMap 内部有一个 HashMapEntry[]对象,每一个键值对都存储在这个对象里,当使用 put 方法添加键值对时,就会 new 一个 HashMapEntry 对象,2、添加数据时扩容时的处理不一样,进行了 new 操作,重新创建对象,开销很大。ArrayMap 用的是 copy 数据,所以效率相对要高。3、ArrayMap 提供了数组收缩的功能,在 clear 或 remove 后,会重新收缩数组,是否空间 4、ArrayMap 采用二分法查找;List,Set,Map 的区别 Set 是最简单的一种集合。集合中的对象不按特定的方式排序,并且没有重复对象。Set 接口主要实现了两个实现类:HashSet:HashSet 类按照哈希算法来存取集合中的对象,存取速度比较快 TreeSet:TreeSet 类实现了 SortedSet 接口,能够对集合中的对象进行排序。

List 的特征是其元素以线性方式存储,集合中可以存放重复对象。ArrayList(): 代表长度可以改变得数组。可以对元素进行随机的访问,向 ArrayList()中插入与删除元素的速度慢。LinkedList(): 在实现中采用链表数据结构。插入和删除速度快,访问速度慢。

Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。Map 没有继承于 Collection 接口从 Map 集合中检索元素时,只要给出键对象,就会返回对应的值对象。HashMap:Map 基于散列表的实现。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量 capacity 和负载因子 loadfactor,以调整容器的性能。LinkedHashMap:类似于 HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用 (LRU) 的次序。只比 HashMap 慢一点。而在迭代访问时发而更快,因为它使用链表维护内部次序。TreeMap:基于红黑树数据结构的实现。查看“键”或“键值对”时,它们会被排序 (次序由 Comparabel 或 Comparator 决定)。TreeMap 的特点在于,你得到的结果是经过排序的。TreeMap 是唯一的带有 subMap() 方法的 Map,它可以返回一个子树。WeakHashMap:弱键(weakkey)Map,Map 中使用的对象也被允许释放: 这是为解决特殊问题设计的。如果没有 map 之外的引用指向某个“键”,则此“键”可以被垃圾收集器回收。

HashMap 和 HashTable 的区别 HashMap 允许 key 和 value 为 null;HashMap 是非同步的,线程不安全,也可以通过 Collections.synchronizedMap()方法来得到一个同步的 HashMapHashMap 存取速度更快,效率高 HashMap 去掉了 HashTable 中的 contains 方法,加上了 containsValue 和 containsKey 方法

HashMap 与 HashSet 的区别

HashSet 与 HashMap 怎么判断集合元素重复?HashSet 不能添加重复的元素,当调用 add(Object)方法时候,首先会调用 Object 的 hashCode 方法判 hashCode 是否已经存在,如不存在则直接插入元素;如果已存在则调用 Object 对象的 equals 方法判断是否返回 true,如果为 true 则说明元素已经存在,如为 false 则插入元素。

集合 Set 实现 Hash 怎么防止碰撞重写 hashcode()和 equles()方法

ArrayList 和 LinkedList 的区别,以及应用场景 ArrayList 是基于数组实现的,ArrayList 线程不安全。LinkedList 是基于双链表实现的:使用场景:(1)如果应用程序对各个索引位置的元素进行大量的存取或删除操作,ArrayList 对象要远优于 LinkedList 对象;(2)如果应用程序主要是对列表进行循环,并且循环时候进行插入或者删除操作,LinkedList 对象要远优于 ArrayList 对象;

数组和链表的区别数组:是将元素在内存中连续存储的;它的优点:因为数据是连续存储的,内存地址连续,所以在查找数据的时候效率比较高;它的缺点:在存储之前,我们需要申请一块连续的内存空间,并且在编译的时候就必须确定好它的空间的大小。在运行的时候空间的大小是无法随着你的需要进行增加和减少而改变的,当数据两比较大的时候,有可能会出现越界的情况,数据比较小的时候,又有可能会浪费掉内存空间。在改变数据个数时,增加、插入、删除数据效率比较低。链表:是动态申请内存空间,不需要像数组需要提前申请好内存的大小,链表只需在用的时候申请就可以,根据需要来动态申请或者删除内存空间,对于数据增加和删除以及插入比数组灵活。还有就是链表中数据在内存中可以在任意的位置,通过应用来关联数据(就是通过存在元素的指针来联系)

堆和树的区别节点的顺序在二叉搜索树中,左子节点必须比父节点小,右子节点必须必比父节点大。但是在堆中并非如此。在最大堆中两个子节点都必须比父节点小,而在最小堆中,它们都必须比父节点大。

内存占用普通树占用的内存空间比它们存储的数据要多。你必须为节点对象以及左 / 右子节点指针分配额外内存。堆仅仅使用一个数据来存储数组,且不使用指针。

平衡二叉搜索树必须是“平衡”的情况下,其大部分操作的复杂度才能达到 O(logn)。你可以按任意顺序位置插入 / 删除数据,或者使用 AVL 树或者红黑树,但是在堆中实际上不需要整棵树都是有序的。我们只需要满足对属性即可,所以在堆中平衡不是问题。因为堆中数据的组织方式可以保证 O(logn)的性能。

搜索在二叉树中搜索会很快,但是在堆中搜索会很慢。在堆中搜索不是第一优先级,因为使用堆的目的是将最大(或者最小)的节点放在最前面,从而快速的进行相关插入、删除操作。

什么是深拷贝和浅拷贝浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。

判断单链表成环与否?使用快慢指针遍历链表:慢指针:从头节点开始,一次跳一个节点。快指针:从头节点开始,一次跳两个节点。如果是成环的,这两个指针一定会相遇。

开启线程的三种方式?java 有三种创建线程的方式,分别是继承 Thread 类、实现 Runable 接口和使用线程池

线程和进程的区别?线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。

为什么要有线程,而不是仅仅用进程?线程可以增加并发的程度啊。其实多进程也是可以并发,但是为什么要是线程呢?因为线程是属于进程的,是个轻量级的对象。所以再切换线程时只需要做少量的工作,而切换进程消耗很大。这是从操作系统角度讲。从用户程序角度讲,有些程序在逻辑上需要线程,比如扫雷,它需要一个线程等待用户的输入,另一个线程的来更新时间。还有一个例子就是聊天程序,一个线程是响应用户输入,一个线程是响应对方输入。如果没有多线程,那么只能你说一句我说一句,你不说我这里就不能动,我还不能连续说。所以用户程序有这种需要,操作系统就要提供响应的机制

run()和 start()方法区别这个问题经常被问到,但还是能从此区分出面试者对 Java 线程模型的理解程度。start()方法被用来启动新创建的线程,而且 start()内部调用了 run()方法,这和直接调用 run()方法的效果不一样。当你调用 run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。

如何控制某个方法允许并发访问线程的个数?semaphore.acquire()请求一个信号量,这时候的信号量个数 -1(一旦没有可使用的信号量,也即信号量个数变为负数时,再次请求的时候就会阻塞,直到其他线程释放了信号量)semaphore.release()释放一个信号量,此时信号量个数 +1

在 Java 中 wait 和 seelp 方法的不同;Java 程序中 wait 和 sleep 都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而 sleep()方法仅仅释放 CPU 资源或者让当前线程停止执行一段时间,但不会释放锁。

谈谈 wait/notify 关键字的理解等待对象的同步锁, 需要获得该对象的同步锁才可以调用这个方法, 否则编译可以通过,但运行时会收到一个异常:IllegalMonitorStateException。调用任意对象的 wait()方法导致该线程阻塞,该线程不可继续执行,并且该对象上的锁被释放。唤醒在等待该对象同步锁的线程 (只唤醒一个, 如果有多个在等待), 注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且不是按优先级。调用任意对象的 notify() 方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。

什么导致线程阻塞?阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,ServerSocket 的 accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。

线程如何关闭?一种是调用它里面的 stop()方法另一种就是你自己设置一个停止线程的标记(推荐这种)

讲一下 java 中的同步的方法 (另一种问法:数据一致性如何保证?)1. 即有 synchronized 关键字修饰的方法。2. 同步代码块(如:双重判断的单例模式)3. 使用特殊域变量(volatile) 实现线程同步 4. 使用重入锁实现线程同步 5. 使用局部变量实现线程同步

如何保证线程安全?1.synchronized;2.Object 方法中的 wait,notify;3.ThreadLocal 机制来实现的。

如何实现线程同步?1、synchronized 关键字修改的方法。2、synchronized 关键字修饰的语句块 3、使用特殊域变量(volatile)实现线程同步

两个进程同时要求写或者读,能不能实现?如何防止进程的同步?可以实现的。同步方式有:互斥锁、条件变量、读写锁、记录锁 (文件锁) 和信号灯

线程间操作 ListListlist=Collections.synchronizedList(newArrayList());

Synchronized 用法及原理用法:修饰静态方法、实例方法、代码块原理:不是一两句话能说清,建议去深入了解一下。

谈谈对 Synchronized 关键字,类锁,方法锁,重入锁的理解 java 的对象锁和类锁:java 的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的 class 对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个 class 对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的

staticsynchronized 方法的多线程访问和作用 1.synchronizedstatic 是某个类的范围,synchronizedstaticcSync{}防止多个线程同时访问这个类中的 synchronizedstatic 方法。它可以对类的所有对象实例起作用。

2.synchronized 是某实例的范围,synchronizedisSync(){}防止多个线程同时访问这个实例中的 synchronized 方法。

同一个类里面两个 synchronized 方法,两个线程同时访问的问题同一个 object 中多个方法都加了 synchronized 关键字的时候,其中调用任意方法之后需等该方法执行完成才能调用其他方法,即同步的,阻塞的;此结论同样适用于对于 object 中使用 synchronized(this)同步代码块的场景;synchronized 锁定的都是当前对象!

volatile 的作用,原理,性能。作用:1、保持内存可见性 2、防止指令重排原理:获取 JIT(即时 Java 编译器,把字节码解释为机器语言发送给处理器)的汇编代码,发现 volatile 多加了 lockaddl 指令,这个操作相当于一个内存屏障,使得 lock 指令后的指令不能重排序到内存屏障前的位置。这也是为什么 JDK1.5 以后可以使用双锁检测实现单例模式。lock 前缀的另一层意义是使得本线程工作内存中的 volatile 变量值立即写入到主内存中,并且使得其他线程共享的该 volatile 变量无效化,这样其他线程必须重新从主内存中读取变量值。性能:读操作与普通变量无差别,写操作会慢一些,大多情况比锁消耗低。

谈谈 NIO 的理解如果问到这个,很容易就会问到和 IO 的比较,所以可以直接看看这个。

synchronized 和 volatile 关键字的区别 1.volatile 本质是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。2.volatile 仅能使用在变量级别;synchronized 则可以使用在变量、方法、和类级别的 3.volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性 4.volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。5.volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化

synchronized 与 Lock 的区别及使用场景 synchronized 原始采用的是 CPU 悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在 CPU 转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起 CPU 频繁的上下文切换导致效率很低;而 Lock 用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是 CAS 操作(CompareandSwap)。我们可以进一步研究 ReentrantLock 的源代码,会发现其中比较重要的获得锁的一个方法是 compareAndSetState。这里其实就是调用的 CPU 提供的特殊指令。使用场景:在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于 synchronized。所以说,在具体使用时要根据适当情况选择。

ReentrantLock、synchronized 和 volatile 比较 java 在过去很长一段时间只能通过 synchronized 关键字来实现互斥,它有一些缺点。比如你不能扩展锁之外的方法或者块边界,尝试获取锁时不能中途取消等。Java5 通过 Lock 接口提供了更复杂的控制来解决这些问题。ReentrantLock 类实现了 Lock,它拥有与 synchronized 相同的并发性和内存语义且它还具有可扩展性。

死锁的四个必要条件?怎么避免死锁?死锁产生的原因

什么是线程池,如何使用? 创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从 JDK1.5 开始,JavaAPI 提供了 Executor 框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)

谈谈对多线程的理解线程是由一个主线程和很多个子线程组成的,主线程消失,子线程也会消失,但是子线程消失其中一个主线程不会消失线程的生命周期分为 5 个步骤像人的一生一样,这 5 个步骤分别对应了 5 个方法新生 –>启动 –>运行 –>阻塞 –>销毁继承 Thread 类 or 实现 runnable 方法 –>start–>run–>sleep(睡眠)orwait(挂起)–>destroy

多线程有什么要注意的问题?给线程起有意义的名字,这样方便找 Bug 缩小同步范围,从而减少锁的争用,例如对于 synchronized,应该尽量使用同步块而不是同步方法多用同步工具少用 wait()和 notify()。首先,CountDownLatch,CyclicBarrier,Semaphore 和 Exchanger 这些同步类简化了编码操作,而用 wait()和 notify()很难实现复杂控制流;其次,这些同步类是由最好的企业编写和维护,在后续的 JDK 中还会不断优化和完善。使用 BlockingQueue 实现生产者消费者问题多用并发集合少用同步集合,例如应该使用 ConcurrentHashMap 而不是 Hashtable 使用本地变量和不可变类来保证线程安全使用线程池而不是直接创建线程,这是因为创建线程代价很高,线程池可以有效地利用有限的线程来启动任务

自己去设计网络请求框架,怎么做?这种并没有一个完全正确的答案,看个人的思路与理解

okhttp 源码自己看一遍源码即可,最好能够手写出他的流程。

从网络加载一个 10M 的图片,说下注意事项图片缓存、异常恢复、质量压缩,从这几方面说就好了

TCP 的 3 次握手和四次挥手三次握手:第一次:客户端发送请求到服务器,服务器知道客户端发送,自己接收正常。SYN=1,seq= x 第二次:服务器发给客户端,客户端知道自己发送、接收正常,服务器接收、发送正常。ACK=1,ack=x+1,SYN=1,seq= y 第三次:客户端发给服务器:服务器知道客户端发送,接收正常,自己接收,发送也正常。seq=x+1,ACK=1,ack=y+1

四次挥手:第一次:客户端请求断开 FIN,seq= u 第二次:服务器确认客户端的断开请求 ACK,ack=u+1,seq= v 第三次:服务器请求断开 FIN,seq=w,ACK,ack=u+ 1 第四次:客户端确认服务器的断开 ACK,ack=w+1,seq=u+1

为什么连接的时候是三次握手,关闭的时候却是四次握手?因为当 Server 端收到 Client 端的 SYN 连接请求报文后,可以直接发送 SYN+ACK 报文。其中 ACK 报文是用来应答的,SYN 报文是用来同步的。但是关闭连接时,当 Server 端收到 FIN 报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个 ACK 报文,告诉 Client 端,” 你发的 FIN 报文我收到了 ”。只有等到我 Server 端所有的报文都发送完了,我才能发送 FIN 报文,因此不能一起发送。故需要四步握手。

为什么不能用两次握手进行连接?3 次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。现在把三次握手改成仅需要两次握手,死锁是可能发生的。

为什么 TIME_WAIT 状态需要经过 2MSL(最大报文段生存时间)才能返回到 CLOSE 状态?虽然按道理,四个报文都发送完毕,我们可以直接进入 CLOSE 状态了,但是我们必须假象网络是不可靠的,有可以最后一个 ACK 丢失。所以 TIME_WAIT 状态就是用来重发可能丢失的 ACK 报文。在 Client 发送出最后的 ACK 回复,但该 ACK 可能丢失。Server 如果没有收到 ACK,将不断重复发送 FIN 片段。所以 Client 不能立即关闭,它必须确认 Server 接收到了该 ACK。Client 会在发送出 ACK 之后进入到 TIME_WAIT 状态。Client 会设置一个计时器,等待 2MSL 的时间。如果在该时间内再次收到 FIN,那么 Client 会重发 ACK 并再次等待 2MSL。所谓的 2MSL 是两倍的 MSL(MaximumSegmentLifetime)。MSL 指一个片段在网络中最大的存活时间,2MSL 就是一个发送和一个回复所需的最大时间。如果直到 2MSL,Client 都没有再次收到 FIN,那么 Client 推断 ACK 已经被成功接收,则结束 TCP 连接。

TCP 与 UDP 的区别 tcp 是面向连接的,由于 tcp 连接需要三次握手,所以能够最低限度的降低风险,保证连接的可靠性。udp 不是面向连接的,udp 建立连接前不需要与对象建立连接,无论是发送还是接收,都没有发送确认信号。所以说 udp 是不可靠的。由于 udp 不需要进行确认连接,使得 UDP 的开销更小,传输速率更高,所以实时行更好。

TCP 与 UDP 的应用从特点上我们已经知道,TCP 是可靠的但传输速度慢,UDP 是不可靠的但传输速度快。因此在选用具体协议通信时,应该根据通信数据的要求而决定。若通信数据完整性需让位与通信实时性,则应该选用 TCP 协议(如文件传输、重要状态的更新等);反之,则使用 UDP 协议(如视频传输、实时通信等)。

Httphttps 区别,此处延伸:https 的实现原理 1、https 协议需要到 ca 申请证书,一般免费证书较少,因而需要一定费用。2、http 是超文本传输协议,信息是明文传输,https 则是具有安全性的 ssl 加密传输协议。3、http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。4、http 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 http 协议安全。https 实现原理:(1)客户使用 https 的 URL 访问 Web 服务器,要求与 Web 服务器建立 SSL 连接。(2)Web 服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。(3)客户端的浏览器与 Web 服务器开始协商 SSL 连接的安全等级,也就是信息加密的等级。(4)客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。(5)Web 服务器利用自己的私钥解密出会话密钥。(6)Web 服务器利用会话密钥加密与客户端之间的通信。7、Http 位于 TCP/IP 模型中的第几层?为什么说 Http 是可靠的数据传输协议?tcp/ip 的五层模型:从下到上:物理层 ->数据链路层 ->网络层 ->传输层 ->应用层其中 tcp/ip 位于模型中的网络层,处于同一层的还有 ICMP(网络控制信息协议)。http 位于模型中的应用层由于 tcp/ip 是面向连接的可靠协议,而 http 是在传输层基于 tcp/ip 协议的,所以说 http 是可靠的数据传输协议。

8、HTTP 链接的特点 HTTP 连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

HTTP 报文结构一个 HTTP 请求报文由四个部分组成:请求行、请求头部、空行、请求数据。1. 请求行请求行由请求方法字段、URL 字段和 HTTP 协议版本字段 3 个字段组成,它们用空格分隔。比如 GET/data/info.htmlHTTP/1.12. 请求头部 HTTP 客户程序(例如浏览器),向服务器发送请求的时候必须指明请求类型(一般是 GET 或者 POST)。如有必要,客户程序还可以选择发送其他的请求头。大多数请求头并不是必需的,但 Content-Length 除外。对于 POST 请求来说 Content-Length 必须出现。3. 空行它的作用是通过一个空行,告诉服务器请求头部到此为止。4. 请求数据若方法字段是 GET,则此项为空,没有数据。若方法字段是 POST, 则通常来说此处放置的就是要提交的数据

HTTP 与 HTTPS 的区别以及如何实现安全性区别:http 是明文传输,传输的数据很可能被中间节点获取,从而导致数据传输不安全 https 是加密传输,可以保证数据的传输安全如何实现:http 是应用层协议,它会将要传输的数据以明文的方式给传输层,这样显然不安全。https 则是在应用层与传输层之间又加了一层,该层遵守 SSL/TLS 协议,用于数据加密。

如何验证证书的合法性?1、证书是否是信任的有效证书。所谓信任:浏览器内置了信任的根证书,就是看看 web 服务器的证书是不是这些信任根发的或者信任根的二级证书机构颁发的。所谓有效,就是看看 web 服务器证书是否在有效期,是否被吊销了。2、对方是不是上述证书的合法持有者。简单来说证明对方是否持有证书的对应私钥。验证方法两种,一种是对方签个名,我用证书验证签名;另外一种是用证书做个信封,看对方是否能解开。以上的所有验证,除了验证证书是否吊销需要和 CA 关联,其他都可以自己完成。验证正式是否吊销可以采用黑名单方式或者 OCSP 方式。黑名单就是定期从 CA 下载一个名单列表,里面有吊销的证书序列号,自己在本地比对一下就行。优点是效率高。缺点是不实时。OCSP 是实时连接 CA 去验证,优点是实时,缺点是效率不高。

client 如何确定自己发送的消息被 server 收到?HTTP 协议里,有请求就有响应,根据响应的状态吗就能知道。

HttpClient 与 HttpUrlConnection 的区别(此处延伸:Volley 里用的哪种请求方式(2.3 前 HttpClient,2.3 后 HttpUrlConnection)首先 HttpClient 和 HttpUrlConnection 这两种方式都支持 Https 协议,都是以流的形式进行上传或者下载数据,也可以说是以流的形式进行数据的传输,还有 ipv6, 以及连接池等功能。HttpClient 这个拥有非常多的 API,所以如果想要进行扩展的话,并且不破坏它的兼容性的话,很难进行扩展,也就是这个原因,Google 在 Android6.0 的时候,直接就弃用了这个 HttpClient. 而 HttpUrlConnection 相对来说就是比较轻量级了,API 比较少,容易扩展,并且能够满足 Android 大部分的数据传输。比较经典的一个框架 volley,在 2.3 版本以前都是使用 HttpClient, 在 2.3 以后就使用了 HttpUrlConnection。

WebSocket 与 socket 的区别 1.WebSocketprotocol 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助 HTTP 请求完成。2.Socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层, 它是一组接口。在设计模式中,Socket 其实就是一个门面模式, 它把 …3. 区别 Socket 是传输控制层协议,WebSocket 是应用层协议。

谈谈你对安卓签名的理解。每个应用都必须签名应用可以被不同的签名文件签名(如果有源代码或者反编译后重新编译)同一个应用如果签名不同则不能覆盖安装

请解释安卓为啥要加签名机制? 发送者的身份认证:由于开发商可能通过使用相同的 PackageName 来混淆替换已经安装的程序,以此保证签名不同的包不被替换保证信息传输的完整性:签名对于包中的每个文件进行处理,以此确保包中内容不被替换防止交易中的抵赖发生:Market(应用市场)对软件的要求

视频加密传输 DES 加密。用 java 中提供的加密包。将视频文件的数据流前 100 个字节中的每个字节与其下标进行异或运算。解密时只需将加密过的文件再进行一次异或运算即可。

App 是如何沙箱化,为什么要这么做?在 Android 系统中,应用(通常)都在一个独立的沙箱中运行,即每一个 Android 应用程序都在它自己的进程中运行,都拥有一个独立的 Dalvik 虚拟机实例。Dalvik 经过优化,允许在有限的内存中同时高效地运行多个虚拟机的实例,并且每一个 Dalvik 应用作为一个独立的 Linux 进程执行。Android 这种基于 Linux 的进程“沙箱”机制,是整个安全设计的基础之一。Android 扩展了 Linux 内核安全模型的用户与权限机制,将多用户操作系统的用户隔离机制巧妙地移植为应用程序隔离。将 UID(一个用户标识)不同的应用程序自然形成资源隔离,如此便形成了一个操作系统级别的应用程序“沙箱”。

(2)Android 面试题(基础 + 进阶)(必须)

四大组件是什么(这个不知道的话,没必要去面试了,转行吧)Android 四大组件有 Activity,Service 服务,ContentProvider 内容提供,BroadcastReceiver。

四大组件的生命周期和简单用法 activity:onCreate()->onStart()->onResume()->onPause()->onStop()->onDetroy()Service:service 启动方式有两种,一种是通过 startService()方式进行启动,另一种是通过 bindService()方式进行启动。不同的启动方式他们的生命周期是不一样。 通过 startService()这种方式启动的 service,生命周期是这样:调用 startService()–>onCreate()–>onStartConmon()–>onDestroy()。这种方式启动的话,需要注意一下几个问题,第一:当我们通过 startService 被调用以后,多次在调用 startService(),onCreate()方法也只会被调用一次,而 onStartConmon()会被多次调用当我们调用 stopService()的时候,onDestroy()就会被调用,从而销毁服务。第二:当我们通过 startService 启动时候,通过 intent 传值,在 onStartConmon()方法中获取值的时候,一定要先判断 intent 是否为 null。通过 bindService()方式进行绑定,这种方式绑定 service,生命周期走法:bindService–>onCreate()–>onBind()–>unBind()–>onDestroy()bingservice 这种方式进行启动 service 好处是更加便利 activity 中操作 service,比如加入 service 中有几个方法,a,b,如果要在 activity 中调用,在需要在 activity 获取 ServiceConnection 对象,通过 ServiceConnection 来获取 service 中内部类的类对象,然后通过这个类对象就可以调用类中的方法,当然这个类需要继承 Binder 对象 contentProvider:contentProvider 的生命周期、理解应该跟进程一样,它作为系统应用组件、其生命周期应该跟 app 应用的生命周期类似,只是它属于系统应用、所以随系统启动而初始化,随系统关机而结束;但也存在其他状态下结束进程、比如说系统内存不够时,进行内存回收、会根据生成时间态、用户操作等情况进行是否内存回收。BroadcastReceiver:广播的生命周期从调用开始到 onReceiver 执行完毕结束,需要注意的是,一般广播的生命周期都极短,需要在 10s 内处理完 onReceiver 中的所有工作,所以,一般不进行耗时长的工作,如果有耗时长的工作,应当通过 Intent 传递给 Service 进行处理。(注意,不要在 onReceiver 中开启线程进行耗时任务处理,否则,在 10s 后,该线程会变成空线程,从而导致任务的丢失。同样的,也不要使用 bindService 来绑定服务。)值得注意的是,如果是在代码中动态注册的广播,如:在 Activity 注册,那么在 Activity 的 onDestory 中需要使用 unregisterReceiver 注销广播。

Activity 之间的通信方式 Intent 借助类的静态变量借助全局变量 /Application 借助外部工具借助 SharedPreference 使用 Android 数据库 SQLite 赤裸裸的使用 FileAndroid 剪切板借助 Service

横竖屏切换的时候,Activity 各种情况下的生命周期分两种情况:1. 不设置 Activity 的 android:configChanges,或设置 Activity 的 android:configChanges=”orientation”,或设置 Activity 的 android:configChanges=”orientation|keyboardHidden”,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行一次。横竖屏切换造成 activity 的生命周期 onPause()-onSaveInstanceState()-onStop()-onDestroy()-onCreat()-onStart()-onRestoreInstanceState()-onResume()即会导致 activity 的销毁和重建。

2. 配置 android:configChanges=”orientation|keyboardHidden|screenSize”,才不会销毁 activity,且只调用 onConfigurationChanged 方法。onSaveInstanceState()与 onRestoreIntanceState()资源相关的系统配置发生改变或者资源不足时(例如屏幕旋转),当前 Activity 会销毁,并且在 onStop 之前回调 onSaveInstanceState 保存数据,在重新创建 Activity 的时候在 onStart 之后回调 onRestoreInstanceState。其中 Bundle 数据会传到 onCreate(不一定有数据)和 onRestoreInstanceState(一定有数据)。用户或者程序员主动去销毁一个 Activity 的时候不会回调(如代码中 finish()或用户按下 back,不会回调),其他情况都会调用,来保存界面信息。

Activity 与 Fragment 之间生命周期比较 a. 在创建的过程中,是 Activity 带领 Fragment 执行生命周期的方法,所以它们生命周期执行的顺序如下:Activity–onCreate(),Fragment–onAttach()->onCreate()->onCreateView()->onActivityCreated.

Activity–onStart()Fragment–onStart()

Activity–onResume()Fragment–onResume()

最后,在销毁时是 Fragment 带领 Activity 执行生命周期的方法:Fragment–onPause()Activity–onPause()

Fragment–onStop()Activity–onStop()

Fragment–onDestroyView()->onDestroy()->onDetach()Activity–onDestroy()

Activity 上有 Dialog 的时候按 Home 键时的生命周期有 Dialog 和无 Dialog 按 Home 键效果一样:

两个 Activity 之间跳转时必然会执行的是哪几个方法?a. 正常情况下 ActivityA 跳转到 ActivityB 时:A 调用 onCreate()方法 ->onStart()方法 ->onResume()方法,此时 A 前台可见。当 A 跳转到 B 时,A 调用 onPause()方法,然后调用新的 ActivityB 中的 onCreate()方法 ->onStart()方法 ->onResume()方法。最后 A 再调用 onStop()方法。b. 当 ActivityB 为透明主题时: 除了最后 ActivityA 不调用 onStop()方法之外,其它都和 a 中的一样。

Activity 的四种启动模式对比此处延伸:栈 (FirstInLastOut) 与队列 (FirstInFirstOut) 的区别区别:队列先进先出,栈先进后出对插入和删除操作的 ” 限定 ”。栈是限定只能在表的一端进行插入和删除操作的线性表。队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。遍历数据速度不同

standard 模式这是默认模式,每次激活 Activity 时都会创建 Activity 实例,并放入任务栈中。使用场景:大多数 Activity。singleTop 模式如果在任务的栈顶正好存在该 Activity 的实例,就重用该实例(会调用实例的 onNewIntent()),否则就会创建新的实例并放入栈顶,即使栈中已经存在该 Activity 的实例,只要不在栈顶,都会创建新的实例。使用场景如新闻类或者阅读类 App 的内容页面。singleTask 模式如果在栈中已经有该 Activity 的实例,就重用该实例(会调用实例的 onNewIntent())。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移出栈。如果栈中不存在该实例,将会创建新的实例放入栈中。使用场景如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走 onNewIntent,并且会清空主界面上面的其他页面。singleInstance 模式在一个新栈中创建该 Activity 的实例,并让多个应用共享该栈中的该 Activity 实例。一旦该模式的 Activity 实例已经存在于某个栈中,任何应用再激活该 Activity 时都会重用该栈中的实例(会调用实例的 onNewIntent())。其效果相当于多个应用共享一个应用,不管谁激活该 Activity 都会进入同一个应用中。使用场景如闹铃提醒,将闹铃提醒与闹铃设置分离。singleInstance 不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A->B(singleInstance)->C,完全退出后,在此启动,首先打开的是 B。

Activity 状态保存于恢复当 Activity 在异常情况 (系统内存不足或者系统配置发生了改变等) 被销毁重建后,在销毁的时候 Activity 会调用 onSaveInstanceState()方法用于保存 Activity 相关的状态和数据,然后在重建后的 Activity 的中我们可以通过 onCreate()或者 onRestoreInstanceState()方法恢复数据,这里我们需要注意的是如果通过 onCreate()方法恢复,那么得先判断它的 intent 参数是否为空,如果在 onRestoreInstanceState()方法恢复就不会,因为只要 onRestoreInstanceState()方法被调用就说明一定有数据,不会为空。Google 推荐使用 onRestoreInstanceState()方法。

如何实现 Fragment 的滑动?将 Fragment 与 viewpager 绑定,通过 viewpager 中的 touch 事件,会进行 move 事件的滑动处理。

fragment 之间传递数据的方式?1、在 fragment 中设置一个方法,然后进行调用 2、采取接口回调的方式进行数据传递。3、广播或者是使用三方开源框架:EventBus

Activity 怎么和 Service 绑定?怎么在 Activity 中启动自己对应的 Service?1、activity 能进行绑定得益于 Serviece 的接口。为了支持 Service 的绑定,实现 onBind 方法。2、Service 和 Activity 的连接可以用 ServiceConnection 来实现。需要实现一个新的 ServiceConnection,重现 onServiceConnected 和 OnServiceDisconnected 方法,一旦连接建立,就能得到 Service 实例的引用。3、执行绑定,调用 bindService 方法,传入一个选择了要绑定的 Service 的 Intent(显示或隐式)和一个你实现了的 ServiceConnection 的实例

service 和 activity 怎么进行数据交互?1. 通过 broadcast: 通过广播发送消息到 activitry2. 通过 Binder:通过与 activity 进行绑定(1)添加一个继承 Binder 的内部类,并添加相应的逻辑方法。(2)重写 Service 的 onBind 方法,返回我们刚刚定义的那个内部类实例。(3)Activity 中创建一个 ServiceConnection 的匿名内部类,并且重写里面的 onServiceConnected 方法和 onServiceDisconnected 方法,这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用(在 onServiceConnected 方法中,我们可以得到一个刚才那个 service 的 binder 对象,通过对这个 binder 对象进行向下转型,得到我们那个自定义的 Binder 实例,有了这个实例,做可以调用这个实例里面的具体方法进行需要的操作了)。

Service 的开启方式,请描述一下 Service 的生命周期,请描述一下 Service 的生命周期 service 启动方式有两种,一种是通过 startService()方式进行启动,另一种是通过 bindService()方式进行启动。不同的启动方式他们的生命周期是不一样。 通过 startService()这种方式启动的 service,生命周期是这样:调用 startService()–>onCreate()–>onStartConmon()–>onDestroy()。这种方式启动的话,需要注意一下几个问题,第一:当我们通过 startService 被调用以后,多次在调用 startService(),onCreate()方法也只会被调用一次,而 onStartConmon()会被多次调用当我们调用 stopService()的时候,onDestroy()就会被调用,从而销毁服务。第二:当我们通过 startService 启动时候,通过 intent 传值,在 onStartConmon()方法中获取值的时候,一定要先判断 intent 是否为 null。通过 bindService()方式进行绑定,这种方式绑定 service,生命周期走法:bindService–>onCreate()–>onBind()–>unBind()–>onDestroy()bingservice 这种方式进行启动 service 好处是更加便利 activity 中操作 service,比如加入 service 中有几个方法,a,b,如果要在 activity 中调用,在需要在 activity 获取 ServiceConnection 对象,通过 ServiceConnection 来获取 service 中内部类的类对象,然后通过这个类对象就可以调用类中的方法,当然这个类需要继承 Binder 对象

请描述一下广播 BroadcastReceiver 的理解广播,是一个全局的监听器,属于 Android 四大组件之一。Android 广播分为两个角色:广播发送者、广播接收者。作用是监听 / 接收应用 App 发出的广播消息,并做出响应可应用在:Android 不同组件间的通信(含:应用内 / 不同应用之间)多线程通信与 Android 系统在特定情况下的通信如:电话呼入时、网络可用时

Broadcast 注册方式与区别(此处延伸:什么情况下用动态注册)Broadcast 广播,注册方式主要有两种。 第一种是静态注册,也可成为常驻型广播,这种广播需要在 Androidmanifest.xml 中进行注册,这中方式注册的广播,不受页面生命周期的影响,即使退出了页面,也可以收到广播这种广播一般用于想开机自启动啊等等,由于这种注册的方式的广播是常驻型广播,所以会占用 CPU 的资源。第二种是动态注册,而动态注册的话,是在代码中注册的,这种注册方式也叫非常驻型广播,收到生命周期的影响,退出页面后,就不会收到广播,我们通常运用在更新 UI 方面。这种注册方式优先级较高。最后需要解绑,否会会内存泄露广播是分为有序广播和无序广播。

在 manifest 和代码中如何注册和使用 BroadcastReceiver?

本地广播和全局广播有什么差别?BroadcastReceiver 是针对应用间、应用与系统间、应用内部进行通信的一种方式 LocalBroadcastReceiver 仅在自己的应用内发送接收广播,也就是只有自己的应用能收到,数据更加安全广播只在这个程序里,而且效率更高。

BroadcastReceiver,LocalBroadcastReceiver 区别一、应用场景不同 1、BroadcastReceiver 用于应用之间的传递消息;2、而 LocalBroadcastManager 用于应用内部传递消息,比 broadcastReceiver 更加高效。二、使用安全性不同 1、BroadcastReceiver 使用的 ContentAPI,所以本质上它是跨应用的,所以在使用它时必须要考虑到不要被别的应用滥用;2、LocalBroadcastManager 不需要考虑安全问题,因为它只在应用内部有效。

AlertDialog,popupWindow 区别(1)Popupwindow 在显示之前一定要设置宽高,Dialog 无此限制。(2)Popupwindow 默认不会响应物理键盘的 back,除非显示设置了 popup.setFocusable(true); 而在点击 back 的时候,Dialog 会消失。(3)Popupwindow 不会给页面其他的部分添加蒙层,而 Dialog 会。

(4)Popupwindow 没有标题,Dialog 默认有标题,可以通过 dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 取消标题(5)二者显示的时候都要设置 Gravity。如果不设置,Dialog 默认是 Gravity.CENTER。(6)二者都有默认的背景,都可以通过 setBackgroundDrawable(newColorDrawable(android.R.color.transparent)); 去掉。最本质的区别:AlertDialog 是非阻塞式对话框:AlertDialog 弹出时,后台还可以做事情;而 PopupWindow 是阻塞式对话框:PopupWindow 弹出时,程序会等待,在 PopupWindow 退出前,程序一直等待,只有当我们调用了 dismiss 方法的后,PopupWindow 退出,程序才会向下执行。

讲解一下 ContextContext 是一个抽象基类。在翻译为上下文,也可以理解为环境,是提供一些程序的运行环境基础信息。Context 下有两个子类,ContextWrapper 是上下文功能的封装类,而 ContextImpl 则是上下文功能的实现类。而 ContextWrapper 又有三个直接的子类,ContextThemeWrapper、Service 和 Application。其中,ContextThemeWrapper 是一个带主题的封装类,而它有一个直接子类就是 Activity,所以 Activity 和 Service 以及 Application 的 Context 是不一样的,只有 Activity 需要主题,Service 不需要主题。Context 一共有三种类型,分别是 Application、Activity 和 Service。这三个类虽然分别各种承担着不同的作用,但它们都属于 Context 的一种,而它们具体 Context 的功能则是由 ContextImpl 类去实现的,因此在绝大多数场景下,Activity、Service 和 Application 这三种类型的 Context 都是可以通用的。不过有几种场景比较特殊,比如启动 Activity,还有弹出 Dialog。出于安全原因的考虑,Android 是不允许 Activity 或 Dialog 凭空出现的,一个 Activity 的启动必须要建立在另一个 Activity 的基础之上,也就是以此形成的返回栈。而 Dialog 则必须在一个 Activity 上面弹出(除非是 SystemAlert 类型的 Dialog),因此在这种场景下,我们只能使用 Activity 类型的 Context,否则将会出错。getApplicationContext()和 getApplication()方法得到的对象都是同一个 application 对象,只是对象的类型不一样。Context 数量 =Activity 数量 +Service 数量 +1(1 为 Application)

Android 属性动画特性 (1) 对任意对象的属性执行动画操作:属性动画允许对任意对象的属性执行动画操作,因为属性动画的性质是通过反射实现的。(2)可改变背景颜色。(3)真正改变 View 本身:因为是通过反射改变其属性,并刷新,如改变 width,他会搜索 getWidth(), 反射获取,再通过进行某种计算,将值通过 setWidth()设置进去并更新。

LinearLayout、RelativeLayout、FrameLayout 的特性及对比,并介绍使用场景。RelativeLayout 的 onMeasure 过程根据源码我们发现 RelativeLayout 会根据 2 次排列的结果对子 View 各做一次 measure。首先 RelativeLayout 中子 View 的排列方式是基于彼此的依赖关系,在确定每个子 View 的位置的时候,需要先给所有的子 View 排序一下,所以需要横向纵向分别进行一次排序测量

LinearLayout 的 onMeasure 过程 LinearLayout 会先做一个简单横纵方向判断需要注意的是在每次对 child 测量完毕后,都会调用 child.getMeasuredHeight()/getMeasuredWidth()获取该子视图最终的高度,并将这个高度添加到 mTotalLength 中。但是 getMeasuredHeight 暂时避开了 lp.weight> 0 且高度为 0 子 View,因为后面会将把剩余高度按 weight 分配给相应的子 View。因此可以得出以下结论:(1)如果我们在 LinearLayout 中不使用 weight 属性,将只进行一次 measure 的过程。(如果使用 weight 属性,则遍历一次 wiew 测量后,再遍历一次 view 测量)(2)如果使用了 weight 属性,LinearLayout 在第一次测量时获取所有子 View 的高度,之后再将剩余高度根据 weight 加到 weight> 0 的子 View 上。由此可见,weight 属性对性能是有影响的。1)RelativeLayout 慢于 LinearLayout 是因为它会让子 View 调用 2 次 measure 过程,而 LinearLayout 只需一次,但是有 weight 属性存在时,LinearLayout 也需要两次 measure。2)在不响应层级深度的情况下,使用 Linearlayout 而不是 RelativeLayout。

谈谈对接口与回调的理解接口回调就是指: 可以把使用某一接口的类创建的对象的引用赋给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口的方法。实际上,当接口变量调用被类实现的接口中的方法时,就是通知相应的对象调用接口的方法,这一过程称为对象功能的接口回调。

Android 中 View,SurfaceView 和 GLSurfaceViewView:显示视图,内置画布,提供图形绘制函数,触屏事件,按键事件函数;必须在 UI 线程中更新画面,速度较慢。SurfaceView:基于 View 视图进行拓展的视图类,更适合 2D 游戏的开发;是 View 的子类,类似双缓机制,在新的线程中更新画面,所以刷新界面速度比 View 快。(双缓机制:即前台缓存和后台缓存,后台缓存计算场景、产生画面,前台缓存显示后台缓存已画好的画面。)GLSurfaceView:基于 SurfaceView 视图再次进行扩展的视图类,专用于 3D 游戏开发的视图;是 SurfaceView 的子类,OpenGL 专用。(OpenGL:是一个开放的三维图形软件包。)

序列化的作用,以及 Android 两种序列化的区别作用:java 序列化主要有 2 个作用:对象持久化,对象生存在内存中,想把一个对象持久化到磁盘,必须已某种方式来组织这个对象包含的信息,这种方式就是序列化;远程网络通信,内存中的对象不能直接进行网络传输,发送端把对象序列化成网络可传输的字节流,接收端再把字节流还原成对象。

SerializableJava 序列化接口在硬盘上读写读写过程中有大量临时变量的生成,内部执行大量的 i / o 操作,效率很低。ParcelableAndroid 序列化接口效率高使用麻烦在内存中读写(AS 有相关插件一键生成所需方法),对象不能保存到磁盘中

差值器和估值器差值器:根据时间流逝的百分比计算当前属性改变的百分比。估值器:根据当前属性改变的百分比计算改变后的属性值

Android 中数据存储方式 1 使用 SharedPreferences 存储数据适用范围:保存少量的数据,且这些数据的格式非常简单:字符串型、基本类型的值。比如应用程序的各种配置信息(如是否打开音效等),解锁口令密码等核心原理:保存基于 XML 文件存储的 key-value 键值对数据,通常用来存储一些简单的配置信息。

2 文件存储数据核心原理:Context 提供了两个方法来打开数据文件里的文件 IO 流:FileInputStreamopenFileInput(Stringname);FileOutputStreamopenFileOutput(Stringname,intmode)3SQLite 数据库存储数据 4 使用 ContentProvider 存储数据 5 网络存储数据 Requestlayout,onlayout,onDraw,DrawChild 区别与联系 requestLayout()方法:会导致调用 measure()过程和 layout()过程。说明:只是对 View 树重新布局 layout 过程包括 measure()和 layout()过程,不会调用 draw()过程,但不会重新绘制任何视图包括该调用者本身。onLayout()方法 (如果该 View 是 ViewGroup 对象,需要实现该方法,对每个子视图进行布局) 调用 onDraw()方法绘制视图本身 (每个 View 都需要重载该方法,ViewGroup 不需要实现该方法)drawChild() 去重新回调每个子视图的 draw()方法

invalidate 和 postInvalidate 的区别及使用 1、postInvalidate()方法在非 UI 线程中调用,通知 UI 线程重绘。2、invalidate()方法在 UI 线程中调用,重绘当前 UI。Invalidate 不能直接在线程中调用,因为他是违背了单线程模型:AndroidUI 操作并不是线程安全的,并且这些操作必须在 UI 线程中调用。

Activity-Window-View 三者的差别这个问题真的很不好回答。所以这里先来个算是比较恰当的比喻来形容下它们的关系吧。Activity 像一个工匠(控制单元),Window 像窗户(承载模型),View 像窗花(显示视图)LayoutInflater 像剪刀,Xml 配置像窗花图纸。1:Activity 构造的时候会初始化一个 Window,准确的说是 PhoneWindow。2:这个 PhoneWindow 有一个“ViewRoot”,这个“ViewRoot”是一个 View 或者说 ViewGroup,是最初始的根视图。3:“ViewRoot”通过 addView 方法来一个个的添加 View。比如 TextView,Button 等 4:这些 View 的事件监听,是由 WindowManagerService 来接受消息,并且回调 Activity 函数。比如 onClickListener,onKeyDown 等。

ActivityThread,AMS,WMS 的工作原理 Activity 与 Window:Activity 只负责生命周期和事件处理 Window 只控制视图一个 Activity 包含一个 Window,如果 Activity 没有 Window,那就相当于 Service。

AMS 与 WMS:AMS 统一调度所有应用程序的 ActivityWMS 控制所有 Window 的显示与隐藏以及要显示的位置。在视图层次中,Activity 在 WIndow 之上

ActivityThread:是 Android 应用的主线程(UI 线程)

WMS(WindowManagerService):管理的整个系统所有窗口的 UI 作用: 为所有窗口分配 Surface:客户端向 WMS 添加一个窗口的过程,其实就是 WMS 为其分配一块 Suiface 的过程,一块块 Surface 在 WMS 的管理下有序的排布在屏幕上。Window 的本质就是 Surface。(简单的说 Surface 对应了一块屏幕缓冲区)管理 Surface 的显示顺序、尺寸、位置管理窗口动画输入系统相关:WMS 是派发系统按键和触摸消息的最佳人选,当接收到一个触摸事件,它需要寻找一个最合适的窗口来处理消息,而 WMS 是窗口的管理者,系统中所有的窗口状态和信息都在其掌握之中,完成这一工作不在话下。

AMS(ActivityManagerService)ActivityManager 是客户端用来管理系统中正在运行的所有 Activity 包括 Task、Memory、Service 等信息的工具。但是这些这些信息的维护工作却不是又 ActivityManager 负责的。在 ActivityManager 中有大量的 get()方法,那么也就说明了他只是提供信息给 AMS,由 AMS 去完成交互和调度工作。

作用:统一调度所有应用程序的 Activity 的生命周期启动或杀死应用程序的进程启动并调度 Service 的生命周期注册 BroadcastReceiver,并接收和分发 Broadcast 启动并发布 ContentProvider 调度 task 处理应用程序的 Crash 查询系统当前运行状态

自定义 View 如何考虑机型适配布局类建议:合理使用 warp_content,match_parent. 尽可能的是使用 RelativeLayout 引入 android 的百分比布局。针对不同的机型,使用不同的布局文件放在对应的目录下,android 会自动匹配。

Icon 类建议:尽量使用 svg 转换而成 xml。切图的时候切大分辨率的图,应用到布局当中。在小分辨率的手机上也会有很好的显示效果。使用与密度无关的像素单位 dp,sp

AsyncTask 如何使用?Android 的 AsyncTask 比 Handler 更轻量级一些,适用于简单的异步处理。首先明确 Android 之所以有 Handler 和 AsyncTask,都是为了不阻塞主线程(UI 线程),且 UI 的更新只能在主线程中完成,因此异步处理是不可避免的。Android 为了降低这个开发难度,提供了 AsyncTask。AsyncTask 就是一个封装过的后台任务类,顾名思义就是异步任务。

AsyncTask 直接继承于 Object 类,位置为 android.os.AsyncTask。要使用 AsyncTask 工作我们要提供三个泛型参数,并重载几个方法(至少重载一个)。

例:

publicclassTaskextendsAsyncTask//Void 是三个泛型参数的原始状态, 并且 Void 也是一个类而不是 void

AsyncTask 定义了三种泛型类型 Params,Progress 和 Result。

Params 启动任务执行的输入参数,比如 HTTP 请求的 URL。(可传入多个参数)Progress 后台任务执行的百分比。Result 后台执行任务最终返回的结果,比如 String。使用过 AsyncTask 的同学都知道一个异步加载数据最少要重写以下这两个方法:

doInBackground(Params……)后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作 UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用 publicProgress(Progress……)来更新任务的进度。publicProgress(Progress……)会将 Progress… 传给 onProgressUpdate(Progress…)作为 ProgressUpdate(Progress…)的接收参数。onPostExecute(Result)相当于 Handler 处理 UI 的方式,在这里面可以使用在 doInBackground 得到的结果处理操作 UI。此方法在主线程执行,任务执行的结果作为此方法的参数返回有必要的话你还得重写以下这三个方法,但不是必须的:

onProgressUpdate(Progress……)可以使用进度条增加用户体验度。此方法在主线程执行,用于显示任务执行的进度。onPreExecute()这里是最终用户调用 Excute 时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。onCancelled()用户调用取消时,要做的操作使用 AsyncTask 类,以下是几条必须遵守的准则:

Task 的实例必须在 UIthread(主线程)中创建;execute 方法必须在 UIthread 中调用;不要手动的调用 onPreExecute(),onPostExecute(Result),doInBackground(Params…),onProgressUpdate(Progress…)这几个方法;该 task 只能被执行一次,否则多次调用时将会出现异常;

SpareArray 与 HashMap 比较,应用场景 SparseArray 采用的不是哈希算法,HashMap 采用的是哈希算法。SparseArray 采用的是两个一维数组分别用于存储键和值,HashMap 采用的是一维数组 + 单向链表或二叉树。SparseArraykey 只能是 int 类型,而 HashMap 的 key 是 Object。SparseArraykey 是有序存储(升序),而 HashMap 不是。SparseArray 默认容量是 10,而 HashMap 默认容量是 16。SparseArray 默认每次扩容是 2 倍于原来的容量,而 HashMap 默认每次扩容时是原容量 *0.75 倍 SparseArrayvalue 的存储被不像 HashMap 一样需要额外的需要一个实体类(Node)进行包装 SparseArray 查找元素总体而言比 HashMap 要逊色,因为 SparseArray 查找是需要经过二分法的过程,而 HashMap 不存在冲突的情况其技术处的 hash 对应的下标直接就可以取到值。针对上面与 HashMap 的比较,采用 SparseArray 还是 HashMap,建议根据如下需求选取:

如果对内存要求比较高,而对查询效率没什么大的要求,可以是使用 SparseArray 数量在百级别的 SparseArray 比 HashMap 有更好的优势要求 key 是 int 类型的,因为 HashMap 会对 int 自定装箱变成 Integer 类型要求 key 是有序的且是升序

请介绍下 ContentProvider 是如何实现数据共享的?一个程序可以通过实现一个 Contentprovider 的抽象接口将自己的数据完全暴露出去,而且 Contentproviders 是以类似数据库中表的方式将数据暴露。Contentproviders 存储和检索数据,通过它可以让所有的应用程序访问到,这也是应用程序之间唯一共享数据的方法。要想使应用程序的数据公开化,可通过 2 种方法:创建一个属于你自己的 Contentprovider 或者将你的数据添加到一个已经存在的 Contentprovider 中,前提是有相同数据类型并且有写入 Contentprovider 的权限。如何通过一套标准及统一的接口获取其他应用程序暴露的数据?Android 提供了 ContentResolver,外界的程序可以通过 ContentResolver 接口访问 ContentProvider 提供的数据。

AndroidService 与 Activity 之间通信的几种方式通过 broadcast: 通过广播发送消息到 activitry 通过 Binder:通过与 activity 进行绑定

IntentService 原理及作用是什么?IntentService 是继承于 Service 并处理异步请求的一个类,在 IntentService 内有一个工作线程来处理耗时操作,启动 IntentService 的方式和启动传统 Service 一样,同时,当任务执行完后,IntentService 会自动停止,而不需要我们去手动控制。另外,可以启动 IntentService 多次,而每一个耗时操作会以工作队列的方式在 IntentService 的 onHandleIntent 回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推。所有请求都在一个单线程中,不会阻塞应用程序的主线程(UIThread),同一时间只处理一个请求。

说说 Activity、Intent、Service 是什么关系一个 Activity 通常是一个单独的屏幕,每一个 Activity 都被实现为一个单独的类,这些类都是从 Activity 基类中继承来的,Activity 类会显示由视图控件组成的用户接口,并对视图控件的事件做出响应。

Intent 的调用是用来进行架构屏幕之间的切换的。Intent 是描述应用想要做什么。Intent 数据结构中两个最重要的部分是动作和动作对应的数据,一个动作对应一个动作数据。

AndroidService 是运行在后台的代码,不能与用户交互,可以运行在自己的进程,也可以运行在其他应用程序进程的上下文里。需要通过某一个 Activity 或者其他 Context 对象来调用。Activity 跳转到 Activity,Activity 启动 Service,Service 打开 Activity,Activity 跳转到 Activity,Activity 启动 Service,Service 打开 Activity

SP 是进程同步的吗? 有什么方法做到同步?SharedPreferences 不支持进程同步一个进程的情况,经常采用 SharePreference 来做,但是 SharePreference 不支持多进程,它基于单个文件的,默认是没有考虑同步互斥,而且,APP 对 SP 对象做了缓存,不好互斥同步。

考虑用 ContentProvider 来实现 SharedPreferences 的进程同步,ContentProvider 基于 Binder,不存在进程间互斥问题,对于同步,也做了很好的封装,不需要开发者额外实现。另外 ContentProvider 的每次操作都会重新 getSP,保证了 sp 的一致性。

谈谈多线程在 Android 中的使用 Handler+ThreadAsyncTaskThreadPoolExecutorIntentService

进程和 Application 的生命周期 onCreate():Application 创建的时候调用 onConfigurationChanged(ConfigurationnewConfig): 当配置信息改变的时候会调用,如屏幕旋转、语言切换时。onLowMemory():Android 系统整体内存较低时候调用,通常在这里释放一些不重要的资源,或者提醒用户清一下垃圾,来保证内存足够而让 APP 进程不被系统杀掉。它和 OnTrimMemory 中的 TRIM_MEMORY_COMPLETE 级别相同。onTrimMemory(intlevel):Android4.0 之后提供的一个 API,用于取代 onLowMemory()。在系统内存不足的时会被调用,提示开发者清理部分资源来释放内存,从而避免被 Android 系统杀死。详见《Android 代码内存优化建议 -OnTrimMemory 优化》onTerminate():Application 结束的时候会调用, 由系统决定调用的时机

封装 View 的时候怎么知道 view 的大小这里考点是自定义的那几个方法含义,源码理解。需要自己去看看源码,然后用自己的话表达出来。

AndroidManifest 的作用与理解 AndroidManifest.xml 是每个 android 程序中必须的文件。它位于整个项目的根目录,描述了 package 中暴露的组件(activities,services 等等),他们各自的实现类,各种能被处理的数据和启动位置。除了能声明程序中的 Activities,ContentProviders,Services,和 IntentReceivers,还能指定 permissions 和 instrumentation(安全控制和测试)。

Handler、Thread 和 HandlerThread 的差别 Handler:在 Android 中负责发送和处理消息,通过它可以实现其他支线线程与主线程之间的消通讯 Thread:线程,可以看作是进程的一个实体,是 CPU 调度和分派的基本单位,他是比进程更小的独立运行的基本单位 HandlerThread:封装了 Handler+ThreadHandlerThread 适合在有需要一个工作线程(非 UI 线程)+ 任务的等待队列的形式,优点是不会有堵塞,减少了对性能的消耗,缺点是不能同时进行多个任务的处理,需要等待进行处理。处理效率低,可以当成一个轻量级的线程池来用

为什么在主线程可以直接使用 Handler?因为主线程已经创建了 Looper 对象并开启了消息循环

关于 Handler,在任何地方 newHandler 都是什么线程下? 不传递 Looper 创建 Handler:Handlerhandler=newHandler(); 上文就是 Handler 无参创建的源码,可以看到是通过 Looper.myLooper()来获取 Looper 对象,也就是说对于不传递 Looper 对象的情况下,在哪个线程创建 Handler 默认获取的就是该线程的 Looper 对象,那么 Handler 的一系列操作都是在该线程进行的。

对于传递 Looper 对象创建 Handler 的情况下,传递的 Looper 是哪个线程的,Handler 绑定的就是该线程。ThreadLocal 原理,实现及如何保证 Local 属性?ThreadLocal:当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用 ThreadLocal。(Looper、ActivityThread 以及 AMS 中都用到了),如使用 ThreadLocal 可以解决不同线程不同 Looper 的需求。

虽然在不同线程中访问的是同一个 ThreadLocal 对象,但是它们通过 ThreadLocal 来获取到的值却是不一样的,这就是 ThreadLocal 的奇妙之处。ThreadLocal 之所以有这么奇妙的效果,是因为不同线程访问同一个 ThreadLocal 的 get 方法,ThreadLocal 内部会从各自的线程中取出一个数组,然后再从数组中根据当前 ThreadLocal 的索引去查找出对应的 value 值,很显然,不同线程中的数组是不同的,这就是为什么通过 ThreadLocal 可以在不同的线程中维护一套数据的副本并且彼此互不干扰。(从 ThreadLocal 的 set 和 get 方法可以看出,它们所操作的对象都是当前线程的 localValues 对象的 table 数组,因此在不同线程中访问同一个 ThreadLocal 的 set 和 get 方法,它们对 ThreadLocal 所做的读写操作仅限于各自线程的内部,这就是为什么 ThreadLocal 可以在多个线程中互不干扰地存储和修改数据。)

Handler 机制和底层实现

上面一共出现了几种类,ActivityThread,Handler,MessageQueue,Looper,msg(Message), 对这些类作简要介绍:ActivityThread:程序的启动入口,该类就是我们说的主线程,它对 Looper 进行操作的。Handler:字面意思是操控者,该类有比较重要的地方,就是通过 handler 来发送消息(sendMessage)到 MessageQueue 和操作控件的更新(handleMessage)。handler 下面持有这 MessageQueue 和 Looper 的对象。MessageQueue:字面意思是消息队列,就是封装 Message 类。对 Message 进行插入和取出操作。Message:这个类是封装消息体并被发送到 MessageQueue 中的,给类是通过链表实现的,其好处方便 MessageQueue 的插入和取出操作。还有一些字段是(intwhat,Objectobj,intarg1,intarg2)。what 是用户定义的消息和代码,以便接收者(handler)知道这个是关于什么的。obj 是用来传输任意对象的,arg1 和 arg2 是用来传递一些简单的整数类型的。

先获取 looper,如果没有就创建

创建过程:

ActivityThread 执行 looperMainPrepare(),该方法先实例化 MessageQueue 对象,然后实例化 Looper 对象,封装 mQueue 和主线程,把自己放入 ThreadLocal 中

再执行 loop()方法,里面会重复死循环执行读取 MessageQueue。接着 ActivityThread 执行 Looper 对象中的 loop()方法)此时调用 sendMessage()方法,往 MessageQueue 中添加数据,其取出消息队列中的 handler,执行 dispatchMessage(),进而执行 handleMessage(),Message 的数据结构是基于链表的

Looper 为什么要无限循环?主线程中如果没有 looper 进行循环,那么主线程一运行完毕就会退出。那么我们还能运行 APP 吗,显然,这是不可能的,Looper 主要就是做消息循环,然后由 Handler 进行消息分发处理,一旦退出消息循环,那么你的应用也就退出了。

主线程中的 Looper.loop()一直无限循环为什么不会造成 ANR?主线程 Looper 从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此 loop 的循环并不会对 CPU 性能有过多的消耗。

请解释下在单线程模型中 Message、Handler、MessageQueue、Looper 之间的关系 Android 中主线程是不能进行耗时操作的,子线程是不能进行更新 UI 的。所以就有了 handler,它的作用就是实现线程之间的通信。handler 整个流程中,主要有四个对象,handler,Message,MessageQueue,Looper。当应用创建的时候,就会在主线程中创建 handler 对象,我们通过要传送的消息保存到 Message 中,handler 通过调用 sendMessage 方法将 Message 发送到 MessageQueue 中,Looper 对象就会不断的调用 loop()方法不断的从 MessageQueue 中取出 Message 交给 handler 进行处理。从而实现线程之间的通信。

请描述一下 View 事件传递分发机制 Touch 事件分发中只有两个主角:ViewGroup 和 View。ViewGroup 包含 onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent 三个相关事件。View 包含 dispatchTouchEvent、onTouchEvent 两个相关事件。其中 ViewGroup 又继承于 View。2.ViewGroup 和 View 组成了一个树状结构,根节点为 Activity 内部包含的一个 ViwGroup。3. 触摸事件由 Action_Down、Action_Move、Aciton_UP 组成,其中一次完整的触摸事件中,Down 和 Up 都只有一个,Move 有若干个,可以为 0 个。4. 当 Acitivty 接收到 Touch 事件时,将遍历子 View 进行 Down 事件的分发。ViewGroup 的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的 View,这个 View 会在 onTouchuEvent 结果返回 true。5. 当某个子 View 返回 true 时,会中止 Down 事件的分发,同时在 ViewGroup 中记录该子 View。接下去的 Move 和 Up 事件将由该子 View 直接进行处理。由于子 View 是保存在 ViewGroup 中的,多层 ViewGroup 的节点结构时,上级 ViewGroup 保存的会是真实处理事件的 View 所在的 ViewGroup 对象: 如 ViewGroup0-ViewGroup1-TextView 的结构中,TextView 返回了 true,它将被保存在 ViewGroup1 中,而 ViewGroup1 也会返回 true,被保存在 ViewGroup0 中。当 Move 和 UP 事件来时,会先从 ViewGroup0 传递至 ViewGroup1,再由 ViewGroup1 传递至 TextView。6. 当 ViewGroup 中所有子 View 都不捕获 Down 事件时,将触发 ViewGroup 自身的 onTouch 事件。触发的方式是调用 super.dispatchTouchEvent 函数,即父类 View 的 dispatchTouchEvent 方法。在所有子 View 都不处理的情况下,触发 Acitivity 的 onTouchEvent 方法。7.onInterceptTouchEvent 有两个作用:1. 拦截 Down 事件的分发。2. 中止 Up 和 Move 事件向目标 View 传递,使得目标 View 所在的 ViewGroup 捕获 Up 和 Move 事件。

事件分发中的 onTouch 和 onTouchEvent 有什么区别,又该如何使用?OnTouch 方法:onTouch()是 OnTouchListener 接口的方法,它是获取某一个控件的触摸事件,因此使用时。通过 getAction()方法可以获取当前触摸事件的状态:如:ACTION_DOWN:表示按下了屏幕的状态。

OnTouchEvent()方法:onTouchEvent 是手机屏幕事件的处理方法,是获取的对屏幕的各种操作,比如向左向右滑动,点击返回按钮等等。通过查看安卓源码中 View 对 dispatchTouchEvent 的实现,可以知道 onTouchListener(onTouch 方法在其中)的接口的执行顺序是要先于 onTouchEvent 的,onTouch 方法会先触发。

如果 onTouchListener 中的 onTouch 方法返回 true,表示此次事件已经被消费了,那 onTouchEvent 是接收不到消息的。(内置诸如 click 事件的实现等等都基于 onTouchEvent, 这些事件将不会被触发),如果 onTouch 方法返回 false 会接着触发 onTouchEvent。

View 刷新机制通过 ViewRootImpl 的 scheduleTraversals()进行界面的三大流程。调用到 scheduleTraversals()时不会立即执行,而是将该操作保存到待执行队列中。并给底层的刷新信号注册监听。当 VSYNC 信号到来时,会从待执行队列中取出对应的 scheduleTraversals()操作,并将其加入到主线程的消息队列中。主线程从消息队列中取出并执行三大流程:onMeasure()-onLayout()-onDraw()

View 绘制流程自定义控件:1、组合控件。这种自定义控件不需要我们自己绘制,而是使用原生控件组合成的新控件。如标题栏。2、继承原有的控件。这种自定义控件在原生控件提供的方法外,可以自己添加一些方法。如制作圆角,圆形图片。3、完全自定义控件:这个 View 上所展现的内容全部都是我们自己绘制出来的。比如说制作水波纹进度条。View 的绘制流程:OnMeasure()——>OnLayout()——>OnDraw()第一步:OnMeasure():测量视图大小。从顶层父 View 到子 View 递归调用 measure 方法,measure 方法又回调 OnMeasure。第二步:OnLayout():确定 View 位置,进行页面布局。从顶层父 View 向子 View 的递归调用 view.layout 方法的过程,即父 View 根据上一步 measure 子 View 所得到的布局大小和布局参数,将子 View 放在合适的位置上。第三步:OnDraw():绘制视图。ViewRoot 创建一个 Canvas 对象,然后调用 OnDraw()。六个步骤:①、绘制视图的背景;②、保存画布的图层(Layer);③、绘制 View 的内容;④、绘制 View 子视图,如果没有就不用;⑤、还原图层(Layer);⑥、绘制滚动条。

自定义 View 如何提供获取 View 属性的接口?自定义属性的实现流程:1. 在 values 目录下定义一个 attrs.xml:在 res/values/attr.xml 中定义相关属性。2. 在对应的类文件里生成某些组件:在对应类的构造函数中通过 obtainStyledAttributes()方法获得自定义属性的相关值 3. 在 layout 布局文件里为这些属性赋值:在布局中添加为该自定义组件设置一个命名空间,并且相关属性赋值

AsyncTask 是什么?AsyncTask 使得可以恰当和简单地使用 UI 线程。这个 class 允许你在后台做一些事情,然后把进度和结果告诉 UI 线程,而不需要操作 handler 和线程。

AsyncTask 设计的思想是什么?AsyncTask 的设计是为了成为一个关于 Thread 和 Handler 的帮助类,并不是一个通用的线程框架。AsyncTask 理想情况下,应该被使用于非常短的操作(最多几秒)。如果您希望您的线程可以运行很长时间,非常建议您使用 java.util.concurrent 包里面的 API。例如 Executor,ThreadPoolExecutor 和 FutureTask

AsyncTask 原理及不足原理:AsyncTask 是 Android 本身提供的一种轻量级的异步任务类。它可以在线程池中执行后台任务,然后把执行的进度和最终的结果传递给主线程更新 UI。实际上,AsyncTask 内部是封装了 Thread 和 Handler。虽然 AsyncTask 很方便的执行后台任务,以及在主线程上更新 UI,但是,AsyncTask 并不合适进行特别耗时的后台操作,对于特别耗时的任务,个人还是建议使用线程池。AsyncTask 提供有 4 个核心方法:1、onPreExecute(): 该方法在主线程中执行,在执行异步任务之前会被调用,一般用于一些准备工作。2、doInBackground(String…params): 这个方法是在线程池中执行,此方法用于执行异步任务。在这个方法中可以通过 publishProgress 方法来更新任务的进度,publishProgress 方法会调用 onProgressUpdate 方法,另外,任务的结果返回给 onPostExecute 方法。3、onProgressUpdate(Object…values): 该方法在主线程中执行,主要用于任务进度更新的时候,该方法会被调用。4、onPostExecute(LongaLong):在主线程中执行,在异步任务执行完毕之后,该方法会被调用,该方法的参数及为后台的返回结果。除了这几个方法之外还有一些不太常用的方法,如 onCancelled(), 在异步任务取消的情况下,该方法会被调用。源码可以知道从上面的 execute 方法内部调用的是 executeOnExecutor()方法,即 executeOnExecutor(sDefaultExecutor,params); 而 sDefaultExecutor 实际上是一个串行的线程池。而 onPreExecute()方法在这里就会被调用了。接着看这个线程池。AsyncTask 的执行是排队执行的,因为有关键字 synchronized,而 AsyncTask 的 Params 参数就封装成为 FutureTask 类,FutureTask 这个类是一个并发类,在这里它充当了 Runnable 的作用。接着 FutureTask 会交给 SerialExecutor 的 execute 方法去处理,而 SerialExecutor 的 executor 方法首先就会将 FutureTask 添加到 mTasks 队列中,如果这个时候没有任务,就会调用 scheduleNext()方法,执行下一个任务。如果有任务的话,则执行完毕后最后在调用 scheduleNext(); 执行下一个任务。直到所有任务被执行完毕。而 AsyncTask 的构造方法中有一个 call()方法,而这个方法由于会被 FutureTask 的 run 方法执行。所以最终这个 call 方法会在线程池中执行。而 doInBackground 这个方法就是在这里被调用的。我们好好研究一下这个 call()方法。mTaskInvoked.set(true); 表示当前任务已经执行过了。接着执行 doInBackground 方法,最后将结果通过 postResult(result); 方法进行传递。postResult()方法中通过 sHandler 来发送消息,sHandler 的中通过消息的类型来判断一个 MESSAGE_POST_RESULT,这种情况就是调用 onPostExecute(result)方法或者是 onCancelled(result)。另一种消息类型是 MESSAGE_POST_PROGRESS 则调用更新进度 onProgressUpdate。

不足:AsyncTask 的优点在于执行完后台任务后可以很方便的更新 UI,然而使用它存在着诸多的限制。先抛开内存泄漏问题,使用 AsyncTask 主要存在以下局限性:在 Android4.1 版本之前,AsyncTask 类必须在主线程中加载,这意味着对 AsyncTask 类的第一次访问必须发生在主线程中;在 Android4.1 以及以上版本则不存在这一限制,因为 ActivityThread(代表了主线程)的 main 方法中会自动加载 AsyncTaskAsyncTask 对象必须在主线程中创建 AsyncTask 对象的 execute 方法必须在主线程中调用一个 AsyncTask 对象只能调用一次 execute 方法

如何取消 AsyncTask?1. 调用 cancel():但是他是在在 doInBackground()之后执行

如果调用 cancel()方法,它不会立即执行,只有当 doInBackground()方法执行完有返回值之后,会在 UI 主线程调用 cancel(),同时也会间接的调用 iscancelled(),并且返回 true,这个时候就不会再调 onPostExecute(),然后在 doInBackground()里定期检查 iscancelled()方法的返回值,是否被 cancel,如果 returntrue,就尽快停止。

2. 在耗时操作中设置一些 flag:我们可以在这个线程中的耗时操作中设置一些 flag,也就是 AsyncTask 的 doInBackground 方法中的某些关键步骤。然后在外层需要终止此线程的地方改变这个 flag 值,线程中的耗时代码一步步执行,当某一时刻发现 flag 的值变了,throwException,线程就不会再继续执行了。为了保险起见,在外层我们还要捕获这个异常,进行相应处理。(子线程被发生异常后会自己死掉而不会引起其他问题,更不会影响到主线程,更何况我们为了更加安全还捕获了异常并做处理)

AsyncTask 适合做什么?必须同时满足以下条件:a. 执行过程单一,仅输入一次,输出一次。b. 花费时间非常短但是仍然需要到后台去做事情,然后更新 UI。例如加载文件,web 页面或者数据库到 UI。c. 执行线程必须是 UI 线程 d. 不需要长期维护状态。

AsyncTask 不适合做什么?a. 长时间的任务。b. 可重复调用的任务。c. 需要线程执行多个不同任务,任务之间又有关联。d. 执行线程不是 UI 线程。e. 任务执行后仍然需要维护一些状态。f. 后台服务模块, 需要提供独立的 API.

为什么不能在子线程更新 UI?目的在于提高移动端更新 UI 的效率和和安全性,以此带来流畅的体验。原因是:Android 的 UI 访问是没有加锁的,多个线程可以同时访问更新操作同一个 UI 控件。也就是说访问 UI 的时候,android 系统当中的控件都不是线程安全的,这将导致在多线程模式下,当多个线程共同访问更新操作同一个 UI 控件时容易发生不可控的错误,而这是致命的。所以 Android 中规定只能在 UI 线程中访问 UI,这相当于从另一个角度给 Android 的 UI 访问加上锁,一个伪锁。ANR 产生的原因是什么?ANR 即 ApplicationNotResponding,顾名思义就是应用程序无响应。

在 Android 中,一般情况下,四大组件均是工作在主线程中的,Android 中的 ActivityManager 和 WindowManager 会随时监控应用程序的响应情况,如果因为一些耗时操作(网络请求或者 IO 操作)造成主线程阻塞一定时间(例如造成 5s 内不能响应用户事件或者 BroadcastReceiver 的 onReceive 方法执行时间超过 10s),那么系统就会显示 ANR 对话框提示用户对应的应用处于无响应状态。

ANR 定位和修正 ANR 一般有三种类型 KeyDispatchTimeout(5seconds)–主要类型按键或触摸事件在特定时间内无响应 BroadcastTimeout(10seconds)BroadcastReceiver 在特定时间内无法处理完成 ServiceTimeout(20seconds)–小概率类型 Service 在特定的时间内无法处理完成

1、主线程当中执行 IO/ 网络操作,容易阻塞。2、主线程当中执行了耗时的计算。(比如自定义控件中的 onDraw()方法)在 onDraw()方法里面创建对象容易导致内存抖动(绘制动作时会大量不间断调用,产生大量垃圾对象导致 GC 很频繁,就造成了内存抖动),内存抖动就容易造成 UI 出现掉帧、卡顿等问题。3、BroadCastReceiver 没有在 10 秒内完成处理。4、BroadCastReceiver 的 onReceived 代码中也要尽量减少耗时的操作,建议使用 IntentService 处理。5、Service 执行了耗时的操作,因为 Service 也是在主线程当中执行的,所以耗时操作应该在 Service 里面开启子线程来做。6、使用 AsyncTask 处理耗时的 IO 等操作。7、Activity 的 onCreate 和 onResume 回调中尽量耗时的操作。

oom 是什么?怎么导致的,怎么解决?内存泄漏是什么?什么情况导致内存泄漏?(1)内存溢出(OOM)和内存泄露(对象无法被回收)的区别。(2)引起内存泄露的原因 (3) 内存泄露检测工具 ——>LeakCanary 内存溢出 outofmemory:是指程序在申请内存时,没有足够的内存空间供其使用,出现 outofmemory;比如申请了一个 integer, 但给它存了 long 才能存下的数,那就是内存溢出。内存溢出通俗的讲就是内存不够用。内存泄露 memoryleak:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存, 迟早会被占光内存泄露原因:一、Handler 引起的内存泄漏。解决:将 Handler 声明为静态内部类,就不会持有外部类 SecondActivity 的引用,其生命周期就和外部类无关,如果 Handler 里面需要 context 的话,可以通过弱引用方式引用外部类二、单例模式引起的内存泄漏。解决:Context 是 ApplicationContext,由于 ApplicationContext 的生命周期是和 app 一致的,不会导致内存泄漏三、非静态内部类创建静态实例引起的内存泄漏。解决:把内部类修改为静态的就可以避免内存泄漏了四、非静态匿名内部类引起的内存泄漏。解决:将匿名内部类设置为静态的。

更多关注公众号:码农乐园

大美
版权声明:本站原创文章,由 大美 2022-11-23发表,共计45074字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。