前言
有一些基础的知识,可能搞了很久Android开发都没有遇到过,可能只有在面试的时候才会被问到。但是其实这些基础知识都是很重要的,所以想在这里做一下总结。可能比较零散,但是至少自己可以有个印象,假如某天需要用到了,也可以来这里查一下。或者就当做是面试题汇总也行,哈哈哈。持续更新,也就是想到什么或者看到什么就写什么的意思吧。
Android部分
Activity
生命周期常用的回调函数主要有onCreate()、onStart()、onRestart()、onResume()、onPause()、onStop()和onDestory(),主要是要理解这些回调发生在什么时候。
一些例子:
- 启动Activity: onCreate()—>onStart()—>onResume(),Activity进入运行状态。
- Activity退居后台: 当前Activity转到新的Activity界面或按Home键回到主屏: onPause()—>onStop(),进入停滞状态。
- Activity返回前台: onRestart()—>onStart()—>onResume(),再次回到运行状态。
- 当失去焦点时,会调用onPause(),当不可见时,调用onStop()
与生命周期相关的还有onSaveInstanceState() 和 onRestoreInstanceState(),但他们并不是生命周期的回调函数,也不一定会调用,但是如果需要保存一些状态时要用到。
还可以在Application中注册ActivityLifecycleCallbacks接口,得到各个Activity的生命周期,可以用来做一些统计和控制的工作。
Activity启动模式
- stardard
默认的启动模式,特点就是不管任务栈中是否存在该Activity的实例,都会生成一个新的实例。 - singleTop
在跳转时会检查任务栈的栈顶,如果当前Activity的实例位于栈顶,则复用该实例,否则生成一个新的实例。 - singleTask
如果任务栈中有当前Activity的实例,则会清空该实例上面的所有其他Activity的实例,将该实例至于栈顶。 - singleInstance
会生成一个新的任务栈,并且不会有其他实例进入该任务栈。
Service
Service(服务)是一个一种可以在后台执行长时间运行操作而没有用户界面的应用组件,具体可以看这里
Service的两种启动方式
- startService:生命周期和调用者不同,启动后若调用者未调用stopService而直接退出,Service仍会运行。
- bindService:生命周期与调用者绑定,调用者一旦退出,Service就会调用unBind() -> onDestory()
Service与IntentService
具体看这里,简单总结一下:
- IntentService是Service的子类,是一个异步的,会自动停止的服务,很好解决了传统的Service中处理完耗时操作忘记停止并销毁Service的问题
- 生成一个默认的且与线程相互独立的工作线程执行所有发送到onStartCommand()方法的Intent,可以在onHandleIntent()中处理
- 串行队列,每次只运行一个任务,不存在线程安全问题,所有任务执行完后自动停止服务,不需要自己手动调用stopSelf()来停止
Activity,Window,View
Activity 可以说是应用程序的载体(也可以理解为界面的载体,但是不界面),用户能够在上面绘制界面(Activity本身不绘制界面),并提供用户处理事件的API,维护应用程序的生命周期(Android应用程序是由多个 Activity 堆积而成,而各个 Activity 又有其独立的生命周期)。
Activity内部组合了一个Window(这是一个抽象类,具体是PhoneWindow)对象。我们自己写的扩展一个Activity时,在onCreate 方法中调用 setContentView,实际上是调用Window对象的 setContentView,所以说界面绘制全部是由Window类的实现类(PhoneWindow类)来完成的。
Broadcast广播
本地广播:
本地广播在本应用范围内传播,不用担心隐私数据泄露,不用担心别的应用伪造广播.相比全局广播,本地广播更高效.
两种注册方式:
- 静态注册:在清单文件中注册,常见的有监听设备启动,常驻注册不会随程序生命周期改变
- 动态注册:在代码中注册,随着程序的结束,也就停止接受广播了
Handler
Binder
多进程
进程等级
- 前台进程(Foreground process)
前台进程是用户当前做的事所必须的进程,如果满足下面各种情况中的一种,一个进程被认为是在前台:- 进程持有一个正在与用户交互的Activity。
- 进程持有一个Service,这个Service处于这几种状态:
- Service与用户正在交互的Activity绑定。
- Service是在前台运行的,即它调用了 startForeground()。
- Service正在执行它的生命周期回调函数(onCreate(), onStart(), or onDestroy())。
- 进程持有一个BroadcastReceiver,这个BroadcastReceiver正在执行它的 onReceive() 方法。
- 杀死前台进程需要用户交互,因为前台进程的优先级是最高的。
- 可见进程(Visible process)
如果一个进程不含有任何前台的组件,但仍可被用户在屏幕上所见。可见的进程也被认为是很重要的,一般不会被销毁,除非是为了保证所有前台进程的运行而不得不杀死可见进程的时候。当满足如下任一条件时,进程被认为是可见的:- 进程持有一个Activity,这个Activity不在前台,但是仍然被用户可见(处于onPause()调用后又没有调用onStop()的状态,比如,前台的activity打开了一个对话框,这样activity就会在其后可见)。
*进程持有一个Service,这个Service和一个可见的(或者前台的)Activity绑定。
- 进程持有一个Activity,这个Activity不在前台,但是仍然被用户可见(处于onPause()调用后又没有调用onStop()的状态,比如,前台的activity打开了一个对话框,这样activity就会在其后可见)。
- 服务进程 (Service process)
如果一个进程中运行着一个service,这个service是通过 startService() 开启的,并且不属于上面两种较高优先级的情况,这个进程就是一个服务进程。
尽管服务进程没有和用户可以看到的东西绑定,但是它们一般在做的事情是用户关心的,比如后台播放音乐,后台下载数据等。所以系统会尽量维持它们的运行,除非系统内存不足以维持前台进程和可见进程的运行需要。 - 后台进程 (Background process)
如果进程不属于上面三种情况,但是进程持有一个用户不可见的activity(activity的onStop()被调用,但是onDestroy()没有调用的状态),就认为进程是一个后台进程。
后台进程不直接影响用户体验,系统会为了前台进程、可见进程、服务进程而任意杀死后台进程。
通常会有很多个后台进程存在,它们会被保存在一个LRU (least recently used)列表中,这样就可以确保用户最近使用的activity最后被销毁,即最先销毁时间最远的activity。 - 空进程
如果一个进程不包含任何活跃的应用组件,则认为是空进程。
例如:一个进程当中已经没有数据在运行了,但是内存当中还为这个应用驻留了一个进程空间。
保存这种进程的唯一理由是为了缓存的需要,为了加快下次要启动这个进程中的组件时的启动时间。系统为了平衡进程缓存和底层内核缓存的资源,经常会杀死空进程。
进程间通信
- Bundle
- 文件共享
- 广播
- ContentProvider
- Messenger
- AIDL
- Socket
名称 | 优点 | 缺点 | 使用场景 |
---|---|---|---|
Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件间的进程间通信 |
文件共享 | 简单易用 | 不适合高并发场景,并且无法做到进程间的即时通信 | 无并发访问情形, 交换简单的数据实时性不高的场景 |
AIDL | 功能强大 | 使用稍复杂,需要处理好线程同步 | 一对多通信且有RPC需求 |
ContentProvider | 在数据源访问方面功能强大,支持一对多并发数据共享 | 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 | 一对多的进程间的数据共享 |
Messenger | 功能一般, 支持一对多串行通信,支持实时通信 | 不能很好处理高并发,不支持RPC,数据通过Message进行传输, 因此只能传输Bundle支持的数据类型 | 低并发的一对多即时通信,无RPC需求,或者无需要返回结果的RPC需求 |
Socket | 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 | 实现细节稍微有点繁琐,不支持直接的RPC | 网络数据交换 |
SparseArray
ANR
ANR全称Application Not Responding,意思就是程序未响应
在Android里, App的响应能力是由Activity Manager和Window Manager系统服务来监控的. 通常在如下两种情况下会弹出ANR对话框:
- 5s内无法响应用户输入事件(例如键盘输入, 触摸屏幕等).
- BroadcastReceiver在10s内无法结束.
造成以上两种情况的首要原因就是在主线程(UI线程)里面做了太多的阻塞耗时操作, 例如文件读写, 数据库读写, 网络查询等等.
如何避免ANR?简单来说就是不要在主线程(UI线程)里面做繁重的操作。
内存泄露
- 资源对象没有关闭造成,如查询数据库没有关闭游标
- 构造Adapter时,没有使用缓存ConvertView
- Bitmap对象在不使用时调用recycle()释放内存
- context逃逸问题
- 注册没有取消,如动态注册广播在Activity销毁前没有unregisterReceiver
- 集合对象未清理,如无用时没有释放对象的引用
- 在Activity中使用非静态的内部类,并开启一个长时间运行的线程,因为内部类持有Activity的引用,会导致Activity本来可以被gc时却长期得不到回收
性能优化
几乎是史上最全最实用的Android性能全面分析与优化方案研究
LinearLayout和RelativeLayout性能对比
- RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子View2次onMeasure
- RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。
- 在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。
布局优化
- 避免OverDraw过渡绘制
- 优化布局层级
- 避免嵌套过多无用布局
- 使用<include />标签把复杂的界面需要抽取出来
- 使用<merge />标签,因为它在优化UI结构时起到很重要的作用。目的是通过删减多余或者额外的层级,从而优化整个Android Layout的结构。核心功能就是减少冗余的层次从而达到优化UI的目的
- ViewStub 是一个隐藏的,不占用内存空间的视图对象,它可以在运行时延迟加载布局资源文件。
ListView卡顿的原因以及优化策略
重用ConvertView: 通过复用ConvertView来减少不必要的view的创建,另外Inflate操作会把xml文件实例化成相应的View实例,属于IO操作,是耗时操作。
减少findViewById()操作: 将xml文件中的元素封装成viewholder静态类,通过ConvertView的setTag和getTag方法将view与相应的holder对象绑定在一起,避免不必要的findViewById操作
避免在 getView 方法中做耗时的操作: 例如加载本地 Image 需要载入内存以及解析 Bitmap ,都是比较耗时的操作,如果用户快速滑动listview,会因为getview逻辑过于复杂耗时而造成滑动卡顿现象。用户滑动时候不要加载图片,待滑动完成再加载
Item的布局层次结构尽量简单,避免布局太深或者不必要的重绘
-
尽量能保证 Adapter 的 hasStableIds() 返回 true
这样在 notifyDataSetChanged() 的时候,如果item内容并没有变化,ListView 将不会重新绘制这个 View,达到优化的目的
-
在一些场景中,ScollView内会包含多个ListView,可以把listview的高度写死固定下来。
由于ScollView在快速滑动过程中需要大量计算每一个listview的高度,阻塞了UI线程导致卡顿现象出现,如果我们每一个item的高度都是均匀的,可以通过计算把listview的高度确定下来,避免卡顿现象出现
使用 RecycleView 代替listview: 每个item内容的变动,listview都需要去调用notifyDataSetChanged来更新全部的item,太浪费性能了。RecycleView可以实现当个item的局部刷新,并且引入了增加和删除的动态效果,在性能上和定制上都有很大的改善
ListView 中元素避免半透明: 半透明绘制需要大量乘法计算,在滑动时不停重绘会造成大量的计算,在比较差的机子上会比较卡。 在设计上能不半透明就不不半透明。实在要弄就把在滑动的时候把半透明设置成不透明,滑动完再重新设置成半透明。
尽量开启硬件加速: 硬件加速提升巨大,避免使用一些不支持的函数导致含泪关闭某个地方的硬件加速。当然这一条不只是对 ListView。
动画
逐帧动画(Drawable Animation): 加载一系列Drawable资源来创建动画,简单来说就是播放一系列的图片来实现动画效果,可以自定义每张图片的持续时间
补间动画(Tween Animation): Tween可以对View对象实现一系列简单的动画效果,比如位移,缩放,旋转,透明度等等。但是它并不会改变View属性的值,只是改变了View的绘制的位置,比如,一个按钮在动画过后,不在原来的位置,但是触发点击事件的仍然是原来的坐标。
属性动画(Property Animation): 动画的对象除了传统的View对象,还可以是Object对象,动画结束后,Object对象的属性值被实实在在的改变了
Java部分
引用类型
- 强引用
- 软引用
- 弱引用
- 虚引用
WeakReference 与 SoftReference的区别
虽然 WeakReference 与 SoftReference 都有利于提高 GC 和 内存的效率,但是 WeakReference ,一旦失去最后一个强引用,就会被 GC 回收,而软引用虽然不能阻止被回收,但是可以延迟到 JVM 内存不足的时候。
集合
HashMap
强烈推荐Java 8系列之重新认识HashMap,看过的最好的一篇文章,感觉看过这一篇之后,就不需要再看其他介绍HashMap的了
LinkedHashMap
ConcurrentHashMap
sleep 方法和 wait 方法的区别
虽然两者都是用来暂停当前运行的线程,但是 sleep() 实际上只是短暂停顿,因为它不会释放锁,而 wait() 意味着条件等待,这就是为什么该方法要释放锁,因为只有这样,其他等待的线程才能在满足条件时获取到该锁。
Java 中堆和栈的区别
JVM 中堆和栈属于不同的内存区域,使用目的也不同。
- 栈常用于保存方法帧和局部变量,而对象总是在堆上分配。
- 栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。
GC
- 引用计数
- 可达性分析
垃圾收集算法
- 标记清除
- 复制收集
- 标记整理
- 分代收集
你能保证 GC 执行吗
不能,虽然你可以调用 System.gc() 或者 Runtime.gc(),但是没有办法保证 GC 的执行。
String,StringBuilder,StringBuffer
- 如果要操作少量的数据用 = String
- 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
- 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer