面试题整理2018

要跳槽了,整理一份面试,方便查看。(不断更新中。。。。)

目录

JVM:

1、JVM 的内存划分

2、垃圾回收机制

3、GC什么时候触发?

4、类加载机制

5、双亲委派模型

Java:

1、String、StringBuffer、StringBuilder的区别

2、java中==和equals和hashCode的区别?

3、HashMap Hashtable 的区别

4、Synchronized 和volatile 关键字的区别 

5、final,finally,finalize的区别

6、手写线程安全单例模式。

8、string 转换成 integer的方式及原理 ?

9、什么是死锁,产生死锁的原因及必要条件?

10、Java中join()方法的理解

11、java 多态的理解?

13、注解

14、反射

15 、泛型

算法相关

16、代码实现 求 N!  

17、代码实现反转二叉树

18、代码实现 fibonacci(斐波那契)序列

19、排序相关

二分法查找:(前提数组是有序的)

Android 相关:

1、LocalBroadcastManager的使用场景

2、Android应用程序的启动过程

3、APK 安装 卸载 流程

4、大图加载优化:

5、 Bitmap图像像素类型有哪几种? 一个图片所占的内存大小

6、事件分发机制

7、View 的绘制原理

8、ListView 和RecycleView的区别?

9、Binder机制

10、apk的release和debug版本的区别

11、线程和进程的区别

12、线程池相关

13、Handler 机制? 一个线程能否创建多个Handler?

14、AsyncTask相关

15、Activity的生命周期相关

16、性能优化 

17、你是如何解决Android的布局嵌套问题的(布局优化)?

18、内存泄漏:

19、HttpURLConnection熟练掌握(遇到过让手写一个请求,以及如何实现文件上传)

20、Volley可以传输大数据吗?为什么?

21、OkHttp 底层实现,与其他区别。

22、Glide源码解析

23、EventBus源码解析

24、Butterknife源码解析:

25、热修复原理

26、插件化开发:

27、Retofit 原理

28、进程保活实现

29、加密算法了解

30、apk瘦身:

31、常见设计模式,以及使用

32、MVP 实现,MVC MVP 比较

33、数据库相关

34、上传异常信息:

35、消息推送原理:

服务端:

1、如何判断服务端是否支持断点续传?

2、http和https的区别?

2、Cookie和Session的区别?

3、TCP和UDP的区别?

4、TCP/IP在连接时有几次握手?释放时有几次握手? 



 

JVM:

https://www.jianshu.com/p/d887e0a4794d

垃圾回收机制相关

什么是JVM

JVM即java 虚拟机 ,是Java程序运行的平台,它就像一台虚拟出来的计算机一样,负责执行Java编译好的字节码文件

JVM的内存结构包括五大区域:程序计数器、虚拟机栈、本地方法栈、堆区、方法区。其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生、随线程而灭,因此这几个区域的内存分配和回收都具备确定性,就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆区和方法区则不一样,这部分内存的分配和回收是动态的,正是垃圾收集器所需关注的部分。

  垃圾收集器在对堆区和方法区进行回收前,首先要确定这些区域的对象哪些可以被回收,哪些暂时还不能回收,这就要用到判断对象是否存活的算法!

 

1、JVM 的内存划分

1. 程序计数器 (Program Counter Register)

概念:一块较小的内存空间,是字节码解释器的行为指示器 。

2. Java 虚拟机栈 (VM Stack)

概念:Java 方法执行的内存模型,存放基本类型的变量数据和对象的引用(不是对象本身),每一个java 方法执行,就会生成一个栈帧,

java 方法运行的过程就是栈帧在虚拟机中入栈出栈的过程

3. 本地方法栈 (Native Method Stack)

概念:执行 Native 方法的栈

4. Java 堆 (Java Heap)

概念:所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域唯一的目的就是存放对象实例,几乎所有的对象都在这分配内存。

5. 方法区 (Method Area)

概念:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码数据等

2、垃圾回收机制

判断何时执行 GC 的两种算法:

https://blog.csdn.net/u014142287/article/details/51424725

    1、引用计数算法:

给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器减1;任何时刻计数器都为0的对象就是不可能再被使用的。但是Java语言中没有选用引用计数算法来管理内存,其中最主要的一个原因是它很难解决对象之间相互循环引用的问题。

优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。

缺点:无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。他们的引用计数永远不可能为0。

    2、可达性分析算法

通过一系列名为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的

 在Java语言里,可作为GC Roots对象的包括如下几种:
              a.虚拟机栈(栈桢中的本地变量表)中的引用的对象
              b.方法区中的类静态属性引用的对象
              c.方法区中的常量引用的对象
              d.本地方法栈中JNI的引用的对象

几种垃圾回收算法:

    1、标记-清除算法(Mark-Sweep)

从根节点开始标记所有可达对象,其余没有标记的即为垃圾对象,执行清除。但回收后的空间是不连续的

但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。

    2、标记-整理法:

     标记-整理算法采用 标记-清除算法一样的方式进行对象的标记,但在清除时,在回收不存活的对象占用的空间后,

会将所有的存活对象往左端空闲空间移动,并更新相应的指针。标记-整理算法是在标记-清除算法的基础上,

又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。

    3、复制算法:

    复制算法采用从根集合扫描,并将存活对象复制到一块新的,没有使用过的空间中,这种算法当控件存活的对象
            比较少时,极为高效,但是带来的成本是需要一块内存交换空间进行对象的移动。

   4、分代收集算法:

它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)

 

新生代:朝生夕灭的对象(例如:方法的局部变量引用的对象等)。采用复制算法

        老年代:存活得比较久,但还是要死的对象(例如:缓存对象、单例对象等)。“标记-清除”或者“标记-整理”算法,回收次数相对较少,每次回收时间比较长。

        永久代:对象生成后几乎不灭的对象(例如:加载过的类信息)。永久代也使用“标记-清除”或者“标记-整理”算法进行垃圾回收。

新生代和老年代都在java堆,永久代在方法区

3、GC什么时候触发?

GC有两种类型:Minor GC 和 Full GC()。

 Minor GC

  一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

Full GC

  对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC:

a) 年老代(Tenured)被写满;

b) 持久代(Perm)被写满;

c) System.gc()被显示调用;

d) 上一次GC之后Heap的各域分配策略动态变化;

4、类加载机制

https://blog.csdn.net/ln152315/article/details/79223441

一个Java文件从编码完成到最终执行,一般主要包括两个过程

      编译:把写好的java文件,通过javac命令编译成字节码,也就是.class文件。

      运行:把编译生成的.class文件交给Java虚拟机(JVM)执行。

 类加载的过程:

      类加载过程即是指JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程。

      类加载的过程主要分为三个部分: 加载、连接、初始化 ; 其中连接 又分为:验证、准备、解析。

加载:

根据一个类的全限定名(如com.test.HelloWorld.class)来读取此类的二进制字节流到JVM内部;

这些二进制数据所代表的存储结构会被转化为方法区中运行时的数据结构,接着会在方法区中创建相应的class对象;

验证: 

主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误

验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证;

准备:

为类中的所有静态变量分配内存空间,并为其设置一个初始值

解析:

将常量池内的符号引用替换为直接引用的过程。这个阶段可以在初始化之后再执行。

初始化:

这个阶段主要是对类变量初始化,是执行类构造器的过程。

换句话说,只对static修饰的变量或语句进行初始化。

如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。

如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。

5、双亲委派模型

https://www.cnblogs.com/wxd0108/p/6681618.html

什么是类加载器?

类加载器就是根据指定全限定名称将class文件加载到JVM内存,转为Class对象。如果站在JVM的角度来看,只存在两种类加载器:

启动类加载器(Bootstrap ClassLoader:由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。

其他类加载器:由Java语言实现,继承自抽象类ClassLoader。如:

扩展类加载器(Extension ClassLoader:负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。

应用程序类加载器(Application ClassLoader):负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

双亲委派模型:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。
优势: 安全性,不能随意篡改系统api

防止重复加载


Java:

1、String、StringBuffer、StringBuilder的区别

http://www.cnblogs.com/su-feng/p/6659064.html

String为字符串常量,一旦创建之后该对象是不可更改的

StringBuilder和StringBuffer均为字符串变量,是可以更改的

在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的

String:适用于少量的字符串操作的情况

StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况

StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

2、java==equalshashCode的区别?

https://www.2cto.com/kf/201804/740402.html

== : 基本数据类型== 比较的是数值的大小,如果是引用数据类型,== 比较的是内存地址

equals : Object类的方法,对比时,默认对比的也是引用对象指向的内存地址,所以一般跟“==”对比的值是相同的。

Object
public boolean equals(Object obj) {

    return (this == obj);

}

hashCode:Object类的方法,HashCode的存在主要是为了查找的快捷性, hashCode方法返回的是对象的哈希值。

hashcode是用于散列数据的快速存取,如利用HashSet/HashMap/Hashtable类来存储数据时,都是根据存储对象的hashcode值来进行判断是否相同的。

延伸——复写equal()方法时,为什么要复写hashcode()?

 

3、HashMap Hashtable 的区别

/**Hashtable*/
public class Hashtable<K,V> extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {
   public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }    
    。。。。。
    }
}
/**HashMap*/
public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {}

