随便写一些杂七杂八的东西,内容来源为各大社区大佬分享的技术文章、最近面试中遇到的面试题,以及参考一些官方文档、书籍、博客等,汇总在一起供自己翻阅,如有错误的地方,欢迎指正!没写完,持续更新中。。。
Kotlin
1. lateinit的作用与限制规则,和by lazy的区别。
lateinit修饰var,不能修饰val,也不能修饰原始类型,使用lateinit修饰时,可以让编译器忽略初始化,后续初始化由开发者自定。
by lazy声明的同时指定了延迟初始化,在属性第一次被使用的时候自动初始化,代价就是不能修饰var。
2. let、run、also、with、apply的使用场景与区别
这五个操作符都属于作用域函数,它们的唯一目的是在对象的上下文中执行代码块。当对一个对象调用这样的函数并提供一个 lambda 表达式时,它会形成一个临时作用域。在此作用域中,可以访问该对象而无需其名称。
区别:
- 引用上下文对象的方式
run、with以及apply通过关键字this引用上下文对象,在它们的 lambda 表达式中可以像在普通的类函数中一样访问上下文对象。在大多数场景,当你访问接收者对象时你可以省略this,来让你的代码更简短。相对地,如果省略了this,就很难区分接收者对象的成员及外部对象或函数。因此,对于主要对对象成员进行操作(调用其函数或赋值其属性)的 lambda 表达式,建议将上下文对象作为接收者。(this)。let及also将上下文对象作为 lambda 表达式参数。如果没有指定参数名,对象可以用隐式默认名称it访问。it比this简短,带有it的表达式通常更容易阅读。然而,当调用对象函数或属性时,不能像this这样隐式地访问对象。因此,当上下文对象在作用域中主要用作函数调用中的参数时,使用it作为上下文对象会更好。若在代码块中使用多个变量,则it也更好。- 返回值
apply及also返回上下文对象。因此,它们可以作为辅助步骤包含在调用链中:你可以继续在同一个对象上进行链式函数调用。let、run及with返回 lambda 表达式结果。所以,在需要使用其结果给一个变量赋值,或者在需要对其结果进行链式操作等情况下,可以使用它们。还可以忽略返回值,仅使用作用域函数为变量创建一个临时作用域。 函数|对象引用| 返回值| 是否是扩展函数| | - | -- | -- | -- | |let|it| Lambda 表达式结果 | 是 | |run|this| Lambda 表达式结果 | 是 | |run| - | Lambda 表达式结果 | 不是:调用无需上下文对象 | |with|this| Lambda 表达式结果 | 不是:把上下文对象当做参数 | |apply|this| 上下文对象 | 是 | |also|it| 上下文对象 | 是 |使用场景
- 对一个非空(non-null)对象执行 lambda 表达式:
let- 将表达式作为变量引入为局部作用域中:
let- 对象配置:
apply- 对象配置并且计算结果:
run- 在需要表达式的地方运行语句:非扩展的
run- 附加效果:
also- 一个对象的一组函数调用:
with
3. ?.、!!.的使用和区别
?.对可空对象的安全调用,避免出现空指针异常。
!!.非空断言运算符(!!)将任何值转换为非空类型,若该值为空则抛出异常。
4. ==、===和equals的区别?
与Java不同,Kotlin中的
==等同于equals,比较两个对象的值;===比较内存地址。
Kotlin的===相当于Java中的==。
5. var和val的区别?
var:可变引用,具有可读和可写权限,值可变,类型不可变val:不可变引用,具有可读权限,值不可变,但是对象的属性可变
Java
1. ==和equals的区别
- ==是比较运算符,equals()方法是Object类的方法
- 如果进行比较的两个值都是数值类型,值相等就返回true
- 如果是引用类型,两者必须是同一类型,或者为父子关系才能进行比较,两个引用对象指向同个一对象才返回true
- 非new生成的Integer对象,区间-128到127之间,则比较结果为true,不在此区间结果为false
- ==比较的是两个变量的内存地址,equals比较的是两个对象的值
equals和hashcode之间的关系:
默认情况下,equals相等,hashcode必相等,hashcode相等,equals不是必相等。hashcode基于内存地址计算得出,可能会相等,虽然几率微乎其微。
2. int和Integer的区别
int是基本数据类型,Integer是int的包装类
int的默认值是0,Integer默认值是null
int可以直接使用,Integer需要实例化才能使用
int是直接存储数据,Integer是实际对象的引用,new一个Integer,实际上是生成一个指针去指向此对象
3. Override和Overload的区别
Override是子类对父类允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
Overload是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载Overload是一个类的多态性表现,而Override是子类与父类的一种多态性表现
4. String、StringBuffer和StringBuilder的区别?
String:String属于不可变对象,每次修改都会生成新的对象。StringBuilder:可变对象,非多线程安全。StringBuffer:可变对象,多线程安全。
5. Java集合体系
Set代表无序、不可重复的集合List代表有序、重复的集合Map代表具有映射关系的集合Queue代表队列集合实现
6. ArrayList和LinkedList的区别
ArrayList是实现了基于动态数组的数据结构,LinkedList是基于链表结构。- 对于随机访问的
get和set方法,ArrayList要优于LinkedList,因为LinkedList要移动指针。- 对于新增和删除操作
add和remove,LinkedList比较占优势,因为ArrayList要移动数据。
Android
1. view的绘制流程
OnMeasure(): 测量试图大小,从顶层父View到子View递归调用measure方法,measure方法又回调到OnMeasure
OnLayout(): 确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放到合适的位置上
OnDraw(): 绘制视图,viewRoot创建一个Canvas对象,然后调用OnDraw()。六个步骤:绘制视图的背景、保存画布的图层(Layer)、绘制view的内容、绘制View子视图,没有就不用、还原图层(Layer)、绘制滚动条
2. 跨进程通讯
Bundle/Intent 传递数据
AIDL
ContentProvider
广播
Socket请求
3. Android启动模式
Standard标准模式,每次启动一个activity都会创建一个实例入栈,无论这个实例是否存在
SingleTop栈顶复用模式,如果在栈顶复用,不在就创建一个新的实例
SingleTask栈内复用模式,如果已存在,将其上面的Activity销毁,使其成为栈顶
SingleInstance单实例模式,全局单例模式
4. handle机制
Android消息机制中的四大概念:
ThreadLocal:当前线程存储的数据仅能从当前线程取出。
MessageQueue:具有时间优先级的消息队列。
Looper:轮询消息队列,看是否有新的消息到来。
Handler:具体处理逻辑的地方。
过程:
- 准备工作:创建
Handler,如果是在子线程中创建,还需要调用Looper#prepare(),在Handler的构造函数中,会绑定其中的Looper和MessageQueue。- 发送消息:创建消息,使用
Handler发送。- 进入
MessageQueue:因为Handler中绑定着消息队列,所以Message很自然的被放进消息队列。Looper轮询消息队列:Looper是一个死循环,一直观察有没有新的消息到来,之后从Message取出绑定的Handler,最后调用Handler中的处理逻辑,这一切都发生在Looper循环的线程,这也是Handler能够在指定线程处理任务的原因。
5. 内存泄露与内存溢出
- 网络、文件等流忘记关闭
- 手动注册广播时,退出时忘记 unregisterReceiver()
- Service 执行完后忘记 stopSelf()
- EventBus 等观察者模式的框架忘记手动解除注册
- 单例/静态变量造成的内存泄漏
- Handle非静态内部类、匿名内部类持有外部类的引用,Activity销毁时,由于Handler可能有未执行完/正在执行的Message。导致Handler持有Activity的引用。进而导致GC无法回收Activity。
6. view事件分发
dispatchTouchEvent():负责将事件分发到其子View或当前View中。onInterceptTouchEvent():仅存在与ViewGroup中,用于拦截点击事件,优先级高于onTouchEvent。onTouchEvent():完成对点击事件的处理,可以拦截并消耗事件。
7. Activity生命周期
OnCreate表示Activity正在被创建。生命周期的第一个方法,当打开一个activity时首先回调这个方法。在这个方法中一般做一些初始化工作,例如加载界面布局资源(setContentView)、数据初始化(findviewbyid)OnRestart表示Activity正在被重新启动。当前activity从不可见变为可见状态时这个方法就会回调。一般是用户行为导致,比如用户摁home键回到桌面,当用户再次回到本activity时,当前activity 走 onRestart->onStart->onResumeOnStart表示activity正在被启动。activity可见,未出现在前台。可以理解为activity已经显示出来了,但是我们看不到,不能与之交互。OnResume表示activity已经可见,并且出现在前台,可以与用户进行交互。例如activity上有Button,此时我们就可以进行点击了。OnPause表示activity正在停止,接着很快执行onStop。注意极端情况下,本Activity跳转其他activity后快速的回到当前activity时,当前activity的生命周期:onPause->onResume,但是这个“快速回到”要很快,一般情况下都是onPause->onStop->onRestart->onStart->onResume。这里不能做太耗时操作,可以做一些数据存储,动画停止工作。OnStop表示activity即将停止,可以做一些回收工作。但是不能太耗时。OnDestroyactivity即将被销毁 ,activity生命周期最后一个回调,这里可以做一些回收工作,资源释放。
8. Service的启动方式
startServiceService会经历 onCreate -> onStart,stopService的时候直接onDestroy,如果是调用者自己直接退出而没有调用stopService的话,Service会一直在后台运行。bindServiceservice只会运行onCreate, 这个时候服务的调用者和服务绑定在一起,调用者退出了,Srevice就会调用onUnbind->onDestroyed所谓绑定在一起就共存亡了。并且这种方式还可以使得调用方(例如)调用服务上的其他的方法。
9. BroadcastReceiver
Android广播分为两个角色:
- 广播发送者
- 广播接收者 广播接收者的注册分为两种:
- 静态注册:通过
AndroidManifest的标签声明BroadcastReceiver- 动态注册:通过AMS.registerReceiver()方式注册,动态注册可在不需要的时候取消注册 广播类型:根据广播的发送方式
- 普通广播:通过Context.sendBroadcast()发送,可并行处理;
- 有序广播:指的是发送出去的广播被BroadcastReceiver按照先后顺序进行接收发送方式变为:sendOrderedBroadcast(intent);广播接收者接收广播的顺序规则(同时面向静态和动态注册的广播接收者):按照Priority属性值从大到小排序,Priority属性相同者,动态注册的广播优先;
- 系统广播:当使用系统广播的时,只需在注册广播接收者时定义相关的action即可,不需要手动发送广播(网络变化、锁屏、飞行方式等),当系统有相关操作时会自动进行系统广播;
- App应用内广播
LocalBroadcast:Android中的广播可以跨进程甚至可以跨App直接通信(exported对于有intent-filter情况下默认值为true)
由此将可能出现安全隐患如下:
- 其他App可能会针对性的发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收到广播并处理;
- 其他App可以注册与当前App一致的
intent-filter用于接收广播,获取广播具体信息。 增加安全性的方案是:- 对于同一App内部发送和接收广播,将
exported属性人为设置成false,使得非本App内部发出的此广播不被接收;- 在广播发送和接收时,都增加上相应的
permission,用于权限验证;\- 发送广播时,指定特定广播接收器所在的包名,具体是通过
intent.setPackage(packageName)指定在,这样此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。 相比于全局广播(普通广播),App应用内广播优势体现在:- 安全性更高
- 更加高效
10. LiveData数据倒灌怎么出现的,怎么解决
LiveData是粘性的,事件发送后,观察者才订阅,订阅后会收到之前的事件。
当livedata对象未添加observe监听时,就postValue,就会出现数据倒灌。
KunMinX大神所开源的一个解决此类问题的方法,为每个传入的observer对象携带一个布尔类型的值,作为其是否能进入observe方法的开关。每当有一个新的observer存进来的时候,开关默认关闭。每次setValue后,打开所有Observer的开关,允许所有observe执行。同时方法进去后,关闭当前执行的observer开关,即不能对其第二次执行了,除非你重新setValue。通过这种机制,使得不用反射技术实现LiveData的非粘性态成为了可能。
11. mvc、mvp、mvvm框架
MVC:Model-View-Controller,是一种分层解偶的框架,Model层提供本地数据和网络请求,View层处理视图,Controller处理逻辑,存在问题是Controller层和View层的划分不明显,Model层和View层的存在耦合。MVP:Model-View-Presenter,是对MVC的升级,Model层和View层与MVC的意思一致,但Model层和View层不再存在耦合,而是通过Presenter层这个桥梁进行交流。MVVM:Model-View-ViewModel,不同于上面的两个框架,ViewModel持有数据状态,当数据状态改变的时候,会自动通知View层进行更新。
12. OkHttp拦截器实现方式
OkHttp拦截器是用于在OkHttp网络请求过程中对请求和响应进行处理和修改的中间件。拦截器允许开发者在不修改现有请求的情况下,对请求和响应进行增强、记录、重试等操作。
OkHttp拦截器的实现原理如下:
- 拦截器链:拦截器链是一系列拦截器的有序集合,每个拦截器都可以对请求进行处理,并传递到下一个拦截器。拦截器链是通过递归调用实现的,每个拦截器中有一个
proceed方法,用于传递请求给下一个拦截器。当拦截器链中的最后一个拦截器完成了对请求的处理,响应将会沿着相同的路径返回。- 拦截器接口:OkHttp的拦截器接口是
Interceptor,它只有一个方法intercept,用于拦截请求和响应。在该方法中,开发者可以对请求进行修改(如添加header、重试等)并通过调用chain.proceed(request)将请求传递给下一个拦截器。- Request拦截器:Request拦截器作用于请求的发起过程,可以对请求进行修改和增强。例如,可以在请求头中添加认证信息、请求参数进行转换等。
- Response拦截器:Response拦截器作用于响应的接收过程,可以对响应进行修改和处理。例如,可以对响应数据进行解析、记录日志等。
- 拦截器的添加和顺序:通过OkHttp的Builder对象,可以使用
addInterceptor和addNetworkInterceptor方法添加自定义的拦截器。拦截器的添加顺序决定了它们被调用的顺序,先添加的拦截器先被调用。总之,OkHttp拦截器通过拦截器链和拦截器接口的配合,实现了对OkHttp请求和响应的可配置处理,提供了灵活而强大的网络请求处理能力。
13. OkHttp在多个请求中内部如何处理的
14. LRUCache缓存原理
LRUCache(最近最少使用缓存)是一种常见的缓存算法,它基于"最近最少使用"的原理来淘汰缓存中很长时间没有使用的数据。LRUCache的实现原理如下:
数据结构:LRUCache通常使用哈希表(HashMap)和双向链表(LinkedList)来实现。
哈希表:哈希表用于快速查找缓存的数据。每个缓存项都有一个唯一的键(Key),哈希表将缓存项的键和对应的值进行映射。这样可以在O(1)的时间复杂度内进行查找。
双向链表:双向链表用于维护缓存项的访问顺序。每个缓存项都可以看作是双向链表中的一个节点。最近访问的节点添加到链表的头部,而最久未经访问的节点位于链表的尾部。
缓存淘汰策略:当缓存满了,新的缓存项需要添加时,LRUCache会根据访问顺序淘汰最久未使用的缓存项。淘汰时,只需要将链表尾部的节点删除,并从哈希表中删除对应的键值对。
缓存访问操作:当访问缓存项时,LRUCache会优先查找哈希表中对应的键值对。如果存在,则将该缓存项移到链表头部,表示最近使用。如果不存在,则说明缓存中没有该项。
LRUCache的优势是较好地利用了缓存的局部性原理,将最常访问的数据保留在缓存中,减少了缓存中存储无用数据的概率,提高了缓存的命中率。它的缺点是需要额外的哈希表和双向链表来维护数据结构,占用一定的内存空间。
15. Glide缓存模式,如何配置
16. Android性能优化
1.UI布局优化
- 能用LinearLayout和FrameLayout,就不要用RelativeLayout,因为RelativeLayout控件相对比较复杂,测绘也想要耗时。
- 使用
include和merge增加复用,减少层级ViewStub按需加载,更加轻便- 复杂界面可选择
ConstraintLayout,可有效减少层级- onDraw中不要创建新的局部对象
- onDraw方法中不要做耗时的任务 2.内存优化
- 避免内存泄漏 3.启动速度优化
- 冷启动
- 热启动 4.包体大小优化
- 删除无用资源
Flutter
1. Flutter嵌入Android原生所使用的技术点
- 混合路由栈模式,native -> Flutter -> native -> Flutter
- 初始化Flutter引擎使用
FlutterEngineGroup- 渲染Flutter UI使用
FlutterActivity和FlutterView- 状态管理、路由使用
GetX- Flutter与Native通信使用
Pigeon- 网络请求、Json解析使用
Dio、json_annotation详细接入方式参考Flutter Native混合开发