前言
耿直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.javapublic 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时,都有本人的认识和态度。
珍惜每 一次和对面试官交换,都将是对本人技术的一次梳理。