admin管理员组

文章数量:1530085

目录

  • Java篇
  • Android篇
  • 网络篇
一、前言

根据多年的工作经验以及面试经验,总结了一下知识点,分为Java篇、Android篇与网络篇,接下来分别介绍。

二、Java篇
1、java中==和equals和hashCode的区别
  • 基本数据类型的==比较的值相等.

  • 类的==比较的内存的地址,即是否是同一个对象,在不覆盖equals的情况下,同比较内存地址,原实现也为 == ,如String等重写了equals方法.
    hashCode也是Object类的一个方法。返回一个离散的int型整数。在集合类操作中使用,为了提高查询速度。(HashMap,HashSet等比较是否为同一个)。

  • 如果两个对象equals,Java运行时环境会认为他们的hashcode一定相等。

  • 如果两个对象不equals,他们的hashcode有可能相等。

  • 如果两个对象hashcode相等,他们不一定equals。

  • 如果两个对象hashcode不相等,他们一定不equals。

  • == 和 equals()的比较
  • “== ” 用于比较基本数据类型时比较的是值,用于比较引用类型时比较的是引用指向的地址
  • Object 中的equals()
    与 “==” 的作用相同,但String类重写了equals()方法,比较的是对象中的内容。
public boolean equals(Object anObject) {  
if (this == anObject) {
      return true;    
   } else { 
      if (anObject instanceof String) {
          String aString = (String)anObject;
       if (this.coder() == aString.coder()) { 
       return this.isLatin1() ? StringLatin1.equals(this.value, aString.value) : StringUTF16.equals(this.value, aString.value); 
       }
     } 
        return false;  
   }
}
  • String对象的两种创建方式:
  • 第一种方式: String str1 = “aaa”; 是在常量池中获取对象(“aaa” 属于字符串字面量,因此编译时期会在常量池中创建一个字符串对象,如果常量池中已经存在该字符串对象则直接引用)
  • 第二种方式: String str2 = new String(“aaa”) ; 一共会创建两个字符串对象一个在堆中,一个在常量池中(前提是常量池中还没有 “aaa” 象)。
    System.out.println(str1==str2);//false
  • String类型的常量池比较特殊。它的主要使用方法有两种:
  • 直接使用双引号声明出来的String对象会直接存储在常量池中。
  • 如果不是用双引号声明的String对象,可以使用 String 提供的 intern 方法。 String.intern() 是一个 Native 方法,它的作用是: 如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;
    如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。
    String s1 = new String("AAA");
    String s2 = s1.intern();
    String s3 = "AAA";
    System.out.println(s2);//AAA
    System.out.println(s1 == s2);//false,因为一个是堆内存中的String对象一个是常量池中的String对象,
    System.out.println(s2 == s3);//true, s1,s2指向常量池中的”AAA“
  • 字符串拼接:
    String a = "a";
    String b = "b";
         
    String str1 = "a" + "b";//常量池中的对象
    String str2 = a + b; //在堆上创建的新的对象     
    String str3 = "ab";//常量池中的对象
    System.out.println(str1 == str2);//false
    System.out.println(str1 == str3);//true 
    System.out.println(str2 == str3);//false
2、int与integer的区别
  • int 基本类型 占4个字节
  • integer 对象 int的封装类
3、String、StringBuffer、StringBuilder区别
  • String:字符串常量 不适用于经常要改变值得情况,每次改变相当于生成一个新的对象
  • StringBuffer:字符串变量 (线程安全,由于StringBuffer有缓冲区)
  • StringBuilder:字符串变量(线程不安全) 确保单线程下可用,效率略高于StringBuffer
  • 三者在执行速度上:StringBuilder > StringBuffer > String (由于String是常量,不可改变,拼接时会重新创建新的对象)。
4、什么是内部类?内部类的作用
  • 内部类可直接访问外部类的属性

  • Java中内部类主要分为

  • 成员内部类
  • 局部内部类(嵌套在方法和作用域内)
  • 匿名内部类(没构造方法)
  • 静态内部类(static修饰的类,不能使用任何外围类的非static成员变量和方法, 不依赖外围类)
5、进程和线程的区别
  • 进程是cpu资源分配的最小单位,线程是cpu调度的最小单位。
  • 进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源。
  • 一个进程内可拥有多个线程,进程可开启进程,也可开启线程。
  • 一个线程只能属于一个进程,线程可直接使用同进程的资源,线程依赖于进程而存在。
  • 线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。
6、final,finally,finalize的区别
  • final:修饰类、成员变量和成员方法,类不可被继承,成员变量不可变,成员方法不可重写
  • 如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。
  • 将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在new一个对象时初始化(即只能在声明变量或构造器或代码块内初始化),而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能覆盖(重写)
  • finally
  • try...catch…共同使用,确保无论是否出现异常都能被执行,在异常处理时提供 finally 块来执行任何清除操作。

  • 如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入finally 块(如果有的话)。

  • finally前有return(当try或catch块中有return语句时),会先执行return语句,并保存下来,再执行finally块,最后return

  • finally前有return、finally块中也有return,先执行前面的return,保存下来,再执行finallyreturn,覆盖之前的结果,并返回。

测试方法如下:

测试方法1public static int Test1()
    {
        int count= 1;
        try
        {
            return ++count;
        }
        finally
        {
            ++count;
            Console.WriteLine("finally-value:" +count);
        }
    }
    public static void Main(string[] args)
    {
        Console.WriteLine("Main-value:" + Test1());
    }
运行结果:
finally-value:3
Main-value:2
 
 
测试方法2public static int Test2()
    {
        int count= 1;
        try
        {
            throw new Exception();
        }
        catch
        {
            return ++count;
        }
        finally
        {
            ++count;
            Console.WriteLine("finally-value:" +count);
        }
    } 
   public static void Main(string[] args)
    {
        Console.WriteLine("Main-value:" + Test2());
    }



运行结果:
finally-value:3
Main-value:2
  • finalize:类的方法,垃圾回收之前会调用此方法,子类可以重写finalize()方法实现对资源的回收
  • Java 技术允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在Object
    类中定义的,因此所有的类都继承了它。子类覆盖(重写) finalize()方法以整理系统资源或者执行其他清理工作。
  • finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。注意:finalize不一定被jvm调用,只有当垃圾回收器要清除垃圾时才被调用
  • 这个方法一个对象只能执行一次,只能在第一次进入被回收的队列,而且对象所属于的类重写了finalize方法才会被执行。第二次进入回收队列的时候,不会再执行其finalize方法,而是直接被二次标记,在下一次GC的时候被GC。
7、Serializable 和Parcelable 的区别
  • Serializable Java 序列化接口 在硬盘上读写 读写过程中有大量临时变量的生成,内部执行大量的i/o操作,效率很低。
  • Parcelable Android 序列化接口 效率高 使用麻烦 在内存中读写(AS有相关插件 一键生成所需方法) ,对象不能保存到磁盘中

具体请参考Android进程通信之文件共享(序列化Serialzable与Parcelable)

8、静态属性和静态方法是否可以被继承?是否可以被重写?以及原因?
  • 可继承 不可重写 而是被隐藏
  • 如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成。
9、成员内部类、静态内部类、局部内部类和匿名内部类的理解,以及项目中的应用
  • Java中内部类主要分为成员内部类、局部内部类(嵌套在方法和作用域内)、匿名内部类(没构造方法)、静态内部类(static修饰的类,不能使用任何外围类的非static成员变量和方法,
    不依赖外围类)

  • 使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

  • 因为Java不支持多继承,支持实现多个接口。但有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。

10、string 转换成 integer的方式及原理
  • String to integer Intrger.parseInt(string);

  • Integer to string Integer.toString();

11、哪些情况下的对象会被垃圾回收机制处理掉?
  • 所有实例都没有活动线程访问。

  • 没有被其他任何实例访问的循环引用实例。

  • Java 中有不同的引用类型。判断实例是否符合垃圾收集的条件都依赖于它的引用类型。
    要判断怎样的对象是没用的对象。这里有2种方法:

  1. 采用标记计数的方法: 给内存中的对象给打上标记,对象被引用一次,计数就加1,引用被释放了,计数就减一,当这个计数为0的时候,这个对象就可以被回收了。当然,这也就引发了一个问题:循环引用的对象是无法被识别出来并且被回收的。所以就有了第二种方法:

  2. 采用根搜索算法: 从一个根出发,搜索所有的可达对象,这样剩下的那些对象就是需要被回收的

Java的四种引用以及标记算法请参考(GC)垃圾标记算法详解攻略

12、静态代理和动态代理的区别,什么场景使用?
  • 静态代理类:
    由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
  • 动态代理类:
    在程序运行时,运用反射机制动态创建而成。
13、Java中实现多态的机制是什么?
  • 答:方法的重写Overriding和重载Overloading是Java多态性的不同表现

  • 重写Overriding是父类与子类之间多态性的一种表现

  • 重载Overloading是一个类中多态性的一种表现.

14、说说你对Java反射的理解

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