1、查看源码可以看到,HashMap 继承 AbstractMap, Hashtable 继承Dictionary 但是都实现了Map、  Cloneable、 Serializable 

2、HashMap 的key  value 可以为null  ,但是 Hashtable 不行。

3、Hashtable 的方法是用synchronized 修饰的,线程安全,而 HashMap 线程不安全,所以,在单线程下,HashMap 执行效率更高。如果想让 HashMap 线程安全,可以: Map map = Collections.synchronizedMap(new HashMap());

4、Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式不同。HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。

延伸- ArrayList跟HashMap是否线程安全,如何保证线程安全?

都是线程不安全,通过 :Collections.synchronizedMap();  Collections.synchronizedList() 保证

延伸——HashMap原理?

HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用LinkedList来解决碰撞问题,当发生碰撞了,对象将会储存在LinkedList的下一个节点中。 HashMap在每个LinkedList节点中储存键值对对象。

 

4.ArrayList 和LinkedList的区别:

 

4、Synchronized 和volatile 关键字的区别 

1、volatile 只能修饰成员变量,synchronized 可以修饰变量,方法,类

2、多线程访问volitile不会发生阻塞,synchronized会阻塞。

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

volatile保证可见性:它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值,这就保证了可见性。

题外:原子性:简单来说就是 要么完整的被执行,要么完全不执行。这种特性就叫原子性

延伸:为什么volatile不能保证变量的原子性?

https://blog.csdn.net/xdzhouxin/article/details/81236356

 

5、final,finally,finalize的区别

1、final(关键字):

被final修饰的类,就意味着不能再派生出新的子类,不能作为父类而被子类继承。因此一个类不能既被abstract声明,又被final声明。将变量或方法声明为final,可以保证他们在使用的过程中不被修改。被声明为final的变量必须在声明时给出变量的初始值,而在以后的引用中只能读取。被final声明的方法也同样只能使用,即不能方法重写。

2、finally:

在异常处理时提供finally块来执行任何清除操作。不管有没有异常被抛出、捕获,finally块都会被执行。finally块是无论异常是否发生,都会执行finally块的内容,所以在代码逻辑中有需要无论发生什么都必须执行的代码,就可以放在finally块中。

延伸 —finally语句到底是在return之前还是之后执行?

是在 return 执行之后,返回结果之前 执行

3、finalize()方法名:

java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者被执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

6、手写线程安全单例模式。

延伸 - 锁为什么写在方法里头?

    为了减少加锁的次数,当intacne 实例化之后,可以直接返回,不需要再次加锁

延伸 - 单例模式造成的内存泄漏问题?

https://blog.csdn.net/lijia1201900857/article/details/82877316

区别:

饿汉式:在类被加载的时候创建实例(线程安全的)

懒汉式:使用的时候创建,需要添加锁实现线程安全

内部类方式:通过类加载机制来保证只创建一个instance实例。和饿汉模式一样,不存在多线程并发的问题。 *

只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。 *

可以同时保证延迟加载和线程安全

三种写法:
懒汉式(需要时在new ,比较懒)
public class SigleInstance {

    private static SigleInstance instance;
    private SigleInstance(){}

    public static SigleInstance getInstance() {

        if (instance == null) {

            syncronized(SigleInstance.class) {

                    if (instance == null) {
                            instance = new SingleInstance();
                    }
            }

       }
        return instance;
    }
}
// 饿汉式(早早的new出来,怕饿死)
public class SigleInstance {

    private static SigleInstance instance = new SingleInstance();
    private SigleInstance(){ }

    public static SigleInstance getInstance() {

        return instance;
    }
}
//内部类方式
public class SigleInstance {

    private SigleInstance(){}
    public static SigleInstance getInstance() {

        return Inner.instance;
    }
    private static class Inner{
        private static SigleInstance instance = new SigleInstance();
    }
}

 

8、string 转换成 integer的方式及原理 ?

 public static int parseInt(String s) throws NumberFormatException {
        return parseInt(s,10);
    }

parseInt() 内部首先 判断字符串是否包含符号(-或者+),则对相应的negative和limit进行 
赋值,然后再循环字符串,对单个char进行数值计算Character.digit(char ch, int radix) 
在这个方法中,函数肯定进入到0-9字符的判断(相对于string转换到int), 
否则会抛出异常,数字就是如上面进行拼接然后生成的int类型数值。
 

9、什么是死锁,产生死锁的原因及必要条件?

https://blog.csdn.net/u014419806/article/details/52856589/

死锁:

指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

死锁产生的原因:

1 、因为系统资源不足。 

2、进程运行推进的顺序不合适。 

3 、资源分配不当等

死锁产生的的必要条件:

1、互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源

2、请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放

3、不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放

4、环路等待条件:是指进程发生死锁后,必然存在一个进程--资源之间的环形链

解决:

解决死锁的方法分为死锁的预防,避免,检测与恢复三种。

延伸——读写锁 ReentrantReadWriteLock 

 rwl.readLock().lock();//上读锁,其他线程只能读不能写

 rwl.writeLock().lock();//上写锁,不允许其他线程读也不允许写

10、Java中join()方法的理解

程实例的方法join()方法可以使得一个线程在另一个线程结束后再执行

延伸-   多线程 顺序 输入 ABC。 

public static void main(String[] args) {
		// 线程A
		final Thread a = new Thread(new Runnable() {
 
			@Override
			public void run() {
				System.out.println("A");
 
			}
		});
		// 线程B
		final Thread b = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					// 执行b线程之前,加入a线程,让a线程执行
					a.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("B");
			}
		});
		// 线程C
		final Thread c = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					// 执行c线程之前,加入b线程,让b线程执行
					b.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("C");
			}
		});
}

 

11、java 多态的理解?

多态:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。

多态的实现方式:接口实现、继承父类进行方法重写、同一个类内进行方法重载。

运行时多态存在的三个必要条件:

(1)要有继承(包括接口的实现);

(2)要有重写;

(3)父类引用指向子类对象。

多态的优点

消除类型之间的耦合关系

可替换性

可扩充性

接口性

灵活性

简化性

延伸 —附上一道经典习题:

public class Demo{

    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new B();
        B b = new B();
        C c = new C();
        D d = new D();
/* 首先清楚调用顺序:
  this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)
*/

/*
第一个打印:(同2 、3)
    1:this.show(O)   先看A 有没有show(B)方法,有就执行,没有看第2步
    2:super.show(O)  看A 的 父类有没有show(B)方法,A没有父类,看第3步:
    3:this.show((super)O)  看A 有没有 show(superB)方法,superB 即A, 即调用 A 中的show(A)
*/

        System.out.println(a1.show(b)); //  ①A and A
        System.out.println(a1.show(c)); //  ②A and A
        System.out.println(a1.show(d)); //  ③A and D
/**
第4个打印:
1:this.show(O)   先看A 有没有show(B)方法,有就执行,没有看第2步
2:super.show(O)  看A 的 父类有没有show(B)方法,A没有父类,看第3步:
3:this.show((super)O)  看A 有没有 show(superB)方法,superB  即A 即调用 A 中的show(A)。
4:但是,B 持有了A 的引用,并复写了 show(A)中的方法,所以调用B 中的 show(A)
*/
        System.out.println(a2.show(b)); //  ④B and A

// 其他都类似

        System.out.println(a2.show(c)); //  ⑤B and A
        System.out.println(a2.show(d)); //  ⑥A and D
        System.out.println(b.show(b));  //  ⑦B and B
        System.out.println(b.show(c));  //  ⑧B and B
        System.out.println(b.show(d));  //  ⑨A and D
    }

    static class A {
        public String show(D obj) {
            return ("A and D");
        }

        public String show(A obj) {
            return ("A and A");
        }
    }

    static class B extends A {
        public String show(B obj) {
            return ("B and B");
        }

        public String show(A obj) {
            return ("B and A");
        }
    }

    static class C extends B {
        public String show(B obj) {
            return ("B and B");
        }

        public String show(A obj) {
            return ("B and A");
        }
    }

    static class D extends B {
        public String show(B obj) {
            return ("B and B");
        }

        public String show(A obj) {
            return ("B and A");
        }
    }
}

13、注解

Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息或者任何元数据(metadata)的途径和方法。

基本规则:Annotation(注解)不能影响程序代码的执行,无论增加,删除Annotation(注解),代码都始终如一的执行。

注解的分类:

 系统内置的标准注解

1.Override  

2.Deprecated

3.SuppressWranning

4种元注解:

14、反射

什么是反射?

  在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意的方法和属性。

使用:

Class c = Class.forName("完整路径");
获取所有的构造函数
 Constructor[]  cons= c.getDeclaredConstructors();
//获取自己包含父类的所有公共方法
Method[] methods1 = c.getMethods();
//获取自己的所有方法(没有父类的)
Method[] methods2 = c.getDeclaredMethods
//获取单个公共的方法
Method method3 = c.getMethod(methodName);//methodName:方法的名字
Method method4 = c.getMethod(methodName1,String.class,int.class)
//获取所有的公共的成员变量
Field[] fields1 = c.getFields();
//获取所有的成员变量
Field[] fields2 = c.getDeclaredFields();

15 、泛型

什么是泛型?

泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

通俗的讲,泛型就是操作类型的 占位符,即:假设占位符为T,那么此次声明的数据结构操作的数据类型为T类型。

延伸——为什么需要泛型?

 可以举例说明

List list = new ArrayList();

list.add("CSDN_SEU_Calvin");

list.add(100);

for (int i = 0; i < list.size(); i++) {

      String name = (String) list.get(i); //取出Integer时,运行时出现异常

}

延伸——什么是泛型中的限定通配符和非限定通配符 ?

限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T>它通过确保类型 必须是T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。

<?>表 示了非限定通配符,因为<?>可以用任意类型来替代。

算法相关

16、代码实现 求 N!  

    public int N(int i) {
        if (i < 0)
            throw new IllegalArgumentException("请输入正整数");
        if (i == 1)
            return 1;
        int num = i * N(i - 1);
        return num;
    }

17、代码实现反转二叉树

// 先创建一棵树
class TreeNode{
    int data;//节点值
    TreeNode left;.
    TreeNode right;

} 

// 递归 翻转二叉树
public TreeNode invertNode(TreeNode root) {
                if(root==null)
                    return root;
		TreeNode temp=root.left;
		root.left=invertNode(root.right);
		root.right=invertNode(temp);
		return root;
}

18、代码实现 fibonacci(斐波那契)序列

斐波那契:用文字来说,就是费波那契数列由0和1开始,之后的费波那契系数就是由之前的两数相加而得出。

    /**
     * 递归实现
     * 求一个数的斐波那契数列
     */
    public static int fib(int n) {
        if(n<=0)
            return 0;
        else if (n == 1 || n == 2) {
            return 1;
        } else {
            return fib(n - 1) + fib(n - 2);
        }
    }

 

19、排序相关

冒泡排序: 时间复杂度为O(n^2),空间复杂度为O(1)

    void sort(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - i - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }

选择排序:

 public static void selectedSort(int [] array){
        for(int i = 0; i < array.length; i++){
           int index = i;
            for(int j = i; j < array.length; j++){
                if(array[index] > array[j]){
                    index = j;
               }
             }

            if(i!=index){
               int temp = array[i];
               array[i] = array[index];       
               array[index] = temp;
             }
             
        }
 }

快速排序:

 /**
     * 查找出中轴(默认是最低位low)的在numbers数组排序后所在位置
     * 
     * @param numbers 带查找数组
     * @param low   开始位置
     * @param high  结束位置
     * @return  中轴所在位置
     */
    public static int getMiddle(int[] numbers, int low,int high)
    {
        int temp = numbers[low]; //数组的第一个作为中轴
        while(low < high){
        while(low < high && numbers[high] > temp){
            high--;
          }
        numbers[low] = numbers[high];//比中轴小的记录移到低端
        while(low < high && numbers[low] < temp){
            low++;
          }
        numbers[high] = numbers[low] ; //比中轴大的记录移到高端
        }

        numbers[low] = temp ; //中轴记录到尾
        return low ; // 返回中轴的位置
    }

    public static void sort(int[] array,int lo ,int hi){
        if(lo>=hi){
            return ;
        }
        int index=partition(array,lo,hi);
        sort(array,lo,index-1);
        sort(array,index+1,hi); 
    }

二分法查找:(前提数组是有序的)

 
public static int search(int[] arr, int key) {
       int start = 0;
       int end = arr.length - 1;
       while (start <= end) {
           int middle = (start + end) / 2;
           if (key < arr[middle]) {
               end = middle - 1;
           } else if (key > arr[middle]) {
               start = middle + 1;
           } else {
               return middle;
           }
       }
       return -1;
   }

 

 



 

Android 相关:

1、LocalBroadcastManager的使用场景

解决  BroadcastReceiver的安全问题;

只能通过代码动态注册。只在应用内发送

2、Android应用程序的启动过程

冷启动

冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用。

冷启动的特点:因为系统会重新创建一个新的进程分配给它,所以会创建和初始化Application,在创建和初始化它的Launch Activity(onCreate onMesure onLayout,ondraw),最后展示在界面上

热启动

热启动:当启动应用时,后台存在该应用的进程(back键,home键,应用退出,但是没有销毁),从已有的进程中启动

热启动的特点:从已有的进程中启动,不需要创建和初始化Application ,直接创建和初始化它的Launch Activity

两者区别:后台进程是否存在相应的进程,创建进程是耗时操作

简述启动流程:

Launcher响应用户点击,调用 startActivity方法,最终调用内部startActivityForResult(),然后内部会通过Instrumentation来尝试启动Activity,这是一个跨进程过程,它会调用AMS(ActivityMangeService)的startActivity方法,当AMS校验完activity的合法性后,会通过ApplicationThread回调到我们的进程,这也是一次跨进程过程,而applicationThread就是一个binder,回调逻辑是在binder线程池中完成的,所以需要通过Handler H将其切换到ui线程,第一个消息是LAUNCH_ACTIVITY,它对应handleLaunchActivity,在这个方法里完成了Activity的创建和启动,接着,在activity的onResume中,activity的内容将开始渲染到window上,然后开始绘制直到我们看见

3、APK 安装 卸载 流程

 

4、大图加载优化:

BitmapFactory.Options options = new BitmapFactory.Options();  

  options.inJustDecodeBounds = true;  

设置 inJustDecodeBounds= true  就可以不把图片加载到内存,获取到图片的宽高。

然后通过设置 BitmapFactory.Options中 inSampleSize 的值就可以实现对图片的压缩

inSampleSize 会对图片的宽高分别压缩相应的倍数。、

如果现实特别长的图,可以使用BitmapRegionDecoder,主要用于显示图片的某一块矩形区域,

bitmapRegionDecoder.decodeRegion(rect, options);

还有就是,创建bitmap 的时候,默认的图片质量是 RGB_888

可以修改图片的质量,减少内存。

 

延伸—— 图片三级缓存 以及 LruCache 算法。

LruCache算法:

1、LruCache采用的缓存算法为LRU(Least Recently Used),最近最少使用算法。

核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象

2、LruCache是线程安全的,因此是可以采用多线程并发处理。

3、它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。

延伸——图片压缩:

质量压缩:

质量压缩并不会改变图片在内存中的大小,仅仅会减小图片所占用的磁盘空间的大小,因为质量压缩不会改变图片的分辨率。质量压缩的原理是通过改变图片的位深和透明度来减小图片占用的磁盘空间大小.

ByteArrayOutputStream baos = new ByteArrayOutputStream();

bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)

采样率压缩:

BitmapFactory.Options options = new BitmapFactory.Options();  

  options.inJustDecodeBounds = true;  

然后通过设置 BitmapFactory.Options中 inSampleSize 的值就可以实现对图片的压缩

1、inSampleSize小于等于1会按照1处理

2、inSampleSize只能设置为2的平方,不是2的平方则最终会减小到最近的2的平方数,如设置7会按4进行压缩,设置15会按8进行压缩。

 

尺寸压缩:通过缩放图片的像素,减小图片占用内存大小,这个比如用于缩略图

Bitmap result=Bitmap.createBitmap(bitmap.getWidth()/ratio,bitmap.getHeight()/ratio, Bitmap.Config.ARGB_8888); Canvas canvas =new Canvas(); Rect rect=new Rect(0,0,bitmap.getWidth()/ratio,bitmap.getHeight()/ratio); canvas.drawBitmap(bitmap,null,rect,null);

 

5、 Bitmap图像像素类型有哪几种? 一个图片所占的内存大小

包括ALPHA_8、RGB_565、ARGB_4444、ARGB_8888  

A:透明度;RGB分别是Red、Green、Blue,三种原色

ARGB_8888:四个通道都是8位,一个像素点占8+8+8+8=32位,每个像素占用4个字节;

ARGB_4444:四个通道都是4位,一个像素点占 4+4+4+4=16位,每个像素占用2个字节;

RGB_565:没有A通道,一个像素点占 5+6+5=16位,每个像素占用2个字节,图片失真小,但是没有透明度;

ALPHA_8:只有A通道,一个像素点占  8 位,每个像素占用1个字节大大小,只有透明度,没有颜色值。

一张图片Bitmap所占用的内存 = 图片长度 x 图片宽度 x 一个像素点占用的字节数

100*100 的ARGB_8888的图片 内存= 100*100*4=40000byte

6、事件分发机制

事件分发的相关三个方法:

    dispatchTouchEvent(MotionEvent  ev) : 事件分发 

    onInterecptTouchEvent(MotionEvent  ev) : 事件拦截

    onTouchEvent(MotionEvent  ev)  : 事件处理

 事件分发传递对象:

