Fork me on GitHub

Android开发艺术探索---读书笔记(1)

Activity

Activity:一种可以包含用户界面的组件,主要用于和用户进行交互。

Activity的生命周期

典型情况下的生命周期

什么是典型情况下的生命周期?
有用户参与情况下,Activity所经过的生命周期的改变。

  1. 针对一个特定的Activity,第一次启动的时候,回调顺序:onCreate–>onStart–>onResume
  2. 当用户打开新的Activity或者切换到桌面的时候,回调顺序:onPause->onStop特殊情况:如果新Activity采用的是透明主题,那么当前Activity不会回调onStop.(是不是可以采用1px的透明像素块去保证当前应用不被杀死?)
  3. 当用户再次回到原来的Activity时,回调顺序:onRestart–>onStart–>onResume
  4. 当按下back键的时候,回调的顺序是:onPause–>onStop–>onDestory
  5. 系统只在Activity异常终止时才会调用onSaveInstanceStateonRestoreInstanceState来存储和恢复数据,其他情况下不会触发这个过程。
  6. 我们知道当系统配置发生改变之后,Activity会重建,如果不想重建的话,那么我们可以通过设置android:configChanges="各种属性"

  • onStartonResumeonPauseonStop从描述上看差不多。对我们来说有什么实质性上的不同?
    这两个配对的回调分别表示不同的意义,onStartonStop是从Activity是否可见的角度来回调的,而onResumeonPause是从Activity是否位于前台这个角度来回调的,除了这个区别之外,使用上没有什么明显的区别。

  • 假设当前Activity为A,如果这时用户打开一个新的Activity B,那么BonResumeAonPause那个先执行?
    旧的ActivityonPause,然后新的Activity再启动。


异常情况下的生命周期分析

异常情况下的生命周期:Activity被系统回收或者由于当前设备的Configuration发生改变从而导致Activity被销毁重建。

资源相关的系统配置发生改变导致Activity被杀死并重新创建

  • 调用onSaveInstanceState的时机是在onStop之前,和onPause没有既定的时序关系,既可能在onPause之前也有可能在其之后调用
  • onSaveInstanceState保存的数据已Bundle对象作为参数传递给onRestoreInstanceStateoncreate(需要进行判断)方法
  • onRestoreInstanceState的调用时机在onStart之后

系统只会在Activity即将被销毁且有可能重新显示的情况下才会调用onSaveInstanceState

资源内存不足导致低优先级的Activity被杀死

Activity优先级:前台Activity > 可见但非前台Activity > 后台Activity


启动模式

名称 启动模式 描述
standard 标准模式 每次启动一个Activity都会创建一个新的实例,不管这个实例是否已经存在
singleTop 栈顶复用模式 如果新的Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会回调,通过此方法的参数可以取出当前请求的信息
singleTask 栈内复用模式 只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例
sigleInstance 单实例模式 Activity只单独地位于一个任务栈中

注意:


任务栈是什么?
任务相关性:TaskAffinity,标识了一个Activity所需要的任务栈的名字,默认情况下,所有Activity所需的任务栈的名字为应用的名字。

  • 指定启动模式有两种方式
    1. 通过AndroidMenifest
    2. 通过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机制

  1. IPC(进程间通信或者跨进程通信)
    • 线程:CPU调度的最小单元,同时线程是一种有限的系统资源
    • 进程:指一个执行单元,在PC和移动设备上指一个程序或者一个应用。
  2. Android中使用多进程只有一种方法,就是给四大组件在AndroidMenifest中指定android:process属性。
  3. 使用多线程会造成如下几方面的问题
    • 静态成员和单例模式完全失效
    • 线程同步机制完全失效
    • 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的事件体系

  1. View是Android中所有控件的基类,不管是简单的ButtonTextView还是复杂的RelativeLayoutListView,它们共同的基类都是View
  2. TouchSlop是系统所能识别出的被认为是滑动的最小距离,换句话说,当手指在屏幕上滑动时,如果两次滑动之间的距离小于这个常量,那么系统就不认为你是在进行滑动操作。
  3. MotionEvent事件类型
    • ACTION_DOWN:手指刚接触屏幕
    • ACTION_MOVE:手指在屏幕上移动
    • ACTION_UP:手指从屏幕上松开的一瞬间
  4. 三种滑动方式
    • 使用scrollTo/scrollBy(scrollTo:绝对滑动,scrollBy:相对滑动)
    • 使用动画(操作View的translationXtranslationY)
    • 改变布局参数(改变LayoutParams)