15、说说你对Java注解的理解
  • 元注解

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

  1. @Target

  2. @Retention

  3. @Documented

  4. @Inherited

16、Java中String的了解
  • 在源码中string是用final 进行修饰,它是不可更改,不可继承的常量。
17、String为什么要设计成不可变的?
  • 字符串池的需求

字符串池是方法区(Method Area)中的一块特殊的存储区域。当一个字符串已经被创建并且该字符串在 池中,该字符串的引用会立即返回给变量,而不是重新创建一个字符串再将引用返回给变量。如果字符串不是不可变的,那么改变一个引用(如:string2)的字符串将会导致另一个引用(如: string1)出现脏数据。

  • 允许字符串缓存哈希码

在java中常常会用到字符串的哈希码,例如: HashMap 。String的不变性保证哈希码始终一,因此,他可以不用担心变化的出现。这种方法意味着不必每次使用时都重新计算一次哈希码——这样,效率会高很多。

  • 安全

String广泛的用于java 类中的参数,如:网络连接(Network connetion),打开文件(opening files)等等。如果String不是不可变的,网络连接、文件将会被改变——这将会导致一系列的安全威胁。操作的方法本以为连接上了一台机器,但实际上却不是。由于反射中的参数都是字符串,同样,也会引起一系列的安全问题。

18、Object类的equal和hashCode方法重写,为什么?

首先equals与hashcode间的关系是这样的:

  • 如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相同;

  • 如果两个对象的hashCode相同,它们并不一定相同(即用equals比较返回false)

由于为了提高程序的效率才实现了hashcode方法,先进行hashcode的比较,如果不同,那没就不必在进行equals的比较了,这样就大大减少了equals比较的次数,这对比需要比较的数量很大的效率提高是很明显的。

19、List,Set,Map的区别
  • List,Set都继承于Collection接口 。
  • Set是最简单的一种集合。集合中的对象不按特定的方式排序,无序并且没有重复对象。 Set接口主要实现了两个实现类:
  • HashSet: HashSet类按照哈希算法来存取集合中的对象,存取速度比较快

  • TreeSet :TreeSet类实现了SortedSet接口,能够对集合中的对象进行排序。

  • List的特征是其元素以线性方式存储,是有序集合,集合中可以存放重复对象。
  • ArrayList() : 代表长度可以改变得数组。可以对元素进行随机的访问,向ArrayList()中插入与删除元素的速度慢。

  • LinkedList(): 在实现中采用链表数据结构。插入和删除速度快,访问速度慢。

  • Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。 Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
  • HashMap:Map基于散列表的实现。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量capacity和负载因子load factor,以调整容器的性能。

  • LinkedHashMap: 类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点。而在迭代访问时发而更快,因为它使用链表维护内部次序。

  • TreeMap : 基于红黑树数据结构的实现。查看“键”或“键值对”时,它们会被排序(次序由Comparabel或Comparator决定)。TreeMap的特点在
    于,你得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。

  • WeakHashMao :弱键(weak key)Map,Map中使用的对象也被允许释放: 这是为解决特殊问题设计的。如果没有map之外的引用指向某个“键”,则此“键”可以被垃圾收集器回收。

20、ArrayMap和HashMap的对比
  • 存储方式不同
    HashMap内部有一个HashMapEntry<K, V>[]对象,每一个键值对都存储在这个对象里,当使用put方法添加键值对时,就会new一个HashMapEntry对象,

  • 添加数据时扩容时的处理不一样,进行了new操作,重新创建对象,开销很大。ArrayMap用的是copy数据,所以效率相对要高。

  • ArrayMap提供了数组收缩的功能,在clear或remove后,会重新收缩数组,是否空间

  • ArrayMap采用二分法查找;

21、HashMap、HashTable和ConcurrentHashMap的区别
  • HashMap
  • 不是线程安全的,效率高一点、方法不是Synchronize的要提供外同步,有containsvalue和containsKey方法。
  • 继承于AbstractMap,可存储null键和值,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,
    而应该用containsKey()方法来判断。
  • 初始化最好赋值size,因为默认的size是16,而且一定是2的指数, 当存储的键值对超过这个size时,会触发扩容,而rehash会对所有键值对rehash,造成不必要的资源浪费。
  • 遍历方式的内部实现:HashMap使用了Iterator
  • 哈希值的使用不同:HashMap重新计算hash值。
  • HashMap里,是一个数组, 数组的每个元素是一个单向链表,链表中每个Node, 包含四个属性:key、value、next、hash,是用链表解决hash冲突的。
  • hashtable
  • 继承于Dictionary, 不可存储null键和值,效率稍低,父类方法少于AbstractMap, 只有基本的get, put, remote, 没有putAll, keySet等, 有contains方法。
  • 线程安全。线程安全主要依靠synchronized关键字,因此效率较低。 初始化最好赋值size,HashTable中hash数组默认大小是11,增加的方式是 old*2+1。
  • 遍历方式的内部实现:Hashtable使用了Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
  • 哈希值的使用不同:HashTable直接使用对象的hashCode
  • ConcurrentHashMap
  • 继承于AbstractMap, 不能存储null键和值, 线程安全 初始化最好赋值size
  • 类似于HashMap,ConcurrentHashMap中使用Segment代替了HashMap中的链表,而Segment继承了ReentrantLock。每次加锁的操作,采用分段加锁,实际是锁住了每一个Segment,这样保证了整体的线程安全。
22、HashMap与HashSet的区别
  • HashMap:HashMap实现了Map接口,HashMap储存键值对,使用put()方法将元素放入map中,HashMap中使用键对象来计算hashcode值,HashMap比较快,因为是使用唯一的键来获取对象。

  • HashSet实现了Set接口,HashSet仅仅存储对象,使用add()方法将元素放入set中,HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false。HashSet较HashMap来说比较慢。

23、HashSet与HashMap怎么判断集合元素重复?
  • HashSet不能添加重复的元素,当调用add(Object)方法时候, 首先会调用Object的hashCode方法判hashCode是否已经存在,如不存在则直接插入元素;如果已存在则调用Object对象的equals方法判断是否返回true,如果为true则说明元素已经存在,如为false则插入元素。
24、ArrayList和LinkedList的区别,以及应用场景
  • ArrayList是基于数组实现的,ArrayList线程不安全

  • LinkedList是基于双链表实现的:

  • 使用场景:

  • (1)如果应用程序对各个索引位置的元素进行大量的存取或删除操作,ArrayList对象要远优于LinkedList对象

  • ( 2 ) 如果应用程序主要是对列表进行循环,并且循环时候进行插入或者删除操作,LinkedList对象要远优于ArrayList对象

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

HashMap是基于哈希表的map接口的非同步实现,它允许使用null值作为key和value。在Java编程语言中最基本的结构就是两种,一种是数组,另一种是模拟指针(引用)。所有的数据结构都可以用这两个基本的结构来构造,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构。即数组和链表的结合体。

从图上可以看出,HashMap底层就是一个数据结构,数组中的每一项又是一个链表。

  • 冲突:

HashMap中调用Hashcode()方法计算Hashclde值,由于Java中两个不同的对象可能有一样的Hashcode。就导致了冲突的产生。

  • 解决:

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

  • 存取:

利用key的hashCode重新hash计算出当前对象的元素在数组中的下标。

  • 存储时一般是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到的,如果出现hash值相同的key,分两种情况:
  1. 如果key相同,则覆盖原来的值。
  2. 如果key不同(出现冲突),则放在链表中。
  • 获取时,直接找到hash值对应的下标,再进一步判断key是否相同,从而拿到对应的值。

Hashmap的核心就是使用数组进行存储,出现key冲突的时候,就存放在链表中。

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

具体想了解多线程相关知识请参考全面详解Android实现多线程的几种方式(史上最全,最详细)

27、run()和start()方法区别
  • 这个问题经常被问到,但还是能从此区分出面试者对Java线程模型的理解程度。start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
28、如何控制某个方法允许并发访问线程的个数?
  • semaphore.acquire() 请求一个信号量,这时候的信号量个数-1(一旦没有可使用的信号量,也即信号量个数变为负数时,再次请求的时候就会阻塞,直到其他线程释放了信号量)

  • semaphore.release() 释放一个信号量,此时信号量个数+1

29、在Java中wait和seelp方法的不同;
  • Java程序中wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要。
  • wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁。
  • sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。
30、谈谈wait/notify关键字的理解
  • 等待对象的同步锁,需要获得该对象的同步锁才可以调用这个方法,否则编译可以通过,但运行时会收到一个异常:IllegalMonitorStateException

  • 调用任意对象的 wait() 方法导致该线程阻塞,该线程不可继续执行,并且该对象上的锁被释放。

  • 唤醒在等待该对象同步锁的线程(只唤醒一个,如果有多个在等待),注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。

  • 调用任意对象的notify()方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。

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

  • 一种是调用它里面的stop()方法

  • 另一种就是你自己设置一个停止线程的标记 (推荐这种)