Activity:

           只响应分发和处理方法,因为Activity是用来展示界面的,如果拦截,内部的View就无法响应。

ViewGroup(Layout):

          响应所有事件

View:

          响应分发和处理事件。   dispatchTouchEvent(MotionEvent  ev)    onTouchEvent(MotionEvent  ev)   

例如:创建一个布局文件吗,点击Textview,不做任何处理的事件传递:

<LinearLayout

    xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent">

<TextView

    android:text="@string/hello_world"

    android:layout_width="100dp"

    android:layout_height="100dp"/>

</LinearLayout>

以上控件,如果不做任何处理,事件传递的方法:

 Activity===dispatch

 ViewGroupOne===dispatch

 ViewGroupOne===onInterrcept

 textview===dispatch

 textview===onTouchEvent

 ViewGroupOne===onTouchEvent

 Activity===onTouchEvent

事件传递机制:

Activity:

dispatchTouchEvent():

          return true 或者 false 自己消费

          return super 向下传递

onTouchEvent():

return true

ViewGroup:

dispatchTouchEvent():

       return true :自己消费,不像上传递,也不向下传递

       return false: 交给父组件(activity)的onTouchEvent();

       return super:  执行自己的 onIntercptTouchEvent();

onIntercptTouchEvent():

                     return true:调用自己的onTouchEvent()方法

                     return  false /super 向下传递

onTouchEvent() :(调用该方法的前提是  onIntercptTouchEvent  返回true)

         return  true  自己消费

         return  false/super  向上传递。

View:

dispatchTouchEvent():

       return true 自己消费,

       return false:  交给父组件(ViewGroup)的onTouchEvent();

onTouchEvent():

      return  true  自己消费

      return  false/super  向上传递。

 

延伸——ScrollView 嵌套 ListView 如何滑动冲突?

两种方法:

 1、外部法:

自定义ScrollView ,重写 onInterceptTouchEvent() 方法;

当点击操作发生在ListView的区域的时候, 

返回 false 让ScrollView的onTouchEvent接收不到MotionEvent,而是把Event传到下一级的控件中

2、内部法: 当触碰ListView 的时候,不让父控件拦截。

     listView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                listView.getParent().requestDisallowInterceptTouchEvent(true);
                return false;
            }
        });

 

7、View 的绘制原理

https://blog.csdn.net/u012124438/article/details/71435787

1.ViewRoot对应于ViewRootImpl类,是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。

2.View的绘制流程从ViewRoot的performTraversals开始,经过measure、layout和draw三个过程才可以把一个View绘制出来,其中measure用来测量View的宽高,layout用来确定View在父容器中的放置位置,而draw则负责将View绘制到屏幕上。

3.performTraversals会依次调用performMeasure、performLayout和performDraw三个方法,这三个方法分别完成顶级View的measure、layout和draw这三大流程。其中performMeasure中会调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中则会对所有子元素进行measure过程,这样就完成了一次measure过程;子元素会重复父容器的measure过程,如此反复完成了整个View数的遍历。

延伸——自定义View 的实现过程?(你自定义过哪种View ,具体怎么实现的)

例如 Listview 侧滑删除:(简单描述)

自定义View ,继承ViewGroup,item布局里面存放两个childView ,一个内容,一个 删除

重写 onMeasure()方法,测量 空间的宽高。

重写onLayout ()方法,放置View 的位置,内容View ,放满屏幕,删除View ,放到内容View 的右侧(超出屏幕)

重写onTouchEvent() 方法,根据手势,scrollBy(int x,int y)进行移动

scrollTo(x,y) 是移动到(x,y)坐标处,scrollBy(x,y)  内部调用 scrollTo(mScrollX + x, mScrollY + y),是在移动的的基础上在进行移动。

 

延伸——自定义View ,invalidate() 、postInvalidate() 、requestLayout()

实现view的更新有两组方法

invalidate() 在UI线程中调用,只会调用draw() 方法

postInvalidate() 可以在UI线程中和子线程中调用

requestLayout() 只调用measure()和layout()过程

延伸——onMeasure 中的相关方法

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
}

Size为具体的值,而Mode就是我们说的三种模式:UNSPECIFIED,EXACTLY和AT_MOST。

UNSPECIFIED
不限定,父View不限制子View的具体的大小,所以子View可以按自己需求设置宽高(ScrollView就给子View设置了这个模式,ListView就会自己确认自己高度)。
EXACTLY
父View决定子View的确切大小,子View被限定在给定的边界里,忽略本身想要的大小。
AT_MOST
最多的,子View最大可以达到的指定大小(当设置为wrap_content时,模式为AT_MOST, 表示子view的大小最多是多少

自定义View为 match_parent,则该模式由父控件来决定。父控件大小是具体的值,则为EXACTLY,父控件为 wrap_content 则为AT_MOST

8、ListView 和RecycleView的区别?

1、 RecyclerView 就能支持 线性布局网格布局瀑布流布局 三种(这里我们暂且不提代码细节,后文再说),而且同时还能够控制横向还是纵向滚动

2、在ListView中通常刷新数据是用notifyDataSetChanged() ,但是这种刷新数据是全局刷新的(每个item的数据都会重新加载一遍),这样一来就会非常消耗资源;

RecyclerView中可以实现局部刷新,例如:notifyItemChanged();

3、

延伸——为什么Llistview 没有被RecycleView 替代?

回收机制不同 Listview采用RecycleBin回收机制

4、RecycleView 性能优化:

数据优化:

数据处理和数据显示分离

更新视图,局部刷新

分页拉取数据,并进行数据缓存,提升加载速度

布局优化:

减少层级(不推荐在 RecyclerView 中使用 ConstraintLayout)

简化 itemview,减少view 的创建

其他:

设置 RecyclerView.addOnScrollListener(listener); 来对滑动过程中停止加载的操作。

如果不要求动画,可以通过 ((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(false); 把默认动画关闭来提升效率。

 

9、Binder机制

Binder是Android系统进程间通信(IPC)方式之一

什么是Binder ?

1. 直观来说,Binder是Android中的一个类,它继承了IBinder接口 
2. 从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在linux中没有 
3. 从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,etc)和相应ManagerService的桥梁 
4. 从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当你bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务

为什么使用Binder?

主要有两个方面的原因:

性能方面 

Binder相对出传统的Socket方式,更加高效。Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次。虽然共享内存方式一次内存拷贝都不需要,但实现方式又比较复杂。

安全方面 

传统的进程通信方式对于通信双方的身份并没有做出严格的验证,比如Socket通信ip地址是客户端手动填入,很容易进行伪造,而Binder机制从协议本身就支持对通信双方做身份校检,因而大大提升了安全性。

Binder通信机制:

Binder基于Client-Server通信模式,Binder的通信模型有4个角色:Binder Client、Binder Server、Binder Driver(Binder驱动)、ServiceManager。ServiceManager、Binder Client、Binder Server处于不同的进程,他们三个都在用户空间,而Binder驱动在内核空间。

Server中有个add() 方法

1. Server进程向ServiceManager注册,生成映射关系表。

2. Client进程向ServiceManager查询,我要调用Server进程的computer对象的add方法。当向ServiceManager查询完毕,Binder驱动将computer对象转换成了computerProxy对象,并转发给了Client进程,Client进程拿到是一个代理对象,即computerProxy对象。这个computerProxy对象也是有add方法(空方法),但是这个add方法只是对参数进行一些包装而已。

3. 当Client进程调用add方法,这个消息发送给Binder驱动,这时驱动发现,原来是computerProxy,这时驱动通知Server进程,调用你的computer对象的add方法,将结果给我。然后Server进程就将计算结果发送给驱动,驱动再转发给Client进程,Client拿到了计算结果。

延伸——AIDL 实现

https://blog.csdn.net/lijia1201900857/article/details/76060544

 

10、apk的release和debug版本的区别

 Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。

Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用

11、线程和进程的区别

1、进程是操作系统资源分配的基本单位

线程是任务调度和执行的基本单位

2、每个进程都有自己的数据段 代码段 和堆栈段。线程 通常叫做 轻型的进程。它包含独立的栈和CPU寄存状态,每个线程共享其所附属进程的所有资源,包含打开的文件,内存页面,信号标识及分配内存等

3、线程比进程花费更小的CPU资源

4、线程和进程的关系是 线程属于进程,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间

12、线程池相关

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                    BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory)
corePoolSize: 核心线程的数量。

maximumPoolSize: 最大线程数量。

keepAliveTime: 非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,
则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,
则该参数也表示核心线程的超时时长。

unit: keepAliveTime这个参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等。

workQueue: 线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。
存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。

threadFactory: 为线程池提供创建新线程的功能,这个我们一般使用默认即可。

handler: 拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到
最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。

ThreadPoolExecutor有两个方法可以供我们执行,分别是submit()和execute()

execute 方法无返回结果。

submit 内部还是调用了 execute,有返回值,返回一个Future对象。Future不仅仅可以获得一个结果,还可以被取消,我们可以通过调用future的cancel()方法,取消一个Future的执行。

常用线程池:

FixedThreadPool

所有的线程都是核心线程。

SingleThreadExecutor

核心线程数只有1

CachedThreadPool:

没有核心线程的,但是它的最大线程数却为Integer.MAX_VALUE,超时时间为60秒

ScheduledThreadPool:

核心线程数量是固定的,但是非核心线程无穷大。当非核心线程闲置时,则会被立即回收。

 

线程池执行策略

1、当提交一个新任务到线程池时首先线程池判断核心线程池(corePoolSize)是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程;其次线程池判断工作队列(workQueue)是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程;最后线程池判断整个线程池(maximumPoolSize)是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务;如果线程池中的线程数量大于 corePoolSize 时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过 keepAliveTime,线程也会被终止。

 

13、Handler 机制? 一个线程能否创建多个Handler?

什么是Handler?

handler 是Android 提供的更新UI的机制,也是一套消息处理机制。

1,Message对象,表示要传递的一个消息,内部使用链表数据结构实现一个消息池,用于重复利用,
避免大量创建消息对象,造成内存浪费

2,MessageQueue对象,存放消息对象的消息队列,先进先出原则

3,Looper对象负责管理当前线程的消息队列

4,handler对象负责把消息push到消息队列中,以及接收Looper从消息队列中取出的消息

延迟——ActivityThread (主线程)

ActivityThread 是程序的入口,里面有一个main 方法,方法中创建了UI线程的Looper对象,looer 的创建过程中,

会创建MessageQueen 对象。Handler发消息就是发送到当前线程中的MessageQueen 对象中、

延伸——怎么保证发送的消息,是发送到指定线程的MessageQueen 对象中的?

通过ThreadLocal 实现的,ThreadLocal 保存的变量和这个线程相关,其他线程只能存取自己线程中的

ThradLocal 保存的变量

延伸——一个线程能否创建多个Handler?

可以创建多个Handler ,但是 多个handler通用一个messageQuene,并且只有一个Looper ,通过 msg.target 来标记 发送的消息 只有发送消息的handler才能响应

延伸——子线程能不能更新UI?有没有特例?

一般来说,子线程是不能更新UI的,因为ViewRootImpl中会 调用checkThread检查是否是主线程,否则报异常。

但是如果在onCreate() 方法中是可以的,因为 执行onCreate方法的那个时候ViewRootImpl还没创建。 它的创建是在 onResume()方法中进行的。

延伸——handler 内存泄露解决?

原因:Activity 销毁的时候,handler 有定时任务,或者后台任务还在执行,导致Hanlder 不能回收,而handler 持有Activiity 

的引用,导致activity也不能回收,造成内存泄露

解决:1 逻辑解决,关闭activity的时候,关闭任务,调用 removeCallback 移除任务

2、把handler 声明成静态,不持有activity的引用,可以正常回收。因为是静态,所以没法调用activity的方法,

可以采用弱引用的方式(WeakReference);

延伸——Looper为什么要无限循环?

ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的应用也就退出了。

延伸——Looper.loop 无限循环为什么不会造成ANR?

https://blog.csdn.net/DJH2717/article/details/82632512

通过源码我们知道,阻塞就是通过 Looper.loop() 来实现的, 我们看看源码:

    public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        ......
        for (;;) {

            Message msg = queue.next(); // might block
        ......
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
          ......
            msg.recycleUnchecked();
        }
    }