名称 优点 缺点
scrollTo/scrollBy 操作简单,适合对View内容的滑动 只能滑动View的内容,并不能滑动View本身
动画 操作简单,主要适用于没有交互的View和实现复杂的动画效果 如果是使用View动画或者在Android3.0以下使用属性动画,均不能改变View本身的属性
改变布局参数 适合有交互的View 操作较复杂
  1. 弹性滑动
    • 使用Scroller(调用invalidate方法导致重绘,再去调用computeScoll)
      • Scroller本身并不能实现View的滑动,它需要配合View的computeScroll方法才能完成弹性滑动的效果,它不断地让View重绘,而每一次重绘距滑动起始时间会有一个时间间隔,通过这个时间间隔scroll就可以得出View当前的滑动位置,知道了滑动位置就可以通过scrollTo方法来完成View的滑动。就这样,View的每一次重绘都会导致View进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动,这就是Scroller的工作机制。
    • 通过动画
    • 使用延时策略(使用Handler或View的postDelayed方法,也可以使用线程的sleep方法)
  1. View的事件分发机制
  • dispatchTouchEvent(进行事件分发)
  • onInterceptTouchEvent(是否拦截某个事件)
  • onTouchEvent(处理点击事件)
1
2
3
4
5
6
7
8
9
public boolean dispatchTouvhEvent(MotionEvent ev){
boolean consume = false;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
}else{
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
  • 当一个点击事件产生后,它的传递过程遵循这样的顺序:Activity->Window->View
  • 触摸事件以down事件开始,中间含有数量不定的move事件,最终以up事件结束
  • 正常情况下一个事件序列只能被一个View拦截且消耗,特殊情况下可以被其他View消耗
  • 某个View一旦决定拦截,那么这个事件序列只能由它来处理,并且它的onInterceptTouchEvent不会再被调用。
  • 某个View一旦开始处理事件,如果onTouchEvent返回了false,那么同一个时间序列中的其他事件都不会再交给它来处理,并且事件将重新交给它的父元素来处理,即父元素的onTouchEvent会被调用。
  • ViewGroup默认不拦截任何事件(onInterceptTouchEvent方法默认返回false
  • view没有拦截方法,只有处理方法。
  • view的onTouchEvent默认返回true,除非它不可点击。
  • 事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View。
  1. View的滑动冲突
    • 解决方式(外部拦截法,内部拦截法)

View的工作原理

  1. ViewRootImpl,连接WindowManagerDecorView的纽带
  2. View的三大流程均是通过ViewRoot来完成的
  3. Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联
  4. 理解MeasureSpec(SpecMode+SpecSize)
    • SpecMode:
    • UNSPECIFIED(不对View有任何限制);
    • EXACTL(精确大小):match_parent/给定大小
    • AT_MOST(不大于给定大小):wrap_content
  5. View的工作流程
    • measure过程
      Viewmeasure过程–>ViewGroupmeasure过程
    • layout过程
    • draw过程
      绘制背景–>绘制自身–>绘制child–>绘制scrollbars

Android的Drawable

Drawable的分类

BitmapDrawable、ShapeDrawable、LayerDrawable、StateListDrawable、LevelListDrawable、TransitionDrawable、InsertDrawable、ScaleDrawable、ClipDrawble

Android动画深入分析

View动画的种类

TranslateAnimation平移动画、ScaleAnimation缩放动画、RotateAnimatoin旋转动画、AlphaAnimation透明度动画

属性动画

使用动画的注意事项

OOM问题(避免使用帧动画)、内存泄漏(退出时及时停止)、兼容性问题、View动画的问题、不要使用px(使用dp)、动画元素的交互、硬件加速


Android的消息机制

  1. Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueueLooper的支撑。
  2. MessageQueue:内部存储了一组消息,以队列的形式对外提供插入和删除的工作。内部存储结构是采用单链表的数据结构存储消息列表的。
  3. Looper:消息循环,因为MessageQueue只是一个消息的存储单元,不能处理消息。Looper以无限循环的形式去查找有新的消息,如果有的话就去处理消息,否则就一直等待着。
  4. ThreadLocal作用是可以在每个线程中存储数据,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,那么Handler内部如何获取到当前线程的Looper?使用ThreadLocal。ThreadLocal可以在不停线程中互不干扰的存储并提供数据,通过ThreadLocal可以轻松地获取每个线程的Looper。
  5. 线程是默认没有Looper的,如果需要使用Handler就必须为线程创建Looper.但是主线程中默认初始化Looper.

Android的消息机制概述

  1. Handler的主要作用是将一个任务切换到某个指定的线程中去执行,为了解决子线程中无法访问UI的矛盾。
  2. UI线程不安全,为什么不加锁?第一,加上锁机制会让UI访问的逻辑变得复杂,第二锁机制会降低UI访问的效率。

Android的消息机制分析

ThreadLocal

  1. ThreadLocal是一个线程内部的数据存储类,通过它可以再指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说是无法获取到数据的。
  2. 一般来说,当某些数据以线程为作用域并且不同线程具有不同数据副本的时候可以考虑采用ThreadLocal
  3. 另一个场景就是复杂逻辑下的对象传递,比如监听器的传递。

消息队列的工作原理

  1. MessageQueue主要包含两个操作:插入和读取
  2. 内部实现是通过一个单链表的数据结构来维护消息列表

Looper的工作原理

  1. 它会不停地从MessageQueue中查看是否有新的消息,如果有新的消息就会立刻处理,否则就一直阻塞在那里。
  2. Handler的工作需要Looper,没有Looper的线程就会报错,通过Looper.prepare()即可为当前线程创建一个Looper,接着通过Looper.loop()来开启循环

Handler的工作原理

  1. 主要工作:消息的发送和接收过程(发送:post、send)
  2. Handler发送消息—>向消息队列中插入了一条消息—>MessageQueue的next方法会返回这条消息给Looper—>Looper收到消息后就开始处理—>最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用—>Handler进入处理消息的阶段

Bitmap的加载和Cache

Bitmap的高效加载

  1. BitmapFactory.OptionsinJustDecodeBounds参数设为true并加载图片(不会真正加载)
  2. BitmapFactory.Options中取出图片的原始宽高信息,他们对应于outWidthoutHeight参数
  3. 根据采样率的规则并合并目标View的所需大小计算出采样率inSampleSize
  4. BitmapFactory.OptionsinJustDecodeBounds参数设为false,然后重新加载图片

Android中的缓存策略

LruCache、DiskLruCache

优化技术

布局优化

  1. 删除无用的控件和层级
  2. 有选择地使用性能较低的ViewGroup(RelativeLayout)
  3. 采用标签、标签和ViewStub

绘制优化(避免在onDraw方法中执行大量操作)

  1. onDraw中不要创建新的布局对象
  2. onDraw方法中不要做耗时的任务,也不能执行成千上万次的循环操作

内存泄漏优化

  1. 开发过程避免写出有内存泄漏的代码
    • 静态变量导致的内存泄漏
    • 单例模式导致的内存泄漏
    • 属性动画导致的内存泄漏
  2. 借助工具

响应速度优化和ANR日志分析

ListView和Bitmap优化

  1. 采用ViewHolder并避免在getView中执行耗时操作
  2. 根据列表的滑动状态来控制任务的执行频率
  3. 尝试开启硬件加速来是ListView的滑动更加顺畅
  4. 通过BitmapFactory.Options来根据需要对图片进行采样
  5. 采样过程中主要用到BitmapFactory.OptionsinSampleSize参数

线程优化

  1. 采用线程池,避免程序中存在大量的Thread.

优化建议

  1. 避免创建过多地对象
  2. 不要过多使用枚举,枚举占用的内存空间要比整形大
  3. 常量请使用static final来修饰
  4. 使用一些Android特有的数据结构,比如SparseArrayPair等,它们具有更好的性能
  5. 适当使用软引用和弱引用
  6. 采用内存缓存和磁盘缓存
  7. 尽量采用静态内部类,这样可以避免潜在的由于内部内而导致的内存泄漏