32、如何保证线程安全?
  • synchronized

  • Object方法中的wait,notify

  • ThreadLocal机制 来实现的。

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

26~33都是关于线程同步与并发的知识点,具体请参考多线程并发与同步总结

34、线程间操作List
List list = Collections.synchronizedList(new ArrayList());
35、谈谈对Synchronized关键字,类锁,方法锁,重入锁(ReentrantLock )的理解
  • java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,
  • 对象锁是用于对象实例方法,或者一个对象实例上的,
  • 类锁是用于类的静态方法或者一个类的class对象上的。
  • 我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的

重入锁具体请参考全面详解Synchronized关键字

36、synchronized 和volatile 关键字的区别
  • volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

  • volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的

  • volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性

  • volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

  • volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

具体请参考全面详解Synchronized关键字和volatile关键字详解

37、死锁的四个必要条件?
  • 死锁产生的原因:
  • 系统资源的竞争
    系统资源的竞争导致系统资源不足,以及资源分配不当,导致死锁。

  • 进程运行推进顺序不合适

  • 死锁四个必要条件:
  • 互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

  • 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

  • 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。

  • 循环等待条件: 若干进程间形成首尾相接循环等待资源的关系

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

  • 死锁的避免与预防:

死锁避免的基本思想:

  • 系统对进程发出每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配。这是一种保证系统不进入死锁状态的动态策略。

  • 理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。所以,在系统设计、进程调度等方面注意如何让这四个必要条件不成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。

  • 死锁避免和死锁预防的区别:
  • 死锁预防是设法至少破坏产生死锁的四个必要条件之一,严格的防止死锁的出现,
  • 死锁避免则不那么严格的限制产生死锁的必要条件的存在,因为即使死锁的必要条件存在,也不一定发生死锁。死锁避免是在系统运行过程中注意避免死锁的最终发生。
38、什么是线程池,如何使用?

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

具体请参考线程池使用及其原理

39、Java中堆和栈有什么不同?

为什么把这个问题归类在多线程和并发面试题里?

  • 因为是一块和线程紧密相关的内存区域。每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。
  • 是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时volatile变量就可以发挥作用了,它要求线程从主存中读取变量的值。
40、有三个线程T1,T2,T3,怎么确保它们按顺序执行?

在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。

41、线程中sleep和wait的区别
  • 这两个方法来自不同的类,sleep是来自Thread,wait是来自Object

  • sleep方法没有释放锁,而wait方法释放了锁

  • wait,notify,notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

42、关键字final和static是怎么使用的。
  • final:
  1. final变量即为常量,只能赋值一次。

  2. final方法不能被子类重写。

  3. final类不能被继承。

  • static:
  1. static变量:对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存, 在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。

  2. static代码块 static代码块是类加载时,初始化自动执行的。

  3. static方法 static方法可以直接通过类名调用,任何的实例也都可以调用,因此static方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。

具体详细的使用请参考Static静态关键字全面详解

43、Java中重载和重写的区别:
  • 重载:一个类中可以有多个相同方法名的,但是参数类型和个数都不一样。这是重载。(包括参数个数和参数类型),与返回值无关,与权限修饰符也无关。调用重载的方法时通过传递给它们不同的参数个数和参数类型来决定具体使用哪个方法,这叫多态。

  • 重写:子类继承父类,则子类可以通过实现父类中的方法,从而新的方法把父类旧的方法覆盖。
    重写就是子类重写基类的方法,方法名,参数列表和返回值都必须相同,否则就不是重写而是重载。权限修饰符不能小于被重写方法的修饰符。重写方法不能抛出新的异常或者是比被重写方法声明更加宽泛的检查型异常。

44、抽象类与接口的对比
  • 抽象类:含有abstract修饰符的class即为抽象类,abstract类不能创建的实例对象。含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。abstract class 类中定义抽象方法必须在具体 (Concrete)子类中实现,所以,不能有抽象构造方法或抽象静态方法。如果子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。

  • 接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final

  • 下面比较一下两者的语法区别:
  1. 抽象类可以有构造方法,接口中不能有构造方法。
  2. 抽象类中可以有普通成员变量,接口中没有普通成员变量
  3. 抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
  4. 抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然 eclipse下不报错,但应该也不行),但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
  5. 抽象类中可以包含静态方法,接口中不能包含静态方法
  6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
  7. 一个之类只能继承一个抽象类,但能实现多个接口。
45、栈内存和堆内存阐释
  • 栈内存中存放函数中定义的一些基本类型的变量和对象的引用变量
  • 堆内存中存放new创建的对象和数组

简单的来说,堆主要是用来存放对象的,栈主要是用来执行程序的
这么做是因为 :

  • 栈的存取速度快,栈数据可以共享,但是栈中的数据大小和生存期必须确定,缺乏灵活性中存放一些基本类型的对象和对象句柄
  • 堆是操作系统分配给自己内存,由于从操作系统管理的内存分配,所以再分配和销毁时都需要占用时间,因此用堆的效率非常低,但是优点在于编译器不需要指导从堆里分配多少存储控件,也不需要知道存储的数据要再堆里停留多长事件,因此用堆保存数据时会得到更大的灵活性

具体详细了解可参考(JVM)Java虚拟机的内存结构详解

二、Android篇
1、Android多线程间通信
  • 我们知道线程是CPU调度的最小单位。在Android中主线程是不能够做耗时操作的,子线程是不能够更新UI的。而线程间通信的方式有很多,比如广播,Eventbus,接口回掉。在Android中主要是使用handler。
  • Android的消息机制的总体流程就是:Handler向MessageQueue发送一条消息(即插入一条消息),MessageQueue通过next方法把消息传给Looper,Looper收到消息后开始处理,然后最终交给Handler自己去处理。换句话说就是:Handler给自己发送了一条消息,然后自己的handleMessage方法处理消息,只是中间过程经过了MessageQueue和Looper。调用的方法过程如下:Handler.sendMessage方法–>Handler.enqueueMessage–>MessageQueue.next–>Looper.loop–>handler.dispatchMessage–>Handler.handleMessage(或者Runnable的run方法或者Callback.handleMessage)。

具体想了解Andr多线程相关知识请参考[全面详解Android实现多线程的几种方式(史上最全,最详细)]

2、线程池

Android中常见的线程池有四种,FixedThreadPool、CachedThreadPool、ScheduledThreadPool、SingleThreadExecutor

  • FixedThreadPool线程池是通过Executors的new FixedThreadPool方法来创建。它的特点是该线程池中的线程数量是固定的。即使线程处于闲置的状态,它们也不会被回收,除非线程池被关闭。当所有的线程都处于活跃状态的时候,新任务就处于队列中等待线程来处理。注意,FixedThreadPool只有核心线程,没有非核心线程。

  • CachedThreadPool线程池是通过Executors的newCachedThreadPool进行创建的。它是一种线程数目不固定的线程池,它没有核心线程,只有非核心线程,当线程池中的线程都处于活跃状态,就会创建新的线程来处理新的任务。否则就会利用闲置的线程来处理新的任务。线程池中的线程都有超时机制,这个超时机制时长是60s,超过这个时间,闲置的线程就会被回收。这种线程池适合处理大量并且耗时较少的任务。这里得说一下,CachedThreadPool的任务队列,基本都是空的。

  • ScheduledThreadPool线程池是通过Executors的newScheduledThreadPool进行创建的,它的核心线程是固定的,但是非核心线程数是不固定的,并且当非核心线程一处于空闲状态,就立即被回收。这种线程适合执行定时任务和具有固定周期的重复任务。

  • SingleThreadExecutor线程池是通过Executors的newSingleThreadExecutor方法来创建的,这类线程池中只有一个核心线程,也没有非核心线程,这就确保了所有任务能够在同一个线程并且按照顺序来执行,这样就不需要考虑线程同步的问题。

具体请参考Android线程池使用及其原理

3、AsyncTask的工作原理

AsyncTask是Android本身提供的一种轻量级的异步任务类。它可以在线程池中执行后台任务,然后把执行的进度和最终的结果传递给主线程更新UI。实际上,AsyncTask内部是封装了Thread和Handler。虽然AsyncTask很方便的执行后台任务,以及在主线程上更新UI,但是,AsyncTask并不合适进行特别耗时的后台操作,对于特别耗时的任务,个人还是建议使用线程池。

AsyncTask提供有4个核心方法:

  • onPreExecute():该方法在主线程中执行,在执行异步任务之前会被调用,一般用于一些准备工作。

  • doInBackground(String... params):这个方法是在线程池中执行,此方法用于执行异步任务。在这个方法中可以通过publishProgress方法来更新任务的进度,publishProgress方法会调用onProgressUpdate方法,另外,任务的结果返回给onPostExecute方法。

  • onProgressUpdate(Object... values):该方法在主线程中执行,主要用于任务进度更新的时候,该方法会被调用。

  • onPostExecute(Long aLong):在主线程中执行,在异步任务执行完毕之后,该方法会被调用,该方法的参数及为后台的返回结果。

