Activity
Activity:一种可以包含用户界面的组件,主要用于和用户进行交互。
Activity的生命周期
典型情况下的生命周期
什么是典型情况下的生命周期?
有用户参与情况下,Activity所经过的生命周期的改变。
- 针对一个特定的
Activity
,第一次启动的时候,回调顺序:onCreate
–>onStart
–>onResume
- 当用户打开新的
Activity
或者切换到桌面的时候,回调顺序:onPause
->onStop
。特殊情况:如果新Activity
采用的是透明主题,那么当前Activity
不会回调onStop
.(是不是可以采用1px的透明像素块去保证当前应用不被杀死?) - 当用户再次回到原来的
Activity
时,回调顺序:onRestart
–>onStart
–>onResume
- 当按下
back
键的时候,回调的顺序是:onPause
–>onStop
–>onDestory
- 系统只在
Activity
异常终止时才会调用onSaveInstanceState
和onRestoreInstanceState
来存储和恢复数据,其他情况下不会触发这个过程。 - 我们知道当系统配置发生改变之后,Activity会重建,如果不想重建的话,那么我们可以通过设置
android:configChanges="各种属性"
onStart
和onResume
、onPause
和onStop
从描述上看差不多。对我们来说有什么实质性上的不同?
这两个配对的回调分别表示不同的意义,onStart
和onStop
是从Activity
是否可见的角度来回调的,而onResume
和onPause
是从Activity
是否位于前台这个角度来回调的,除了这个区别之外,使用上没有什么明显的区别。假设当前Activity为
A
,如果这时用户打开一个新的ActivityB
,那么B
的onResume
和A
的onPause
那个先执行?
旧的Activity
先onPause
,然后新的Activity
再启动。
异常情况下的生命周期分析
异常情况下的生命周期:Activity被系统回收或者由于当前设备的Configuration
发生改变从而导致Activity被销毁重建。
资源相关的系统配置发生改变导致Activity被杀死并重新创建
- 调用
onSaveInstanceState
的时机是在onStop
之前,和onPause
没有既定的时序关系,既可能在onPause
之前也有可能在其之后调用 onSaveInstanceState
保存的数据已Bundle对象作为参数传递给onRestoreInstanceState
和oncreate
(需要进行判断)方法onRestoreInstanceState
的调用时机在onStart
之后
系统只会在Activity即将被销毁且有可能重新显示的情况下才会调用onSaveInstanceState
资源内存不足导致低优先级的Activity被杀死
Activity优先级:前台Activity
> 可见但非前台Activity
> 后台Activity
启动模式
名称 | 启动模式 | 描述 |
---|---|---|
standard |
标准模式 | 每次启动一个Activity都会创建一个新的实例,不管这个实例是否已经存在 |
singleTop |
栈顶复用模式 | 如果新的Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会回调,通过此方法的参数可以取出当前请求的信息 |
singleTask |
栈内复用模式 | 只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例 |
sigleInstance |
单实例模式 | Activity只单独地位于一个任务栈中 |
注意:
任务栈是什么?
任务相关性:TaskAffinity,标识了一个Activity所需要的任务栈的名字,默认情况下,所有Activity所需的任务栈的名字为应用的名字。
- 指定启动模式有两种方式
- 通过
AndroidMenifest
- 通过
Intent
设置标志位
优先级2 > 1
- 通过
Intent
显式Intent
1
startActivity(new Intent(AActivity.this,BActivity.class));
隐式Intent
下面就是
IntentFilter的匹配规则
- 启动Activity分为两种,
显示调用
和隐式调用
。如果一个Intent
既是显示调用又是隐式调用的话,那么应该以显示调用为主。 - 隐式调用需要
Intent
能够匹配目标组件的Intentfilter
中设置的过滤信息。IntentFilter
中的过滤信息有action
,category
,data
. action
的匹配要求Intent
中的action
存在且必须和过滤规则中的其中一个action
相同,另外,action
区分大小写,大小写不用字符串相同的action
会匹配失败。category
要求Intent
中如果含有category
,那么所有的category
都必须和过滤规则中的其中一个category
相同。Intent
中可以没有category
,仍然会匹配成功。data
的匹配规则要求Intent
中必须含有data
数据,并且data
数据能够完全匹配过滤规则中的某一个data
。
Service
Service:Android中实现程序后台运行的解决方案,适合去执行一些不需要和用户交互但需要长期运行的任务。(没有界面的Activity)
Service的生命周期
- startService
onCreate()–>onStartCommand()–>startService()–>stopService()/stopSelf()–>onDestory() - bindService
onCreate()–>onBind()–>startService()–>stopService()–>unBindService()–>onDestory()
IntentService
IntentService 是Service 的子类,它使用工作线程逐一处理所有启动请求,如果不要求服务同时处理多个请求,这是最好的选择。 您只需实现 onHandIntent方法即可,该方法会接收每个启动请求的 Intent,就能够执行后台工作。
BroadcasrReceiver
广播分类
- 标准广播
- 有序广播
广播注册方式
- 静态注册
- 动态注册(记得取消注册)
ContentProvider
ContentProvider主要用于在不同的应用程序之间实现数据共享的功能,提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。
IPC机制
- IPC(进程间通信或者跨进程通信)
- 线程:CPU调度的最小单元,同时线程是一种有限的系统资源
- 进程:指一个执行单元,在PC和移动设备上指一个程序或者一个应用。
- Android中使用多进程只有一种方法,就是给四大组件
在AndroidMenifest
中指定android:process
属性。 - 使用多线程会造成如下几方面的问题
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharedPreferences的可靠性下降
- Application会多次创建
实现序列化的几种方式
Serializable
接口- 首先静态成员属于类不属于对象,所以不会参与序列化过程;
- 其次用transient关键字标记的成员变量不会参与序列化过程
- 使用简单但是开销大适用于储存或者网络传输
- 只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程
1 | private static final long serialVersionUID = 8711368828010083044L; |
- serialVersionUID工作机制:序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中(或者其他中介中),当反序列化的时候系统会检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,可以成功序列化,否则说明当前类和序列化的类相比发生了某些变换,无法正常序列化。
Parcelable
接口- 使用麻烦但是效率很高,主要用在内存序列化上
- Binder
- 当客户端发起请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个线程方法是耗时的,那么不能在UI中发起此远程请求 - 由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。 - Binder提供了`linkToDeath`和`unlinkToDeath`
Android中的IPC方式
Android中Activity、Service、BroadcastReceiver都支持在Intent中传递Bundle数据。
Bundle、文件共享、Messenger、AIDL、ContentProvider、Socket、Binder连接池
名称 | 优点 | 缺点 | 使用场景 |
---|---|---|---|
Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件间的进程间通信 |
文件共享 | 简单易用 | 不适合高并发场景,并且无法做到进程间的即时通信 | 无并发访问情形,交换简单的数据实时性不高的场景 |
AIDL | 功能强大,支持一对多并发通信,支持实时通信 | 使用较复杂,需要处理好线程同步 | 一对多通信且有RPC需求 |
Messenger | 功能一般,支持一对多串行通信,支持实时通信 | 不能很好处理高并发情形,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型 | 低并发的一对多即时通信,无RPC需求,或者无需要返回结果的RPC需求 |
ContntProvider | 在数据源访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作 | 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 | 一对多的进程间的数据共享 |
Socket | 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 | 实现细节稍微有点繁琐,不支持直接RPC | 网络数据交换 |
View的事件体系
- View是
Android
中所有控件的基类,不管是简单的Button
和TextView
还是复杂的RelativeLayout
和ListView
,它们共同的基类都是View
。 - TouchSlop是系统所能识别出的被认为是滑动的最小距离,换句话说,当手指在屏幕上滑动时,如果两次滑动之间的距离小于这个常量,那么系统就不认为你是在进行滑动操作。
- MotionEvent事件类型
ACTION_DOWN
:手指刚接触屏幕ACTION_MOVE
:手指在屏幕上移动ACTION_UP
:手指从屏幕上松开的一瞬间
- 三种滑动方式
- 使用
scrollTo
/scrollBy
(scrollTo:绝对滑动,scrollBy:相对滑动) - 使用动画(操作View的
translationX
和translationY
) - 改变布局参数(改变LayoutParams)
- 使用
名称 | 优点 | 缺点 |
---|---|---|
scrollTo/scrollBy | 操作简单,适合对View内容的滑动 | 只能滑动View的内容,并不能滑动View本身 |
动画 | 操作简单,主要适用于没有交互的View和实现复杂的动画效果 | 如果是使用View动画或者在Android3.0以下使用属性动画,均不能改变View本身的属性 |
改变布局参数 | 适合有交互的View | 操作较复杂 |
- 弹性滑动
- 使用
Scroller
(调用invalidate方法导致重绘,再去调用computeScoll)- Scroller本身并不能实现View的滑动,它需要配合View的computeScroll方法才能完成弹性滑动的效果,它不断地让View重绘,而每一次重绘距滑动起始时间会有一个时间间隔,通过这个时间间隔scroll就可以得出View当前的滑动位置,知道了滑动位置就可以通过scrollTo方法来完成View的滑动。就这样,View的每一次重绘都会导致View进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动,这就是Scroller的工作机制。
- 通过动画
- 使用延时策略(使用Handler或View的postDelayed方法,也可以使用线程的sleep方法)
- 使用
- View的事件分发机制
- dispatchTouchEvent(进行事件分发)
- onInterceptTouchEvent(是否拦截某个事件)
- onTouchEvent(处理点击事件)
1 | public boolean dispatchTouvhEvent(MotionEvent ev){ |
- 当一个点击事件产生后,它的传递过程遵循这样的顺序:
Activity
->Window
->View
- 触摸事件以
down
事件开始,中间含有数量不定的move
事件,最终以up
事件结束 - 正常情况下一个事件序列只能被一个
View
拦截且消耗,特殊情况下可以被其他View
消耗 - 某个
View
一旦决定拦截,那么这个事件序列只能由它来处理,并且它的onInterceptTouchEvent
不会再被调用。 - 某个
View
一旦开始处理事件,如果onTouchEvent
返回了false
,那么同一个时间序列中的其他事件都不会再交给它来处理,并且事件将重新交给它的父元素来处理,即父元素的onTouchEvent
会被调用。 - ViewGroup默认不拦截任何事件(
onInterceptTouchEvent
方法默认返回false
) - view没有拦截方法,只有处理方法。
- view的
onTouchEvent
默认返回true
,除非它不可点击。 - 事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View。
- View的滑动冲突
- 解决方式(外部拦截法,内部拦截法)
View的工作原理
- ViewRootImpl,连接
WindowManager
和DecorView
的纽带 - View的三大流程均是通过
ViewRoot
来完成的 - 当
Activity
对象被创建完毕后,会将DecorView
添加到Window
中,同时会创建ViewRootImpl
对象,并将ViewRootImpl
对象和DecorView
建立关联 - 理解MeasureSpec(
SpecMode
+SpecSize
)- SpecMode:
UNSPECIFIED
(不对View有任何限制);EXACTL
(精确大小):match_parent/给定大小AT_MOST
(不大于给定大小):wrap_content
- View的工作流程
- measure过程
View
的measure
过程–>ViewGroup
的measure
过程 - layout过程
- draw过程
绘制背景–>绘制自身–>绘制child
–>绘制scrollbars
- measure过程
Android的Drawable
Drawable的分类
BitmapDrawable、ShapeDrawable、LayerDrawable、StateListDrawable、LevelListDrawable、TransitionDrawable、InsertDrawable、ScaleDrawable、ClipDrawble
Android动画深入分析
View动画的种类
TranslateAnimation
平移动画、ScaleAnimation
缩放动画、RotateAnimatoin
旋转动画、AlphaAnimation
透明度动画
属性动画
使用动画的注意事项
OOM问题(避免使用帧动画)、内存泄漏(退出时及时停止)、兼容性问题、View动画的问题、不要使用px(使用dp)、动画元素的交互、硬件加速
Android的消息机制
- Android的消息机制主要是指
Handler
的运行机制,Handler的运行需要底层的MessageQueue
和Looper
的支撑。 - MessageQueue:内部存储了一组消息,以队列的形式对外提供插入和删除的工作。内部存储结构是采用单链表的数据结构存储消息列表的。
- Looper:消息循环,因为MessageQueue只是一个消息的存储单元,不能处理消息。Looper以无限循环的形式去查找有新的消息,如果有的话就去处理消息,否则就一直等待着。
- ThreadLocal作用是可以在每个线程中存储数据,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,那么Handler内部如何获取到当前线程的Looper?使用ThreadLocal。ThreadLocal可以在不停线程中互不干扰的存储并提供数据,通过ThreadLocal可以轻松地获取每个线程的Looper。
- 线程是默认没有Looper的,如果需要使用
Handler
就必须为线程创建Looper
.但是主线程中默认初始化Looper
.
Android的消息机制概述
- Handler的主要作用是将一个任务切换到某个指定的线程中去执行,为了解决子线程中无法访问UI的矛盾。
- UI线程不安全,为什么不加锁?第一,加上锁机制会让UI访问的逻辑变得复杂,第二锁机制会降低UI访问的效率。
Android的消息机制分析
ThreadLocal
- ThreadLocal是一个线程内部的数据存储类,通过它可以再指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说是无法获取到数据的。
- 一般来说,当某些数据以线程为作用域并且不同线程具有不同数据副本的时候可以考虑采用ThreadLocal
- 另一个场景就是复杂逻辑下的对象传递,比如监听器的传递。
消息队列的工作原理
- MessageQueue主要包含两个操作:插入和读取
- 内部实现是通过一个单链表的数据结构来维护消息列表
Looper的工作原理
- 它会不停地从
MessageQueue
中查看是否有新的消息,如果有新的消息就会立刻处理,否则就一直阻塞在那里。 - Handler的工作需要
Looper
,没有Looper
的线程就会报错,通过Looper.prepare()
即可为当前线程创建一个Looper
,接着通过Looper.loop()
来开启循环
Handler的工作原理
- 主要工作:消息的发送和接收过程(发送:post、send)
- Handler发送消息—>向消息队列中插入了一条消息—>MessageQueue的next方法会返回这条消息给Looper—>Looper收到消息后就开始处理—>最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用—>Handler进入处理消息的阶段
Bitmap的加载和Cache
Bitmap的高效加载
- 将
BitmapFactory.Options
的inJustDecodeBounds
参数设为true
并加载图片(不会真正加载) - 从
BitmapFactory.Options
中取出图片的原始宽高信息,他们对应于outWidth
和outHeight
参数 - 根据采样率的规则并合并目标
View
的所需大小计算出采样率inSampleSize
- 将
BitmapFactory.Options
的inJustDecodeBounds
参数设为false
,然后重新加载图片
Android中的缓存策略
LruCache、DiskLruCache
优化技术
布局优化
- 删除无用的控件和层级
- 有选择地使用性能较低的ViewGroup(RelativeLayout)
- 采用
标签、 标签和ViewStub
绘制优化(避免在onDraw方法中执行大量操作)
onDraw
中不要创建新的布局对象onDraw
方法中不要做耗时的任务,也不能执行成千上万次的循环操作
内存泄漏优化
- 开发过程避免写出有内存泄漏的代码
- 静态变量导致的内存泄漏
- 单例模式导致的内存泄漏
- 属性动画导致的内存泄漏
- 借助工具
响应速度优化和ANR日志分析
ListView和Bitmap优化
- 采用
ViewHolder
并避免在getView
中执行耗时操作 - 根据列表的滑动状态来控制任务的执行频率
- 尝试开启硬件加速来是
ListView
的滑动更加顺畅 - 通过
BitmapFactory.Options
来根据需要对图片进行采样 - 采样过程中主要用到
BitmapFactory.Options
的inSampleSize
参数
线程优化
- 采用线程池,避免程序中存在大量的
Thread
.
优化建议
- 避免创建过多地对象
- 不要过多使用枚举,枚举占用的内存空间要比整形大
- 常量请使用
static final
来修饰 - 使用一些
Android
特有的数据结构,比如SparseArray
和Pair
等,它们具有更好的性能 - 适当使用软引用和弱引用
- 采用内存缓存和磁盘缓存
- 尽量采用静态内部类,这样可以避免潜在的由于内部内而导致的内存泄漏