当系统收到来自因用户操作而产生的通知时, 会通过 Binder 方式跨进程的通知我们的 application 进程中的 ApplicationThread , 然后 ApplicationThread 又通过 Handler 机制往主线程的 messageQueue 中插入消息, 从而让主线程的  Message msg = queue.next() 这句代码获得一条 message ,然后通过 msg.target.dispatchMessage(msg) 来处理消息,从而实现了整个 Android 程序能够响应用户交互和回调生命周期方法, 让整个 APP 活了起来.

    而至于为什么当主线程处于死循环的 Message msg = queue.next() 这句会阻塞线程的代码的时候不会产生 ANR 异常, 那是因为此时 messageQueue 中并没有消息, 因此主线程处于休眠状态,无需占用 cpu 资源, 而当 messageQueue 中有消息时, 系统会唤醒主线程,来处理这条消息.

那么我们在主线程中耗时为什么会造成 ANR 异常呢? 

    那是因为我们在主线程中进行耗时的操作是属于在这个死循环的执行过程中, 如果我们进行耗时操作, 可能会导致这条消息还未处理完成,后面有接受到了很多条消息的堆积,从而导致了 ANR 异常.

延伸——HanderThread:

       HandlerThread继承自Thread,当线程开启时,也就是它run方法运行起来后,线程同时创建了一个含有消息队列的Looper,并对外提供自己这个Looper对象的get方法。创建完成后通过handler发送消息,就会在子线程中执行。如果想让HandlerThread退出,则需要调用handlerThread.quit();

好处:

1.开发中如果多次使用类似new Thread(){...}.start()这种方式开启一个子线程,会创建多个匿名线程,使得程序运行起来越来越慢,而HandlerThread自带Looper使他可以通过消息来多次重复使用当前线程,节省开支;

2.android系统提供的Handler类内部的Looper默认绑定的是UI线程的消息队列,对于非UI线程又想使用消息机制,那么HandlerThread内部的Looper是最合适的,它不会干扰或阻塞UI线程。

延伸——IntentService

IntentService 继承自Service,是一个抽象类

IntentService有以下特点:

(1)  它创建了一个独立的工作线程来处理所有的通过onStartCommand()传递给服务的intents。

(2)  创建了一个工作队列,来逐个发送intent给onHandleIntent()。

(3)  不需要主动调用stopSelft()来结束服务。因为,在所有的intent被处理完后,系统会自动关闭服务。

(4)  默认实现的onBind()返回null

(5)  默认实现的onStartCommand()的目的是将intent插入到工作队列中

继承IntentService的类至少要实现两个函数:构造函数和onHandleIntent()函数

14、AsyncTask相关

AsyncTask 内部实现原理?

AsyncTask是对Handler和Thread的封装,使用它编码更简洁,效率更高。

有多个AsyncTask任务同时开始调用。但是执行的时候是串行的。

核心线程数   
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
最大线程数 
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;

 AsyncTask用的是线程池机制,线程容量是128,最大线程数为CPU*2+1,超过后,剩下的任务排队

AsyncTask对象必须在UI线程创建

execute方法必须在UI线程调用

一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行时异常

但是从3.0开始,为了避免AsyncTask所带来的并发错误,AsyncTask又采用一个线程来串行执行任务

如果想并行执行,需要.executeOnExecutor()

相关方法:

1. onPreExecute()
这个方法会在后台任务开始执行之间调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。


2. doInBackground(Params...)
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress...)方法来完成。


3. onProgressUpdate(Progress...)
当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。


4. onPostExecute(Result)
当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。

 

15、Activity的生命周期相关

oncreate():Activity 创建调用的方法

onStart():布局可见,但不可交互

onResume():布局可见,可以交互.可以初始化一些资源

onPause(): Activity 可见不可交互

onStop():内存不够,会被回收掉

onReStart() :Activity  不可见到可见

onDestroy() :Activity 被销毁,资源回收释放

延伸—横竖屏切换的时候,Activity 各种情况下的生命周期?

切换横竖屏时,会自动查找layout-port 、layout-land中的布局文件,默认情况下,

如果不指定 android:configChanges ,切换时,将执行摧毁onPause onStop onDestroy,再重置加载新的布局onCreate onStart onResume,

横屏一次,竖屏执行两次

如果 指定 android:configChanges =“orientation ” 横竖屏切换,只会执行一次生命周期

如果指定 android:configChanges="orientation|keyboardHidden|screenSize" ,不知再次执行生命周期,会走onConfigChange 方法

延伸—Activity按Home键时的生命周期?

按下—> onPause()  onStop();

再次点开—> onRestart() onStart() onResume();

延伸—Activity上有Dialog的时候按Home键时的生命周期?

同上,Dailog对Activity 的声明周期没有影响

 

16、性能优化 

UI 卡顿造成的原因?

1、人为在UI线程中做轻微耗时操作,导致UI线程卡顿;

2、布局Layout过于复杂,无法在16ms内完成渲染;

3、同一时间动画执行的次数过多,导致CPU或GPU负载过重;

4、View过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或GPU负载过重;

5、View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染;

6、 内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作;

7、冗余资源及逻辑等导致加载和执行缓慢;

8、臭名昭著的ANR;

解决方法:

android studio 3.0Android Profiler 内存分析

运行Linit,去除冗余资源

17、你是如何解决Android的布局嵌套问题的(布局优化)?

(android studio Tools—Android—Layout Inspector 查看布局层级结构)

RelativeLayout和LinearLayout对比:

RelativeLayout要两次测量(measure)它的子View才能知道确切的高度;如果LinearLayout布局的子View有设置了layout_weight,那么它也需要测量两次才能获得布局的高度。

.RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。

1、布局中使用ViewStub、include,merge

ViewStub:

ViewStub标签引入的布局默认不会inflate,既不会显示也不会占用位置。