除了这几个方法之外还有一些不太常用的方法,如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。

4、Binder的工作机制

直观来说,Binder是Android中的一个类,它实现了IBinder接口,

  • 从IPC的角度来说,Binder是Android中的一种跨进程通信的一种方式,同时还可以理解为是一种虚拟的物理设备,它的设备驱动是/dev/binder/。
  • 从Framework角度来说,Binder是ServiceManager的桥梁。
  • 从应用层来说,Binder是客户端和服务端进行通信的媒介。

  • 我们先来了解一下这个类中每个方法的含义:
  • DESCRIPTOR:Binder的唯一标识,一般用于当前Binder的类名表示。

  • asInterface(android.os.IBinder obj):用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转化过程是区分进程的,如果客户端和服务端位于同一个进程,那么这个方法返回的是服务端的stub对象本身,否则返回的是系统封装后的Stub.proxy对象。

  • asBinder():用于返回当前Binder对象。

  • onTransact:该方法运行在服务端的Binder线程池中,当客户端发起跨进程通信请求的时候,远程请求通过系统底层封装后交给该方法处理。注意这个方法public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags),服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数,然后执行目标方法。当目标方法执行完毕后,就像reply中写入返回值。这个方法的执行过程就是这样的。如果这个方法返回false,客户端是会请求失败的,所以我们可以在这个方法中做一些安全验证。

  • Binder的工作机制但是要注意一些问题:
  • 当客户端发起请求时,由于当前线程会被挂起,直到服务端返回数据,如果这个远程方法很耗时的话,那么是不能够在UI线程,也就是主线程中发起这个远程请求的。

  • 由于Service的Binder方法运行在线程池中,所以Binder方法不管是耗时还是不耗时都应该采用同步的方式,因为它已经运行在一个线程中了。

5、view的绘制过程及工作原理

Android自定义view,我们都知道实现有三部曲,onMeasure(),onLayout(),onDraw()。View的绘制流程是从viewRoot的perfromTraversal方法开始的。它经过measure,layout,draw方法才能够将view绘制出来。其中measure是测量宽高的,layout是确定view在父容器上的摆布位置的,draw是将view绘制到屏幕上的。

  1. Measure:

view的测量是需要MeasureSpc(测量规格),它代表一个32位int值,高2位代表SpecMode(测量模式),低(30)位的代表SpecSize(某种测量模式下的规格大小)。而一组SpecMode和SpeSize可以打包为一个MeasureSpec,反之,MeasureSpec可以解包得到SpecMode和SpeSize的值。SpecMode有三类:

  • unSpecified:父容器不对view有任何限制,要多大有多大。一般系统用这个多。

  • Exactly:父容器已经检测出view所需要的精确大小,这个时候,view的大小就是SpecSize所指定的值,它对应者layout布局中的math_parent或者是具体的数值

  • At_most:父容器指定了一个可用大小的SpecSize,view的大小不能够大于这个值,它对应这布局中的wrao_content.

  • 对于普通的view,它的MeasureSpec是由父容器的MeasureSpec和自身的layoutParam共同决定的,一旦MeasureSpec确定后,onMeasure就可以确定view的宽高了。

  • View的measure过程:

onMeasure方法中有个setMeasureDimenSion方法来设置view的宽高测量值,而setMeasureDimenSion有个getDefaultSize()方法作为参数。一般情况下,我们只需要关注at_most和exactly两种情况,getDefaultSize的返回值就是measureSpec中的SpecSize,而这个值基本就是view测量后的大小。而UnSpecified这种情况,一般是系统内部的测量过程,它是需要考虑view的背景这些因素的。

  • viewGroup的measure过程:

对于viewGroup来说,除了完成自己的measure过程以外,还要遍历去调用子类的measure方法,各个子元素在递归执行这个过程,viewGroup是一个抽象的类,没有提供有onMeasure方法,但是提供了一个measureChildren的方法。measureChild方法的思想就是取出子元素的layoutParams,然后通过getChildMeasureSpec来常见子元素的MeasureSpec,然后子元素在电泳measure方法进行测量。由于viewGroup子类有不同的布局方式,导致他们的测量细节不一样,所以viewGroup不能象view一样调用onMeasure方法进行测量。

注意:

  • 在activity的生命周期中是没有办法正确的获取view的宽高的,原因就是view没有测量完。

  • 在onWindowFocuschanged方法中获取 ----改方法含义是view已经初始化完毕 View.post()方法,将润那边了投递到消息队列的尾部。

  • 使用viewTreeObserver的回调来完成。

  • 通过view.measure方式手动测量。

  1. onLayout

普通的view的话,可以通过setFrame方法来的到view四个顶点的位置,也就确定了view在父容器的位置,接着就调用onLayout方法,该方法是父容器确定子元素的位置。

  1. onDraw

该方法就是将view绘制到屏幕上。分以下几步:

绘制背景,
绘制自己,
绘制child,
绘制装饰。

具体请参考Android自定义View详解

6、Android中性能优化
  • 具体请参考Android性能优化总结(史上最全总结)
7、加密算法(base64、MD5、对称加密和非对称加密)和使用场景。
  1. 什么是Rsa加密?

RSA算法是最流行的公钥密码算法,使用长度可以变化的密钥。RSA是第一个既能用于数据加密也能用于数字签名的算法。

  • RSA算法原理如下:
  1. 随机选择两个大质数p和q,p不等于q,计算N=pq;

  2. 选择一个大于1小于N的自然数e,e必须与(p-1)(q-1)互素。

  3. 用公式计算出d:d×e = 1 (mod (p-1)(q-1)) 。

  4. 销毁p和q。

  • 最终得到的N和e就是“公钥”,d就是“私钥”,发送方使用N去加密数据,接收方只有使用d才能解开数据内容。

  • RSA的安全性依赖于大数分解,小于1024位的N已经被证明是不安全的,而且由于RSA算法进行的都是大数计算,使得RSA最快的情况也比DES慢上倍,这是RSA最大的缺陷,因此通常只能用于加密少量数据或者加密密钥,但RSA仍然不失为一种高强度的算法。

  • 使用场景:

项目中除了登陆,支付等接口采用rsa非对称加密,之外的采用aes对称加密,今天我们来认识一下aes加密。

  1. 什么是MD5加密?

MD5英文全称“Message-Digest Algorithm
5”,翻译过来是“消息摘要算法5”,由MD2、MD3、MD4演变过来的,是一种单向加密算法,是不可逆的一种的加密方式。

  • MD5加密有哪些特点?
  • 压缩性:任意长度的数据,算出的MD5值长度都是固定的。

  • 容易计算:从原数据计算出MD5值很容易。

  • 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。

  • 强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。

  • MD5应用场景:
  • 一致性验证

  • 数字签名

  • 安全访问认证

  1. 什么是aes加密?

