共计 36452 个字符,预计需要花费 92 分钟才能阅读完成。
前言
耿直 2020 金九银十,第一次换工作或是面试候选人,咱们都会对面试常识做一次总结梳理,毕竟开发技术无边界,不同人对技术的解读不同。文章总结了最近大半年中的面试考查点 V1.0,心愿对你有所帮忙。
注:整顿中有反复的知识点,阐明频率较高,同时也是有不同角度的答复,也同时帮你更全面的意识。
面试倡议:算法、根底是敲门砖,我的项目是试金石,良好的面试形象是加分项。
举荐:2020 年 中级 Android 面试总结
Android 面试指南 — 算法面试心得
Android 性能优化前因后果总结
Android 面试系列
快手,字节跳动,百度,美团 Offer 之旅(Android 面经分享)| 掘金技术征文
一、JAVA 根底
1.synchronized 的润饰对象
当 synchronized 用来润饰静态方法或者类时,将会使得这个类的所有对象都是共享一把类锁,导致线程阻塞,所以这种写法肯定要 躲避
无论 synchronized 关键字加在办法上还是对象上,如果它作用的对象是非动态的,则它获得的锁是对象;如果 synchronized 作用的对象是一个静态方法或一个类,则它获得的锁是对类,该类所有的对象同一把锁。
每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就能够运行它所管制的那段代码。
实现同步是要很大的零碎开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
2. try{}catch{}finally 中的执行程序
任何执行 try 或者 catch 中的 return 语句之前,都会先执行 finally 语句,如果 finally 存在的话。
如果 finally 中有 return 语句,那么程序就 return 了,所以 finally 中的 return 是肯定会被 return 的,
编译器把 finally 中的 return 实现为一个 warning。
上面是个测试程序
public class FinallyTest
{public static void main(String[] args) {System.out.println(test());;
}
static int test()
{
int x = 1;
try
{
x++;
return x;
}
finally
{++x;}
}
}
运行后果:2
阐明:在 try 语句中,在执行 return 语句时,要返回的后果曾经筹备好了,就在此时,程序转到 finally 执行了。
在转去之前,try 中先把要返回的后果寄存到不同于 x 的局部变量中去,执行完 finally 之后,在从中取出返回后果,
因而,即便 finally 中对变量 x 进行了扭转,然而不会影响返回后果。
它应该应用栈保留返回值。
3. JAVA 中的死锁
-
JAVA 中的 ArrayList 是否是线程平安
-
为什么 ArrayList 线程不平安?不平安为什么要应用?如何解决线程不平安?
首先说一下什么是线程不平安:线程平安就是多线程拜访时,采纳了加锁机制,当一个线程拜访该类的某个数据时,进行爱护,其余线程不能进行拜访直到该线程读取完,其余线程才可应用。不会呈现数据不统一或者数据净化。线程不平安就是不提供数据拜访爱护,有可能呈现多个线程先后更改数据造成所失去的数据是脏数据。List 接口上面有两个实现,一个是 ArrayList,另外一个是 vector。
从源码的角度来看,因为 Vector 的办法前加了,synchronized 关键字,也就是同步的意思,sun 公司心愿 Vector 是线程平安的,而心愿 arraylist 是高效的,毛病就是另外的长处。
。说下原理:一个 ArrayList,在增加一个元素的时候,它可能会有两步来实现:
- 在 Items[Size] 的地位寄存此元素;
- 增大 Size 的值。
在单线程运行的状况下,如果 Size = 0,增加一个元素后,此元素在地位 0,而且 Size=1;
而如果是在多线程状况下,比方有两个线程,线程 A 先将元素寄存在地位 0。然而此时 CPU 调度线程 A 暂停,线程 B 失去运行的机会。线程 B 也向此 ArrayList 增加元素,因为此时 Size 依然等于 0(留神哦,咱们假如的是增加一个元素是要两个步骤哦,而线程 A 仅仅实现了步骤 1),所以线程 B 也将元素寄存在地位 0。而后线程 A 和线程 B 都持续运行,都减少 Size 的值。
那好,当初咱们来看看 ArrayList 的状况,元素实际上只有一个,寄存在地位 0,而 Size 却等于 2。这就是“线程不平安”了。
不平安为什么要应用?
这个 ArrayList 比线程平安的 Vector 效率高。
如何解决线程不平安
应用 synchronized 关键字,这个大家应该都很相熟了,不解释了;
二:应用 Collections.synchronizedList(); 应用办法如下:
如果你创立的代码如下:
List<Map<String,Object>>data=new ArrayList<Map<String,Object>>();
那么为了解决这个线程平安问题你能够这么应用 Collections.synchronizedList(),如:
List<Map<String,Object>> data=Collections.synchronizedList(newArrayList<Map<String,Object>>());
其余的都没变,应用的办法也简直与 ArrayList 一样,大家能够参考下 api 文档;
额定说下 ArrayList 与 LinkedList;这两个都是接口 List 下的一个实现,用法都一样,但用的场合的有点不同,ArrayList 适宜于进行大量的随机拜访的状况下应用,LinkedList 适宜在表中进行插入、删除时应用,二者都是非线程平安,解决办法同上(为了防止线程平安,以上采取的办法,特地是第二种,其实是十分损耗性能的)。
原文链接:https://blog.csdn.net/qq_2808…
-
JAVA 和 Vector 的区别
首先看这两类都实现 List 接口,而 List 接口一共有三个实现类,别离是 ArrayList、Vector 和 LinkedList。List 用于寄存多个元素,可能保护元素的秩序,并且容许元素的反复。3 个具体实现类的相干区别如下:
- ArrayList 是最罕用的 List 实现类,外部是通过数组实现的,它容许对元素进行疾速随机拜访。数组的毛病是每个元素之间不能有距离,当数组大小不满足时须要减少存储能力,就要讲曾经有数组的数据复制到新的存储空间中。当从 ArrayList 的两头地位插入或者删除元素时,须要对数组进行复制、挪动、代价比拟高。因而,它适宜随机查找和遍历,不适宜插入和删除。
- Vector 与 ArrayList 一样,也是通过数组实现的,不同的是它反对线程的同步,即某一时刻只有一个线程可能写 Vector,防止多线程同时写而引起的不一致性,但实现同步须要很高的破费,因而,拜访它比拜访 ArrayList 慢。
- LinkedList 是用链表构造存储数据的,很适宜数据的动静插入和删除,随机拜访和遍历速度比较慢。另外,他还提供了 List 接口中没有定义的办法,专门用于操作表头和表尾元素,能够当作堆栈、队列和双向队列应用。
- vector 是线程(Thread)同步(Synchronized)的,所以它也是线程平安的,而 Arraylist 是线程异步(ASynchronized)的,是不平安的。如果不思考到线程的平安因素,个别用 Arraylist 效率比拟高。
- 如果汇合中的元素的数目大于目前汇合数组的长度时,vector 增长率为目前数组长度的 100%, 而 arraylist 增长率为目前数组长度
的 50%. 如过在汇合中应用数据量比拟大的数据,用 vector 有肯定的劣势。 - 如果查找一个指定地位的数据,vector 和 arraylist 应用的工夫是雷同的,都是 0(1), 这个时候应用 vector 和 arraylist 都能够。而
如果挪动一个指定地位的数据破费的工夫为 0(n-i)n 为总长度,这个时候就应该思考到应用 Linkedlist, 因为它挪动一个指定地位的数据
所破费的工夫为 0(1), 而查问一个指定地位的数据时破费的工夫为 0(i)。
ArrayList 和 Vector 是采纳数组形式存储数据,此数组元素数大于理论存储的数据以便减少和插入元素,
都容许间接序号索引元素,然而插入数据要设计到数组元素挪动 等内存操作,所以索引数据快插入数据慢,
Vector 因为应用了 synchronized 办法(线程平安)所以性能上比 ArrayList 要差
,LinkedList 应用双向链表实现存储,按序号索引数据须要进行向前或向后遍历,然而插入数据时只须要记录本项的前后项即可,所以插入数度较快! -
抽象来说:LinkedList:增删改快
ArrayList:查问快(有索引的存在)
4.synchronized 和 volatile 关键字的区别
1.volatile 实质是在通知 jvm 以后变量在寄存器 (工作内存) 中的值是不确定的,须要从主存中读取;
synchronized 则是锁定以后变量,只有以后线程能够拜访该 变量,其余线程被阻塞住。
2.volatile 仅能应用在变量级别;synchronized 则能够应用在变量、办法、和类级 别的
3.volatile 仅能实现变量的批改可见性,不能保障原子性; 而 synchronized 则能够 保障变量的批改可见性和原子性
4.volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
5.volatile 标记的变量不会被编译器优化;synchronized 标记的变量能够被编译器优化
5.Java 中的主动装箱和主动拆箱
所以,当“==”运算符的两个操作数都是 包装器类型的援用,则是比拟指向的是否是同一个对象,而如果其中有一个操作数是表达式(即蕴含算术运算)则比拟的是数值(即会触发主动拆箱的过程)。
通过下面的剖析咱们须要晓得两点:
1、什么时候会引发装箱和拆箱
2、装箱操作会创建对象,频繁的装箱操作会耗费许多内存,影响性能,所以能够防止装箱的时候应该尽量避免。
https://zhidao.baidu.com/ques…
为什么咱们在 Java 中应用主动装箱和拆箱?
6.Java 中的乐观锁和乐观锁
- 乐观锁
总是假如最坏的状况,每次去拿数据的时候都认为他人会批改,所以每次在拿数据的时候都会上锁,这样他人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程应用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比方行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java 中 synchronized
和ReentrantLock
等独占锁就是乐观锁思维的实现。
- 乐观锁
总是假如最好的状况,每次去拿数据的时候都认为他人不会批改,所以不会上锁,然而在更新的时候会判断一下在此期间他人有没有去更新这个数据,能够应用版本号机制和 CAS 算法实现。乐观锁实用于多读的利用类型,这样能够进步吞吐量,像数据库提供的相似于 write_condition 机制,其实都是提供的乐观锁。在 Java 中 java.util.concurrent.atomic
包上面的原子变量类就是应用了乐观锁的一种实现形式 CAS 实现的。
- 两种锁的应用场景
从上面对两种锁的介绍,咱们晓得两种锁各有优缺点,不可认为一种好于另一种,像乐观锁实用于写比拟少的状况下(多读场景),即抵触真的很少产生的时候,这样能够省去了锁的开销,加大了零碎的整个吞吐量。但如果是多写的状况,个别会常常产生抵触,这就会导致下层利用会一直的进行 retry,这样反倒是升高了性能,所以个别多写的场景下用乐观锁就比拟适合。
[Java 多线程之乐观锁与乐观锁]
7. 抽象类和接口
如何了解与记忆:
1. 抽象类 –> 像 xxx 一样
接 口 –> 能 xxx 这样
2. 接口是设计的后果,抽象类是重构的后果。
抽象类和接口的详解(实例)
8.JAVA 内存模型
jmm 内存调配的概念:
- 堆 heap:长处:运行时数据区,动态分配内存大小,有 gc;, 毛病:因为要在运行时动态分配,所以存取速度慢,对象存储在堆上,动态类型的变量跟着类的定义一起存储在堆上。
栈 stack:存取速度快,仅次于寄存器,毛病:数据大小与生存期必须是确定的,不足灵活性,栈中次要寄存根本类型变量(比方,int,shot,byte,char,double,foalt,boolean 和对象句柄),jmm 要求,调用栈和本地变量寄存在线程栈上
当一个线程能够拜访一个对象时,也能够拜访对象的成员变量,如果有两个线程拜访对象的成员变量,则每个线程都有对象的成员变量的公有拷贝。
- 处理器(cpu):寄存器:每个 cpu 都蕴含一系列寄存器,他们是 cpu 的根底,寄存器执行的速度,远大于在主存上执行的速度
- cpu 高速缓存:因为处理器与内存访问速度差距十分大,所以增加了读写速度尽可能靠近处理器的高速缓存,来作为内存与处理器之间的缓冲,将数据读到缓存中,让运算疾速进行,当运算完结,再从缓存同步到主存中,就毋庸期待迟缓的内存读写了。处理器拜访缓存的速度快与拜访主存的速度,但比拜访外部寄存器的速度还是要慢点,每个 cpu 有一个 cpu 的缓存层,一个 cpu 含有多层缓存,,某一时刻,一个或者多个缓存行可能同时被读取到缓存取,也可能同时被刷新到主存中,同一时刻,可能存在多个操作,
- 内存:一个计算机蕴含一个主存,所有 cpu 都能够拜访主存,主存通常远大于 cpu 中的缓存,
运作原理:通常,当一个 cpu 须要读取主存时,他会将主存的内容读取到缓存中,将缓存中的内容读取到外部寄存器中,在寄存器中执行操作,当 cpu 须要将后果回写到主存中时,他会将外部寄存器的值刷新到缓存中,而后会在某个工夫点将值刷新回主存。 - ……
原文链接:https://blog.csdn.net/wangnan…
9.GC Roots 如何确定?哪些对象能够作为 GC Roots?
原文链接:https://blog.csdn.net/weixin_…
- 判断对象是否能够被回收之援用计数法:
Java 中,援用和对象是有关联的。如果要操作对象则必须用援用进行。
因而,很显然一个简略的方法是通过援用计数来判断一个对象是否能够回收。简略说,给对象中增加一个援用计数器,每当有一个中央援用它,计数器值加 1,每当有一个援用生效时,计数器值减 1。
任何时刻计数器值为零的对象就是不可能再被应用的,那么这个对象就是可回收对象。
那为什么支流的 Java 虚拟机外面都没有选用这种算法呢?其中最次要的起因是它很难解决对象之间互相循环援用的问题。
- 判断对象是否能够被回收之枚举根节点可达性剖析
为了解决援用计数法的循环援用问题,Java 应用了可达性剖析的办法。
所谓 ”GC roots,或者说 tracing GC 的“根汇合”就是一组必须沉闷的援用。
基本思路就是通过一系列名为”GCRoots”的对象作为起始点,从这个被称为 GC Roots 的对象开始向下搜寻,如果一个对象到 GCRoots 没有任何援用链相连时,则阐明此对象不可用。也即给定一个汇合的援用作为根登程,通过援用关系遍历对象图,能被遍历到的(可达到的)对象就被断定为存活,没有被遍历到的就天然被断定为死亡。 - Java 中能够作为 GC Roots 的对象:
- 虚拟机栈(栈帧中的本地变量表)中援用的对象
- 办法区中类动态属性援用的对象
- 办法区中常量援用的对象
- 本地办法栈中 JNI(即个别说的 native 办法)中援用的对象
抽象类和接口的区别
四种援用的区别
二、Android 方面
1. 热修复的原理
咱们晓得 Java 虚拟机 —— JVM 是加载类的 class 文件的,而 Android 虚拟机——Dalvik/ART VM 是加载类的 dex 文件,
而他们加载类的时候都须要 ClassLoader,ClassLoader 有一个子类 BaseDexClassLoader,
而 BaseDexClassLoader 下有一个数组——DexPathList,是用来寄存 dex 文件,当 BaseDexClassLoader 通过调用 findClass 办法时,实际上就是遍历数组,找到相应的 dex 文件,找到则间接将它 return。
而热修复的解决办法就是将新的 dex 增加到该汇合中,并且是在旧的 dex 的后面,
所以就会优先被取出来并且 return 返回。
2.Android 中跨过程通信的几种形式
Android 跨过程通信,像 intent,contentProvider, 播送,service 都能够跨过程通信。
intent:这种跨过程形式并不是拜访内存的模式,它须要传递一个 uri, 比如说打电话。
contentProvider:这种模式,是应用数据共享的模式进行数据共享。
service:近程服务,aidl
3.AIDL 了解
此处延长:简述 Binder
AIDL: 每一个过程都有本人的 Dalvik VM 实例,都有本人的一块独立的内存,都在本人的内存上存储本人的数据,执行着本人的操作,都在本人的那片狭小的空间里过完本人的毕生。而 aidl 就相似与两个过程之间的桥梁,使得两个过程之间能够进行数据的传输,跨过程通信有多种抉择,比方 BroadcastReceiver , Messenger 等,然而 BroadcastReceiver 占用的系统资源比拟多,如果是频繁的跨过程通信的话显然是不可取的;Messenger 进行跨过程通信时申请队列是同步进行的,无奈并发执行。
Binde 机制简略了解:
在 Android 零碎的 Binder 机制中,是有 Client,Service,ServiceManager,Binder 驱动程序组成的,其中 Client,service,Service Manager 运行在用户空间,Binder 驱动程序是运行在内核空间的。而 Binder 就是把这 4 种组件粘合在一块的粘合剂,其中外围的组件就是 Binder 驱动程序,Service Manager 提供辅助治理的性能,而 Client 和 Service 正是在 Binder 驱动程序和 Service Manager 提供的基础设施上实现 C /S 之间的通信。其中 Binder 驱动程序提供设施文件 /dev/binder 与用户控件进行交互,
Client、Service,Service Manager 通过 open 和 ioctl 文件操作相应的办法与 Binder 驱动程序进行通信。而 Client 和 Service 之间的过程间通信是通过 Binder 驱动程序间接实现的。而 Binder Manager 是一个守护过程,用来治理 Service,并向 Client 提供查问 Service 接口的能力。
4.Android 内存泄露及治理(深度延长上来)
(1)内存溢出(OOM)和内存泄露(对象无奈被回收)的区别。
(2)引起内存泄露的起因
(3) 内存泄露检测工具 ——>LeakCanary
内存溢出 out of memory:是指程序在申请内存时,没有足够的内存空间供其应用,呈现 out of memory;比方申请了一个 integer, 但给它存了 long 能力存下的数,那就是内存溢出。内存溢出艰深的讲就是内存不够用。
内存泄露 memory leak:是指程序在申请内存后,无奈开释已申请的内存空间,一次内存泄露危害能够疏忽,但内存泄露沉积结果很重大,无论多少内存, 迟早会被占光
内存泄露起因:
一、Handler 引起的内存透露。
解决:将 Handler 申明为动态外部类,就不会持有外部类 SecondActivity 的援用,其生命周期就和外部类无关,
如果 Handler 外面须要 context 的话,能够通过弱援用形式援用外部类
二、单例模式引起的内存透露。
解决:Context 是 ApplicationContext,因为 ApplicationContext 的生命周期是和 app 统一的,不会导致内存透露
三、非动态外部类创立动态实例引起的内存透露。
解决:把外部类批改为动态的就能够防止内存透露了
四、非动态匿名外部类引起的内存透露。
解决:将匿名外部类设置为动态的。
五、注册 / 反注册未成对应用引起的内存透露。
注册播送接受器、EventBus 等,记得解绑。
六、资源对象没有敞开引起的内存透露。
在这些资源不应用的时候,记得调用相应的相似 close()、destroy()、recycler()、release()等办法开释。
5.Java 虚拟机和 Dalvik 虚拟机的区别
Java 虚拟机:
1、java 虚拟机基于栈。基于栈的机器必须应用指令来载入和操作栈上数据,所需指令更多更多。
2、java 虚拟机运行的是 java 字节码。(java 类会被编译成一个或多个字节码.class 文件)
Dalvik 虚拟机:
1、dalvik 虚拟机是基于寄存器的
2、Dalvik 运行的是自定义的.dex 字节码格局。(java 类被编译成.class 文件后,会通过一个 dx 工具将所有的.class 文件转换成一个.dex 文件,而后 dalvik 虚构机会从其中读取指令和数据
3、常量池已被批改为只应用 32 位的索引,以 简化解释器。
4、一个利用,一个虚拟机实例,一个过程(所有 android 利用的线程都是对应一个 linux 线程,都运行在本人的沙盒中,不同的利用在不同的过程中运行。每个 android dalvik 应用程序都被赋予了一个独立的 linux PID(app_*))
6. 四种 LaunchMode 及其应用场景(联合书中上场景再总结)
standard 模式
这是默认模式,每次激活 Activity 时都会创立 Activity 实例,并放入工作栈中。应用场景:大多数 Activity。
singleTop 模式
如果在工作的栈顶正好存在该 Activity 的实例,就重用该实例(会调用实例的 onNewIntent() ),否则就会创立新的实例并放入栈顶,即便栈中曾经存在该 Activity 的实例,只有不在栈顶,都会创立新的实例。应用场景如新闻类或者浏览类 App 的内容页面。
singleTask 模式
如果在栈中曾经有该 Activity 的实例,就重用该实例(会调用实例的 onNewIntent() )。重用时,会让该实例回到栈顶,因而在它下面的实例将会被移出栈。如果栈中不存在该实例,将会创立新的实例放入栈中。应用场景如浏览器的主界面。不论从多少个利用启动浏览器,只会启动主界面一次,其余状况都会走 onNewIntent,并且会清空主界面下面的其余页面。
singleInstance 模式
在一个新栈中创立该 Activity 的实例,并让多个利用共享该栈中的该 Activity 实例。一旦该模式的 Activity 实例曾经存在于某个栈中,任何利用再激活该 Activity 时都会重用该栈中的实例(会调用实例的 onNewIntent() )。其成果相当于多个利用共享一个利用,不论谁激活该 Activity 都会进入同一个利用中。应用场景如闹铃揭示,将闹铃揭示与闹铃设置拆散。singleInstance 不要用于两头页面,如果用于两头页面,跳转会有问题,比方:A -> B (singleInstance) -> C,齐全退出后,在此启动,首先关上的是 B。
7. 启动模式(其余利用场景)
- standard 规范模式
- singleTop 栈顶复用模式(例如:推送点击音讯界面)
- singleTask 栈内复用模式(例如:首页)
- singleInstance 单例模式(独自位于一个工作栈中,例如:拨打电话界面)
8. 过程 IPC 过程通信形式
- Intent、Bundle:要求传递数据能被序列化,实现 Parcelable、Serializable,实用于四大组件通信。
- 文件共享:实用于替换简略的数据实时性不高的场景。
- AIDL:AIDL 接口本质上是零碎提供给咱们能够不便实现 BInder 的工具
- Messenger:基于 AIDL 实现,服务端串行解决,次要用于传递音讯,实用于低并发一对多通信
- ContentProvider:基于 Binder 实现,实用于一对多过程间数据共享(通讯录 短信 等)
- Socket:TCP、UDP,实用于网络数据交换
9. 为什么要用 Binder?(有什么劣势?)
- Android 应用的 Linux 内核 领有有着十分多的跨过程通信机制
- 性能
- 平安
10.View 工作流程
通过 SetContentView(),调用 到 PhoneWindow,后实例 DecorView,通过 LoadXmlResourceParser() 进行 IO 操作 解析 xml 文件 通过反射 创立出 View,并将 View 绘制在 DecorView 上,这里的绘制则交给了 ViewRootImpl 来实现,通过 performTraversals() 触发绘制流程,performMeasure 办法获取 View 的尺寸,performLayout 办法获取 View 的地位,而后通过 performDraw 办法遍历 View 进行绘制。
11. 事件散发
一个 MotionEvent 产生后,按 Activity -> Window -> DecorView(ViewGroup)-> View 程序传递,View 传递过程就是事件散发,因为开发过程中存在事件抵触,所以须要相熟流程:
- dispatchTouchEvent:用于散发事件,只有承受到点击事件就会被调用,返回后果示意是否耗费了以后事件
- onInterceptTouchEvent:用于判断是否拦挡事件(只有 ViewGroup 中存在),当 ViewGroup 确定要拦挡事件后,该事件序列都不会再触发调用此 ViewGroup 的 onIntercept
- onTouchEvent:用于处理事件,返回后果示意是否解决了以后事件,未解决则传递给父容器解决。(事件程序是:OnTouchListener -> OnTouchEvent -> OnClick)
12. Handler 机制整体流程;
IdHandler(闲时机制);
postDelay()的具体实现;
post()与 sendMessage()区别;
应用 Handler 须要留神什么问题,怎么解决的?
问题很细,能筹备多具体就筹备多具体。人家本人封装了一套 Handler 来防止内存透露问题
13.Looper.loop()为什么不会阻塞主线程;
https://www.jianshu.com/p/e14…
主线程 Looper 从音讯队列读取音讯,当读完所有音讯时,主线程阻塞。子线程往音讯队列发送音讯,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取音讯,当音讯读取结束,再次睡眠。因而loop 的循环并不会对 CPU 性能有过多的耗费。
主线程中如果没有 looper 进行循环,那么主线程一运行结束就会退出。那么咱们还能运行 APP 吗,显然,这是不可能的,Looper 次要就是做音讯循环,而后由 Handler 进行音讯散发解决,一旦退出音讯循环,那么你的利用也就退出了。
总结:Looper 的有限循环必不可少。
补充阐明:
我看有一部分人了解”Looper.loop()的阻塞“和”UI 线程上执行耗时操作卡死“的区别时还一脸懵逼的情况,简略答复一波:
- 首先这两之间一点分割都没有,齐全两码事。
- Looper 上的阻塞,前提是没有输出事件,MsgQ 为空,Looper 闲暇状态,线程进入阻塞,开释 CPU 执行权,期待唤醒。
- UI 耗时导致卡死,前提是要有输出事件,MsgQ 不为空,Looper 失常轮询,线程并没有阻塞,然而该事件执行工夫过长(5 秒?),而且与此期间其余的事件(按键按下,屏幕点击..)都没方法解决(卡死),而后就 ANR 异样了。
链接:https://www.zhihu.com/questio…
起源:知乎
14 . Android — Looper.prepare()和 Looper.loop() —深刻版
Android 中的 Looper 类,是用来封装音讯循环和音讯队列的一个类,用于在 android 线程中进行音讯解决。handler 其实能够看做是一个工具类,用来向音讯队列中插入音讯的。
(1) Looper 类用来为一个线程开启一个音讯循环。默认状况下 android 中新诞生的线程是没有开启音讯循环的。(主线程除外,主线程零碎会主动为其创立 Looper 对象,开启音讯循环。)Looper 对象通过 MessageQueue 来寄存音讯和事件。一个线程只能有一个 Looper,对应一个 MessageQueue。
(2) 通常是通过 Handler 对象来与 Looper 进行交互的。Handler 可看做是 Looper 的一个接口,用来向指定的 Looper 发送音讯及定义解决办法。默认状况下 Handler 会与其被定义时所在线程的 Looper 绑定,比方,Handler 在主线程中定义,那么它是与主线程的 Looper 绑定。mainHandler = new Handler() 等价于 new Handler(Looper.myLooper()). Looper.myLooper():获取以后过程的 looper 对象,相似的 Looper.getMainLooper() 用于获取主线程的 Looper 对象。
(3) 在非主线程中间接 new Handler() 会报如下的谬误:
E/AndroidRuntime(6173): Uncaught handler: thread Thread-8 exiting due to uncaught exception E/AndroidRuntime(6173): java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()
起因是非主线程中默认没有创立 Looper 对象,须要先调用 Looper.prepare()启用 Looper。
(4) Looper.loop();
让 Looper 开始工作,从音讯队列里取音讯,解决音讯。
留神:写在 Looper.loop()之后的代码不会被执行,这个函数外部应该是一个循环,当调用 mHandler.getLooper().quit()后,loop 才会停止,其后的代码能力得以运行。
(5) 基于以上常识,可实现主线程给子线程(非主线程)发送音讯。
15. 线程的切换又是怎么回事?
那么 线程的切换又是怎么回事 呢?
很多人搞不懂这个原理,然而其实非常简单,咱们将所波及的办法调用栈画进去,如下:
Thread.foo(){Looper.loop()
-> MessageQueue.next()
-> Message.target.dispatchMessage()
-> Handler.handleMessage()}
不言而喻,Handler.handleMessage() 所在的线程最终由调用 Looper.loop() 的线程所决定。
平时咱们用的时候从异步线程发送音讯到 Handler,这个 Handler 的 handleMessage()
办法是在主线程调用的,所以音讯就从异步线程切换到了主线程。
作者:Android 架构小麦
链接:https://juejin.im/post/5eca83…
16. Handler 是如何实现线程之间的切换的
Handler 是如何实现线程之间的切换的呢?例如当初有 A、B 两个线程,在 A 线程中有创立了 handler,而后在 B 线程中调用 handler 发送一个 message。
通过下面的剖析咱们能够晓得,当在 A 线程中创立 handler 的时候,同时创立了 MessageQueue 与 Looper,Looper 在 A 线程中调用 loop 进入一个有限的 for 循环从 MessageQueue 中取音讯,当 B 线程调用 handler 发送一个 message 的时候,会通过 msg.target.dispatchMessage(msg); 将 message 插入到 handler 对应的 MessageQueue 中,Looper 发现有 message 插入到 MessageQueue 中,便取出 message 执行相应的逻辑,因为 Looper.loop()是在 A 线程中启动的,所以则回到了 A 线程,达到了从 B 线程切换到 A 线程的目标。
小结:
1.Handler 初始化之前,Looper 必须初始化实现。UI 线程之所以不必初始化,因为在 ActivityThread 曾经初始化,其余子线程初始化 Handler 时,必须先调用 Looper.prepare()。
2. 通过 Handler 发送音讯时,音讯会回到 Handler 初始化的线程,而不肯定是主线程。
3. 应用 ThreadLocal 时,须要留神内存透露的问题。
艰深点的说法 Handler 机制其实就是借助共享变量来进行线程切换的.
Handler 是如何实现线程之间的切换的
妙用 Looper 机制
咱们能够利用 Looper 的机制来帮忙咱们做一些事件:
- 将 Runnable post 到主线程执行;
- 利用 Looper 判断以后线程是否是主线程。
残缺示例代码如下:
public final class MainThread {private MainThread() { }
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
public static void run(@NonNull Runnable runnable) {if (isMainThread()) {runnable.run();
}else{HANDLER.post(runnable);
}
}
public static boolean isMainThread() {return Looper.myLooper() == Looper.getMainLooper();}
}
可能省去不少样板代码。
作者:Android 架构小麦
链接:https://juejin.im/post/5eca83…
先 明确咱们的问题:
- Handler 是如何与线程关联的?
- Handler 收回去的音讯是谁治理的?
- 音讯又是怎么回到 handleMessage() 办法的?
- 线程的切换是怎么回事?
答复:Handler 发送的音讯由 MessageQueue 存储管理,并由 Loopler 负责回调音讯到 handleMessage()。
线程的转换由 Looper 实现,handleMessage() 所在线程由 Looper.loop() 调用者所在线程决定。
10.Android 为什么举荐应用 SparseArray 来代替 HashMap?
SparseArray 有两个长处:
1. 防止了主动装箱(auto-boxing)
2. 数据结构不会依赖于内部对象映射。咱们晓得 HashMap 采纳一种所谓的“Hash 算法”来决定每个元素的存储地位,寄存的都是数组元素的援用,通过每个对象的 hash 值来映射对象。而 SparseArray 则是用数组数据结构来保留映射,而后通过折半查找来找到对象。但其实一般来说,SparseArray 执行效率比 HashMap 要慢一点,因为查找须要折半查找,而增加删除则须要在数组中执行,而 HashMap 都是通过内部映射。但相对来说影响不大,最次要是 SparseArray 不须要开拓内存空间来额定存储内部映射,从而节俭内存。
https://blog.csdn.net/woshizi…
11.Glide 缓存机制
12.Binder 机制
13. 内存透露
查找内存透露能够应用 Android Studio 自带的 AndroidProfiler 工具或 MAT,也能够应用 Square 产品的 LeakCanary.
14. 类的初始化程序顺次是?
(动态变量、动态代码块)>(变量、代码块)> 构造方法
15.Retrofit 库的外围实现原理是什么? 如果让你实现这个库的某些外围性能,你会思考怎么去实现?
Retrofit 次要是在 create 办法中采纳 动静代理 模式 (通过拜访代理对象的形式来间接拜访指标对象) 实 现接口办法,这个过程构建了一个 ServiceMethod 对象,依据办法注解获取申请形式,参数类型和参数 注解拼接申请的链接,当所有都筹备好之后会把数据增加到 Retrofit 的 RequestBuilder 中。而后当咱们 被动发动网络申请的时候会调用 okhttp 发动网络申请,okhttp 的配置包含申请形式,URL 等在 Retrofit 的 RequestBuilder 的 build()办法中实现,并发动真正的网络申请。
你从这个库中学到什么有价值的或者说可借鉴的设计思维?
外部应用了优良的架构设计和大量的设计模式,在我剖析过 Retrofit 最新版的源码和大量优良的 Retrofit 源码剖析文章后,我发现,要想真正了解 Retrofit 外部的外围源码流程和设计思维,首先,须要对它使 用到的九大设计模式有肯定的理解,上面我简略说一说:
1、创立 Retrofit 实例: 应用建造者模式通过外部 Builder 类建设了一个 Retroift 实例。网络申请工厂应用了工厂办法模式。
2、创立网络申请接口的实例:
首先,应用外观模式对立调用创立网络申请接口实例和网络申请参数配置的办法。而后,应用动静代理动静地去创立网络申请接口实例。
接着,应用了建造者模式 & 单例模式创立了 serviceMethod 对象。
再者,应用了策略模式对 serviceMethod 对象进行网络申请参数配置,即通过解析网络申请接口方 法的参数、返回值和注解类型,从 Retrofit 对象中获取对应的网络的 url 地址、网络申请执行器、网 络申请适配器和数据转换器。
最初,应用了装璜者模式 ExecuteCallBack 为 serviceMethod 对象退出线程切换的操作,便于承受 数据后通过 Handler 从子线程切换到主线程从而对返回数据后果进行解决。
3、发送网络申请: 在异步申请时,通过动态 delegate 代理对网络申请接口的办法中的每个参数应用对应的
ParameterHanlder 进行解析。
4、解析数据
5、切换线程: 应用了适配器模式通过检测不同的 Platform 应用不同的回调执行器,而后应用回调执行器切换线
程,这里同样是应用了装璜模式。
6、处理结果
16.ARouter 路由原理:
ARouter 保护了一个路由表 Warehouse,其中保留着全副的模块跳转关系,ARouter 路由跳转实际上还 是调用了 startActivity 的跳转,应用了原生的 Framework 机制,只是通过 apt 注解的模式制作出跳转规 则,并人为地拦挡跳转和设置跳转条件。
17.Glide 中的动静代理
代理模式的介绍
18. 服务的两种启动形式和应用场景(两次都问到)
19. 内存抖动
Gc 引起卡顿 +OOM,怎么优化
Gson 反序列化导致产生大量对象
解决思考:对象池
20.LeakCanary 原理
它的根本工作原理如下:
RefWatcher.watch() 创立一个 KeyedWeakReference 到要被监控的对象。
而后在后盾线程查看援用是否被革除,如果没有,调用 GC。
如果援用还是未被革除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。
在另外一个过程中的 HeapAnalyzerService 有一个 HeapAnalyzer 应用 HAHA 解析这个文件。
得益于惟一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存透露。
HeapAnalyzer 计算 到 GC roots 的最短强援用门路,并确定是否是透露。如果是的话,建设导致透露的援用链。
援用链传递到 APP 过程中的 DisplayLeakService,并以告诉的模式展现进去。
总的来说,LeakCanary 有如下几个显著长处:
针对 Android Activity 组件齐全自动化的内存透露查看。
可定制一些行为(dump 文件和 leaktrace 对象的数量、自定义例外、剖析后果的自定义解决等)。
集成到本人工程并应用的老本很低。
敌对的界面展现和告诉。
原文链接:https://blog.csdn.net/import_…
21. 如何做内存优化
22.ArrayMap 和 HashMap 的区别
HashMap 和 ArrayMap 各自的劣势
1. 查找效率:
HashMap 因为其依据 hashcode 的值间接算出 index, 所以其查找效率是随着数组长度增大而减少的。
ArrayMap 应用的是二分法查找, 所以当数组长度每增加一倍时, 就须要多进行一次判断, 效率降落。
所以对于 Map 数量比拟大的状况下, 举荐应用
2. 扩容数量:
HashMap 初始值 16 个长度, 每次扩容的时候, 间接申请双倍的数组空间。
ArrayMap 每次扩容的时候, 如果 size 长度大于 8 时申请 size*1.5 个长度, 大于 4 小于 8 时申请 8 个, 小于 4 时申请 4 个。这样比拟 ArrayMap 其实是申请了更少的内存空间, 然而扩容的频率会更高。
因而, 如果当数据量比拟大的时候, 还是应用 HashMap 更适合, 因为其扩容的次数要比 ArrayMap 少很多。
3. 扩容效率:
HashMap 每次扩容的时候时从新计算每个数组成员的地位, 而后放到新的地位。
ArrayMap 则是间接应用 System.arraycopy。
所以效率上必定是 ArrayMap 更占优势。这里须要阐明一下, 网上有一种风闻说因为 ArrayMap 应用 System.arraycopy 更省内存空间, 这一点我真的没有看进去。arraycopy 也是把老的数组的对象一个一个的赋给新的数组。当然效率上必定 arraycopy 更高, 因为是间接调用的 c 层的代码。
4. 内存消耗:
以 ArrayMap 采纳了一种独特的形式, 可能反复的利用因为数据扩容而遗留下来的数组空间, 不便下一个 ArrayMap 的应用。而 HashMap 没有这种设计。因为 ArrayMap 只缓存了长度是 4 和 8 的时候, 所以如果频繁的应用到 Map, 而且数据量都比拟小的时候,ArrayMap 无疑是相当的节俭内存的。
5. 总结:
综上所述, 数据量比拟小, 并且须要频繁的应用 Map 存储数据的时候, 举荐应用 ArrayMap。
而数据量比拟大的时候, 则举荐应用 HashMap。
原文链接:https://blog.csdn.net/zuo_er_…
23.HashMap 原理
数据结构和算法思考
1. 为什么抉择数组和链表构造?
①数组内存间断块调配,效率体现查问更快。HashMap 中用作查找数组桶的地位,利用元素的 key 的 hash 值对数组长度取模失去。
②链表效率体现减少和删除。HashMap 中链表是用来解决 hash 抵触,增删空间耗费均衡。
扩大:为什么不是 ArrayList 而是应用 Node<K,V>[] tab?因为 ArrayList 的扩容机制是 1.5 倍扩容,而 HashMap 扩容是 2 的次幂。
2.HashMap 呈现线程问题
①多线程扩容,引起的死循环问题(jdk1.8 中,死循环问题曾经解决)。
②多线程 put 的时候可能导致元素失落
③put 非 null 元素后 get 进去的却是 null
3. 应用线程平安 Map
①HashMap 并不是线程平安,要实现线程平安能够用 Collections.synchronizedMap(m)获取一个线程平安的 HashMap。
②CurrentHashMap 和 HashTable 是线程平安的。CurrentHashMap 应用分段锁技术,要操作节点先获取段锁,在批改节点。
4.Android 提倡应用 ArrayMap
①ArrayMap 数据结构是两个数组,一个寄存 hash 值,另一个寄存 key 和 value。
②依据 key 的 hash 值利用二分查找在 hash 数组中找出 index。
③依据 index 在 key-value 数组中对应地位查找,如果不相等认为抵触了,会以 key 为核心,别离高低开展,逐个查找。
劣势,数据量少时(少于 1000)相比 HashMap 更节俭内存。劣势,删除和插入时效率要比 HashMap 要低。
kotlin 协程的应用与原理
24. 多线程间通信和多过程之间通信有什么不同,别离怎么实现?
1、过程间的通信形式
- 管道(pipe): 管道是一种半双工的通信形式,数据只能单向流动,而且只能在具备亲缘关系的过程间应用。过程的亲缘关系通常是指父子过程关系。
- 有名管道 (namedpipe) : 有名管道也是半双工的通信形式,然而它容许无亲缘关系过程间的通信。
- 信号量(semophore) : 信号量是一个计数器,能够用来管制多个过程对 共享资源的拜访。它常作为一种锁机制,避免某过程正在访问共享资源时,其 他过程也拜访该资源。因而,次要作为过程间以及同一过程内不同线程之间的 同步伎俩。
- 音讯队列(messagequeue) : 音讯队列是由音讯的链表,寄存在内核中 并由音讯队列标识符标识。音讯队列克服了信号传递信息少、管道只能承载无格局字节流以及缓冲区大小受限等毛病。
- 信号 (sinal) : 信号是一种比较复杂的通信形式,用于告诉接管过程某个 事件曾经产生。
- 共享内存(shared memory) : 共享内存就是映射一段能被其余过程所拜访 的内存,这段共享内存由一个过程创立,但多个过程都能够拜访。共享内存是 最快的 IPC 形式,它是针对其余过程间通信形式运行效率低而专门设计的。它往往与其余通信机制,如信号两,配合应用,来实现过程间的同步和通信。
- 套接字(socket) : 套解口也是一种过程间通信机制,与其余通信机制不同 的是,它可用于不同及其间的过程通信。
2 . 线程间的通信形式
-
锁机制: 包含互斥锁、条件变量、读写锁
- 互斥锁提供了以排他形式避免数据结构被并发批改的办法。
- 读写锁容许多个线程同时读共享数据,而对写操作是互斥的。
- 条件变量能够以原子的形式阻塞过程,直到某个特定条件为真为止。对条 件的测试是在互斥锁的爱护下进行的。条件变量始终与互斥锁一起应用。
- 信号量机制(Semaphore): 包含无名线程信号量和命名线程信号量
- 信号机制(Signal): 相似过程间的信号处理
线程间的通信目标次要是用于线程同步,所以线程没有像过程通信中的用于 数据交换的通信机制。
25. 为什么在子线程中创立 Handler 会抛异样
Handler 的工作是依赖于 Looper 的,而 Looper(与音讯队列)又是属于某一 个线程(ThreadLocal 是线程外部的数据存储类,通过它能够在指定线程中存储 数据,其余线程则无奈获取到),其余线程不能拜访。因而 Handler 就是间接 跟线程是绑定在一起了。因而要应用 Handler 必须要保障 Handler 所创立的线 程中有 Looper 对象并且启动循环。因为子线程中默认是没有 Looper 的,所以 会报错。正确的应用办法是:
public class WorkThread extends Thread {
private Handler mHander;
public Handler getHander() {return mHander;}
public void quit(){mHander.getLooper().quit();}
@Override
public void run() {super.run();
// 创立该线程对应的 Looper,
// 外部实现
// 1。new Looper()
// 2。将 1 步中的 lopper 放在 ThreadLocal 里,ThreadLocal 是保留数据的,次要利用场景是: 线程间数据互不影响的状况
// 3。在 1 步中的 Looper 的构造函数中 new MessageQueue();
// 对音讯机制不懂得同学能够查阅材料,网上很多也讲的很不错。Looper.myLooper();
mHander = new Handler(){@SuppressLint("HandlerLeak")
@Override
public void handleMessage(Message msg) {super.handleMessage(msg);
Log.d("WorkThread", (Looper.getMainLooper() == Looper.myLooper()) + "," + msg.what);
}
};
Looper.loop();
// 留神这 3 个的程序不能颠倒
Log.d("WorkThread", "end");
}
}
26.谈谈 Android 的 GC
Java 语言建设了垃圾收集机制,用以跟踪正在应用的对象和发现并回收不再 应用 (援用) 的对象。该机制能够无效防备动态内存调配中可能产生的两个危险: 因内存垃圾过多而引发的内存耗尽,以及不失当的内存开释所造成的内存非法援用。
垃圾收集算法的核心思想是: 对虚拟机可用内存空间,即堆空间中的对象进 行辨认,如果对象正在被援用,那么称其为存活对象,反之,如果对象不再被援用,则为垃圾对象,能够回收其占据的空间,用于再调配。
垃圾收集算法的抉择 和垃圾收集零碎参数的正当调节间接影响着零碎性能,因而须要开发人员做比拟 深刻的理解。
27. 怎么保障 App 不被杀死?
强烈建议不要这么做,不仅仅从用户角度思考,作为 Android 开发者也有责任去保护 Android 的生态环境。当然从可行性讲,谷歌也不会让容易的实现。同 时这样的 app 个别属于流氓利用
通常为了保障本人 app 防止被杀死,咱们个别应用以下办法:
1.Service 设置成 START_STICKY,kill 后会被重启(期待 5 秒左右),重传 Intent,放弃与重启前一样
2. 通过 startForeground 将过程设置为前台过程,做前台服务,优先级和前台 利用一个级别,除非在零碎内存十分缺,否则此过程不会被 kill
3.. 双过程 Service: 让 2 个过程相互爱护,其中一个 Service 被清理后,另外没 被清理的过程能够立刻重启过程
4.QQ 黑科技: 在利用退到后盾后,另起一个只有 1 像素的页面停留在桌面上,让本人放弃前台状态,爱护本人不被后盾清理工具杀死
5. 在曾经 root 的设施下,批改相应的权限文件,将 App 伪装成零碎级的利用 (Android4.0 系列的一个破绽,曾经确认可行)
6.Android 零碎中以后过程(Process)fork 进去的子过程,被零碎认为是两个不 同的过程。当父过程被杀死的时候,子过程依然能够存活,并不受影响。鉴于目 前提到的在 Android-Service 层做双守护都会失败,咱们能够 fork 出 c 过程多过程守护。死循环在那查看是否还存在,具体的思路如下(Android5.0 以下 可行):
- 1. 用 C 编写守护过程(即子过程),守护过程做的事件就是循环查看指标过程是否 存在,不存在则启动它。
- 2. 在 NDK 环境中将 1 中编写的 C 代码编译打包成可执行文件 (BUILD_EXECUTABLE)。
- 3. 主过程启动时将守护过程放入公有目录下,赋予可执行权限,启动它即可。
7 分割厂商,退出白名单
28. 源码剖析:Handler 发送延时音讯
总结:
handler 发送延时音讯是通过 postDelayed()办法将 Runnanle 对象封装成 Message,而后调用 sendMessageAtTime(),设置的工夫是过后的工夫 + 延时的工夫。
发送延时音讯实际上是往 messageQueue 中退出一条 Message。
Message 在 MessageQueue 中理论是以单链表来存储的,且是依照工夫程序来插入的。工夫程序是以 Message 中的 when 属性来排序的。
重点:
postDelay 并不是期待 delayMillis 延时时常后再退出音讯队列,而是退出音讯队列后阻塞 (音讯队列会依照阻塞工夫排序) 期待 delayMillis 后唤醒音讯队列再执行。
sleep 会阻塞线程
postDelayed 不会阻塞线程
参考:
https://blog.csdn.net/u013552…
https://blog.csdn.net/qq_2080…
29.Android 打包流程
相熟 Android 打包编译的流程
- AAPT(Android Asset Packaging Tool)工具,Android 资源打包工具。会打包资源文件(res 文件夹下的文件),并生成 R.java 和 resources.arsc 文件。
- AIDL 工具会将所有的.aidl 文件编译成.java 文件。
- JAVAC 工具将 R.java、AIDL 接口生成的 java 文件、利用代码 java 文件编译成.class 文件。
- dex 脚本将很多.class 文件转换打包成一个.dex 文件。
- apkbuilder 脚本将资源文件和.dex 文件生成未签名的.apk 文件。
- jarsigner 对 apk 进行签名。
三、我的项目
1.Android 罕用设计模式及源码应用
单例模式
初始化比较复杂,并且程序中只须要一个。防止反复创立耗费内存
Android 中 获取 WindowManager 服务援用 WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);
l 另外一种不错实现单例的形式 应用 eunm,
public class Singleton {
private static volatile Singleton s;
private Singleton(){};
public static Singleton getInstance() {if(s == null) {synchronized (Singleton.class) {if(s == null) {s = new Singleton();
}
}
}
return s;
- 创建者模式
创立某对象时,须要设定很多的参数(通过 setter 办法),然而这些参数必须依照某个程序设定
Android 中 创立所有的 Dialog 中应用的
public class TestClient {
private int index;
private String name;
public TestClient() {this(new Builder());
}
public TestClient(Builder builder){
this.index = builder.index;
this.name = builder.name;
}
public static final class Builder {
private int index;
private String name;
public Builder() {
this.index = 1;
this.name = "xxx";
}
public Builder(TestClient testClient){
this.index = testClient.index;
this.name = testClient.name;
}
public Builder setIndex(int index) {
this.index = index;
return this;
}
public Builder setName(String name) {
this.name = name;
return this;
}
public TestClient build(){return new TestClient(this);
}
}
}
原型模式
工厂模式
定义一个创建对象的工厂,依据不同传参 创立不同的对象。
Android 中 BitmapFactory 和 Iterator 依据循环对象不同返回不同的对象
策略模式
有一系列的算法,将算法封装起来(每个算法能够封装到不同的类中),各个算法之间能够替换,策略模式让算法独立于应用它的客户而独立变动
Android 中的 工夫插值器,能够应用不同的 减速 加速 或者自定义加速器 展现不同的动画成果
责任链模式
B
命令模式
命令模式将每个申请封装成一个对象,从而让用户应用不同的申请把客户端参数化;将申请进行排队或者记录申请日志,以及反对可撤销操作。
Android 事件机制中,底层逻辑对事件的转发解决。每次的按键事件会被封装成 NotifyKeyArgs 对象,通过 InputDispatcher 封装具体的事件操作 / Runable 实现中封装咱们须要的实现
观察者模式
Java 的 Observable 类和 Observer 接口就是实现了观察者模式。一个 Observer 对象监督着一个 Observable 对象的变动,当 Observable 对象发生变化时,Observer 失去告诉,就能够进行相应的工作。
中介者模式
在 Binder 机制中,即 ServiceManager 持有各种零碎服务的援用,当咱们须要获取零碎的 Service 时,首先是向 ServiceManager 查问指定标示符对应的 Binder,再由 ServiceManager 返回 Binder 的援用。并且客户端和服务端之间的通信是通过 Binder 驱动来实现,这里的 ServiceManager 和 Binder 驱动就是中介者。代理模式
给某一个对象提供一个代理,并由代理对象管制对原对象的援用 (, 动态代理 和 动静代理)
适配器模式
把一个类的接口变换成客户端所期待的另一个接口,从而使本来因接口不匹配而无奈在一起工作的两个类可能在一起工作。
补充阐明:
例模式
// 获取 WindowManager 服务援用
WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);
根本所有的获取零碎服务都是单例。
Builder 模式
个别罕用于构建须要 3 个以上的参数。
AlertDialog.Builer builder=new AlertDialog.Builder(context);
builder.setIcon(R.drawable.icon)
.setTitle("title")
.setMessage("message")
.setPositiveButton("Button1", new DialogInterface.OnclickListener(){public void onClick(DialogInterface dialog,int whichButton){setTitle("click");
}
})
.create()
.show();
原型模式
Uri uri=Uri.parse("smsto:10086");
Intent shareIntent=new Intent(Intent.ACTION_SENDTO,uri);
// 克隆正本
Intent intent=(Intetn)shareIntent.clone(); startActivity(intent);
工厂模式
public Object getSystemService(String name) {if (getBaseContext() == null) {throw new IllegalStateException("System services not available to Activities before onCreate()");
}
//........
if (WINDOW_SERVICE.equals(name)) {return mWindowManager;} else if (SEARCH_SERVICE.equals(name)) {ensureSearchManager();
return mSearchManager;
} //.......
return super.getSystemService(name); }
策略者模式
依据是否是 v 包抉择不同的构建 view 计划
责任链模式
view 的点击事件,view 的 touch 事件等
命令模式
按键事件会被封装成 notifyKeyArgs 对象,通过 inputDispatcheri 封装具体事件操作
观察者模式
监听器这一类都是
备忘录模式
activtity onSaveInstanceState 等
迭代器模式
数据库的 cursor,罕用于查问
代理模式
Binder 代理
适配器模式
adapter 类
装璜者模式
Rxjava2,或者 contextThemeWapper 等
2. app 优化(我的项目中解决的一些难点)
次要分为 启动优化,布局优化,打包优化 等
启动优化
- 闪屏页 优化,设置 theme 默认欢送背景
- 懒加载 第三方库,不要都放在 application 中初始化
- 如果我的项目中有 webview,能够提前在 app 闲暇工夫加载 webview 的内核,如果多处应用 能够创立缓存池,缓存 webview,
- 如果 android 5.0- 在 applicaton 的 attchbaseContext() 中加载 MultiDex.install 会更加耗时,能够采纳 子线程(子线程加载 须要放心 ANR 和 ContentProvider 未加载报错的问题)或者独自开一个过程 B,过程 B 开启子线程运行 MultiDex.install,让 applicaton 进入 while 循环期待 B 过程加载后果。
MultiDex 优化,apk 打包分为 android 5.0 + 应用 ART 虚拟机 不必放心
布局 UI 优化
看过布局绘制源码流程后,能够晓得 setContextView 中 在 ViewRootImpl 中应用 pull 的办法(这里能够 扩大 xml 读取形式 SAX:逐行解析、dom:将整个文件加载到内存 而后解析,不举荐、pull:相似于 SAX 进行了 android 平台的优化,更加轻量级 不便)迭代读取 xml 标签,而后对 view 进行 measure,layout 和 draw 的时候都存在耗时。通常优化形式有:
- 缩小 UI 层级、应用 merge、Viewstub 标签 优化反复的布局
- 优化 layout,尽量多应用 ConstraintLayout,因为 relalayout 和 linearlayout 比重的状况下都存在屡次测量
- recyclerView 缓存 (可扩大 阐明 rv 的缓存原理 )
- 比拟极其的 将 measure 和 layout 放在子线程,在主线程进行 draw。或者 子线程中 加载 view 进行 IO 读取 xml, 通过 Handler 回调主线程 加载 view(比方 android 原生类 AsyncLayoutInflate)
- 将 xml 间接通过 第三方工具(原理 APT 注解 翻译 xml)间接将 xml 转为 java 代码
更多 UI 优化文章
Handler 线程间通信
作用:线程之间的音讯通信
流程:主线程默认实现了 Looper(调用 loop.prepare 办法 向 sThreadLocal 中 set 一个新的 looper 对象,looper 构造方法中又创立了 MsgQueue)手动创立 Handler,调用 sendMessage 或者 post (runable) 发送 Message 到 msgQueue,如果没有 Msg 这增加到表头,有数据则判断 when 工夫 循环 next 放到适合的 msg 的 next 后。Looper.loop 一直轮训 Msg,将 msg 取出 并散发到 Handler 或者 post 提交的 Runable 中解决,并重置 Msg 状态位。回到主线程中 重写 Handler 的 handlerMessage 回调的 msg 进行主线程绘制逻辑。
问题:
- Handler 同步屏障机制:通过发送异步音讯,在 msg.next 中会优先解决异步音讯,达到优先级的作用
- Looper.loop 为什么不会卡死 :为了 app 不挂掉,就要保障主线程始终运行存在,应用死循环代码阻塞在 msgQueue.next() 中的 nativePollOnce()办法里,主线程就会挂起休眠开释 cpu,线程就不会退出。Looper 死循环之前,在 ActivityThread.main()中就会创立一个 Binder 线程(ApplicationThread),接管零碎服务 AMS 发送来的事件。当零碎有音讯产生(其实零碎每 16ms 会发送一个刷新 UI 音讯唤醒)会通过 epoll 机制 向 pipe 管道写端写入数据 就会发送音讯给 looper 接管到音讯后处理事件,保障主线程的始终存活。只有在主线程中解决超时才会让 app 解体 也就是 ANR。
- Messaage 复用:将应用完的 Message 革除附带的数据后, 增加到复用池中 , 当咱们须要应用它时, 间接在复用池中取出对象应用, 而不须要从新 new 创建对象。复用池实质还是 Message 为 node 的单链表构造。所以举荐应用 Message.obation 获取 对象。
自定义 View!!
筹备自定义 View 方面的面试最简略的办法:
- 就是本人入手实现几个 View(由简略到简单);
- 剖析一些热门 App 中的自定义 View 的成果是怎么实现的;
- 阿里面试官:自定义 View 跟绘制流程相干知识点?(规范参考解答,值得珍藏)
四、第三方库源码总结
LeakCanary 原理
参考博客
通过 registerActivityLifecycleCallbacks 监听 Activity 或者 Fragment 销毁时候的生命周期(如果不想那个对象被监控则通过 AndroidExcludedRefs 枚举,防止被检测)
public void watch(Object watchedReference, String referenceName) {if (this == DISABLED) {return;}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
而后通过弱援用和援用队列监控对象是否被回收(弱援用和援用队列 ReferenceQueue 联结应用时,如果弱援用持有的对象被垃圾回收,Java 虚拟机就会把这个弱援用退出到与之关联的援用队列中。即 KeyedWeakReference 持有的 Activity 对象如果被垃圾回收,该对象就会退出到援用队列 queue)
void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {@Override public boolean queueIdle() {postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
IdleHandler,就是当主线程闲暇的时候,如果设置了这个货色,就会执行它的 queueIdle()办法,所以这个办法就是在 onDestory 当前,一旦主线程闲暇了,就会执行一个延时五秒的子线程工作,工作:检测到未被回收则被动 gc,而后持续监控,如果还是没有回收掉,就证实是内存透露了。通过抓取 dump 文件,在应用 第三方 HAHA 库 剖析文件,获取到达到泄露点最近的线路,通过 启动另一个过程的 DisplayLeakService 发送告诉 进行音讯的展现。
OkHttp
参考博客
☆平头哥 博客链接
同步和异步 网络申请应用办法
// 同步 get 申请
OkHttpClient okHttpClient=new OkHttpClient();
final Request request=new Request.Builder().url("xxx").get().build();
final Call call = okHttpClient.newCall(request);
try {Response response = call.execute();
} catch (IOException e) { }
// 异步 get 申请
OkHttpClient okHttpClient=new OkHttpClient();
final Request request=new Request.Builder().url("xxx").get().build();
final Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) { }
@Override
public void onResponse(Call call, Response response) throws IOException {}});
// 异步 post 申请
OkHttpClient okHttpClient1 = new OkHttpClient();
RequestBody requestBody = new FormBody.Builder()
.add("xxx", "xxx").build();
Request request1 = new Request.Builder().url("xxx").post(requestBody).build();
okHttpClient1.newCall(request1).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) { }
@Override
public void onResponse(Call call, Response response) throws IOException {}});
同步申请流程 :
通过 OkHttpClient new 生成 call 实例 Realcall
Dispatcher.executed() 中 通过增加 realcall 到 runningSyncCalls 队列中
通过 getResponseWithInterceptorChain() 对 request 层层拦挡,生成 Response
通过 Dispatcher.finished(),把 call 实例从队列中移除,返回最终的 response
异步申请流程 :
生成一个 AsyncCall(responseCallback)实例 (实现了 Runnable)
AsyncCall 通过调用 Dispatcher.enqueue(),并判断 maxRequests(最大申请数)maxRequestsPerHost(最大 host 申请数) 是否满足条件,如果满足就把 AsyncCall 增加到 runningAsyncCalls 中,并放入线程池中执行;如果条件不满足,就增加到期待就绪的异步队列,当那些满足的条件的执行时,在 Dispatcher.finifshed(this)中的 promoteCalls(); 办法中 对期待就绪的异步队列进行遍历,生成对应的 AsyncCall 实例,并增加到 runningAsyncCalls 中,最初放入到线程池中执行,始终到所有申请都完结。
责任链模式 和 拦截器
责任链
源码跟进 execute() 进入到 getResponseWithInterceptorChain() 办法
Response getResponseWithInterceptorChain() throws IOException {
// 责任链 模式
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
chain.proceed() 办法外围代码。每个拦截器 intercept()办法中的 chain,都在上一个 chain 实例的 chain.proceed()中被初始化,并传递了拦截器 List 与 index,调用 interceptor.intercept(next),间接最初一个 chain 实例执行即进行。
// 递归循环下一个 拦截器
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
@Override public Response intercept(Chain chain) throws IOException {Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
while (true) {
...
// 循环中 再次调用了 chain 对象中的 proceed 办法,达到递归循环。response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
...
}
}
拦截器
- RetryAndFollowUpInterceptor:重连并跟踪 拦截器。
- BridgeInterceptor : 将用户申请构建为网络申请(hander cooker content-type 等)并发动申请。
- CacheInterceptor:缓存拦截器 负责从缓存中返回响应和把网络申请响应写入缓存。
- ConnectInterceptor:与服务端 建设连贯,并且取得通向服务端的输出和输入流对象。
OkHttp 流程
- 采纳责任链形式的拦截器, 实现分成解决网络申请, 可更好的扩大自定义拦截器(采纳 GZIP 压缩,反对 http 缓存)
- 采纳线程池 (thread pool) 和连接池 (Socket pool) 解决多并发问题, 同时连接池反对多路复用(http2 才反对, 能够让一个 Socket 同时发送多个网络申请, 外部主动维持程序. 相比 http 只能一个一个发送, 更能缩小创立开销))
- 底层采纳 socket 和服务器进行连贯. 采纳 okio 实现高效的 io 流读写
ButterKnife
参考文章
butterKnife 应用的是 APT 技术 也就是编译时注解,不同于运行时注解(在运行过程中通过反射动静地获取相干类,办法,参数等信息,效率低),编译时注解 则是在代码编译过程中对注解进行解决(annotationProcessor 技术),通过注解获取相干类,办法,参数等信息,而后在我的项目中生成代码,运行时调用,其实和间接手写代码一样,没有性能问题,只有编辑时效率问题。
ButterKnife 在 Bind 办法中 获取到 DecorView,而后通过 Activity 和 DecorView 对象获取 xx_ViewBinding 类的结构对象,而后通过构造方法反射实例化了这个类 Constructor。
在编写完 demo 之后,须要先 build 一下我的项目,之后能够在 build/generated/source/apt/debug/ 包名 / 上面找到 对应的 xx_ViewBinding 类,查看 bk 帮咱们做的事件,
xx_ViewBinding.java
@UiThread
public ViewActivity_ViewBinding(ViewActivity target, View source) {
this.target = target;
target.view = Utils.findRequiredView(source, R.id.view, "field'view'");
}
Utils.java
public static View findRequiredView(View source, @IdRes int id, String who) {View view = source.findViewById(id);
if (view != null) {return view;}
String name = getResourceEntryName(source, id);
throw new IllegalStateException("Required view ...."}
通过上述上述代码 能够看到 注解也是帮咱们实现了 findviewbyid 的工作。
butterknife 实现流程
- 扫描 Java 代码中所有的 ButterKnife 注解
- 发现注解,ButterKnifeProcessor 会帮你生成一个 Java 类,名字 < 类名 >$$ViewBinding.java,这个新生成的类实现了 Unbinder 接口,类中的各个 view 申明和增加事件都增加到 Map 中,遍历每个注解对应通过 JavaPoet 生成的代码。
Rxjava 2
Rxjava 源码
切换到子线程用的 线程池,切换到主线程则用的 Handler。
底层的切换原理还是基于 Handler 来的。
思路 1 :
在子线程发送音讯,却可能在主线程接管音讯,主线程和子线程是怎么样切换的?
子线程用 handler 发送音讯,发送的音讯被送到与主线程相关联的 MessageQueue,也是主线程相关联的 Looper 在循环音讯,handler 所关联的是主线程的 Looper 和 MessageQueue, 所以最初音讯的解决逻辑也是在主线程。只有发送音讯是在子线程,其它都是在主线程,Handler 与哪个线程的 Looper 相关联,音讯解决逻辑就在与之相干的线程中执行,相应的音讯的走向也就在相关联的 MessageQueue 中。所以子线程切换到主线程是很天然的过程,并没有设想中的简单。
https://zhuanlan.zhihu.com/p/…
思路 2 :
Handler 利用线程关闭的 ThreadLocal 维持一个音讯队列,Handler 的外围是通过这个音讯队列来传递 Message,从而实现线程间通信。
思路 3 :理论线程间切换,就是通过线程间共享变量实现的。
首现 在一个线程中,new Handler() 必须先执行,Looper.prepare() 创先 looper,
主线程没有手动调用 Looper.prepare(),是因为 app 启动时,在 ActivityThread main 主入口,执行了 Looper.prepareMainThread.
public static void prepareMainLooper() { …}
链接:https://www.jianshu.com/p/1ef…
五、跨平台
Flutter 和 React Native 不同次要在于 Flutter UI 是间接通过 skia 渲染的,而 React Native 是将 js 中的控件转化为原生控件,通过原生去渲染的。
挪动端跨平台开发的深度解析
六、设计模式
手写 DCL 单例模式
七、算法
希尔算法、疾速算法、反转单链表、LRU 的几种实现形式
八、网络
HTTP 申请形式和报文解析
1.HTTPS(Secure) 平安的 HTTP 协定
https://< 主机 >:<443>/< 门路 >
补充阐明(其余版本)
HTTPS 就是“平安版”的 HTTP, HTTPS = HTTP + SSL。HTTPS 相当于在应用层和 TCP 层之间退出了一个 SSL(或 TLS),SSL 层对从应用层收到的数据进行加密。TLS/SSL 中应用了 RSA 非对称加密,对称加密以及 HASH 算法。RSA 算法基于一个非常简略的数论事实:将两个大素数相乘非常容易,但那时想要对其乘积进行因式分解却极其艰难,因而能够将乘积公开作为加密密钥。
1.1 加密模型
- 「对称加密:加密与解密都应用同一个秘钥」。
-
「非对称加密:公钥加密,私钥解密,并且公钥与私钥是领有肯定数学关系的一组秘钥」。
- 「私钥:本人应用,不对外公开」。
- 「公钥:给大家应用,对外公开」。
1.2 数字证书 签名校验
数字证书格局
- 证书格局、版本号
- 证书序列号
- 签名算法
- 有效期
- 对象名称
- 对象公开秘钥
1.3 SSL(Secure Sockets Layer)安全套接层
「SSL 位于传输层与应用层之间,它是一个子层,作用次要有两点」:
- 1)、「数据安全(保证数据不会被透露)与数据残缺(保证数据不会被篡改)」。
- 2)、「对数据进行加密后传输」。
1.4 HTTPS 的通信过程
- 1)、「443 端口的 TCP 连贯」。
- 2)、「SSL 平安参数握手」。
- 3)、「客户端发送数据」。
- 4)、「服务端发送数据」。
1.5 SSL(Secure Sockets Layer) 安全套接层握手过程
1)、生成随机数 1、2、3 的过程
2)、双端依据随机数 1、2、3 与雷同的算法生成对称秘钥进行加密通信
「HTTPS 综合地使用了对称加密与非对称加密,在进行随机数校验的阶段是应用了非对称加密来进行通信的,而后等单方都确定了三个随机数之后,就能够应用雷同的算法来生成对称秘钥进行加密通信了。HTTPS 的劣势在于双端别离生成了秘钥,没有通过传输,缩小了秘钥透露的可能性」。
https 在我的项目的使用实际:Android HTTPS 自制证书实现双向认证(OkHttp + Retrofit + Rxjava)
作者:jsonchao
链接:https://juejin.im/post/5eba5a…
九、思考架构师成长之路,对标目前的技术要求
十、软件是如何跑起来的
集体总结后感觉须要深刻学习的一本书《软件是如何跑起来的》
常识碎片 整合起来
十一、面试自测题库
公司一
- 组件化和 arouter 原理
- recyclerview 和 listview 区别
- glide 流程,缓存前压缩,缓存命中
- APP 性能优化,内存优化,布局优化,绘制优化,内存透露
- Http 和 Https 区别
- socket 心跳包
- jvm 虚拟机,堆和栈的构造
- activity 启动模式,有哪些不同
- stack 栈的特点,自定义 stack 构造
- kotlin 优劣势
公司二
- 自定义 view,中英文字符串宽高测量显示,测量算法,可扩展性
- 事件散发机制
- Activity,view,window 分割
- 热修复和插件化原理
- Synchronized 底层原理,java 锁机制
- java 容器,hashmap 和 hashtable 区别,hashmap 原理,扩容流程,扰动算法的劣势
- ArrayList 和 LinkendList 区别,List 泛型擦除,为什么反射可能在 ArrayList< String > 中增加 int 类型
- Http 和 Https 区别,SSL/TLS 过程
- Android 性能优化
- jvm 虚拟机,堆和栈的构造,栈帧,JMM
- 组件化留神点,组件间通信机制
- 线程平安的单例模式有哪几种
- 相熟的设计模式
公司三
- MVC,MVP,MVVM
- Activity 和 fragment 生命周期区别,fragment 失常增加和 viewpager 增加的区别,fragment 懒加载原理,FragmentPagerAdapter 和 FragmentStatePagerAdapter
- 热修复和插件化
- 友盟 bug 统计,混同后怎么定位 bug。没接入热修复的 APP 中,上线后遇到 bug 怎么解决
- view 绘制原理 (能够先说下根本 view 绘制,而后再说下屏幕刷新机制)
- 应用 Analyze 缩小 APK 体积,原理
- Android 版本差别
公司四
- 根底类型字节,汉字占几个字节,线程和过程
- 四大组件,fileprovider 和 Contentprovide 区别,activity 启动流程
- MVC,MVP,MVVM
- TCP 三次握手,四次挥手
- Eventbus,glide 原理
- 性能优化,内存抖动,内存透露,内存溢出,handler 机制,IntentService 和 handlerThread,子线程更新 view 内容的办法
- GC 回收算法
- recyclerview 和 listview 区别
- 组件化,模块化,插件化,热修复
- 工作中遇到的难题怎么解决的
- Kotlin Java 优缺点,kotlin 什么时候用分号,run,with,apply, 内联函数,高阶函数
- APK 体积优化
- 过程间通信
- 单例模式,哪些是平安的
- retrofit 设计模式
- 自定义 view
- 是否做过音视频和 IM?
- APK 性能优化
- CurrentHashMap1.7 和 1.8 区别
- volatile 关键字的作用,怎么保障原子性呢?
- 网络优化
- 对新技术的认识
- java 泛型,协变和逆变
公司五
- HTTPS 具体步骤
- 罕用的设计模式,代理模式和装璜者模式区别
- 服务端返回谬误的 json 数据,客户端怎么自定义 model,防止出错
- Hook 技术
- kotlin 理解,协程
- 屏幕适配
- 抓包工具应用和原理
- 网络优化
- 将来冀望,对公司的理解
- Okhttp,rxjava,glide,retrofit 等原理,okhttp 底层数据传输原理,http 报文体构造
- APK 体积优化
- Android jetpack 应用和原理,新技术认识
- crashHandler 获取利用 crash 信息
- recyclerview 和 listview 缓存区别
- Android 常见解体问题剖析及个别的解决方案
- NestedScrollView 触摸机制,AOP 相干常识
- 设计 APP,整体架构选型
- Android 沙盒和底层 Linux 通信
- ACTION_CANCLE 什么时候触发
- 线程池原理
公司六
- 组件化,arouter,组件化 UI,还有哪些路由框架。AS 调试办法
- MVC,MVP,MVVM,Jetpack
- JVM,JMM,java 加载对象的步骤,classLoader,GC 回收算法
- 插件化和热修复
- 惟一安卓 ID,安卓平安的常识,加密算法,判断 activity 前台过程
- TCP 三次握手和四次挥手
- hash 算法,hashmap, 怎么解决 hash 抵触
- 加载大图,glide 缓存机制,设计模式,双重检测的单例模式为什么要查看两次,本人设计图片加载框架思路
- 启动未注册的 Activity
- AOP, 蓝牙开发,IOT
- glide 缓存革除:lrucache 算法
- glide 缓存文件太大,查找效率慢怎么优化?glide 下载高清图片优化
- 最近钻研的技术,遇到最难的事,对公司的冀望
公司七
- 组件化, arouter 优缺点
- MVC,MVP,MVVM
- 我的项目中的亮点,对架构的了解
- handler 原理及相干知识点,message 回收策略
- hashmap 原理,arraymap 原理,比照性能。
- hashmap 为什么大于 8 才转化为红黑树,加载因子为什么是 0.75
- Synchronized 底层原理,java 锁机制
- 服务和播送
- activity 启动模式(给例子具体分析,A(规范)-》B(单例)-》C(singleTop)-》D(singleTask),剖析有几个栈,每个栈内的 activity)
- 罕用设计模式,线程平安的单例模式
公司八
- static、final; 继承与多态
- 组件化, arouter 优缺点
- context 相干知识点
- handler 原理及相干知识点,handler 缓存池大小。
- 性能优化,启动速度优化,架构
- java 虚拟机与 Dalvik 和 ART 区别
- Kotlin 协程,扩大函数和属性以及伴生对象
- 电商 APP 的首页,怎么设计一个 APP 架构
- MVP 中数据申请为什么要和 M 一起,答:网络申请和 javabean 都是数据模型相干
- Glide 的存储 EngineKey 是怎么保障惟一的。面试官答:有个队列会将 EngineKey 存储起来,每次生成后进行比照存储。这个我在源码中没找对地位,如果晓得的同学,麻烦帮忙解释下。
- retrofit 是怎么将 service 接口转化为咱们须要的 javabean 的?
- 怎么做治理,新技术学习
公司九
- SqLite 与 contentProvider 区别
- fragment 周期,两个 fragment 切换周期变动,fragment 通信
- https 证书校验,加密相干,网络申请框架
- glide 加载流程,大图显示,图片大小计算
- view 绘制(从 onSync()开始)
- 线程内存模型,线程间通信
- 获取 view 的宽高,更新 view 的形式,主线程音讯机制
- OOM,内存透露,内存溢出,java 援用类型,ANR 剖析
- APP 性能优化,webview 相干,webview 优化,webview 中 Android 与 js 互调
- 插件化和热修复
十二、总结
简历上写的货色,肯定要先搞懂,特地是简历上的 专业技能。如果面试未通过,根底上能够归结为:基础知识不够扎实,技术深度不够。深度和广度是永远的思考点。工作上了肯定的年限,在面试时也得留神一下根底。对数据结构与算法来说,根底的数据结构的考查不会进行,对资深的要求岗位,对我的项目上的性能和效率工具的全面考查更是 微小的考验。
平时工作是多做总结是必要的,对罕用的技术和难点无意识做总结,在总结时同时查阅相应的材料有助于深度思考。
心愿大家在和面试官 PK 时,都有本人的认识和态度。
珍惜每 一次和对面试官交换,都将是对本人技术的一次梳理。