各种不常用的布局比如进度条、显示错误消息等可以使用<ViewStub />标签,以减少内存使用量

include:

include主要解决的是相同布局的复用问题

merge: 

一般配合include 使用

merge用于消除视图层次结构中的冗余视图,例如根布局是Linearlayout,那么我们又include一个LinerLayout布局就没意义了,反而会减慢UI加载速度。

又或者根布局是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity的ContentView父元素就是FrameLayout,所以可以用merge消除只剩一个。

merge使用注意:

     1、merge必须放在布局文件的根节点上。

     2、merge并不是一个ViewGroup,也不是一个View,它相当于声明了一些视图,等待被添加。

     3、因为merge不是View,所以对merge标签设置的所有属性都是无效的。

2、使用ConstraintLayout即约束布局:

控件之间根据依赖关系而存在,但比RelativeLayout更加灵活

 

18、内存泄漏:

导致原因:

当一个对象不需要被使用,本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。

延伸——检测内存泄漏工具:

android studio 自带Android Profiler

LeakCanary 一款轻量级的第三方内存泄漏检测工具

常见的内存泄漏及解决方法:

1、单例造成的内存泄漏

2、非静态内部类创建静态实例造成的内存泄漏

3、Handler造成的内存泄漏

4、资源未关闭造成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。

 

19、HttpURLConnection熟练掌握(遇到过让手写一个请求,以及如何实现文件上传)

 // Get 请求
 public String httpGet(String urlStr) {
        StringBuilder builder = null;
        BufferedReader reader = null;
  HttpURLConnection connection = null;
        try {
            URL url = new URL(urlStr);
            connection = (HttpURLConnection) url.openConnection();
            connection.setConnectTimeout(10000);//设置链接超时
            connection.setReadTimeout(5000);//设置读写超时
            connection.setRequestMethod("GET");// 设置求取方式
            connection.connect();//链接
            if (connection.getResponseCode() == 200) {//读取成功
                builder = new StringBuilder();
                reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String line;
                while ((line = reader.readLine()) != null) {
                    builder.append(line);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (reader != null)
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
        return builder.toString();
    }    
// 文件上传

    /***
     * 文件上传
     * @param urlStr 访问路径
     * @param filePath 文件路径
     */
    public void postFile(String urlStr,String filePath) throws Exception {
            File file = new File("H:/Users/chengtingyu/Desktop/test/list.txt");
            URL url=new URL(urlStr);
            HttpURLConnection connection= (HttpURLConnection) url.openConnection();
           // 必须设置这两项
            connection.setDoInput(true); // 设置是否从httpUrlConnection读入
            connection.setDoOutput(true);// 设置是否向httpUrlConnection输出
            connection.setUseCaches(false); // Post 请求不能使用缓存
            connection.setRequestMethod("POST");// 设定请求的方法,默认是GET
            connection.setRequestProperty("Connection", "Keep-Alive");  // 设置字符编码连接参数
            // 设置字符编码
            connection.setRequestProperty("Charset", "UTF-8");
            // 设置访问参数等
        //  connection.setRequestProperty();
            connection.connect();
            OutputStream outputStream=connection.getOutputStream();
            DataInputStream in = new DataInputStream(new FileInputStream(file));
            int length;
            byte[] buffer = new byte[1024];
            while ((length = in.read(buffer)) != -1) {
                outputStream.write(buffer, 0, length);
            }
            outputStream.flush();
            outputStream.close();
            in.close();
            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                // 返回结果
                System.out.println("---line---"+line);
            }
            reader.close();
    }

20、Volley可以传输大数据吗?为什么?

不适合,Volley的网络请求线程池默认大小为4。意味着可以并发进行4个请求,大于4个,会排在队列中volley中。为了提高请求处理的速度,采用了ByteArrayPool进行内存中的数据存储的,如果下载大量的数据,这个存储空间就会溢出,所以不适合大量的数据,但是由于他的这个存储空间是内存中分配的,当存储的时候优先从ByteArrayPool中取出一块已经分配的内存区域, 不必每次存数据都要进行内存分配,而是先查找缓冲池中有无适合的内存区域,如果有,直接拿来用,从而减少内存分配的次数 ,所以他比较适合大量的数据量少的网络数据交互情况。

优缺点:Volley默认是不支持HTTPS

21、OkHttp 底层实现,与其他区别。

优点:

  1. 支持HTTP2/SPDY(SPDY是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。)
  2. socket自动选择最好路线,并支持自动重连,拥有自动维护的socket连接池,减少握手次数,减少了请求延迟,共享Socket,减少对服务器的请求次数。
  3. 基于Headers的缓存策略减少重复的网络请求。
  4. 拥有Interceptors轻松处理请求与响应(自动处理GZip压缩)

异步调用:

OkHttpClient client = new OkHttpClient();//1
    Request request = new Request.Builder()
            .url("http://xxxxxx")
            .build();//2
    client.newCall(request).enqueue(new Callback() {//3
        @Override
        public void onFailure(Call call, IOException e) {
        }
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            if(response.isSuccessful()){//回调的方法执行在子线程。
                
            }
        }
    });

 

okhttp拦截器主要在以下几种情况使用:

分为应用程序拦截器(addInterceptor())和网络拦截器(addNetworkInterceptor()

  1. 网络请求、响应日志输出

  2. 在Header中统一添加cookie、token

  3. 设置网络缓存

OkHttpClient mOkHttpClient = new OkHttpClient().newBuilder()
        .connectTimeout(REQUEST_TIME, TimeUnit.SECONDS)
        .readTimeout(REQUEST_TIME, TimeUnit.SECONDS)
        .writeTimeout(REQUEST_TIME, TimeUnit.SECONDS)
        .addInterceptor(new LoggerInterceptor())
        .build();

延伸— 项目中是怎样封装的?

https://blog.csdn.net/lijia1201900857/article/details/79270524

        我项目使用的是 代理模式对框架进行的封装。基本实现如下: 创建一个代理接口,接口中 添加get put delete 等网络实现接口。添加一个代理类,相当于一个工具类。 然后可以添加多个委托对象,例如 OkHttp、Volley等,实现代理接口,并实现接口中的相关方法。。。

22、Glide源码解析

https://blog.csdn.net/pgg_cold/article/details/79418480

Glide.with(this).load(url).into(imageView);

with():多个构造方法

   public static RequestManager with(Context context) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(context);
    }

  public RequestManager get(Context context) {
        if (context == null) {
            throw new IllegalArgumentException("You cannot start a load on a null Context");
        } else if (Util.isOnMainThread() && !(context instanceof Application)) {
            if (context instanceof FragmentActivity) {
                return get((FragmentActivity) context);
            } else if (context instanceof Activity) {
                return get((Activity) context);
            } else if (context instanceof ContextWrapper) {
                return get(((ContextWrapper) context).getBaseContext());
            }
        }
        return getApplicationManager(context);
    }

传入Application参数的情况:如果在Glide.with()方法中传入的是一个Application对象,那么这里就会调用带有Context参数的get()方法重载,然后调用getApplicationManager()方法来获取一个RequestManager对象。它自动就是和应用程序的生命周期是同步的,如果应用程序关闭的话,Glide的加载也会同时终止。

传入非Application参数的情况:不管传入的是Activity、FragmentActivity、v4包下的Fragment、还是app包下的Fragment,最终会向当前的Activity当中添加一个隐藏的Fragment。目的是 Glide需要知道加载的生命周期。可是Glide并没有办法知道Activity的生命周期,Fragment的生命周期和Activity是同步的,如果Activity被销毁了,Fragment是可以监听到的。

load():

延伸——Glide 采用什么缓存策略?如何控制内存缓存的大小?

在Glide中磁盘缓存默认使用的是LRU(Least Recently Used)算法

设置缓存 需要在 AndroidManifest.xml 设置module

  <meta-data
      android:name=".Cache"
      android:value="GlideModule" />
 
   public class Cache implements GlideModule{

        @Override
        public void applyOptions(Context context, GlideBuilder builder) {
            //  设置 内存 缓存大小
            builder.setMemoryCache(new LruResourceCache(100));
            //  设置磁盘缓存路径,缓存大小
            builder.setDiskCache(new InternalCacheDiskCacheFactory(context,"路径",100));
        }
        @Override
        public void registerComponents(Context context, Glide glide) {
        }
    }
 

延伸——为什么选Glide?

ImageLoader:

比较老的框架, 稳定, 加载速度适中, 缺点在于不支持GIF图片加载, 使用稍微繁琐, 并且缓存机制没有和http的缓
存很好的结合, 完全是自己的一套缓存机制(完整大小).
Picasso:

使用方便, 一行代码完成加载图片并显示, 框架体积小,
缺点在于不支持 GIF, 并且它可能是想让服务器去处理图片的缩放, 它缓存的图片是未缩放的, 并且默认使用
ARGB_8888 格式缓存图片, 缓存体积大.
Glide:

可以说是 Picasso 的升级版, 有 Picasso 的优点, 并且支持 GIF 图片加载显示, 图片缓存也会自动缩放, 默认使用
RGB_565 格式缓存图片, 是 Picasso 缓存体积的一半.

23、EventBus源码解析

getDefault():这里就是采用双重校验并加锁的单例模式生成EventBus实例。

EventBus.getDefault().register(this);
    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }
// 找到所有集合,并进行订阅
 List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }
//  ignoreGeneratedIndex 默认false
        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }


private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
         //获取订阅者信息,没有配置MyEventBusIndex返回null
            findState.subscriberInfo = getSubscriberInfo(findState);
            if (findState.subscriberInfo != null) {
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
                //通过反射来查找订阅方法
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

register()订阅:

首先得到订阅者的包名.类名,然后使用 subscriberMethodFinder 的 findSubscriberMethods 方法,遍历查找订阅者中所有的订阅方法。

findSubscriberMethods找出一个SubscriberMethod的集合(.首先从缓存中查找,如果找到了就立马返回。如果缓存中没有的话,则 通过 反射 finduserInfo  中的  findUsingReflectionInSingleClass 找到订阅方法。将订阅方法保存到findState中,最后再通过getMethodsAndRelease方法对findState做回收处理并反回订阅方法的List集合)。

 public void post(Object event) {
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);

        if (!postingState.isPosting) {
            postingState.isMainThread = isMainThread();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                while (!eventQueue.isEmpty()) {
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

    public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);
        }
        // Should be posted after it is putted, in case the subscriber wants to remove immediately
        post(event);
    }

post();

首先从PostingThreadState对象中取出事件队列,然后再将当前的事件插入到事件队列当中。最后将队列中的事件依次交由postSingleEvent方法进行处理,并移除该事件。

 

24、Butterknife源码解析:

ButterKnife 工作流程

当你编译你的Android工程时,ButterKnife工程中ButterKnifeProcessor类的process()方法会执行以下操作: 

开始它会扫描Java代码中所有的ButterKnife注解@Bind、@OnClick、@OnItemClicked等 

当它发现一个类中含有任何一个注解时,ButterKnifeProcessor会帮你生成一个Java类,名字类似$$ViewBinder,这个新生成的类实现了ViewBinder接口。这个ViewBinder类中包含了所有对应的代码,比如@Bind注解对应findViewById(), @OnClick对应了view.setOnClickListener()等等。最后当Activity启动ButterKnife.bind(this)执行时,ButterKnife会去加载对应的ViewBinder类调用它们的bind()方法。

ButterKnife.bind 执行阶段

最后,执行bind方法时,我们会调用ButterKnife.bind(this): 

ButterKnife会调用findViewBinderForClass(targetClass)加载ExampleActivity$$ViewBinder.java类。然后调用ViewBinder的bind方法,动态注入ExampleActivity类中所有的View属性。 

如果Activity中有@OnClick注解的方法,ButterKnife会在ViewBinder类中给View设置onClickListener,并且将@OnClick注解的方法传入其中。

在上面的过程中可以看到,为什么你用@Bind、@OnClick等注解标注的属性或方法必须是public或protected的,因为ButterKnife是通过ExampleActivity.this.editText来注入View的。因为如果你把View设置成private,那么框架必须通过反射来注入View,不管现在手机的CPU处理器变得多快,如果有些操作会影响性能,那么是肯定要避免的,这就是ButterKnife与其他注入框架的不同。

 

25、热修复原理

Android系统中有两个类加载器分别为PathClassLoader和DexclassLoader。

PathClassLoader和DexClassLoader都是继承与BaseDexClassLoader,

DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk;

 PathClassLoader只能加载系统中已经安装过的apk。应用路径下的目录(/data/data/app目录);

通过源码可以发现,在DexClassLoader构造方法中初始化了DexPathList,然后DexPathList的构造方法是将dex文件或者包含dex文件的压缩文件路径转化为一个个Element对象,然后保存到elements数组中。整体就是循环遍历dexElements,然后通过取出element的dexFile,并通过loadClassBinaryName方法返回所需要的类,不为空就直接返回,到这里,就终于看到了返回的类,这里也是热修复的关键:循环遍历含有dex路径的element数组,如果能从这个element中返回类,则直接返回,循环终止!

原理:app的类加载器通过DexPathList循环遍历elements数组,并从遍历的Element对象中查找所需要的类,如果找到则直接返回。注意,从上面的源码分析来看,这里面的每个Element相当于是一个dex文件,那么假如我们的项目有个class有了bug,我们如果将修复好的class文件编译成dex文件,然后放到elements数组的前面,那么就会直接返回修复好的class,后面含有bug的class所在的element则不会遍历到,达到修复的目的

BaseDexClassLoader 构造方法
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
        String librarySearchPath, ClassLoader parent) {
    super(parent);
    this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}

DexPathList 构造方法
public DexPathList(ClassLoader definingContext, String dexPath,
        String librarySearchPath, File optimizedDirectory) {
//省略部分代码
//赋值类加载器
    this.definingContext = definingContext;
    // 将dex文件或压缩包中的信息保存到dexElements中
    this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext);
   //省略部分代码
}

public Class findClass(String name, List<Throwable> suppressed) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;
 
        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;

26、插件化开发:

为什么引入插件化?

  • 模块解耦,应用程序扩展性强
  • 解除单个dex函数不能超过 65535的限制
  • 动态升级,下载更新节省流量

插件化开发原理?


你项目中使用过吗?

项目中使用过插件化 换肤

 

27、Retofit 原理

简单使用:

接口地址:
http://api.map.baidu.com/telematics/v3/weather?location=广州&output=JSON&ak=FK9mkfdQsloEngodbFl4FeY3

定义一个接口:
public interface ApiService {
    @GET("/telematics/v3/weather")
    Call<POWeather> getWeather (@Query("location") String location, @Query("output") String ouput, @Query("ak") String ak);
}

创建Retrofit实例
Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://api.map.baidu.com/")
            //增加返回值为Gson的支持(以实体类返回)
            .addConverterFactory(ScalarsConverterFactory.create())
            //增加返回值为Gson的支持(以实体类返回)
            .addConverterFactory(GsonConverterFactory.create())
            .build();