高级加密标准(英语:Advanced Encryption
Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。

8、Activity生命周期?
  • Activity 的形态
  • Active/Running: Activity处于活动状态,此时Activity处于栈顶,是可见状态,可与用户进行交互。
  • Paused: 当Activity失去焦点时,或被一个新的非全屏的Activity,或被一个透明的Activity放置在栈顶时,Activity就转化为Paused状态。但我们需要明白,此时Activity只是失去了与用户交互的能力,其所有的状态信息及其成员变量都还存在,只有在系统内存紧张的情况下,才有可能被系统回收掉。
  • Stopped: 当一个Activity被另一个Activity完全覆盖时,被覆盖的Activity就会进入Stopped状态,此时它不再可见,但是跟Paused状态一样保持着其所有状态信息及其成员变量。
  • Killed: 当Activity被系统回收掉时,Activity就处于Killed状态。
  • Activity 生命周期

    Activity经历从创建,运行,停止,销毁等正常的生命周期过程。我们这里先来介绍一下几个主要方法的调用时机:
  • onCreate : 该方法是在Activity被创建时回调,它是生命周期第一个调用的方法,我们在创建Activity时一般都需要重写该方法,然后在该方法中做一些初始化的操作,如通过setContentView设置界面布局的资源,初始化所需要的组件信息等。
  • onStart : 此方法被回调时表示Activity正在启动,此时Activity已处于可见状态,只是还没有在前台显示,因此无法与用户进行交互。可以简单理解为Activity已显示而我们无法看见摆了。
  • onResume : 当此方法回调时,则说明Activity已在前台可见,可与用户交互了(处于前面所说的Active/Running形态),onResume方法与onStart的相同点是两者都表示Activity可见,只不过onStart回调时Activity还是后台无法与用户交互,而onResume则已显示在前台,可与用户交互。当然从流程图,我们也可以看出当Activity停止后(onPause方法和onStop方法被调用),重新回到前台时也会调用onResume方法,因此我们也可以在
  • onResume方法中初始化一些资源,比如重新初始化在onPause或者onStop方法中释放的资源。
  • onPause : 此方法被回调时则表示Activity正在停止(Paused形态),一般情况下onStop方法会紧接着被回调。但通过流程图我们还可以看到一种情况是onPause方法执行后直接执行了onResume方法,这属于比较极端的现象了,这可能是用户操作使当前Activity退居后台后又迅速地再回到到当前的Activity,此时onResume方法就会被回调。当然,在onPause方法中我们可以做一些数据存储或者动画停止或者资源回收的操作,但是不能太耗时,因为这可能会影响到新的Activity的显示——onPause方法执行完成后,新Activity的onResume方法才会被执行。
  • onStop : 一般在onPause方法执行完成直接执行,表示Activity即将停止或者完全被覆盖(Stopped形态),此时Activity不可见,仅在后台运行。同样地,在onStop方法可以做一些资源释放的操作(不能太耗时)。
  • onRestart :表示Activity正在重新启动,当Activity由不可见变为可见状态时,该方法被回调。这种情况一般是用户打开了一个新的Activity时,当前的Activity就会被暂停(onPause和onStop被执行了),接着又回到当前Activity页面时,onRestart方法就会被回调。
  • onDestroy :此时Activity正在被销毁,也是生命周期最后一个执行的方法,一般我们可以在此方法中做一些回收工作和最终的资源释放。
9、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对象

10、Activity的启动过程(不要回答生命周期)

app启动的过程有两种情况,第一种是从桌面launcher上点击相应的应用图标,第二种是在activity中通过调用startActivity来启动一个新的activity。

我们创建一个新的项目,默认的根activity都是MainActivity,而所有的activity都是保存在堆栈中的,我们启动一个新的activity就会放在上一个activity上面,而我们从桌面点击应用图标的时候,由于launcher本身也是一个应用,当我们点击图标的时候,系统就会调用startActivitySately(),一般情况下,我们所启动的activity的相关信息都会保存在intent中,比如action,category等等。我们在安装这个应用的时候,系统也会启动一个PackaManagerService的管理服务,这个管理服务会对AndroidManifest.xml文件进行解析,从而得到应用程序中的相关信息,比如service,activity,Broadcast等等,然后获得相关组件的信息。

  • 当我们点击应用图标的时候,就会调用startActivitySately()方法,而这个方法内部则是调用startActivty(),而startActivity()方法最终还是会调用startActivityForResult()这个方法。而在startActivityForResult()这个方法。因为startActivityForResult()方法是有返回结果的,所以系统就直接给一个-1,就表示不需要结果返回了。而startActivityForResult()这个方法实际是通过Instrumentation类中的execStartActivity()方法来启动activity,Instrumentation这个类主要作用就是监控程序和系统之间的交互。而在这个execStartActivity()方法中会获取ActivityManagerService的代理对象,通过这个代理对象进行启动activity。启动会就会调用一个checkStartActivityResult()方法,如果说没有在配置清单中配置有这个组件,就会在这个方法中抛出异常了。
  • 当然最后是调用的是Application.scheduleLaunchActivity()进行启动activity,而这个方法中通过获取得到一个ActivityClientRecord对象,而这个ActivityClientRecord通过handler来进行消息的发送,系统内部会将每一个activity组件使用ActivityClientRecord对象来进行描述,而ActivityClientRecord对象中保存有一个LoaderApk对象,通过这个对象调用handleLaunchActivity来启动activity组件,而页面的生命周期方法也就是在这个方法中进行调用。

具体请参考Android APP启动以及性能优化

11、Broadcast注册方式与区别
  • Broadcast广播,注册方式主要有两种.
  • 第一种是静态注册,也可成为常驻型广播,这种广播需要在Androidmanifest.xml中进行注册,这中方式注册的广播,不受页面生命周期的影响,即使退出了页面,也可以收到广播这种广播一般用于想开机自启动啊等等,由于这种注册的方式的广播是常驻型广播,所以会占用CPU的资源。

  • 第二种是动态注册,而动态注册的话,是在代码中注册的,这种注册方式也叫非常驻型广播,收到生命周期的影响,退出页面后,就不会收到广播,我们通常运用在更新UI方面。这种注册方式优先级较高。最后需要解绑,否会会内存泄露

  • 广播是分为有序广播和无序广播。
12、HttpClient、HttpURLConnection、Volley、OkHttp的区别
  • 具体请参考HttpClient、HttpURLConnection、Volley、OkHttp网络框架分析与对比
13、java虚拟机和Dalvik虚拟机的区别
  • Java虚拟机:
  1. java虚拟机基于栈。 基于栈的机器必须使用指令来载入和操作栈上数据,所需指令更多更多。

  2. java虚拟机运行的是java字节码。(java类会被编译成一个或多个字节码.class文件)

具体请参考(JVM)Java虚拟机详解(全面详细)

  • Dalvik虚拟机:
  1. dalvik虚拟机是基于寄存器的

  2. Dalvik运行的是自定义的.dex字节码格式。(java类被编译成.class文件后,会通过一个dx工具将所有的.class文件转换成一个.dex文件,然后dalvik虚拟机会从其中读取指令和数据

  3. 常量池已被修改为只使用32位的索引,以 简化解释器。

  4. 一个应用,一个虚拟机实例,一个进程(所有android应用的线程都是对应一个linux线程,都运行在自己的沙盒中,不同的应用在不同的进程中运行。每个android
    dalvik应用程序都被赋予了一个独立的linux PID(app_*))

14、进程保活(不死进程)

当前业界的Android进程保活手段主要分为** 黑、白、灰 **三种,其大致的实现思路如下:

  • 黑色保活:不同的app进程,用广播相互唤醒(包括利用系统提供的广播进行唤醒)

  • 白色保活:启动前台Service

  • 灰色保活:利用系统的漏洞启动前台Service

接下来分别介绍

  • 黑色保活

所谓黑色保活,就是利用不同的app进程使用广播来进行相互唤醒。举个3个比较常见的场景:

  • 场景1:开机,网络切换、拍照、拍视频时候,利用系统产生的广播唤醒app
  • 场景2:接入第三方SDK也会唤醒相应的app进程,如微信sdk会唤醒微信,支付宝sdk会唤醒支付宝。由此发散开去,就会直接触发了下面的 场景3
  • 场景3:假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其他阿里系的app给唤醒了。(只是拿阿里打个比方,其实BAT系都差不多)
  • 白色保活

白色保活手段非常简单,就是调用系统api启动一个前台的Service进程,这样会在系统的通知栏生成一个Notification,用来让用户知道有这样一个app在运行着,哪怕当前的app退到了后台。如下方的LBE和QQ音乐这样:

  • 灰色保活

灰色保活,这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到Notification),但你的进程优先级又是高于普通后台进程的。那么如何利用系统的漏洞呢,大致的实现思路和代码如下:

  • 思路一:API < 18,启动前台Service时直接传入new Notification();

  • 思路二:API >= 18,同时启动两个id相同的前台Service,然后再将后启动的Service做stop处理

熟悉Android系统的童鞋都知道,系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app。这套杀进程回收内存的机制就叫 Low Memory Killer ,它是基于Linux内核的 OOM Killer(Out-Of-Memory killer)机制诞生。
进程的重要性,划分5级:

  • 前台进程 (Foreground process)

  • 可见进程 (Visible process)

  • 服务进程 (Service process)

  • 后台进程 (Background process)

  • 空进程 (Empty process)

了解完 Low Memory Killer,再科普一下oom_adj。什么是oom_adj?它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。对于oom_adj的作用,你只需要记住以下几点即可:

  • 进程的oom_adj越大,表示此进程优先级越低,越容易被杀回收;越小,表示进程优先级越高,越不容易被杀回收

  • 普通app进程的oom_adj>=0,系统进程的oom_adj才可能<0

  • 有些手机厂商把这些知名的app放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、陌陌都在小米的白名单中)。如果从白名单中移除,他们终究还是和普通app一样躲避不了被杀的命运,为了尽量避免被杀,还是老老实实去做好优化工作吧。

所以,进程保活的根本方案终究还是回到了性能优化上,目前来说现在进程保活随着Android版本的升级,越来越困难,所有最终还是要根据具体需求来定制相应的方案。

15、讲解一下Context
  • Context是一个抽象基类。在翻译为上下文,也可以理解为环境,是提供一些程序的运行环境基础信息。
  • 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上面弹出(除非是System
    Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。
  • getApplicationContext()和getApplication()方法得到的对象都是同一个application对象,只是对象的类型不一样。

  • Context数量 = Activity数量 + Service数量 + 1 (1为Application)

16、理解Activity,View,Window三者关系

这个问题真的很不好回答。所以这里先来个算是比较恰当的比喻来形容下它们的关系吧。Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图)LayoutInflater像剪刀,Xml配置像窗花图纸。

  • Activity构造的时候会初始化一个Window,准确的说是PhoneWindow

  • 这个PhoneWindow有一个“ViewRoot”,这个“ViewRoot”是一个View或者说ViewGroup,是最初始的根视图。

  • ViewRoot”通过addView方法来一个个的添加View。比如TextView,Button等

  • 这些View的事件监听,是由WindowManagerService来接受消息,并且回调Activity函数。比如onClickListener,onKeyDown等。

