自我介绍
项目经历,重点介绍工作经历和内容
Java
多线程和并发编程
a.线程和进程
线程:调度CPU资源的最先单位
b.线程状态
新建-》运行-》阻塞-》等待-》终止
New-》Runnable-》Blocked-》Waiting-》Timed_Waiting-》Terminated
c.wait和sleep
d.synchronized
e.volatile
f.死锁
两个线程互相等待的现象
线程池
线程复用,减少线程创建,销毁的开销,提升性能
参数
corePooledSize 核心线程数
maximumPoolSize 最大线程数
keepAliveTime 超时时间
unit 超时时间单位
workQueue 阻塞队列
threadFactory 线程工厂
handler拒绝策略工作原理
1.如果当前运行线程数小于corePoolSize,立即执行任务
2.如果当前运行线程数大于或等于corePoolSize,放入队列
3.队列满了且线程数小于maximumPoolSize,创建非核心线程立即执行
4.如果当前线程数大于maximumPoolSize,线程池会启动拒绝饱和策略
5.当一个线程完成任务,它会从队列中取下一个任务来执行
java基础面试题
4种引用类型
强引用StrongReference: 在程序内存不足OOM时会被回收
软引用SoftReference: 在内存不足时,JVM会回收早先创建的对象
弱引用WeakReference: JVM垃圾回收器发现就会回收
虚引用PhantomReference: 对象销毁前的一些操作,如资源释放深浅拷贝区别
接口和抽象区别
集合List,Set和Map
android基础面试题
启动模式和哪些场景会用到
横竖屏切换的声明周期
onconfigchanges配置了orientation或keyboardhidden,
onpause,onstop,onrestart,onstart,onresume,onconfigurationchanged的service启动方式
activity和fragment如何通信
android和h5通信
android调用js: webview.loadurl()
js调用android: addJavascriptInterface()service如何增加权限
自定义View,两行左对齐,一行中间对齐
子线程切换主线程刷新UI
new Handler(Looper.getMainLooper()).post()
Activity.runOnUiThread()
View.post()屏幕适配
屏幕尺寸:单位英寸
屏幕像素密度:每英寸像素点素,单位dpi
密度无关像素:dp
独立比例像素:sp
使用dp和sp指定尺寸;布局优先使用RelativeLayout;使用wrap_content,match_parent,权重 ;设置组件最小宽高minWidth,minHeight和行数lines,不破坏整体布局;定义dimens,不同的屏幕尺寸定义不同的dimens;
根据屏幕的适配加载不同的布局,配置限定符(size限定符,最小宽度限定符layout-sw600dp)
进程间通信binder
线程间通信handler
原理:
子线程Hanlder#sendMessage(),将消息发送到UI线程的MessageQueue消息队列中,Looper#loop()不断轮询消息,最后Handler#dispatchMessage()
1.一个线程有几个handler,looper,如何区分不同handler发送的消息(荣耀负一屏面试题)
2.handler让子线程和子线程通信
Looper#prepare()
Looper#loop()
3.handler导致的内存泄漏如何分析解决?
分析:
当使用内部类创建handler,handler会隐式持有外部类对象,通常是一个activity引用
- handler#postdelayed方法,在delay到达之前,messagequeue-message-handler-activity,导致activity无法释放
- 网络请求过程中关闭了activity
线程持有handler,handler持有activity,导致activity无法回收
解决:handler#removeCallbacks(),将handler声明为静态类+activity弱应用
4.Looper#loop()死循环为什么没有阻塞主线程
android是基于事件驱动模型的
循环内部会调用nativePoolOnce的native方法,这是一个C/C++方法,利用了Linux中的管道机制epoll,在没有消息的时候会调用epoll_wait进行等待,会释放CPU, 应用会处在休眠状态
自定义View
自定义下载进度按钮
绘制背景圆形矩形,绘制进度,绘制文字,加入动画ValueAnimator,执行View.invalidate方法
View事件分发和滑动冲突
1.事件的传递顺序:
Activity->PhoneWindow->DecorView->ViewGroup->View
对于一个根ViewGroup,点击事件产生后,首先会传递给它,这时它的dispatchTouchEvent方法被调用,如果这个ViewGroup#onInterceptTouchEvent方法返回true就表示拦截当前事件,接着事件就交给这个ViewGroup处理,即它的onTouchEvent方法就会被调用,如果返回false就表示它不拦截当前事件,这时事件就会传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,如此反复直至事件最终被处理
2.滑动冲突
外部拦截法:
ViewGroup#onInterceptTouchEvent方法,在ACTION_MOVE中根据需要进行拦截返回true,ACTION_DOWN和ACTION_UP需要返回false
内部拦截法:
父ViewGroup#onInterceptTouchEvent方法,不拦截ACTION_DOWN返回false,其他返回true; 子View#dispatchTouchView方法,在ACTION_MOVE如果父View需要处理事件,调用parent#requestDisallowInterceptTouchEvent(false),让父容器拦截事件
性能优化
1.卡顿优化
android系统每隔16ms发送vsync垂直同步信号,对UI进行渲染,
在一个vsync到来前,新的帧并没有生成完成,只能显示上一帧的画面,这时就出现了卡顿现象,系统运算资源比较紧张;
原因:
1.层级和过渡绘制
2.内存引起:
内存抖动,gc频繁对内存进行回收,暂停其它线程,除了gc的线程, ui线程也被暂停,ui线程要求16ms进行一次更新,影响ui渲染
3.线程引起
定位:
systrace
查看UI线程状态
紫色:有大面积生产对象的代码(事件和draw)
灰色:有锁问题
蓝色:系统资源不足
橙色:有IO问题
blockcanary
2.崩溃优化
anr分析?如果没有app堆栈信息如何分析?(荣耀负一屏面试)
结合trace.txt和mainlog主日志文件
1.anr时间点 2.主线程状态 3.问题类型
堆栈信息,内存信息和CPU负载
主线程处于BLOCK,WAITING,TIME_WAITING状态,基本就是函数阻塞导致的anr,
若主线程无异常,则排查下CPU负载,是否有其他应用抢占了CPU资源导致的ANR
如果CPU和堆栈都很正常,考虑是否内存紧张,系统日志里搜索下am_meminfo,onTrimMemory
案例:
- 主线程状态是Runnable,观察堆栈是我们自己的代码中
- 主线程状态是Blocked, 有waiting to lock held by xxx(其他线程),就是其他线程持有了锁,并长时间未释放,主线程等待这把锁发生超时了
- 主线程状态正常,查看CPU负载,各个进程占用CPU的详细情况,是否CPU被抢占
- 记录anr发生的时间点,去系统日志中搜索am_meminfo,onTrimMemory level:80 pid:XXX, 查看是否内存紧张,如果是内存紧张,会导致其他多个应用anr
- 系统服务超时,搜索BindProxy
3.启动优化
4.内存优化
OOM
内存抖动
内存泄漏
内存泄漏分析?如何分析一份内存快照?
首先Android Profiler抓取内存快照,capture heap dump点击record,选择arrange by class生成hprof文件,查看到内存泄漏的是哪个activity;在platform-tools下hprof-conv.exe转换hprof文件,打开MAT,点击Histogram柱状图,搜索内存泄漏的activity, 右键排除虚软弱应用,结合堆栈和代码,可以分析activity被哪些对象引用了
Android Profiler->Memory Allocations-》Allocation Call Stack
Heap Dump->xxx.hprof
5.存储优化
6.UI渲染优化
7.图片加载优化
8.Apk瘦身
MVP,MVC,MVVM
http和https
动画
帧动画
补间动画(淡入淡出,位移,缩放,旋转)
属性动画ObjectAnimator继承自ValueAnimator
TypeEvaluator决定了动画如何从初始值过渡到结束值
TimeInterpolator决定了动画从初始值过渡到结束值的节奏
- android中有哪些动画?
- 属性动画和view动画区别?(荣耀商城面试)
android中设计模式
jetpack
kotlin
变量
属性在声明的同时需要初始化,kotlin的变量没有默认值
- 空安全设计:
var name: String? = "Mike"
view?.setBackgroundColor(Color.RED) - 延迟初始化 lateinit
- 类型推断
在声明的时候就赋值,那不写变量类型 - val 和 var
val只读变量,它只能赋值一次,不能修改
var可读可写变量
函数
以 fun 关键字开头
返回值写在了函数和参数后面
没有返回值,kotlin里返回Unit
对象
: 继承
构造方法
constructor()
class A constructor() : B {}
class A : B() {}
open
继承:kotlin类默认是final的
open class A : B() {}override
override fun onCreate() {}
final override fun onCreate() {} 关闭override遗传性
实例化对象:没有new关键字
类型判断:is关键字;强转调用:as关键字(activity as? NewActivity)?.action()
init代码块
object关键字
object class A{}
单例类
既有 class 关键字的功能,又实现了单例,用 object 修饰的对象中的变量和函数都是静态的匿名类
val listener = object:ViewPager.SimpleOnPageChangeListener() {}
companion object
top level顶层声明
这样写的属性和函数,不属于任何 class,而是直接属于 package常量
const数组
val strs: Array
= arrayOf("a", "b", "c")
kotlin数组不支持协变,就是子类数组对象不能赋值给父类的数组变量
Kotlin 中要用专门的基本类型数组类 (IntArray FloatArray LongArray) 才可以免于装箱集合
List, Set, Map
kotlin集合是支持协变的,就是可以把子类的 List 赋值给父类的 List 变量
创建方法:listOf(),setOf(),mapOf()可变集合
mutableListOf(),mutableSetOf(),mutableMapOf()
不可变的可以通过 toMutable*() 系函数转换成可变的集合Sequence序列
可见性修饰符
public
internal 对module内可见
protected private+子类可见主构造器和次构造器
class User constructor(var name: String){}
-主构造器
constructor(name:String,id: Int): this(name)
-次构造器命名参数
sayHi(name = "wo", age = 21, isStudent = false, isFat = true, isTall = false)
嵌套函数
字符串模板
原生字符串
用法就是使用一对 """ 将字符串括起来
trimMargin() 函数去除每行前面的空格数组和集合的操作符
forEach 遍历每一个元素
filter 对每一个元素进行过滤操作
map 遍历每一个元素并执行给定表达式
flatmap 遍历每一个元素,并为每一个元素创建新的集合,最后合并到一个集合中Range
0..1000
-[0,1000]
0 until 1000
-[0,1000)
for (i in range step 2)
条件控制
if/else:
val max = if (a > b) a else b
when:
when (x) {
1 -> { println("1") }
2 -> { println("2") }
else -> { println("else") }
}
val value: Int = when (x) {
1 -> { x + 1 }
2 -> { x * 2 }
else -> { x + 5 }
}
Kotlin 中多种情况执行同一份代码时,可以将多个分支条件放在一起,用 , 符号隔开
when (x) {
1, 2 -> print("x == 1 or x == 2")
else -> print("else")
}
使用 in 检测是否在一个区间或者集合中
when (x) {
in 1..10 -> print("x 在区间 1..10 中")
in listOf(1,2) -> print("x 在集合中")
// not in
!in 10..20 -> print("x 不在区间 10..20 中")
else -> print("不在任何区间上")
}
使用 is 进行特定类型的检测
val isString = when(x) {
is String -> true
else -> false
}
省略 when 后面的参数,每一个分支条件都可以是一个布尔表达式
when {
str1.contains("a") -> print("字符串 str1 包含 a")
str2.length == 3 -> print("字符串 str2 的长度为 3")
}
for:
val array = intArrayOf(1, 2, 3, 4)
for (item in array) {
...
}
- ?:
如果左侧表达式 str?.length 结果为空,则返回右侧的值 -1
val str: String? = "Hello"
val length: Int = str?.length ?: -1
- == 和 ===
==: 基本数据类型以及 String 等类型进行内容比较,相当于 Java 中的 equals
===:内存地址进行比较,相当于 Java 中的 ==
泛型
extends> 协变 out 只能读取不能修改,只用来输出,不用来输入,你只能读我不能写我
super> 逆变 in 只能修改不能读取,只用来输入,不用来输出,你只能写我不能读我
- *号
等价于out Any,java中的? extends Object - where关键字
class Monster where T : Animal, T : Food
- reified 关键字
协程Coroutines
协程是轻量级的线程
用同步的方式写异步的代码
闭包:当函数的最后一个参数是 lambda 表达式时,可以将 lambda 写在括号外
协程三种写法:
1.runBlocking顶层函数, 线程阻塞的
2.GlobalScope.launch
3.
val coroutineScope = CoroutineScope(context)
coroutineScope.launch {
getImage(imageId)
}
//withContext切换到指定线程
//supend关键字
suspend fun getImage(imageId: Int) = withContext(Dispatchers.IO) {
...
}
4.async函数
返回的 Coroutine 实现了 Deferred 接口
启动一个协程可以使用 launch 或者 async 函数,协程其实就是这两个函数中闭包的代码块
挂起:launch ,async 或者其他函数创建的协程,在执行到某一个 suspend 函数的时候,这个协程会被「suspend」,也就是被挂起,所谓的被挂起就是切了线程,挂起就是线程调度操作