第 01 期:自定义 Handler 时如何有效地避免内存泄漏问题?
原因:
非静态内部类持有外部类的引用的情况下,造成外部类在使用完成后不能被系统回收内存,从而造成内存泄漏。这里Handler
持有外部类Activity
的引用,一旦Activity
被销毁,而此时Handler
依然持有Activity
引用,就会造成内存泄漏。
答:
1.使用静态内部类+弱引用
2.在销毁的时候将消息队列清空,优雅的使用lifecycle
来进行生命周期的控制处理
思考:
大多数其实不是惧怕内存泄漏,就算有一个Handler
能有多少,不算大问题(积少成多,方案简单,请务必解决),主要是怕使用Handler
进行了延时操作,导致在不需要的时候仍然进行逻辑的处理
第 02 期:Activity 与 Fragment 之间常见的几种通信方式?
答:
1.Activity
中持有多个Fragment的引用
Activity
-> Fragment
持有引用直接调用
Fragment
-> Activity
通过getActivity()
来获取宿主进行调用
Fragment
-> Fragment
通过getActivity()
来获取宿主,宿主持用其他Fragment进行调用
2.使用Bus工具
3.设置接口回调
思考:
官方推荐,在Fragment
创建的时候使用Bundle来传递数据,使用getArgument()
第 03 期:一般什么情况下会导致内存泄漏问题?
原因:
内存泄漏:生命周期长的对象持有了生命周期短的对象的引用
内存溢出:不合理的内存占用过多,导致在需要使用内存空间的时候没有了
答:
内存泄漏
1.非静态内部类持有外部类的引用,这是编译器在编译的时候自动为内部类的构造方法中添加了外部类的引用。如果内部类的生命周期大于外部类就会造成内存泄漏,如Handler
(Activity
需要销毁,但是MessageQueue
中的Message
未处理完毕),Thread
(Activity
需要销毁,但是子线程未处理完毕),Ansytask
(内部开启子线程)
2.资源读取未关闭,Cursor
,IO流
3.接收器、监听器注册没取消,BroadCast
,Bus
4.集合中的对象直接间接持有Context
5.static
静态对象持有Context
内存溢出
1.加载超大文件 超巨图Bitmap
,进行分片加载
2.循环中创建大量对象 特别是View的绘制过程中onDraw
3.过多的内存泄漏
思考:
编码的时候就要注意,对于一些可能出现的地方多关注一点
合理使用Lifecycle来进行生命周期的控制
注意View也是持有Context的
使用AndroidStudio自带工具,LeakCanary检测搞起来
第 04 期:LaunchMode 的应用场景?
答:比较复杂,建议写个demo具体查看
A启动B
standard
默认,Activity
在栈中持续添加
singleTop
Activity
在栈顶的时候复用,否则持续添加
singleTask
栈内唯一,如果有多个栈,在每个栈内唯一,而不是所有栈唯一
判断:A与B的taskAffinity
是否相同
1.相同 此时只有一个栈
1-1 存在B,将B上方所有Activity
销毁,B成为栈顶Activity
1-2 不存在B,将B添加入栈中
2.不同 此时有两个栈
2-1 存在B,在B所在栈中,将B上方所有Activity
销毁,B成为栈顶Activity
2-2 不存在B,创建新栈,将B放入栈中
singleInstance
无关taskAffinity
创建的新栈中只能存在一个Activity
且Activity
为B
已存在B,直接跳转复用B
不存在B,创建新栈,将B放入
Flag
FLAG_ACTIVITY_NEW_TASK
,不创建新栈,不复用实例,同standard
FLAG_ACTIVITY_SINGLE_TOP
,同singleTop
FLAG_ACTIVITY_CLEAR_TOP
,如果B为standard
模式,会将B和B上方Activity
都销毁,然后重新创建B
注意点:
Application跳转Activity需要添加FLAG_ACTIVITY_NEW_TASK
第 05 期:哪些情况下会导致oom问题?
见 第03期
第 06 期:如何实现多线程中的同步?
场景:网络请求,从多个来源请求数据,自己的数据请求A,第三方SDK的数据请求B,获取到所有数据后在进行操作,避免时间浪费,A与B进行分别进行异步请求
答:
使用Synchronized进行同步
使用ReentrantLock加解锁进行同步
第 07 期:Android 补间动画和属性动画的区别?
答:补间动画只支持 平移 缩放 透明度 旋转,视觉动画,其属性不变
属性动画只要提供了set方法的属性都可以进行变化,将属性进行改变不断重新绘制从而产生的动画效果
第 08 期:ANR 出现的场景以及解决方案?
答:在主线程进行了耗时操作,例如在Service,BroadCast中进行了耗时操作,应该将耗时操作放到子线程中进行操作。
一些常见的错误操作,网络请求,数据库查询等。
曾经遇到的问题:将小区的数据(超大json文件)放在主线程中进行解析,同时使用loading动画,明显可以看到手机的动画卡顿
第 09 期:谈谈 Handler 机制和原理?
private val mHandler = LifecycleHandler(this) {
LogUtils.iTag("handler",it.what)
}
{
...
val msg = Message.obtain()
msg.what = 1
mHandler.sendMessageDelayed(msg, 1000)
}
-
Message.obtain()
复用Message
,减少内存开销???在Message
被处理完成后就会放入消息池中,new
出来的Message
会在处理完成后不断加入消息池,obtain()
就是从消息池中获取一个消息 -
sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
为什么使用SystemClock.uptimeMillis()
系统启动时间而不是用System.currentTimeMillis()
系统当前时间?因为这个时间是单调前进的,而System.currentTimeMillis()
会因为用户修改时间或者网络校对时间而产生前进后退不可知的变化。系统启动时间在系统深度睡眠的时候才会停止,无法人为修改 -
looper.loop()
既然是死循环为什么不会卡死?因为queue.next()
在MessageQueue获取下一个消息的时候如果没有Message就会阻塞线程,在queue.enqueueMessage
唤醒线程 -
handler
的postDelay
, 时间准吗?不准。因为MessageQueue
是链表形式,消息需要一个一个处理,如果在第一个消息处理的过程中进行耗时操作例如Thread.sleep(time)
,并且time时间大于delay时间,那么第二个消息会在time时间过后进行处理,此时时间不准确
mHandler.postDelayed({
//9秒后打印
LogUtils.iTag("handler", "5000 - " + System.currentTimeMillis() / 1000)
}, 5000)
mHandler.postDelayed({
//4s后打印
LogUtils.iTag("handler", "4000 - " + System.currentTimeMillis() / 1000)
tryCatch { Thread.sleep(5000) }
}, 4000)
- 多个Message是如何放入MessageQueue?将放入的Message按照延时时间(没有delay为0)从头放入,最后放入的在队列的第一个。
第 10 期:抽象类与接口的区别?
抽象类体现的是is 接口体现的是like
A继承B,B继承C -> 可以认为A是B B是C A也是C
A实现B,C -> 可以认为A即像B又像C
抽象类中可以包含具体方法,可以没有抽象方法
接口中不可以包含具体方法,可以没有抽象方法
第 11 期:BroadcastReceiver 与 LocalBroadcastReceiver 有什么区别?
BroadcastReceiver
是所有的广播接收,可以接收到自己,其他应用,系统发出的广播,支持动态注册(代码注册)和静态注册(清单文件注册),不过建议使用动态注册
LocalBroadcastReceiver
是本应用的广播接收,只能接收到自己发出的广播,相对来说效率更高,安全性更高,仅支持动态注册(代码注册),大部分可以使用Bus替代
第 12 期:请简要谈一谈单例模式?
什么是单例?怎么实现?有什么好处?使用场景?如何保证唯一性?
1)什么是单例?一种设计模式,保证了在当前进程中只有一个实例
2)怎么实现?懒汉式,饿汉式,双重校验锁式,Holder静态内部类式,枚举式
3)什么好处?保证了在当前进程中只有一个实例,避免重复创建和随意修改,并且将生命周期延长到APP的生命周期,随时获取修改
4)如何保证唯一性?由于多线程的存在,建议使用Holder静态内部类的方式进行设计单例模式,因为静态方法只有调用的时候才会创建方法所在的类,而类的创建的线程安全的
class Data{
private object Holder { val instance = Data() }
companion object { fun get() = Holder.instance }
}
第 13 期:Window和DecorView是什么?DecorView又是如何和Window建立联系的?
Activity
包含一个Window
,唯一实例PhoneWindow
,Window
包含一个DecorView
,DecorView
作为最底层的View
,实质是一个FrameLayout
,内只有一个竖直方向的LinearLayout
,包含titleBar
和contentParent
,titleBar
使用ActionBar
内容,contentParent
使用setContentView(layoutRes)
传入的布局文件
第 14 期:对于 Context,你了解多少?
Context
与Application
类型相同,但是生命周期不同,需要注意不要把生命周期长的类持有生命周期短的Context
,这样会造成内存泄漏。
Context
可以主观认为是(UI-Context = Context + Theme)
和(NonUI-Context = Context)
,我们一般使用这种UI-Context
来创建布局因为他带有我们设置的主题,NonUI-Context
不带有主题,但同样可用
思考:
1.因为applicationContext
的生命周期等同于App
的生命周期,所以可以使用静态修饰,在需要使用Context
的时候可以合理的使用applicationContext
,比如获取资源文件,但是最好是在合适的地方使用合适的Context
而不是无脑使用ApplicationContext
2.如果使用Application来启动Activity,必须intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
第 15 期:SharedPreferences 是线程安全的吗?它的 commit 和 apply 方法有什么区别?
答:线程安全,SP内部使用synchronized来保证数据同步安全,进程不安全,本质上SP存储就是对文件的读写,同一个文件可以被多个进程进行修改。
commit将数据放入内存中,同步将数据写入文件有返回值true or false来判断操作是否成功
apply将数据放入内存中,异步将数据写入文件没有返回值
思考:
因为SP本质上是对文件的读写,并没有增量的概念,所以在修改的时候是全量修改,无论修改什么数据都是会将文件全部写入,这就非常不适合存储大容量数据。如果非要进行大数据修改,建议一个数据创建一个xml的SP文件,而不是只创建一个xml的SP文件存储所有
第 16 期:HashMap 的实现原理?
这题太难了 呜呜呜。。。
第 17 期:简述一下 Android 中 UI 的刷新机制?
手机屏幕刷新率为60hz,约每16ms刷新屏幕一次,就意味着CPU会计算16ms中屏幕中的界面数据发生了哪些变化,然后在16ms时间到的时候进行一次刷新,如果时间到了但是CPU没有计算完成,此时就发生了卡顿现象。
1.减少界面的层级嵌套,合理的使用布局控件ConstraintLayout,特别是注意背景的使用
2.不要在主线程尽心复杂逻辑操作
3.RecyclerView在Adapter设置Item数据的时候不要进行复杂逻辑运算,可以使用变量放在每一个实体中使用lazy进行获取,这样复用的时候也只会进行一次运算,比如时间格式化,String字符串的分割,Glide如果加载的是本地图片可以去除本地缓存
。。。还有很多。。。
第 18 期:谈谈 Android 中内存优化的方式?
第 19 期:Serializable和Parcelable的区别?
Serializable
属于Java自带,Class实现Serializable
可以在Activity
之间传递
Parcelable
是专为Android
设计,需要实现相应方法,在kotlin+AndroidX
中可以使用注解@Parcelize
第 20 期:请回答一下Android进程间的通信方式?
👀 👀 👀Android进程间通信
1、Intent
- 通过Intent存入Bundle数据跳转到另一个APP的页面,如跳转高德地图导航页,拨号
- 启动多进程的Service,如双进程APP保活
- 发送广播到另一个进程的广播接收者
2、文件共享
一个进程将数据存入文件后,另一个进程去获取文件
3、Messager
没有使用过
4、AIDL
5、ContentProvider
多用于获取应用中数据,如获取通讯录
6、Socket
网络通讯 网络请求
第 21 期:请简述一下String、StringBuffer和StringBuilder三者的区别?
String
:使用+
进行字符串的拼接,这样产生的每一个新的字符串都会重现创建一个变量来进行存放,String
是使用final
来进行修饰的,内容无法进行变化只能重新赋值
StringBuffer
:使用append
来进行字符串的拼接,线程安全,可是在多线程中进行使用
StringBuilder
:使用append
来进行字符串的拼接,线程不安全,只能在单线程进行使用
速度比较:StringBuilder > StringBuffer > String
第 22 期:请简述从点击图标开始app的启动流程?
1.点击app图标,Launcher进程使用Binder IPC向system__server进程发起startActivity请求;
2.system__server进程收到1中的请求后,向zygote进程发送创建新进程的请求;
3.zygote进程fork出新的App进程
4.App进程通过Binder IPC向system__server进程发起attachApplication请求;
5.system__server进程收到4中的请求后,通过Binder IPC向App进程发送scheduleLauncherActivity请求;
6.App进程的ApplicationThread线程收到5的请求后,通过handler向主线程发送LAUNCHER_ACTIVITY消息;
7.主线程收到6中发送来的Message后,反射创建目标Activity,回调oncreate等方法,开始执行生命周期方法,我们就可以看到应用页面了。
热启动:应用还在后台存活,直接执行Activity的onResume()方法
冷启动:应用启动,初始化Application,如果有多个进程那么会多次进行Application的初始化,然后打开启动Activity进行Activity的初始化
第 23 期:IntentService 的应用场景和使用姿势?
IntentService
继承Service
,内部自行维护一个WorkerThread
,通过重写onHandleIntent
方法来进行一步操作,该方法在子线程中,在执行完后会自行调用stopSelf
方法,来关闭Service
。IntentService
可以执行多次,每次都会按照队列一个一个执行,当所有都执行完才进行stopSelf
场景:应用更新,使用IntentService
进行下载文件,然后在通知栏显示进度条通知。
第 24 期:IntentFilter是什么?有哪些使用场景?
目前主要是在BroadCastReceiver中进行接收广播,过滤掉不需要的广播,在极光推送中可以看到
场景,发送广播的地点只有一处,但是接受广播的地点有很多地方,不同地点需要的广播不同,但是为了方便管理之使用一种广播,这个使用就需要IntentFilter来进行Receiver的筛选,这样发送一个广播,接收者可以直接收自己想要的广播。
第 25 期:回答一下什么是强、软、弱、虚引用以及它们之间的区别?
强引用:表示该引用很重要,大部分使用中的对象都是强引用,在GC的时候不会被回收
软引用:表示该引用比较重要,只有在内存不足的时候进行GC的时候会被回收
弱引用:表示该引用不怎么重要,在任何时候GC时都会被回收
虚引用:表示一个虚假的引用,实际上并没有这个引用,在GC的时候会被回收,可以用来追踪GC调用的时机
第 26 期:AsyncTask的优点和缺点?
优点:在少量异步操作的过程中使用简单
缺点:生命周期控制麻烦,如果需要使用建议添加Lifecycle
来进行控制
不建议使用,可以使用Rxjava、协程等代替
第 27 期:对于面向对象的六大基本原则了解多少?
理论知识,建议面向百度了解
第 28 期:LinearLayout, FrameLayout, RelativeLayout哪个效率高, 为什么?
主要是测量的次数,RelativeLayout
会在横向和纵向进行两次测量,LinearLayout
在使用Widget
的时候会总的测量一次,然后按照比例测量一次进行两次测量
简单布局使用LinearLayout
和FrameLayout
复杂布局使用ConstraintLayout
最后使用RelativeLayout
第 29 期:请简述一下 Android 的新特性?
Android 6.0
动态权限
Android 7.0
FileProvider来进行文件uri的获取
7.1.1 Toast BadTokenException 一些手机没有解决
Android 8.0
Http网络访问限制
通知Notification 需要Channel
所有广播需要添加动态注册
Android 9.0
HTTP无法访问
Apache HTTP 客户端弃用 一些第三方SDK会使用 在清单文件中添加
<uses-library android:name="org.apache.http.legacy"android:required="false"/>
前台服务需要添加权限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
第 30 期:请谈谈你对 MVC 和 MVP 的理解?
MVC 由于Android中对于Activity的定位不明确,导致视图层V和逻辑层C混淆在一起
MVP M==>P<==>V M进行数据获取 P进行逻辑处理 V进行对界面的展示
第 31 期:谈谈 Android 的事件分发机制?
第 32 期:谈谈 ArrayList 和 LinkedList 的区别?
ArrayList:在内存中找到一段地址连续的空间进行存放数据 查找方便插入删除麻烦
LinkedList:在内存中任意找到一个空闲的空间进行存放数据属于双链表,查询麻烦,插入删除简单
第 33 期:HandlerThread 的使用场景和用法?
Handler和Thread的封装 主要用于子线程和主线程的数据交互
第 34 期:针对RecyclerView你做了哪些优化?
RecyclerView优化 一句话思路 空间换取时间 使用内存空间来换取数据转化的时间
1.在Adapter中最好不要进行任何的逻辑操作,比如日期转换,字符串切割等等,可以在model内部自行添加一个参数使用
by lazy 来存储数据转换后的结果,这样数据逻辑操作就只执行一次,而不会随着数据回收重复计算
2.新增删除数据不刷线全部,而是刷新局部
3.布局优化,尽量少的布局嵌套,尽量少的控件
4.对于一些RecyclerView嵌套RecyclerView的布局可以进行多布局展示,而不是使用嵌套
5.资源文件的读取,初始化的时候使用 by lazy 生成
6.如果RecyclerView条目高度固定,使用setHasFixedSize(true),避免多次测量条目高度
7.对于RecyclerView,如果不需要动画,就把条目显示动画取消setSupportsChangeAnimations(false)
8.在RecyclerView添加滑动监听,一些图片加载可以在RecyclerView快速滑动的时候不进行加载图片
9.对于一个页面中的多个RecyclerView,如果使用同一个Adapter,可以使用setRecycledViewPool(pool),共用回收池,
避免来每一个RecyclerView都创建一个回收池,特别是RecyclerView嵌套RecyclerView时候,
内部的RecyclerView必定使用的都是同一个Adapter,这个时候就很有必要使用回收池了
10.视情况使用setItemViewCacheSize(size)来加大RecyclerView缓存数目,用空间换取时间提高流畅度
11.对于条目点击时间不要在复用部分进行setOnClickListener,这样会重复设置点击监听,而是应该创建一个listener对象,
传入控件的id,和当前的条目position,通过id和position判断处理点击监听
12.可以进行预加载,重写LayoutManager的getExtraLayoutSpace()方法,可以返回屏幕高度,预先加载一屏幕高度的数据,
视情况,例如:一个item就占据一个页面,RecyclerView滑动到第二张,此时第一张可见,RecyclerView无法找到可复用
的View,此时会重新new一个出来,滑动卡顿,第三张及以后可以找到复用的View,滑动流畅
第 35 期:请说一下HashMap与HashTable的区别?
没有使用过HashTable
使用key-value的健值对来进行数据存储 可以使用null最为key值,因为null也有hashcode
第 36 期:谈谈自定义View的流程?
issues 自定义View的流程
最多使用的还是组合View,因为一些界面上的元素过多,使用一个组合View将复杂界面分成一块一块,分别去处理各个模块的相关逻辑
第 37 期:谈谈如何优化ListView?
不推荐使用ListView,大部分使用列表的场景中RecyclerView的效果都比ListView的效果好
第 38 期:谈谈线程死锁,如何有效的避免线程死锁?
在安卓开发过程中基本上不会使用到线程死锁,目前个人在工作过程中除了双重校验锁单例需要自己加锁,还有一种就是分别从不同数据源中获取数据,不过在使用Rxjava后也取消了