17、Android四种LaunchMode(启动模式)及其使用场景

此处延伸:栈(First In Last Out)与队列(First In First Out)的区别

栈与队列的区别:

  1. 队列先进先出,栈先进后出

  2. 对插入和删除操作的"限定"。 栈是限定只能在表的一端进行插入和删除操作的线性表。 队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。

  3. 遍历数据速度不同

  • Standard 标准模式
  • 说明: Android创建Activity时的默认模式,假设没有为Activity设置启动模式的话,默觉得标准模式。每次启动一个Activity都会又一次创建一个新的实例入栈,无论这个实例是否存在。

  • 生命周期:如上所看到的,每次被创建的实例Activity 的生命周期符合典型情况,它的onCreate、onStart、onResume都会被调用。

  • 使用场景大多数Activity

  • 举例:此时Activity 栈中以此有A、B、C三个Activity,此时C处于栈顶,启动模式为Standard 模式。 若在C Activity中加入点击事件,须要跳转到还有一个同类型的C Activity。结果是还有一个C Activity进入栈中,成为栈顶。

  • SingleTop 栈顶复用模式
  • 说明:分两种处理情况:须要创建的Activity已经处于栈顶时,此时会直接复用栈顶的Activity。不会再创建新的Activity;若须要创建的Activity不处于栈顶,此时会又一次创建一个新的Activity入栈,同Standard模式一样。

  • 生命周期:若情况一中栈顶的Activity被直接复用时,它的onCreate、onStart不会被系统调用,由于它并没有发生改变。可是一个新的方法
    onNewIntent会被回调(Activity被正常创建时不会回调此方法)。

  • 使用场景:如新闻类或者阅读类App的内容页面

  • 举例:此时Activity 栈中以此有A、B、C三个Activity,此时C处于栈顶,启动模式为SingleTop 模式。
    情况一:在C Activity中加入点击事件,须要跳转到还有一个同类型的C Activity。结果是直接复用栈顶的C Activity。
    情况二:在C Activity中加入点击事件,须要跳转到还有一个A Activity。结果是创建一个新的Activity入栈。成为栈顶。

  • SingleTask 栈内复用模式
  • 说明:若须要创建的Activity已经处于栈中时,此时不会创建新的Activity,而是将存在栈中的Activity上面的其他Activity所有销毁,使它成为栈顶。

  • 生命周期:同SingleTop 模式中的情况一同样。仅仅会又一次回调Activity中的 onNewIntent方法

  • 使用场景:浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。

  • 举例:此时Activity 栈中以此有A、B、C三个Activity。此时C处于栈顶,启动模式为SingleTask 模式。
    情况一:在C Activity中加入点击事件,须要跳转到还有一个同类型的C Activity。结果是直接用栈顶的C Activity。
    情况二:在C Activity中加入点击事件,须要跳转到还有一个A Activity。 结果是将A Activity上面的B、C所有销毁,使A Activity成为栈顶。

  • SingleInstance 单实例模式
  • 说明: SingleInstance比較特殊,是全局单例模式,是一种加强的SingleTask模式。在一个新栈中创建该Activity的实例,并让多个应用共享该栈中的该Activity实例。一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例(会调用实例的
    onNewIntent() )。其效果相当于多个应用共享一个应用,不管谁激活该
    Activity都会进入同一个应用中,它除了具有它所有特性外,还加强了一点:具有此模式的Activity仅仅能单独位于一个任务栈中。

  • 使用场景:这个经常使用于系统中的应用,比如Launch、锁屏键的应用,闹铃提醒,将闹铃提醒与闹铃设置分离等等,整个系统中仅仅有一个!所以在我们的应用中一般不会用到。了解就可以。

  • 举例:比方 A Activity是该模式,启动A后。系统会为它创建一个单独的任务栈,由于栈内复用的特性。兴许的请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁。

18、Activity,View,ViewGroup事件分发机制
  • 具体请参考Android事件分发机制详解
19、保存Activity状态

onSaveInstanceState(Bundle)会在activity转入后台状态之前被调用,也就是onStop()方法之前,onPause方法之后被调用;

20、Android中的几种动画
  • 帧动画:指通过指定每一帧的图片和播放时间,有序的进行播放而形成动画效果,比如想听的律动条。

  • 补间动画:指通过指定View的初始状态、变化时间、方式,通过一系列的算法去进行图形变换,从而形成动画效果,主要有Alpha、Scale、Translate、Rotate四种效果。注意:只是在视图层实现了动画效果,并没有真正改变View的属性,比如滑动列表,改变标题栏的透明度。

  • 属性动画:在Android3.0的时候才支持,通过不断的改变View的属性,不断的重绘而形成动画效果。相比于视图动画,View的属性是真正改变了。比如view的旋转,放大,缩小。

  • 过度动画:其实是加强版的属性动画,运行前后有两个布局文件。

21、Android中跨进程通讯的几种方式
  • 具体请参考Android进程间通信方式总结
22、AIDL理解
  • AIDL:

每一个进程都有自己的Dalvik
VM实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行着自己的操作,都在自己的那片狭小的空间里过完自己的一生。而aidl就类似与两个进程之间的桥梁,使得两个进程之间可以进行数据的传输,跨进程通信有多种选择,比如
BroadcastReceiver , Messenger 等,但是 BroadcastReceiver
占用的系统资源比较多,如果是频繁的跨进程通信的话显然是不可取的;Messenger 进行跨进程通信时请求队列是同步进行的,无法并发执行。

  • Binde机制简单理解:
  • 在Android系统的Binder机制中,是有Client,Service,ServiceManager,Binder驱动程序组成的,其中Client,service,ServiceManager运行在用户空间,Binder驱动程序是运行在内核空间的。而Binder就是把这4种组件粘合在一块的粘合剂,其中核心的组件就是Binder驱动程序,ServiceManager提供辅助管理的功能,而Client和Service正是在Binder驱动程序和ServiceManager提供的基础设施上实现C/S 之间的通信。其中Binder驱动程序提供设备文件/dev/binder与用户控件进行交互,

  • Client、Service,Service Manager通过open和ioctl文件操作相应的方法与Binder驱动程序进行通信。而Client和Service之间的进程间通信是通过Binder驱动程序间接实现的。而BinderManager是一个守护进程,用来管理Service,并向Client提供查询Service接口的能力。

具体详细请参考Android进程通信之AIDL的使用与Binder浅谈

23、Handler的原理

Android中主线程是不能进行耗时操作的,子线程是不能进行更新UI的。所以就有了handler,它的作用就是实现线程之间的通信。

  • handler整个流程中,主要有四个对象,handler,Message,MessageQueue,Looper。当应用创建的时候,就会在主线程中创建handler对象,

  • 我们通过要传送的消息保存到Message中,handler通过调用sendMessage方法将Message发送到MessageQueue中,Looper对象就会不断的调用loop()方法

  • 不断的从MessageQueue中取出Message交给handler进行处理。从而实现线程之间的通信。

具体的请参考全面详解Android实现多线程的几种方式(史上最全,最详细)中的handl的实现原理。

24、Binder机制原理

在Android系统的Binder机制中,是有:

Client,Service,ServiceManager,Binder驱动程序组成的,其中Client,service,ServiceManager运行在用户空间,Binder驱动程序是运行在内核空间的。而Binder就是把这4种组件粘合在一块的粘合剂,其中核心的组件就是Binder驱动程序,ServiceManager提供辅助管理的功能,而Client和Service正是在Binder驱动程序和ServiceManager提供的基础设施上实现C/S之间的通信。其中Binder驱动程序提供设备文件/dev/binder与用户控件进行交互,Client、Service,ServiceManager通过open和ioctl文件操作相应的方法与Binder驱动程序进行通信。而Client和Service之间的进程间通信是通过Binder驱动程序间接实现的。而BinderManager是一个守护进程,用来管理Service,并向Client提供查询Service接口的能力。

25、热修复的原理
  • Java虚拟机 —— JVM 是加载类的class文件的,而Android虚拟机——Dalvik/ART VM 是加载类的dex文件。

  • 而他们加载类的时候都需要ClassLoader,ClassLoader有一个子类BaseDexClassLoader,而BaseDexClassLoader下有一个数组——DexPathList,是用来存放dex文件。

  • BaseDexClassLoader通过调用findClass方法时,实际上就是遍历数组,找到相应的dex文件,找到,则直接将它return。

  • 而热修复的解决方法就是将新的dex添加到该集合中,并且是在旧的dex的前面,所以就会优先被取出来并且return返回。

26、Fragment与Fragment、Activity通信的方式
  1. 直接在一个Fragment中调用另外一个Fragment中的方法

  2. 使用接口回调

  3. 使用广播

  4. Fragment直接调用Activity中的public方法

27、HybridApp WebView和JS交互

