# Android-Interview **Repository Path**: liulei0911/Android-Interview ## Basic Information - **Project Name**: Android-Interview - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2020-10-21 - **Last Updated**: 2021-05-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 1.Activity的四大启动模式,以及应用场景 ``` standard:标准模式,每次都会在活动栈中生成一个新的Activity实例。通常我们使用的活动都是标准模式。 singleTop:栈顶复用,如果Activity实例已经存在栈顶,那么就不会在活动栈中创建新的实例。比较常见的场景就是给通知跳转的Activity设置,因为你肯定不想前台Activity已经是该Activity的情况下,点击通知,又给你再创建一个同样的Activity。 singleTask:栈内复用,如果Activity实例在当前栈中已经存在,就会将当前Activity实例上面的其他Activity实例都移除栈。常见于跳转到主界面。 singleInstance:单实例模式,创建一个新的任务栈,这个活动实例独自处在这个活动栈中。 ``` 2.Handler机制 ``` MessageQueue:具有时间优先级的消息队列。 Looper:轮询消息队列,看是否有新的消息到来。 Handler:具体处理逻辑的地方。 附加: looper是个死循环,为什么不会导致主线程卡死? 1.因为消息等待阻塞机制主要是nativePollOnce去实现的,如果没有消息时会进入等待休眠状态,不会消耗CPU,采用了linux的IO多路复用 的机制(epoll) 2.当消息达到的时候会wake Looper来唤醒继续工作 epoll机制: 可以同时监控多个描述符,当某个描述符就绪,则立即通知相应的程序进行读或写操作,同步I/O,即读写是阻塞的。 ``` 3.View事件分发机制和View绘制原理 ``` ViewGroup 在接收到 ACTION_DOWN 事件时,其 dispatchTouchEvent 方法内部会先调用 onInterceptTouchEvent 判断是否要进行拦截, 如果 onInterceptTouchEvent 方法返回了 false,则意味着其不打算拦截该事件,那么就会继续调用 child 的 dispatchTouchEvent 方法,继续重复以上步骤。 如果拦截了,那么就会调用 onTouchEvent 进行消费 对于 View 来说,其不包含 onInterceptTouchEvent 方法,dispatchTouchEvent 方法会调用其 onTouchEvent 方法来决定是否消费该事件。 如果返回 false,则意味着其不打算消费该事件,事件将依次调用父容器的 onTouchEvent 方法;返回 true 的话则意味着事件被其消费了,事件终止传递 ``` 4.Binder机制 ``` 从IPC角度来说,Binder是Android中的一种跨进程通信方式;Binder还可以理解为虚拟的物理设备,它的设备驱动是/dev/binder;从Android Framework来讲, Binder是Service Manager连接各种Manager和对应的ManagerService的桥梁。从面向对象和CS模型来讲,Client通过Binder和远程的Server进行通讯。 基于Binder,Android还实现了其他的IPC方式,比如AIDL、Messenger和ContentProvider. 与其他IPC比较: 效率高:除了内存共享外,其他IPC都需要进行两次数据拷贝,而因为Binder使用内存映射的关系,仅需要一次数据拷贝。 安全性好:接收方可以从数据包中获取发送发的进程Id和用户Id,方便验证发送方的身份,其他IPC想要实验只能够主动存入,但是这有可能在发送的过程中被修改。 Binder通信的实质是利用内存映射,将用户进程的内存地址和内核的内存地址映射为同一块物理地址,也就是说他们使用的同一块物理空间,每次创建Binder的时候大概分配128的空间。 数据进行传输的时候,从这个内存空间分配一点,用完了再释放即可。 ``` 5.Bitmap优化 6.App的启动过程 ``` 用户点击App图标,Lanuacher进程通过Binder联系到System Server进程发起startActivity。 System Server通过Socket联系到Zygote,fork出一个新的App进程。 创建出一个新的App进程以后,Zygote启动App进程的ActivityThread#main()方法。 在ActivtiyThread中,调用AMS进行ApplicationThread的绑定。 AMS发送创建Application的消息给ApplicationThread,进而转交给ActivityThread中的H,它是一个Handler,接着进行Application的创建工作。 AMS以同样的方式创建Activity,接着就是大家熟悉的创建Activity的工作了。 ``` 7.Window,Activity,DecorView关系 ``` Activity不负责控制视图,只是控制生命周期和处理事件,真正控制视图的是Window,Window 视图的承载器,内部持有DecorView,而DecorView是View的根布局,Window是一个抽象类,真正的实现类是PhoneWindow,PhoneWindow有个内部类DecorView, 通过其来加载R.layout.activity_main。Window通过WindowManager将DecorView加载其中,并将DecorView交给ViewRoot,进行视图的绘制及其他交互. DecorView是FrameLayout的子类,是android的根视图,相当于顶级View,一般来说内部包含竖直方向LinearLayout,在linearlayout中含有三部分,上面是ViewStub,延迟加载的视图,中间是标题栏,下面是内容栏 ``` 8.Glide,LruCache,LinkedHashMap ``` LinkedHashMap 1,LinkedHashMap 是一个双向的链表。 2,LinkedHashMap 还有一个特有的变量 accessOrder,他是布尔型变量,true 表示链表按访问顺序,false 表示按插入顺序。 3,当 accessOrder的值为true,那么get后会把这个Node放在链表的末尾,这就是利用LinkedHashMap实现LRUCache的核心思想。 4,利用 LinkedHashMap 实现LRUCache 必须同时满足 accessOrder = true 且重写 removeEldestEntry 方法。 -LinkedHashMap LruCache Glide使用LruCache和弱引用结合的方式做缓存个人理解优势为: 1,分担lrucache的压力。减少trimToSize的概率。如果正在remove的是张大图,lrucache正好处在临界点,此时remove操作,将延缓lrucache的trimToSize操作。 2 ,提高效率:activeResource用的是hashmap,lrucache用的是linkedhashmap,从访问效率而言,肯定是hashmap高不少。hashmap起一个辅助作用。并不是保护图片不被回收。 3,其实弱引用的一个核心就是系统内存资源不足时,释放内存, 而lrucash是核心是使用的内存没有超过自己的设定的最大值,就会一直缓存,但是不能感知系统内存到底够不够,所以当系统内存不够使用的时候就引起oom,而弱引用的引入恰好解决了这个问题 ``` 9.Java内存模型 ``` 程序计数器: 程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。 由于 Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器内核都只会执行一条线程中的指令。 Java虚拟机栈: 与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。 虚拟机栈描述的是 Java 方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame,是方法运行时的基础数据结构)用于存储局部变量表、操作数栈、 动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 本地方法栈: 本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈 则为虚拟机使用到的 Native 方法服务。Sun HotSpot 虚拟机直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出 StackOverflowError 和 OutOfMemoryError 异常。 Java堆: 对于大多数应用来说,Java 堆(Java Heap)是 Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。 此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 方法区: 方法区(Method Area)与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。 ``` 10.HashMap的特点是什么?HashMap的原理? ``` HashMap的特点: 基于Map接口,存放键值对。 允许key/value为空。 非多线程安全。 不保证有序,也不保证使用的过程中顺序不会改变。 简单来讲,核心是数组+链表/红黑树,HashMap的原理就是存键值对的时候: 通过键的Hash值确定数组的位置。 找到以后,如果该位置无节点,直接存放。 该位置有节点即位置发生冲突,遍历该节点以及后续的节点,比较key值,相等则覆盖。 没有就新增节点,默认使用链表,相连节点数超过8的时候,在jdk 1.8中会变成红黑树。 如果Hashmap中的数组使用情况超过一定比例,就会扩容,默认扩容两倍 ``` 11.说一下对泛型的理解 ``` 泛型的本质是参数化类型,在不创建新的类型的情况下,通过泛型指定不同的类型来控制形参具体限制的类型。也就是说在泛型的使用中,操作的数据类型被指定为一个参数, 这种参数可以被用在类、接口和方法中,分别被称为泛型类、泛型接口和泛型方法。 泛型是Java中的一种语法糖,能够在代码编写的时候起到类型检测的作用,但是虚拟机是不支持这些语法的。 泛型的优点: 类型安全,避免类型的强转。 提高了代码的可读性,不必要等到运行的时候才去强制转换。 ``` 12.什么是类型擦除? ``` 不管泛型的类型传入哪一种类型实参,对于Java来说,都会被当成同一类处理,在内存中也只占用一块空间。通俗一点来说,就是泛型只作用于代码编译阶段,在编译过程中, 对于正确检验泛型结果后,会将泛型的信息擦除,也就是说,成功编译过后的class文件是不包含任何泛型信息的。 ``` 13.线程池中的几个参数是什么意思,线程池的种类有哪些? ``` corePoolSize:核心线程数量,不会释放。 maximumPoolSize :允许使用的最大线程池数量,非核心线程数量,闲置时会释放。 keepAliveTime :闲置线程允许的最大闲置时间。 unit :闲置时间的单位。 workQueue :阻塞队列,不同的阻塞队列有不同的特性。 线程池分为四个类型: CachedThreadPool:闲置线程超时会释放,没有闲置线程的情况下,每次都会创建新的线程。 FixedThreadPool:线程池只能存放指定数量的线程池,线程不会释放,可重复利用。 SingleThreadExecutor:单线程的线程池。 ScheduledThreadPool:可定时和重复执行的线程池。 ``` 14.synchronized的原理? ``` 任何一个对象都有一个monitor与之相关联,JVM基于进入和退出mointor对象来实现代码块同步和方法同步,两者实现细节不同: 代码块同步:在编译字节码的时候,代码块起始的地方插入monitorenter 指令,异常和代码块结束处插入monitorexit指令,线程在执行monitorenter指令的时候尝试获取monitor对象的所有权,获取不到的情况下就是阻塞 方法同步:synchronized方法在method_info结构有AAC_synchronized标记,线程在执行的时候获取对应的锁,从而实现同步方法 ``` 15.volatile的原理? ``` 可见性 如果对声明了volatile的变量进行写操作的时候,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写入到系统内存。 多处理器的环境下,其他处理器的缓存还是旧的,为了保证各个处理器一致,会通过嗅探在总线上传播的数据来检测自己的数据是否过期,如果过期,会强制重新将系统内存的数据读取到处理器缓存。 有序性 Lock前缀的指令相当于一个内存栅栏,它确保指令排序的时候,不会把后面的指令拍到内存栅栏的前面,也不会把前面的指令排到内存栅栏的后面。 ``` 16.Jvm内存区域是如何划分的? ``` 内存区域划分: 程序计数器:当前线程的字节码执行位置的指示器,线程私有。 Java虚拟机栈:描述的Java方法执行的内存模型,每个方法在执行的同时会创建一个栈帧,存储着局部变量、操作数栈、动态链接和方法出口等,线程私有。 本地方法栈:本地方法执行的内存模型,线程私有。 Java堆:所有对象实例分配的区域。 方法区:所有已经被虚拟机加载的类的信息、常量、静态变量和即时编辑器编译后的代码数据。 ``` 17.如何判断对象可回收? ``` 判断一个对象可以回收通常采用的算法是引用几算法和可达性算法。由于互相引用导致的计数不好判断,Java采用的可达性算法。 可达性算法的思路是:通过一些列被成为GC Roots的对象作为起始点,自上往下从这些起点往下搜索,搜索所有走过的路径称为引用链, 如果一个对象没有跟任何引用链相关联的时候,则证明该对象不可用,所以这些对象就会被判定为可以回收。 可以被当作GC Roots的对象包括: Java虚拟机栈中的引用的对象 方法区中静态属性引用的对象 方法区中常量引用的对象 本地方法中JNI引用的对象 ``` 18.GC的常用算法? ``` 标记 - 清除:首先标记出需要回收的对象,标记完成后统一回收所有被标记的对象。容易产生碎片空间。 复制算法:它将可用的内存分为两块,每次只用其中的一块,当需要内存回收的时候,将存活的对象复制到另一块内存,然后将当前已经使用的内存一次性回收掉。需要浪费一半的内存。 标记 - 整理:让存活的对象向一端移动,之后清除边界外的内存。 分代搜集:根据对象存活的周期,Java堆会被分为新生代和老年代,根据不同年代的特性,选择合适的GC收集算法。 ``` 19.HTTP 1.1 和HTTP 2有什么区别? ``` HTTP 2.0基于HTTP 1.1,与HTTP 2.0增加了: 二进制格式:HTTP 1.1使用纯文本进行通信,HTTP 2.0使用二进制进行传输。 Head压缩:对已经发送的Header使用键值建立索引表,相同的Header使用索引表示。 服务器推送:服务器可以进行主动推送 多路复用:一个TCP连接可以划分成多个流,每个流都会分配Id,客户端可以借助流和服务端建立全双工进行通信,并且流具有优先级。 ``` 20.HTTPS传输过程中是如何处理进行加密的?为什么有对称加密的情况下仍然需要进行非对称加密? ``` 过程是依次获取证书,公钥,最后生成对称加密的钥匙进行对称加密。 对称加密可以保证加密效率,但是不能解决密钥传输问题;非对称加密可以解决传输问题,但是效率不高。 ``` Https加密过程: ``` client->请求url到服务器,服务器->返回证书公钥,client验证证书是否有效,如果有效生成一个随机数,用证书的公钥加密随机数, 将加密后的秘钥发给Server,Server用私钥解密秘钥,用秘钥加密要发送的内容,Server将加密后的内容发送给Client,Client用秘钥解密信息 ``` 21.TCP三次握手、四次挥手过程,具体介绍下TCP/IP ``` TCP/IP一般指的是TCP/IP协议簇,主要包括了多个不同网络间实现信息传输涉及到的各种协议 主要包括以下几层: 应用层:主要提供数据和服务。比如HTTP,FTP,DNS等 传输层:负责数据的组装,分块。比如TCP,UDP等 网络层:负责告诉通信的目的地,比如IP等 数据链路层:负责连接网络的硬件部分,比如以太网,WIFI等 ``` 22.TCP和UDP有什么区别? ``` TCP:基于字节流、面向连接、可靠、能够进行全双工通信,除此以外,还能进行流量控制和拥塞控制,不过效率略低 UDP:基于报文、面向无连接、不可靠,但是传输效率高。 ``` 23.设计模式的六大原则? ``` 单一职责:合理分配类和函数的职责 开闭原则:开放扩展,关闭修改 里式替换:继承 依赖倒置:面向接口 接口隔离:控制接口的粒度 迪米特:一个类应该对其他的类了解最少 ``` 24.Android view的双缓冲机制 ``` 顾名思义,双缓冲意味着会有两个缓冲层,缓冲的出现就是为了提高view的刷新速度。 ``` 25.Android打包流程 ``` 1. 打包资源文件,生成R.java文件 打包资源的工具是aapt,在这个过程中,项目中的AndroidManifest.xml文件和布局文件XML都会编译,然后生成相应的R.java,另外AndroidManifest.xml会被aapt编译成二进制。 2. 处理aidl文件,生成相应的Java文件 3. 编译项目源代码,生成class文件 4. 转换所有的class文件,生成classes.dex文件 dx工具生成可供Android系统Dalvik虚拟机执行的classes.dex文件 5. 打包生成APK文件 6. 对APK文件进行签名 一旦APK文件生成,它必须被签名才能被安装在设备上。 7. 对签名后的APK文件进行对齐处理 如果你发布的apk是正式版的话,就必须对APK进行对齐处理,用到的工具是zipalign 对齐的主要过程是将APK包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快。对齐的作用就是减少运行时内存的使用。 ``` 26.ListView和RecycleView的区别 ``` 一、 缓存机制对比 1. 层级不同: RecyclerView比ListView多两级缓存,支持多个离ItemView缓存,支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用同一个RecyclerViewPool(缓存池)。 ListView(两级缓存): mActiviViews 和mScrapViews两种 RecycleView(四级缓存):mActiviViews ,mScrapViews,mViewCacheExtension,mRecyclerPools四种 ListView和RecyclerView缓存机制基本一致: 1). mActiveViews和mAttachedScrap功能相似,意义在于快速重用屏幕上可见的列表项ItemView,而不需要重新createView和bindView; 2). mScrapView和mCachedViews + mReyclerViewPool功能相似,意义在于缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用. 3). RecyclerView的优势在于a.mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用;b.mRecyclerPool可以供多个RecyclerView共同使用, 在特定场景下,如viewpaper+多个列表页下有优势.客观来说,RecyclerView在特定场景下对ListView的缓存机制做了补强和完善。 2. 缓存不同: 1). RecyclerView缓存RecyclerView.ViewHolder,抽象可理解为: View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态); RecyclerView中mCacheViews(屏幕外)获取缓存时,是通过匹配pos获取目标位置的缓存,这样做的好处是,当数据源数据不变的情况下,无须重新bindView: 2). ListView缓存View。而同样是离屏缓存,ListView从mScrapViews根据pos获取相应的缓存,但是并没有直接使用,而是重新getView(即必定会重新bindView)。 二、 局部刷新 RecyclerView更大的亮点在于提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的bindView。ListView和RecyclerView最大的区别在于数据源改变时的缓存的处理逻辑, ListView是"一锅端",将所有的mActiveViews都移入了二级缓存mScrapViews,而RecyclerView则是更加灵活地对每个View修改标志位,区分是否重新bindView。 ``` 27.Android子线程通信 ``` 我们可以把looper绑定到子线程中,调用Looper.prepare()方法为该子线程生成looper,生成后调用Looper.loop()启动消息队列,并且在该子线程中创建一个handler。 在另一个子线程中调用该handler发送消息。这样就可以实现子线程间的通信了。 private Handler handler; //子线程一 class ThreadA implements Runnable{ private Handler mHandler; //run运行后才不为null在main里判断 public Handler getHandler(){ return mHandler; } @SuppressLint("HandlerLeak") @Override public void run() { Looper.prepare(); mHandler=new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ case 1: Log.e("线程A","线程B发过来消息了--"+msg.obj); break; } } }; Looper.loop(); } } //子线程二 class ThreadB implements Runnable{ @Override public void run() { Message mess=Message.obtain(); mess.what=1; mess.obj= "线程B"+System.currentTimeMillis(); handler.sendMessage(mess); } }  在activity onCreate方法里 ThreadA threadA = new ThreadA(); ThreadB threadb = new ThreadB(); new Thread(threadA).start(); if(threadA.getHandler() == null) { try { Thread.sleep(1000); handler = threadA.getHandler(); } catch (InterruptedException e) { e.printStackTrace(); } } ``` 28.热更新原理 Android热更新技术是以ClassLoader类加载为基础的, ,一个类加载时会先从DexPathList对象中的dexElements数组中获取,如果一个类能够被成功加载,那么它的dex一定会出现在dexElements所对应的dex文件中。 同时,由于采用的是数组遍历的方式,所以dexElements中dex出现的顺序也非常重要,在dexElements前面出现的dex会被优先加载,一旦Class被加载成功, 就会立即返回。 也就是说,我们如果想实现热更新,就一定要保证我们的热更新dex文件出现在原先dexElements数组之前。 到此为止,那么我们的目标就很明确了,就是要在运行时去修改PathClassLoader.pathList.dexElements,具体实现步骤如下: 通过构造一个DexClassLoader对象来加载我们的热更新dex文件; 通过反射获取系统默认的PathClassLoader.pathList.dexElements; 将我们的热更新dex与系统默认的Elements数组合并,同时保证热更新dex在系统默认Elements数组之前; 将合并完成后的数组设置回PathClassLoader.pathList.dexElements。 29.死锁触发的四大条件以及如何解决、避免 ``` 触发的条件: 若线程A获得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也调用lock试图获得锁1, 结果是需要挂起等待线程A释放锁1,于是线程A和B都永远处于挂起状态了 解决死锁的方法: 互斥,共享资源 X 和 Y 只能被一个线程占用; 占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源Y人时候,不释放共享资源X; 不可抢占,其他线程不能强行抢占线程T1占有的资源; 循环等待,线程T1等待线程T2占有的资源,线程T2等待线程T1占有的资源,就是循环等待。 ``` 30.synchronized的参数放入对象和Class有什么区别? ``` 锁住的对象不同:成员方法锁住的实例对象,静态方法锁住的是Class。 访问控制不同:如果锁住的是实例,只会针对同一个对象方法进行同步访问,多线程访问同一个对象的synchronized代码块是串行的,访问不同对象是并行的。 如果锁住的是类,多线程访问的不管是同一对象还是不同对象的synchronized代码块是都是串行的 ``` 31.V1和V2签名的区别 ``` v1签名是对jar进行签名,V2签名是对整个apk签名 官方介绍就是:v2签名是在整个APK文件的二进制内容上计算和验证的,v1是在归档文件中解压缩文件内容。 二者签名所产生的结果: v1:在v1中只对未压缩的文件内容进行了验证,所以在APK签名之后可以进行很多修改——文件可以移动,甚至可以重新压缩。即可以对签名后的文件在进行处理 v2:v2签名验证了归档中的所有字节,而不是单独的ZIP条目,如果您在构建过程中有任何定制任务,包括篡改或处理APK文件,请确保禁用它们,否则您可能会使v2签名失效, 从而使您的APKs与Android 7.0和以上版本不兼容。 ```