访问接口
ApiService service = retrofit.create(ApiService.class);
Call<POWeather> weather = service.getWeather("广州","JSON","FK9mkfdQsloEngodbFl4FeY3");
weather.enqueue(new Callback<POWeather>() {
    @Override
    public void onResponse(Call<POWeather> call, Response<POWeather> response) {
        Log.e("test",response.body().getMessage());
    }
    @Override
    public void onFailure(Call<POWeather> call, Throwable t) {

    }

原理:

1. 通过建造者模式构建一个Retrofit实例,配置baseUrl,callAdapterFactory(将代理返回的Call对象转化为其他,比如Rxjava的Observer),converterFactory(结果的转化器,将请求的结果转化为特定的对象时使用,比如GsonConverterFactory)

2.通过Retrofit对象的create(Class<T> service)方法返回一个Service的动态代理对象,在调用service的方法的时候,就是调用动态代理的invoke方法,会将method进行解析,解析我们在接口里面配置的各种注解,最后构造成ServiceMethod对象,并将结果缓存起来,下次再次调用就不用解析了。ServiceMethod对象可以生成Request对象,所以将ServiceMethod对象注入到OkHttpCall,然后通过callAdapter转化为用户希望得到的返回对象,默认是直接返回Call对象。

3.返回Call对象之后,我们再调用execute或者enqueue方法,前者是同步请求,后者是异步请求,再方法里面会调用Okhttp的网络请求方法。

28、进程保活实现

 

 

29、加密算法了解

1、数字摘要加密:

MD5 SHA1  加密不可逆,只能加密不能解密

    //    使用md5方式进行加密
    public static String digest(String content) {
        StringBuilder builder = new StringBuilder();
        try {
            MessageDigest msgDitest = MessageDigest.getInstance("SHA1");
            MessageDigest msgDitest1 = MessageDigest.getInstance("MD5");
            msgDitest.update(content.getBytes());
            byte[] digests = msgDitest.digest();
            //将每个字节转为16进制
            for (int i = 0; i < digests.length; i++) {
                builder.append(Integer.toHexString(digests[i] & 0xff + 8));//+8为加盐操作
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return builder.toString();
    }

2、对称加密

DES RES:

加密(encryption)与解密(decryption)使用的是同样的密钥(secret key)

优点:是加密速度快,

缺点:是安全性低,因为只要密钥暴漏,数据就可以被解密。

3、非对称加密

RSA:

   非对称加密算法需要两个密钥来进行加密和解密,这两个秘钥是公钥和私钥。私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥。比如,你向银行请求公钥,银行将公钥发给你,你使用公钥对消息加密,那么只有私钥的持有人--银行才 能对你的消息解密。与对称加密不同的是,银行不需要将私钥通过网络发送出去,因此安全性大大提高。

优点:安全性更高,公钥是公开的,秘钥是自己保存的,不需要将私钥给别人。

缺点:加密和解密花费时间长、速度慢,只适合对少量数据进行加密

30、apk瘦身:

在Android Studio工具栏里,打开build–>Analyze APK, 选择要分析的APK包

1、使用一套图

2、开启minifyEnabled混淆代码

在gradle使用shrinkResources去除无用资源,效果非常好。

3.去除多余资源

Andorid Studio Lint

4. 移除无用的库、避免功能雷同的库

5、对图片进行压缩 使用webp格式的图片(4.2以后才支持)

对于本地图片,下载webp 转换工具 输入命令进行转换

 

延伸——apk目录中有什么?

APK文件结构

1. META-INF\ 该目录存放的是签名相关的信息

2. lib/如果该目录存在,一般存放的是NDK编译出来的so

3. res\(存放资源文件的目录)

4.assets  资源文件

5. AndroidManifest.xml(程序全局配置文件)

6. classes.dex(Dalvik字节码)

7. resources.arsc(编译后的二进制资源文件)

 

31、常见设计模式,以及使用

单例设计模式:

观察者模式:

代理模式:

建造者模式:

public class PeopleBulide {
        String name;
        int age;
        //如果使用传统的,需要建造多个构造方法
        public PeopleBulide(Bulider bulider) {
            this.age = bulider.age;
            this.name = bulider.name;
        }

        public static class Bulider {
            String name;
            int age;
            public Bulider() {
                this.age = 10;
                this.name = "小明";
            }
            public Bulider setName(String name) {
                this.name = name;
                return this;
            }
            public Bulider setAge(int age) {
                this.age = age;
                return this;
            }
            public PeopleBulide bulid() {
                return new PeopleBulide(this);
            }
        }
    }

 

32、MVP 实现,MVC MVP 比较

MVC:

MVP:

区别:

Activity职责不同:Activity在MVP中是View层,在MVC中是Controller层,这是MVC和MVP很主要的一个区别,可以说Android从MVC转向MVP开发也主要是优化Activity的代码,避免Activity的代码臃肿庞大。

View层不同:MVC的View层指的是XML布局文件或者是用Java自定义的View,MVP的View层是Activity或者Fragment

控制层不同:MVC的控制层是Activity,或者是Fragment,Activity所在的Controller任务是非常重的,各层次之间的耦合情况也比较严重,不方便单元测试。MVP的控制层是Presenter,里面没有很多的实际东西,主要是做Model和View层的交互。

MVP 的代码简单实现: MainActivirty 中显示

33、数据库相关

延伸——数据库升级,添加语句字段:

升级的时候调用方法
@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        增加一个字段
	    String sql = "Alter table 表名 add column 字段名 TEXT ";
        删除一个字段
        String sql = "Alter table 表名 drop column 字段名 TEXT ";
	    db.execSQL(sql);
	}

延伸——数据库升级修改字段?

目前还不支持直接修改字段类型:

思路:将现有的表重命名,然后创建一个 新表,名称和原表名一致,将数据插入到新表中,删除新表。

 

升级的时候调用方法
@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
         数据库重命名
	    String sql1 = "alter table 原表名 rename to 新表名";
         创建数据库(新需求)
        String sql2 =" create table 原表名(name txt ,age integer)"    
        把原有数据复制到 原表名 中
        String sql3 = "insert into select * from 新表名";
        删除新表 
        String sql4 = "drop table 新表名";
	    db.execSQL(sql1);
        db.execSQL(sql2);
        db.execSQL(sql3);
        db.execSQL(sql4);
	}

34、上传异常信息:

public class CrashHandler implements Thread.UncaughtExceptionHandler { 
    //系统默认的UncaughtException处理类
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    private Context mContext;

    public void init(Context context) {
        mContext = context;
        //获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        //设置该CrashHandler为程序的默认处理器
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
     * 当UncaughtException发生时会转入该函数来处理
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {

     处理相关信息       
    }
}

35、消息推送原理:

常见的解决方案:

  1)轮询(Pull)方式:应用程序应当阶段性的与服务器进行连接并查询是否有新的消息到达,你必须自己实现与服务器之间的通信,例如消息排队等。而且你还要考虑轮询的频率,如果太慢可能导致某些消息的延迟,如果太快,则会大量消耗网络带宽和电池。 
   
  但对于即时通讯产品来说, 这种方案完全不能用. 假设即时通讯软件在网络畅通的情况下发送的消息要求对方10s内就能收到, 如果用轮询, 那么客户端要每隔5s连一次服务器, 如果在移动端, 手机的电量和流量很快就会被消耗殆尽.

  2)SMS(Push)方式:在Android平台上,你可以通过拦截SMS消息并且解析消息内容来了解服务器的意图,并获取其显示内容进行处理。这个方案的好处是,可以实现完全的实时操作。但是问题是这个方案的成本相对比较高,我们需要向运营商缴纳相应的费用。

  3)长连接(Push)方式:应用程序和服务器保持一个长连接,服务器的消息可以直接通过这个链接push到应用程序。这个方案可以解决由轮询带来的性能问题,但是还是会消耗手机的电池。

延伸——怎么维持心跳?

 

 


服务端:

1、如何判断服务端是否支持断点续传?

通常情况下,Web服务器(如Apache)会默认开启对断点续传的支持。

一般使用命令行判断,若能够找到 Content-Range,则表明服务器支持断点续传。有些服务器还会返回 Accept-Ranges,输出结果 Accept-Ranges: bytes ,说明服务器支持按字节下载。

 

2、http和https的区别

HTTP:超文本传输协议 (HTTP-Hypertext transfer protocol),是互联网上应用最为广泛的一种网络协议,是一种详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议。用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。http协议属于明文传输协议,交互过程以及数据传输都没有进行加密,通信双方也没有进行任何认证,通信过程非常容易遭遇劫持、监听、篡改,严重情况下,会造成恶意的流量劫持等问题,甚至造成个人隐私泄露(比如银行卡卡号和密码泄露)等严重的安全问题。

HTTPS是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。现在它被广泛用于万维网上安全敏感的通讯,例如交易支付方面。

HTTPS协议可以分为两种:一是通过建立一个信息安全通道,来保证数据传输的安全;二是通过确认网站的真实性。

区别:

(1)HTTP 是超文本传输协议,属于明文传输协议,HTTPS 则是具有安全性的基于ssl加密的传输协议

(2)HTTP 和 HTTPS 使用的是连接方式不同,而且用的端口也不一样,前者是80,后者是443。

(3)HTTP 是简单的无状态的连接。HTTPS 协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 要比 HTTP 协议安全

(4)HTTPS 内容经过对称加密,每个连接生成一个唯一的加密密钥(对称秘钥:对称密钥加密又叫专用密钥加密,即发送和接收数据的双方必使用相同的密钥对明文进行加密和解密运算。)

(5)HTTPS 内容传输经过完整性校验

2、Cookie和Session的区别

1、Cookie和Session都是会话技术,Cookie是运行在客户端,Session是运行在服务器端。

2、Cookie有大小限制以及浏览器在存cookie的个数也有限制,Session是没有大小限制和服务器的内存大小有关。

3、Cookie有安全隐患,通过拦截或本地文件找得到你的cookie后可以进行攻击。

4、Session是保存在服务器端上会存在一段时间才会消失,如果session过多会增加服务器的压力。 

5、session是基于Cookie技术实现,重启浏览器后再次访问原有的连接依然会创建一个新的session, 
因为Cookie在关闭浏览器后就会消失,但是原来服务器的Session还在,只有等到了销毁的时间会自动销毁
 

3、TCP和UDP的区别?

1、tcp是基于连接的,可靠性高;udp是基于无连接的,可靠性较低;

2、由于tcp需要有三次握手、重新确认等连接过程,实时性差;同时过程复杂,也使其易于被攻击;而udp无连接,因而实时性较强,也稍安全;

3、在传输相同大小的数据时,tcp首部开销20字节;udp首部开销只有8个字节,tcp报头比udp复杂,故实际包含的用户数据较少。tcp无丢包,而udp有丢包,故tcp开销大,udp开销较小;

4、每条tcp连接只能是点到点的;udp支持一对一、一对多、多对一、多对多的交互通信。

所以对于应用这方面:

如果对实时性要求高和高速传输的场合下需要使用udp;如果需要传输大量数据且对可靠性要求高的情况下应该使用tcp;在可靠性要求较低,追求效率的情况下应该使用udp

4、TCP/IP在连接时有几次握手?释放时有几次握手? 

链接3次握手,释放4次握手

位码即tcp标志位,有6种标示:

SYN(synchronous建立联机)

ACK(acknowledgement 确认)

PSH(push传送)

FIN(finish结束)

RST(reset重置)

URG(urgent紧急)

Sequence number(顺序号码)

Acknowledge number(确认号码)

三次 握手:

(1)第一次:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。

(2)第二次:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack (number )=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。

(3)第三次:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。

所谓四次挥手(Four-Way Wavehand)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开

(1)第一次挥手:Client发送一个FIN(序列号 seq =u),用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。

(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号ack = u+1,Server进入CLOSE_WAIT状态。

(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。

(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值