Android与JS通过WebView互相调用方法,实际上是:

  • Android去调用JS的代码
  1. 通过WebView的loadUrl(),使用该方法比较简洁,方便。但是效率比较低,获取返回值比较困难。

  2. 通过WebView的evaluateJavascript(),该方法效率高,但是4.4以上的版本才支持,4.4以下版本不支持。所以建议两者混合使用。

  • JS去调用Android的代码
  1. 通过WebView的addJavascriptInterface()进行对象映射 ,该方法使用简单,仅将Android对象和JS对象映射即可,但是存在比较大的漏洞。
  • 漏洞产生原因是: 当JS拿到Android这个对象后,就可以调用这个Android对象中所有的方法,包括系统类(java.lang.Runtime
    类),从而进行任意代码执行。

  • 解决方式: (1)Google 在Android 4.2 版本中规定对被调用的函数以@JavascriptInterface进行注解从而避免漏洞攻击。 (2)在Android
    4.2版本之前采用拦截prompt()进行漏洞修复。

  1. 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url 。这种方式的优点:不存在方式1的漏洞;缺点:JS获取Android方法的返回值复杂。(ios主要用的是这个方式)
  • Android通过 WebViewClient 的回调方法shouldOverrideUrlLoading ()拦截 url

  • 解析该 url 的协议

  • 如果检测到是预先约定好的协议,就调用相应方法

  1. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息

这种方式的优点:不存在方式1的漏洞;
缺点:JS获取Android方法的返回值复杂。

28、JAVA GC原理
  • 垃圾收集算法的核心思想是:

对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能。

具体的参考(JVM)Java虚拟机详解(全面详细)

30、ANR

ANR全名Application Not Responding, 也就是"应用无响应". 当操作在一段时间内系统无法处理时, 系统层面会弹出上图那样的ANR对话框.

  • 产生原因:
  • 5s内无法响应用户输入事件(例如键盘输入, 触摸屏幕等).
  • BroadcastReceiver在10s内无法结束
  • Service 20s内无法结束(低概率)
  • 解决方式:
  • 不要在主线程中做耗时的操作,而应放在子线程中来实现。如onCreate()和onResume()里尽可能少的去做创建操作。
  • 应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。
  • 避免在IntentReceiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。
  • service是运行在主线程的,所以在service中做耗时操作,必须要放在子线程中。
31、Android开发中常见的设计模式
  • 具体请参考Android开发中常见的设计模式
32、MVP,MVC,MVVM
  • MVP模式,对应着Model–业务逻辑和实体模型,view–对应着activity,负责View的绘制以及与用户交互,Presenter–负责View和Model之间的交互,MVP模式是在MVC模式的基础上,将Model与View彻底分离使得项目的耦合性更低。
  • 在MVC中项目中的activity对应着mvc中的C–Controllor,而项目中的逻辑处理都是在这个C中处理,同时View与Model之间的交互,也是也就是说,mvc中所有的逻辑交互和用户交互,都是放在Controllor中,也就是activity中。View和model是可以直接通信的。
  • 而MVP模式则是分离的更加彻底,分工更加明确Model–业务逻辑和实体模型,view–负责与用户交互,Presenter 负责完成View于Model间的交互。
  • MVP和MVC最大的区别是MVC中是允许Model和View进行交互的,而MVP中很明显,Model与View之间的交互由Presenter完成。还有一点就是Presenter与View之间的交互是通过接口的。
32、JNI
  • 安装和下载Cygwin,下载 Android NDK

  • 在ndk项目中JNI接口的设计

  • 使用C/C++实现本地方法

  • JNI生成动态链接库.so文件

  • 将动态链接库复制到java工程,在java工程中调用,运行java工程即可

33、RecyclerView和ListView的区别
  • RecyclerView可以完成ListView,GridView的效果,还可以完成瀑布流的效果。同时还可以设置列表的滚动方向(垂直或者水平);

  • RecyclerView中view的复用不需要开发者自己写代码,系统已经帮封装完成了。

  • RecyclerView可以进行局部刷新。

  • RecyclerView提供了API来实现item的动画效果。

  • RecyclerView优点数据绑定,Item View创建,View的回收以及重用等机

在性能上:

  • 如果需要频繁的刷新数据,需要添加动画,则RecyclerView有较大的优势。
  • 如果只是作为列表展示,则两者区别并不是很大。
34、Universal-ImageLoader,Picasso,Fresco,Glide对比
  • Fresco 是 Facebook 推出的开源图片缓存工具,主要特点包括:两个内存缓存加上 Native 缓存构成了三级缓存,

优点:

  1. 图片存储在安卓系统的匿名共享内存, 而不是虚拟机的堆内存中, 图片的中间缓冲数据也存放在本地堆内存, 所以, 应用程序有更多的内存使用, 不会因为图片加载而导致oom, 同时也减少垃圾回收器频繁调用回收 Bitmap 导致的界面卡顿, 性能更高。
  2. 渐进式加载 JPEG 图片, 支持图片从模糊到清晰加载。
  3. 图片可以以任意的中心点显示在 ImageView, 而不仅仅是图片的中心。
  4. JPEG 图片改变大小也是在 native 进行的, 不是在虚拟机的堆内存, 同样减少 OOM。
  5. 很好的支持 GIF 图片的显示。

缺点:

  1. 框架较大, 影响 Apk 体积
  2. 使用较繁琐
  • Universal-ImageLoader:(估计由于HttpClient被Google放弃,作者就放弃维护这个框架)

优点:

  1. 支持下载进度监听

  2. 可以在 View 滚动中暂停图片加载,通过 PauseOnScrollListener 接口可以在 View 滚动中暂停图片加载。

  3. 默认实现多种内存缓存算法 这几个图片缓存都可以配置缓存算法,不过 ImageLoader 默认实现了较多缓存算法,如 Size 最大先删除、使用最少先删除、最近最少使用、先进先删除、时间最长先删除等。

  4. 支持本地缓存文件名规则定义

  • Picasso

优点:

  1. 自带统计监控功能。支持图片缓存使用的监控,包括缓存命中率、已使用内存大小、节省的流量等。
  2. 支持优先级处理。每次任务调度前会选择优先级高的任务,比如 App 页面中 Banner 的优先级高于 Icon 时就很适用。
  3. 支持延迟到图片尺寸计算完成加载
  4. 支持飞行模式、并发线程数根据网络类型而变。 手机切换到飞行模式或网络类型变换时会自动调整线程池最大并发数,比如 wifi 最大并发为 4,4g 为 3,3g 为 2。 这里 Picasso 根据网络类型来决定最大并发数,而不是 CPU 核数。
  5. “无”本地缓存。无”本地缓存,不是说没有本地缓存,而是 Picasso 自己没有实现,交给了 Square 的另外一个网络库 okhttp 去实现,这样的好处是可以通过请求 Response Header 中的 Cache-Control 及 Expired
    控制图片的过期时间。
  • Glide

优点:

  1. 不仅仅可以进行图片缓存还可以缓存媒体文件。Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。甚至是 Video,所以更该当做一个媒体缓存。
  2. 支持优先级处理。
  3. 与 Activity/Fragment 生命周期一致,支持 trimMemory。Glide 对每个 context 都保持一个 RequestManager,通过 FragmentTransaction 保持与 Activity/Fragment
    生命周期一致,并且有对应的 trimMemory 接口实现可供调用。
  4. 支持 okhttp、Volley。Glide 默认通过 UrlConnection 获取数据,可以配合 okhttp 或是 Volley 使用。实际 ImageLoader、Picasso 也都支持 okhttp、Volley。
  5. 内存友好。Glide 的内存缓存有个 active 的设计,从内存缓存中取数据时,不像一般的实现用 get,而是用 remove,再将这个缓存数据放到一个 value 为软引用的 activeResources map 中,并计数引用数,在图片加载完成后进行判断,如果引用计数为空则回收掉。内存缓存更小图片,Glide 以 url、view_width、view_height、屏幕的分辨率等做为联合 key,将处理后的图片缓存在内存缓存中,而不是原始图片以节省大小与 Activity/Fragment 生命周期一致,支持 trimMemory。图片默认使用默认 RGB_565 而不是 ARGB_888,虽然清晰度差些,但图片更小,也可配置到 ARGB_888。
  6. Glide 可以通过 signature 或不使用本地缓存支持 url 过期
