java基础
1.java基本类型:byte(8),short(16),int(32),float(32),double(64),long(64),char(16),boolean(不做规定);
2.装箱和拆箱:是指基本类型和对应的包装类型的转换,int转成Integer叫装箱,Integer转成int叫拆箱;
3.java中的参数,是以值的形式传入方法的,如果参数是基本类型,传的是基本类型的值,如果是其他非基本类型,传递的是指针(也是具体值);
4.重写equals方法时, 也要重写hashCode方法,这是为了保证两个等价的对象,hash值也要相等,以便使用hashMap时不出错;
5.泛型,只在编译期有效,编译过程中,正确校验泛型结果后,会将泛型的相关信息擦去,叫泛型擦除;
6.面向对象三大特性:封装、继承、多态;封装隐藏对象的属性和实现细节,仅对外公开接口;继承实现了代码的复用;多态是指相同的类,调用其相同的方法,表现出不同的行为,是通过类的继承,重写,向上转型来实现的,父类引用指向子类对象叫向上转型;
7.JVM运行时数据区域:虚拟机栈、本地方法栈、堆、程序计数器、方法区;
8.判断一个对象是否可以回收:引用计数法,可达性分析;
9.可达性分析算法中的GC Roots有:虚拟机栈中局部变量引用的对象、本地方法栈中JNI引用的对象、方法区中类静态属性引用的对象、方法区中的常量引用的对象;
10.引用类型:强引用、软引用(GC且内存不够时回收)、弱引用(GC时就回收)、虚引用;
11.垃圾收集算法:标记清除、标记整理、复制、分代收集;
12.JVM的classLoader有三大类:BootstrapClassLoader启动类加载器、ExtensionClassLoader扩展类加载器、ApplicationClassLoader应用类加载器;
13.Android中的classLoader:PathClassLoader、DexClassLoader、BaseDexClassLoader、BootClassLoader;path用来加载安装到手机的apk的dex文件,dex没有限制,可以加载sd卡上的apk文件或者dex文件;
14.双亲委派:当类加载器需要加载类的时候,先判断是否已经加载,如果没有加载,将加载请求转发给父类加载器,只有当父类加载器不能加载时,才尝试自己加载;
15.并发三特性:原子性、可见性、有序性。原子性是指一个或多个操作,要么全部执行,过程不被打断,要么全部不执行;可见性指当一个线程修改类共享变量的值,其他线程能够看到修改的值;有序性指程序执行的顺序按照代码的先后顺序执行; synchronized关键字可以保证三性,volatile可以保证可见性和有序性,常用做修饰状态标记量;
16.HasnMap源码,数组+链表+红黑树,动态扩容;
17.支持并发的数据结构:CopyOnWriteArrayList、ConcurrentHashMap、ConcurrentLinkedQueue。
18.synchronized和volatile,synchronized修饰方法或者代码块,修饰方法时,字节码实现是在方法的flag增加ACC_SYNCHRONIZED,修饰代码块时,字节码实现是增加一个monitor_enter和两个monitor_exit;volatile修饰变量,可以保证可见性和有序性,常用来修饰状态标记量,或者双重校验锁实现的单例对象,或者和CAS一起,通过自旋方式实现乐观锁,CAS compare and swap,比较和交换,在取数据的时候,判断期间是否被修改,如果修改了,就失败,然后自旋。
19.class字节码,java文件,kt文件等通过编译器编译成class字节码文件,字节码文件由无符号数(u1,u2,u4)和表组成,表由无符号树和其他表组成。
20.java内存模型,存在缓存一致性问题和指令重排问题,volatile可以解决。
21.可以使用ReentrantLock实现公平锁和读写锁;
android基础
1.Activity生命周期:onCreate、onStart、onResume、onPause、onStop、onDestroy、onRestart;
2.Activity启动模式:standard、singleTop、singleTask、singleInstance;
standard,默认启动模式,创建新的实例,入栈顶;
singleTop,先检查该activity是否在栈顶存在,如果存在就直接使用,生命周期onNewIntent,不存在就创建并入栈顶;
singleTask,检查整个任务栈是否存在,存在就将该activity上的所有activity出栈,使之成为栈顶;
singleInstance,创建一个独立的任务栈,只放这一个activity;
3.事件分发:从Activity开始,然后分发到ViewGroup,先判断是否拦截,不拦截就分发到View,如果View不处理,返回给ViewGroup,ViewGroup不处理,再返回给Activity;
伪代码:
//Activity
public boolean dispatchTouchEvent(MotionEvent event) {
if (ViewGroup.dispatchTouchEvent(event)) {
return true;
}
return Activity.onTouchEvent(event);
}
public boolean onTouchEvent(MotionEvent event) {
}
//ViewGroup
public boolean dispatchTouchEvent(MotionEvent event) {
if (ViewGroup.onInterceptTouchEvent(event)) {
return onTouchEvent(event);
}
if (View.dispatchTouchEvent(event)) {
return true;
}
return ViewGroup.onTouchEvent(event);
}
public boolean onInterceptTouchEvent(MotionEvent event) {
}
public boolean onTouchEvent(MotionEvent event) {
}
//View
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && mOnTouchListener.onTouch(event)) {
return true;
}
return View.onTouchEvent(event);
}
public boolean onTouchEvent(MotionEvent event) {
//处理 onClick() onLongClick() 等方法
}
4.View绘制过程:onMeasure、onLayout、onDraw;先测量自己及子view,然后布局,然后绘制;
5.Handler相关机制,发送消息过程,处理消息,同步屏障,IdleHandler;
在Framework层涉及到的类有Handler、Message、Looper、MessageQueue,ThreadLocal;
首先从主线程启动开始,通过Looper的prepare方法,创建一个Looper对象和消息队列对象MessageQueue,然后通过ThreadLocal把Looper对象存起来,ThreadLocal是一个类似于Map的结构,key是当前线程,value是具体对象,这样可以保证在一个线程中只有一个对象数据,然后调用Looper的loop方法不断去从消息队列里面读消息,这里的实现方式是开启一个死循环,然后调用MessageQueue的next方法,如果拿到消息就交给消息的handler处理,是Message的一个属性target,如果没有就等待,此时主线程处于阻塞状态,不占用CPU,一直到有消息来。在MessageQueue中,所有的消息会在发送消息的时候,按照指定的时间先后顺序,放入队列结构中,在next方法中,先取消息队列的头部消息,然后和当前时间比较,如果超过当前时间,可以返还给Looper处理,如果还没有到时间,就调用一个native方法去设置唤醒时间,到达时间后再返回消息给Looper,以上是消息的普通处理逻辑,如果开启了同步屏障,或者设置了IdleHandler,会在此基础上增加一些逻辑。
消息的发送是从Handler开始,创建Handler时需要传Looper对象,默认不传会使用当前线程的Looper,使用ThreadLocal取取的,如果线程没有Looper就会报错。发送消息是使用Handler的post方法或者send方法,这些方法最终都会走到MessageQueue的enqueueMessage方法中,然后根据消息的处理时间放到队列的合适位置。以上是消息的发送过程。
消息处理时提到同步屏障和IdleHandler,同步屏障顾名思义是阻碍同步消息,消息队列的postSyncBarrier()方法开启,removeSyncBarrier()方法关闭。在消息队列的next方法中,如果拿到的队列头部消息的target对象为空,表示开启了同步屏障,那么接下来的所有同步消息都不处理,而是只处理异步消息,系统源码中,ui更新时会用到这个机制。而IdleHandler是一个回调接口,通过消息队列的addidleHandler方法添加实现类,当消息队列空闲时(队列为空,或者队首消息是延时任务时),会回调这个接口。
6.系统启动过程:接通电源后,系统加载BootLoader到内存,并拉起LinuxKernel内核,然后启动第一个用户空间的进程,init进程,在init进程中,启动Zygote进程,开启Socket监听,然后通过fork Zygote进程的方式,启动system_server进程,然后启动Binder线程池,启动AMS,WMS,PMS等系统服务,然后通过AMS启动Launcher进程;
7.app启动过程:点击桌面应用Icon后,通过系统的IMS和WMS服务,把点击事件传给Launcher进程,然后Launcher调用startActivity方法,经过一系列的调用链后,由Instrumenttation通过Binder机制,发送消息到system_server进程,然后由AMS通过socket的方式通知Zygote创建应用进程,进程创建后,先实例化ActivityThread,并执行main函数,创建ApplicationThread,开启主线程消息循环loop,然后调用attach方法进行binder通信,通知system_server进程初始化Application和Activity,然后开始界面的布局和绘制;
8.应用启动优化:1)视觉上的优化,设置闪屏图片主题,可以有效消除应用启动时候的白屏现象;2)代码层面的优化,把Application里面初始化的组件分为三种,必须立即在主线程初始化的,可以延迟在主线程初始化的,以及可以在子线程初始化的,然后根据不同手机初始化需要的时间不同,动态的设置闪屏页的时间,闪屏总时间=组件初始化时间+剩余展示时间;
9.网络优化:1)请求速度优化,自己解析域名后,直接用IP地址(https地址不可以直接使用IP地址);连接复用,避免每次请求都建立连接;数据压缩优化,使用protobuf代替json;2)弱网优化,主动和被动地判断是否是弱网,判断为弱网后,切换到QUIC协议(基于UDP);
10.双token机制,首次登陆时,下发access_token和refresh_token,以及对应的过期时间,请求接口时,先判断access_token是否过期,如果没有,直接使用,如果过期了,判断refresh_token是否过期,如果没有,使用并更新两个token和过期时间,如果过期了,需要重新登录。
11.Android虚拟机,5.0之前是DVM,之后是ART,最大区别是ART在安装应用时会进行AOT预编译成机器码,提高运行效率;
12.dex文件,android把所有class文件进行合并优化,然后生成最终的dex文件,dex文件相较于class文件,减少了冗余信息,结构更紧凑,在解析的时候可以减少IO操作,提高类的查找速度。
13.JVM指令集是基于堆栈结构执行的,每条指令都很短,而Android是基于寄存器的,每条指令会更长,但是当他们做同样的计算时,android需要的指令数量更少,所以整体执行速度会更快。
网络
1.TCP和UDP,TCP是面向连接的,可靠的流协议,实行顺序控制,重发控制,流量控制,拥塞控制等保证数据的可靠,提升网络利用率;UDP面向报文,是无连接的,尽最大可能交付;
2.TCP三次握手和四次挥手:
3.TCP流量控制,流量控制是为了控制发送方发送速率,保证接收方来得及接收。靠窗口滑动实现,既保证了分组无差错、有序接收,也实现了流量控制。
4.TCP拥塞控制,TCP主要通过4个算法进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。
慢开始与拥塞避免
发送的最初,执行慢开始,令cwnd=1,发送方只能发送一个报文段;每次接收到确认后,将cwnd加倍,然后发送方能发送的报文段数量为2、4、8、16...
需要注意,因为每次cwnd都是加倍的,所以到了后面增长速度非常快,这会导致拥塞的可能性更高。所以设置一个慢开始门限ssthresh,当cwnd>=ssthresh时,进入拥塞避免,每次只将cwnd加1。
快重传与快恢复
在接收方,要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如收到M1,M2,此时再收到M4,应当发送对M2的确认。
在发送方,如果收到三个重复确认,那么就可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个M2的确认,则认为M3丢失,立即重传M3(快重传)。
这种情况下,因为只是丢失个别报文段,而不是真正的网络拥塞,所以执行快恢复,令ssthresh=cwnd/2, cwnd=ssthresh,注意到此时直接进入拥塞避免。
慢开始和快恢复的快慢指的是cwnd的设定值大小,而不是cwnd的增长速率。慢开始cwnd设定为1,而快重传cwnd设定为ssthresh。
5.HTTP:HTTP是基于TCP/IP协议的应用层协议。它不涉及数据包(packet)传输,主要规定了客户端和服务器之间的通信格式,默认使用80端口。HTTP的方法有:GET(获取资源)、HEAD(获取报文首部)、POST(传输实体主体)、PUT(上传文件)、PATCH(对资源进行部分修改)、DELETE(删除文件)、OPTIONS(查询支持的方法)、CONNECT(要求在与代理服务器通信时建立隧道)、TRACE(追踪路径);
6.HTTPS:HTTP存在以下安全性问题:
1.使用明文进行通信,内容可能会被窃听;
2.不验证通信方 的身份,通信方的身份有可能遭遇伪装;
3.无法证明报文的完整性,报文有可能被篡改。
为了解决以上安全问题,有了HTTPS协议,提供加密处理数据(加密,防窃听)、验证对方身份(认证,防伪装)以及数据完整性保护(防篡改)。
基于两种加密算法的特点,HTTPS采用的加密方式是,利用非对称密钥加密的方式,把对称密钥Secret Key传输给通信方(用对方的公钥加密数据,这样就只能用对方的私钥才能解密数据),然后使用对称密钥加密对数据加密。这样做的目的是,在保证通信安全的前提下,也能保证通信的效率。
7.
Flutter
三棵树,Widget、Element、RenderObject
Flutter中,Container、Text等组件都属于Widget,由他们构成的树结构叫Widget树,控件树,表示我们在dart代码中所写的控件的结构。widget 的定义就是 对一个 Element 配置的描述,也就是说,widget 只是一个配置的描述,并不是真正的渲染对象,就相当于是 Android 里面的 xml,只是描述了一下属性,但他并不是真正的 View
真正的组件渲染不在控件层,而是渲染层,引入了Element树和RenderingObject树。
Element可以称之为Widget另一种抽象,其实他才是实际的控件,是真正显示而代码中所写的Widget是构建组件的配置信息,当我们第一次调用build方法想在屏幕上显示组件时,Flutter会根据这些信息生成该Widget对应的Element,这些Element就形成了Element树。Element与Widget另外一个区别在于,Widget天然是不可变的,它如果需要更新重建,是使用StatefulWidget通过State对象,将之扩充到Element以及合并到树中;
而RenderObject在Flutter中做组件布局渲染工作,为了组件间的渲染搭配及布局约束,有对应的RenderObject树,也叫渲染树
生命周期
生命周期一:createState,执行StatefulWidget的构造函数后,会先执行createState函数
生命周期二:initState,此函数只会执行一次,可以在这下面做一些初始化操作
生命周期三:didChangeDependencies,当State对象的依赖项更改时被调用
生命周期四:build,在方法中创建各种组件,绘制到屏幕上,以下情况下会调用此方法:
调用initState方法后;
调用didUpdateWidget方法后;
收到setState的调用后;
此State对象的依存关系发生更改后(例如依赖的InheritedWidget发生更改)
调用deactivate后
生命周期五:didUpdateWidget,
生命周期六:deactivate,当框架从树中移除此State对象时调用,后续有可能重新插入
生命周期七:dispose当框架从树中永久移除state时调用,生命周期结束
单线程
dart是单线程的,是靠事件循环(EventLoop)实现的异步,dart中有两个队列,一个是微任务队列(Microtask Queue),一个是事件队列(Event Queue),EventLoop不断的轮询,先判断微任务队列是非为空,不为空则取出任务执行,为空再判断事件队列是否为空,取出任务执行。可以看出,微任务的优先级最高,通常情况下,微任务的使用场景有手势识别、文本输入、滚动试图等;
dart为事件队列提供了一层封装,叫Future,把一个函数体放入Future中,就完成了同步任务到异步任务的包装,Future具备链式调用的能力,可以在异步任务执行完毕后执行其他任务
Key
key是Widget的唯一标识,如果省略,flutter会自动生成一个。分为两种,LocalKey和GlobalKey,LocalKey继承自Key,在具有相同父级的Element中必须是唯一的,有三种子类,ValueKey、ObjectKey、UniqueKey。GlobalKey继承自Key,作用域是全局的,而LocalKey只作用当前层级。
key的作用是控制widget树上的widget是否被替换,当需要在一个StatefulWidget集合中进行添加、删除、重排序等操作时,需要用到key
Stream
Stream和Future都是Flutter中用来处理异步事件的对象,其中future只能处理单次异步操作,Stream具有多次响应异步事件监听的功能,是一系列异步事件的序列。
Stream从订阅模式上可以分为两类,单订阅模式和多订阅模式,当订阅只能有一个监听器对事件进行监听,即使前面的监听器取消了监听,也无法继续对这个stream进行二次监听,而多订阅模式则可以有多个监听器。
局部刷新
1.使用GlobalKey可以访问element相关联的其他对象
2.使用ValueNotifier+ValueListenableBuilder
3.把需要局部刷新的Widget抽成一个单独的StatefulWidget类,然后在这个类中setState
4.使用其他支持局部刷新的插件,比如Provider
flutter和原生通信
使用平台通道,PlatformChannel,有三种方式:
1.MethodChannel,Flutter与Native端互相调用,调用后可以返回结果,可以Native端主动调用,也可以Flutter主动调用,属于双向通信,Native端调用需要在主线程中执行,用于方法调用;
2.BasicMessageChannel,用于使用指定的编解码器对消息进行编码和解码,属于双向通信,可以native端主动调用,也可以Flutter主动调用,用于数据传递;
3.EventChannel,用于数据流(EventStreams)的通信,Native端主动发送数据给Flutter,通常用于状态的监听,比如网络变化、传感器数据等,用于数据流传递。
大厂面试题及答案
https://juejin.cn/post/6940510893901873166
Kotlin
空安全
所有变量默认是不能为空的,如果不赋值或者赋值为null,ide就会报错,不能编译。如果一个变量是可以为空的,在类型的右边加上?解除非空限制,表示这是一个可空类型,可空类型的变量调用时,使用?.的方式调用,这个写法会对变量做一次非空确认之后再调用方法,而且是保证线程安全的。除了?.的写法外,海可以用双感叹号的写法 view!!.set(),告诉编译器这里的对象一定不为空。
延迟初始化
如果定义变量的时候没法给具体的值,可以使用lateinit关键字修饰,表示这个变量会延迟初始化,这样ide就不会对变量检查初始化和报错
类型推断
声明变量的时候就赋值,那么就可以不写变量类型,编译器在编译的时候会自动补上
var和val
var是一种可读可写变量;val是一种只读变量,只能赋值一次,不能修改,和java的final类似
函数
函数的声明以fun关键字开头,返回值写在函数和参数后面,参数可以有默认值,也可以使用命名参数显示指定参数名称
基本类型
数字Int(类似的有Double、Float等)、字符Char、布尔值Boolean、数组IntArray(类似的有FloatArray,CharArray等)、字符串String
类型判断和强转
is关键字和as关键字
swith语句换成了when,省略了case和break
for遍历方式换成了in关键字,如for(i in list)
kotlin中的可以对基本数据类型以及String类型进行内容比较,相当于java中的equals
而=是对引用的内存地址进行比较,相当于java中的==
协程
通过launch函数实现线程切换的功能
suspend关键字,是一个提醒,告诉函数使用者,这是一个耗时函数,需要用挂起的方式运行在后台线程。当代码执行到suspend函数的时候会被非阻塞式的挂起,不会阻塞当前线程。接下来的工作是在suspend指定的线程下执行,执行完成之后,自动再切回原来的线程。
比如协程原来是运行在主线程中的,当代码遇到suspend函数的时候,发生线程切换,根据Dispatcher调度器的设置,切换到了IO线程,当函数执行完毕后,又自动的切会主线程。
协程的本质就是一个基于线程的上层框架,协程就是切线程,挂起就是可以自动切回来的切线程,挂起的非阻塞式指的是他用看起来阻塞的代码写出非阻塞的操作
扩展函数,屏幕适配的时候,用的今日头条的适配方案,根据手机的宽度,来按比例计算,有用到扩展函数,把宽高转成适配后的dp值
inline
被inline修饰的函数,会以内联的方式进行编译,也就是直接将函数内容插到调用处
noinline,局部关掉内联
crossinline,局部加强内联优化
by
分为两种,类委托和属性委托