35、Xutils, OKhttp, Volley, Retrofit对比
  • Xutils:这个框架非常全面,可以进行网络请求,可以进行图片加载处理,可以数据储存,还可以对view进行注解,使用这个框架非常方便,但是缺点也是非常明显的,使用这个项目,会导致项目对这个框架依赖非常的严重,一旦这个框架出现问题,那么对项目来说影响非常大的。、

  • OKhttp:Android开发中是可以直接使用现成的api进行网络请求的。就是使用HttpClient,HttpUrlConnection进行操作。okhttp针对Java和Android程序,封装的一个高性能的http请求库,支持同步,异步,而且okhttp又封装了线程池,封装了数据转换,封装了参数的使用,错误处理等。API使用起来更加的方便。但是我们在项目中使用的时候仍然需要自己在做一层封装,这样才能使用的更加的顺手。

  • Volley:Volley是Google官方出的一套小而巧的异步请求库,该框架封装的扩展性很强,支持HttpClient、HttpUrlConnection,
    甚至支持OkHttp,而且Volley里面也封装了ImageLoader,所以如果你愿意你甚至不需要使用图片加载框架,不过这块功能没有一些专门的图片加载框架强大,对于简单的需求可以使用,稍复杂点的需求还是需要用到专门的图片加载框架。Volley也有缺陷,比如不支持post大数据,所以不适合上传文件。不过Volley设计的初衷本身也就是为频繁的、数据量小的网络请求而生。

  • Retrofit:Retrofit是Square公司出品的默认基于OkHttp封装的一套RESTful网络请求框架,RESTful是目前流行的一套api设计的风格,
    并不是标准。Retrofit的封装可以说是很强大,里面涉及到一堆的设计模式,可以通过注解直接配置请求,可以使用不同的http客户端,虽然默认是用http
    ,可以使用不同Json Converter 来序列化数据,同时提供对RxJava的支持,使用Retrofit + OkHttp +
    RxJava + Dagger2 可以说是目前比较潮的一套框架,但是需要有比较高的门槛。

  • Volley VS OkHttp

Volley的优势在于封装的更好,而使用OkHttp你需要有足够的能力再进行一次封装。而OkHttp的优势在于性能更高,因为OkHttp基于NIO和Okio ,所以性能上要比 Volley更快。IO 和NIO这两个都是Java中的概念,如果我从硬盘读取数据,第一种方式就是程序一直等,数据读完后才能继续操作这种是最简单的也叫阻塞式IO,还有一种是你读你的,程序接着往下执行,等数据处理完你再来通知我,然后再处理回调。而第二种就是
NIO 的方式,非阻塞式, 所以NIO当然要比IO的性能要好了,而 Okio是 Square公司基于IO和NIO基础上做的一个更简单、高效处理数据流的一个库。理论上如果Volley和OkHttp对比的话,更倾向于使用Volley,因为Volley内部同样支持使用OkHttp,这点OkHttp的性能优势就没了, 而且 Volley本身封装的也更易用,扩展性更好些。

  • OkHttp VS Retrofit

毫无疑问,Retrofit 默认是基于 OkHttp 而做的封装,这点来说没有可比性,肯定首选 Retrofit。

  • Volley VS Retrofit

这两个库都做了不错的封装,但Retrofit解耦的更彻底,尤其Retrofit2.0出来,Jake对之前1.0设计不合理的地方做了大量重构, 职责更细分,而且Retrofit默认使用OkHttp,性能上也要比Volley占优势,再有如果你的项目如果采用了RxJava ,那更该使用 Retrofit 。所以这两个库相比,Retrofit更有优势,在能掌握两个框架的前提下该优先使用Retrofit。但是Retrofit门槛要比Volley稍高些,要理解他的原理,各种用法,想彻底搞明白还是需要花些功夫的,如果你对它一知半解,那还是建议在商业项目使用Volley吧。

36、Android中sqlit数据库如果进行版本控制
  • 具体请参考Android中sqlit数据库升级或者降级
37、Room Database数据库的使用与版本控制
  • 具体请参考Room Database数据库使用以及数据库版本升级详解
三、网络篇
1、什么是HTTP?
  • 具体请参考HTTP详解总结
2、什么是HTTPS?
  • https实现原理:
  1. 客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
  2. Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
  3. 客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
  4. 客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
  5. Web服务器利用自己的私钥解密出会话密钥。
  6. Web服务器利用会话密钥加密与客户端之间的通信。
  • 具体请参考HTTPS详解总结
3、Http 与Https区别
  1. https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。

  2. http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。

  3. http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

  4. http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

4、Http位于TCP/IP模型中的第几层?为什么说Http是可靠的数据传输协议?

tcp/ip的五层模型:

  • 从下到上:物理层->数据链路层->网络层->传输层->应用层

  • 其中tcp/ip位于模型中的网络层,处于同一层的还有ICMP(网络控制信息协议)。http位于模型中的应用层

  • 由于tcp/ip是面向连接的可靠协议,而http是在传输层基于tcp/ip协议的,所以说http是可靠的数据传输协议。

5、HTTP链接的特点
  • HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。

  • 从建立连接到关闭连接的过程称为“一次连接”。

6、TCP(传输控制协议)和UDP(用户数据报协议)的区别
  • UDP(用户数据报协议)是iso参考模型中一种无连接的传输层协议,提供简单不可靠的非连接传输层服务,面向报文

特点:

  • 沟通简单,不需要大量的数据结构,处理逻辑和包头字段
  • 轻信他人。它不会建立连接,但是会监听这个地方,谁都可以传给它数据,它也可以传给任何人数据,甚至可以同时传给多个人数据。
  • 愣头青,做事不懂变通。不会根据网络的情况进行拥塞控制,无论是否丢包,它该怎么发还是怎么发

应用场景:

  • 需要资源少,网络情况稳定的内网,或者对于丢包不敏感的应用,比如 DHCP 就是基于 UDP 协议的。
  • 不需要一对一沟通,建立连接,而是可以广播的应用。因为它不面向连接,所以可以做到一对多,承担广播或者多播的协议。
  • 需要处理速度快,可以容忍丢包,但是即使网络拥塞,也毫不退缩,一往无前的时候

UDP例子:

  • 直播。直播对实时性的要求比较高,宁可丢包,也不要卡顿的,所以很多直播应用都基于 UDP 实现了自己的视频传输协议
  • 实时游戏。游戏的特点也是实时性比较高,在这种情况下,采用自定义的可靠的 UDP 协议,自定义重传策略,能够把产生的延迟降到最低,减少网络问题对游戏造成的影响
  • 物联网。一方面,物联网领域中断资源少,很可能知识个很小的嵌入式系统,而维护 TCP 协议的代价太大了;另一方面,物联网对实时性的要求也特别高。比如 Google 旗下的 Nest 简历 Thread
    Group,推出了物联网通信协议 Thread,就是基于 UDP 协议的
  • TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,是专门为了在不可靠的网络中提供一个可靠的端对端字节流而设计的,面向字节流

什么是面向连接,什么是面向无连接?

  • 在互通之前,面向连接的协议会先建立连接,如 TCP 有三次握手,而 UDP 不会

TCP 为什么是可靠连接?
答:

  • 通过 TCP 连接传输的数据无差错,不丢失,不重复,且按顺序到达。
  • TCP 报文头里面的序号能使 TCP 的数据按序到达
  • 报文头里面的确认序号能保证不丢包,累计确认及超时重传机制
  • TCP 拥有流量控制及拥塞控制的机制

注意:

TCP 的顺序问题,丢包问题,流量控制都是通过滑动窗口来解决的 拥塞控制时通过拥塞窗口来解决的

二者区别:

  • TCP是面向连接的,由于tcp连接需要三次握手,所以能够最低限度的降低风险,保证连接的可靠性高;UDP是基于非连接的,可靠性低 。
  • TCP 是面向字节流的,UDP 是基于数据报的,`面向报文
  • 由于TCP是连接的通信,需要有三次握手、重新确认等连接过程,会有延时,实时性差,同时过程复杂,也使其易于攻击;UDP没有建立连接的过程,因而实时性较强,也稍安全。
  • 在传输相同大小的数据时,TCP首部开销20字节;UDP首部开销8字节,TCP报头比UDP复杂,故实际包含的用户数据较少。TCP在IP协议的基础上添加了序号机制、确认机制、超时重传机制等,保证了传输的可靠性,不会出现丢包或乱序,而UDP有丢包,故TCP开销大,UDP开销较小 。
  • 每条TCP连接只能时点到点的;UDP支持一对一、一对多、多对一、多对多的交互通信。

具体TCP的详解请参考TCP协议的全面详解

7、Socket建立网络连接的步骤

建立Socket连接至少需要一对套接字,其中一个运行与客户端–ClientSocket,一个运行于服务端–ServiceSocket

  1. 服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

  2. 客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。注意:客户端的套接字必须描述他要连接的服务器的套接字,指出服务器套接字的地址和端口号,然后就像服务器端套接字提出连接请求。

  3. 连接确认:当服务器端套接

发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务端套接字则继续处于监听状态,继续接收其他客户端套接字的连接请求。

具体使用请参考Android进程间通信之Socket使用与解析(TCP、UDP)

8、Okhttp原理
  • 具体请参考Android之OKhttp原理解析
9、HTTP1.0、HTTP1.1 和 HTTP2.0 的区别
  • 具体请参考HTTP1.0、HTTP1.1 和 HTTP2.0 的区别
10、HttpClient、HttpURLConnection、Volley、OkHttp网络框架分析与对比

具体请参考HttpClient、HttpURLConnection、Volley、OkHttp网络框架分析与对比

以上就我这几年面试的总结。

本文标签: 面试题工程师适合android