标签归档:源码分析

Android Universal Image Loader 源码分析

本文为 Android 开源项目源码解析 中 Android Universal Image Loader 部分
项目地址:Android-Universal-Image-Loader,分析的版本:eb794c3,Demo 地址:UIL Demo
分析者:huxian99,校对者:GrumoonTrinea,校对状态:完成

1. 功能介绍

1.1 Android Universal Image Loader

Android Universal Image Loader 是一个强大的、可高度定制的图片缓存,本文简称为UIL
简单的说 UIL 就做了一件事——获取图片并显示在相应的控件上。

1.2 基本使用

1.2.1 初始化

添加完依赖后在ApplicationActivity中初始化ImageLoader,如下:

public class YourApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(this)
            // 添加你的配置需求
            .build();
        ImageLoader.getInstance().init(configuration);
    }
}

其中 configuration 表示ImageLoader的配置信息,可包括图片最大尺寸、线程池、缓存、下载器、解码器等等。

1.2.2 Manifest 配置
<manifest>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application
        android:name=".YourApplication"
        …… >
        ……
    </application>
</manifest>

添加网络权限。如果允许磁盘缓存,需要添加写外设的权限。

1.2.3 下载显示图片

下载图片,解析为 Bitmap 并在 ImageView 中显示。

imageLoader.displayImage(imageUri, imageView);

下载图片,解析为 Bitmap 传递给回调接口。

imageLoader.loadImage(imageUri, new SimpleImageLoadingListener() {
    @Override
    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
        // 图片处理
    }
});

以上是简单使用,更复杂 API 见本文详细设计

1.3 特点

  • 可配置度高。支持任务线程池、下载器、解码器、内存及磁盘缓存、显示选项等等的配置。
  • 包含内存缓存和磁盘缓存两级缓存。
  • 支持多线程,支持异步和同步加载。
  • 支持多种缓存算法、下载进度监听、ListView 图片错乱解决等。

2. 总体设计

2.1. 总体设计图

总体设计图
上面是 UIL 的总体设计图。整个库分为ImageLoaderEngineCacheImageDownloaderImageDecoderBitmapDisplayerBitmapProcessor五大模块,其中Cache分为MemoryCacheDiskCache两部分。

简单的讲就是ImageLoader收到加载及显示图片的任务,并将它交给ImageLoaderEngineImageLoaderEngine分发任务到具体线程池去执行,任务通过CacheImageDownloader获取图片,中间可能经过BitmapProcessorImageDecoder处理,最终转换为Bitmap交给BitmapDisplayerImageAware中显示。

2.2. UIL 中的概念

简单介绍一些概念,在4. 详细设计中会仔细介绍。
ImageLoaderEngine:任务分发器,负责分发LoadAndDisplayImageTaskProcessAndDisplayImageTask给具体的线程池去执行,本文中也称其为engine,具体参考4.2.6 ImageLoaderEngine.java

ImageAware:显示图片的对象,可以是ImageView等,具体参考4.2.9 ImageAware.java

ImageDownloader:图片下载器,负责从图片的各个来源获取输入流, 具体参考4.2.22 ImageDownloader.java

Cache:图片缓存,分为MemoryCacheDiskCache两部分。

MemoryCache:内存图片缓存,可向内存缓存缓存图片或从内存缓存读取图片,具体参考4.2.24 MemoryCache.java

DiskCache:本地图片缓存,可向本地磁盘缓存保存图片或从本地磁盘读取图片,具体参考4.2.38 DiskCache.java

ImageDecoder:图片解码器,负责将图片输入流InputStream转换为Bitmap对象, 具体参考4.2.53 ImageDecoder.java

BitmapProcessor:图片处理器,负责从缓存读取或写入前对图片进行处理。具体参考4.2.61 BitmapProcessor.java

BitmapDisplayer:Bitmap对象显示在相应的控件ImageAware上, 具体参考4.2.56 BitmapDisplayer.java

LoadAndDisplayImageTask:用于加载并显示图片的任务, 具体参考4.2.20 LoadAndDisplayImageTask.java

ProcessAndDisplayImageTask:用于处理并显示图片的任务, 具体参考4.2.19 ProcessAndDisplayImageTask.java

DisplayBitmapTask:用于显示图片的任务, 具体参考4.2.18 DisplayBitmapTask.java

3. 流程图


上图为图片加载及显示流程图,在 uil 库中给出,这里用中文重新画出。

4. 详细设计

4.1 类关系图

4.2 核心类功能介绍

4.2.1 ImageLoader.java

图片加载器,对外的主要 API,采取了单例模式,用于图片的加载和显示。

主要函数:

(1). getInstance()

得到ImageLoader的单例。通过双层是否为 null 判断提高性能。

(2). init(ImageLoaderConfiguration configuration)

初始化配置参数,参数configurationImageLoader的配置信息,包括图片最大尺寸、任务线程池、磁盘缓存、下载器、解码器等等。
实现中会初始化ImageLoaderEngine engine属性,该属性为任务分发器。

(3). displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)

加载并显示图片或加载并执行回调接口。ImageLoader 加载图片主要分为三类接口:

  • displayImage(…) 表示异步加载并显示图片到对应的ImageAware上。
  • loadImage(…) 表示异步加载图片并执行回调接口。
  • loadImageSync(…) 表示同步加载图片。

以上三类接口最终都会调用到这个函数进行图片加载。函数参数解释如下:
uri: 图片的 uri。uri 支持多种来源的图片,包括 http、https、file、content、assets、drawable 及自定义,具体介绍可见ImageDownloader
imageAware: 一个接口,表示需要加载图片的对象,可包装 View。
options: 图片显示的配置项。比如加载前、加载中、加载失败应该显示的占位图片,图片是否需要在磁盘缓存,是否需要在内存缓存等。
listener: 图片加载各种时刻的回调接口,包括开始加载、加载失败、加载成功、取消加载四个时刻的回调函数。
progressListener: 图片加载进度的回调接口。

函数流程图如下:
ImageLoader Display Image Flow Chart

4.2.2 ImageLoaderConfiguration.java

ImageLoader的配置信息,包括图片最大尺寸、线程池、缓存、下载器、解码器等等。

主要属性:

(1). Resources resources

程序本地资源访问器,用于加载DisplayImageOptions中设置的一些 App 中图片资源。

(2). int maxImageWidthForMemoryCache

内存缓存的图片最大宽度。

(3). int maxImageHeightForMemoryCache

内存缓存的图片最大高度。

(4). int maxImageWidthForDiskCache

磁盘缓存的图片最大宽度。

(5). int maxImageHeightForDiskCache

磁盘缓存的图片最大高度。

(6). BitmapProcessor processorForDiskCache

图片处理器,用于处理从磁盘缓存中读取到的图片。

(7). Executor taskExecutor

ImageLoaderEngine中用于执行从源获取图片任务的 Executor。

(18). Executor taskExecutorForCachedImages

ImageLoaderEngine中用于执行从缓存获取图片任务的 Executor。

(19). boolean customExecutor

用户是否自定义了上面的 taskExecutor。

(20). boolean customExecutorForCachedImages

用户是否自定义了上面的 taskExecutorForCachedImages。

(21). int threadPoolSize

上面两个默认线程池的核心池大小,即最大并发数。

(22). int threadPriority

上面两个默认线程池的线程优先级。

(23). QueueProcessingType tasksProcessingType

上面两个默认线程池的线程队列类型。目前只有 FIFO, LIFO 两种可供选择。

(24). MemoryCache memoryCache

图片内存缓存。

(25). DiskCache diskCache

图片磁盘缓存,一般放在 SD 卡。

(26). ImageDownloader downloader

图片下载器。

(27). ImageDecoder decoder

图片解码器,内部可使用我们常用的BitmapFactory.decode(…)将图片资源解码成Bitmap对象。

(28). DisplayImageOptions defaultDisplayImageOptions

图片显示的配置项。比如加载前、加载中、加载失败应该显示的占位图片,图片是否需要在磁盘缓存,是否需要在内存缓存等。

(29). ImageDownloader networkDeniedDownloader

不允许访问网络的图片下载器。

(30). ImageDownloader slowNetworkDownloader

慢网络情况下的图片下载器。

4.2.3 ImageLoaderConfiguration.Builder.java 静态内部类

Builder 模式,用于构造参数繁多的ImageLoaderConfiguration
其属性与ImageLoaderConfiguration类似,函数多是属性设置函数。

主要函数及含义:

(1). build()

按照配置,生成 ImageLoaderConfiguration。代码如下:

public ImageLoaderConfiguration build() {
    initEmptyFieldsWithDefaultValues();
    return new ImageLoaderConfiguration(this);
}
(2). initEmptyFieldsWithDefaultValues()

初始化值为null的属性。若用户没有配置相关项,UIL 会通过调用DefaultConfigurationFactory中的函数返回一个默认值当配置。
taskExecutorForCachedImagestaskExecutorImageLoaderEnginetaskDistributor的默认值如下:

parameters taskDistributor taskExecutorForCachedImages/taskExecutor
corePoolSize 0 3
maximumPoolSize Integer.MAX_VALUE 3
keepAliveTime 60 0
unit SECONDS MILLISECONDS
workQueue SynchronousQueue LIFOLinkedBlockingDeque / LinkedBlockingQueue
priority 5 3

diskCacheFileNameGenerator默认值为HashCodeFileNameGenerator
memoryCache默认值为LruMemoryCache。如果内存缓存不允许缓存一张图片的多个尺寸,则用FuzzyKeyMemoryCache做封装,同一个图片新的尺寸会覆盖缓存中该图片老的尺寸。
diskCache默认值与diskCacheSizediskCacheFileCount值有关,如果他们有一个大于 0,则默认为LruDiskCache,否则使用无大小限制的UnlimitedDiskCache
downloader默认值为BaseImageDownloader
decoder默认值为BaseImageDecoder
详细及其他属性默认值请到DefaultConfigurationFactory中查看。

(3). denyCacheImageMultipleSizesInMemory()

设置内存缓存不允许缓存一张图片的多个尺寸,默认允许。
后面会讲到 View 的 getWidth() 在初始化前后的不同值与这个设置的关系。

(4). diskCacheSize(int maxCacheSize)

设置磁盘缓存的最大字节数,如果大于 0 或者下面的maxFileCount大于 0,默认的DiskCache会用LruDiskCache,否则使用无大小限制的UnlimitedDiskCache

(5). diskCacheFileCount(int maxFileCount)

设置磁盘缓存文件夹下最大文件数,如果大于 0 或者上面的maxCacheSize大于 0,默认的DiskCache会用LruDiskCache,否则使用无大小限制的UnlimitedDiskCache

4.2.4 ImageLoaderConfiguration.NetworkDeniedImageDownloader.java 静态内部类

不允许访问网络的图片下载器,实现了ImageDownloader接口。
实现也比较简单,包装一个ImageDownloader对象,通过在 getStream(…) 函数中禁止 Http 和 Https Scheme 禁止网络访问,如下:

@Override
public InputStream getStream(String imageUri, Object extra) throws IOException {
    switch (Scheme.ofUri(imageUri)) {
        case HTTP:
        case HTTPS:
            throw new IllegalStateException();
        default:
            return wrappedDownloader.getStream(imageUri, extra);
    }
}
4.2.5 ImageLoaderConfiguration.SlowNetworkImageDownloader.java 静态内部类

慢网络情况下的图片下载器,实现了ImageDownloader接口。
通过包装一个ImageDownloader对象实现,在 getStream(…) 函数中当 Scheme 为 Http 和 Https 时,用FlushedInputStream代替InputStream处理慢网络情况,具体见后面FlushedInputStream的介绍。

4.2.6 ImageLoaderEngine.java

LoadAndDisplayImageTaskProcessAndDisplayImageTask任务分发器,负责分发任务给具体的线程池。

主要属性:

(1). ImageLoaderConfiguration configuration

ImageLoader的配置信息,可包括图片最大尺寸、线程池、缓存、下载器、解码器等等。

(2). Executor taskExecutor

用于执行从源获取图片任务的 Executor,为configuration中的 taskExecutor,如果为null,则会调用DefaultConfigurationFactory.createExecutor(…)根据配置返回一个默认的线程池。

(3). Executor taskExecutorForCachedImages

用于执行从缓存获取图片任务的 Executor,为configuration中的 taskExecutorForCachedImages,如果为null,则会调用DefaultConfigurationFactory.createExecutor(…)根据配置返回一个默认的线程池。

(4). Executor taskDistributor

任务分发线程池,任务指LoadAndDisplayImageTaskProcessAndDisplayImageTask,因为只需要分发给上面的两个 Executor 去执行任务,不存在较耗时或阻塞操作,所以用无并发数(Int 最大值)限制的线程池即可。

(5). Map<integer, string=””> cacheKeysForImageAwares

ImageAware与内存缓存 key 对应的 map,key 为ImageAware的 id,value 为内存缓存的 key。

(6). Map<string, reentrantlock=””> uriLocks

图片正在加载的重入锁 map,key 为图片的 uri,value 为标识其正在加载的重入锁。

(7). AtomicBoolean paused

是否被暂停。如果为true,则所有新的加载或显示任务都会等待直到取消暂停(为false)。

(8). AtomicBoolean networkDenied

是否不允许访问网络,如果为true,通过ImageLoadingListener.onLoadingFailed(…)获取图片,则所有不在缓存中需要网络访问的请求都会失败,返回失败原因为网络访问被禁止

(9). AtomicBoolean slowNetwork

是否是慢网络情况,如果为true,则自动调用SlowNetworkImageDownloader下载图片。

(10). Object pauseLock

暂停的等待锁,可在engine被暂停后调用这个锁等待。

主要函数:

(1). void submit(final LoadAndDisplayImageTask task)

添加一个LoadAndDisplayImageTask。直接用taskDistributor执行一个 Runnable,在 Runnable 内部根据图片是否被磁盘缓存过确定使用taskExecutorForCachedImages还是taskExecutor执行该 task。

(2). void submit(ProcessAndDisplayImageTask task)

添加一个ProcessAndDisplayImageTask。直接用taskExecutorForCachedImages执行该 task。

(3). void pause()

暂停图片加载任务。所有新的加载或显示任务都会等待直到取消暂停(为false)。

(4). void resume()

继续图片加载任务。

(5). stop()

暂停所有加载和显示图片任务并清除这里的内部属性值。

(6). fireCallback(Runnable r)

taskDistributor立即执行某个任务。

(7). getLockForUri(String uri)

得到某个 uri 的重入锁,如果不存在则新建。

(8). createTaskExecutor()

调用DefaultConfigurationFactory.createExecutor(…)创建一个线程池。

(9). getLoadingUriForView(ImageAware imageAware)

得到某个imageAware正在加载的图片 uri。

(10). prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey)

准备开始一个Task。向cacheKeysForImageAwares中插入ImageAware的 id 和图片在内存缓存中的 key。

(11). void cancelDisplayTaskFor(ImageAware imageAware)

取消一个显示任务。从cacheKeysForImageAwares中删除ImageAware对应元素。

(12). denyNetworkDownloads(boolean denyNetworkDownloads)

设置是否不允许网络访问。

(13). handleSlowNetwork(boolean handleSlowNetwork)

设置是否慢网络情况。

4.2.7 DefaultConfigurationFactory.java

ImageLoaderConfigurationImageLoaderEngine提供一些默认配置。

主要函数:

(1). createExecutor(int threadPoolSize, int threadPriority, QueueProcessingType tasksProcessingType)

创建线程池。
threadPoolSize表示核心池大小(最大并发数)。
threadPriority表示线程优先级。
tasksProcessingType表示线程队列类型,目前只有 FIFO, LIFO 两种可供选择。
内部实现会调用createThreadFactory(…)返回一个支持线程优先级设置,并且以固定规则命名新建的线程的线程工厂类DefaultConfigurationFactory.DefaultThreadFactory

(2). createTaskDistributor()

ImageLoaderEngine中的任务分发器taskDistributor提供线程池,该线程池为 normal 优先级的无并发大小限制的线程池。

(3). createFileNameGenerator()

返回一个HashCodeFileNameGenerator对象,即以 uri HashCode 为文件名的文件名生成器。

(4). createDiskCache(Context context, FileNameGenerator diskCacheFileNameGenerator, long diskCacheSize, int diskCacheFileCount)

创建一个 Disk Cache。如果 diskCacheSize 或者 diskCacheFileCount 大于 0,返回一个LruDiskCache,否则返回无大小限制的UnlimitedDiskCache

(5). createMemoryCache(Context context, int memoryCacheSize)

创建一个 Memory Cache。返回一个LruMemoryCache,若 memoryCacheSize 为 0,则设置该内存缓存的最大字节数为 App 最大可用内存的 1/8。
这里 App 的最大可用内存也支持系统在 Honeycomb 之后(ApiLevel >= 11) application 中android:largeHeap="true"的设置。

(6). createImageDownloader(Context context)

创建图片下载器,返回一个BaseImageDownloader

(7). createImageDecoder(boolean loggingEnabled)

创建图片解码器,返回一个BaseImageDecoder

(8). createBitmapDisplayer()

创建图片显示器,返回一个SimpleBitmapDisplayer

4.2.8 DefaultConfigurationFactory.DefaultThreadFactory

默认的线程工厂类,为
DefaultConfigurationFactory.createExecutor(…)

DefaultConfigurationFactory.createTaskDistributor(…)
提供线程工厂。支持线程优先级设置,并且以固定规则命名新建的线程。

PS:重命名线程是个很好的习惯,它的一大作用就是方便问题排查,比如性能优化,用 TraceView 查看线程,根据名字很容易分辨各个线程。

4.2.9 ImageAware.java

需要显示图片的对象的接口,可包装 View 表示某个需要显示图片的 View。

主要函数:

(1). View getWrappedView()

得到被包装的 View,图片在该 View 上显示。

(2). getWidth() 与 getHeight()

得到宽度高度,在计算图片缩放比例时会用到。

(3). getId()

得到唯一标识 id。ImageLoaderEngine中用这个 id 标识正在加载图片的ImageAware和图片内存缓存 key 的对应关系,图片请求前会将内存缓存 key 与新的内存缓存 key 进行比较,如果不相等,则之前的图片请求会被取消。这样当ImageAware被复用时就不会因异步加载(前面任务未取消)而造成错乱了。

4.2.10 ViewAware.java

封装 Android View 来显示图片的抽象类,实现了ImageAware接口,利用Reference来 Warp View 防止内存泄露。

主要函数:

(1). ViewAware(View view, boolean checkActualViewSize)

构造函数。
view表示需要显示图片的对象。
checkActualViewSize表示通过getWidth()getHeight()获取图片宽高时返回真实的宽和高,还是LayoutParams的宽高,true 表示返回真实宽和高。
如果为true会导致一个问题,View在还没有初始化完成时加载图片,这时它的真实宽高为 0,会取它LayoutParams的宽高,而图片缓存的 key 与这个宽高有关,所以当View初始化完成再次需要加载该图片时,getWidth()getHeight()返回的宽高都已经变化,缓存 key 不一样,从而导致缓存命中失败会再次从网络下载一次图片。可通过ImageLoaderConfiguration.Builder.denyCacheImageMultipleSizesInMemory()设置不允许内存缓存缓存一张图片的多个尺寸。

(2). setImageDrawable(Drawable drawable)

如果当前操作在主线程并且 View 没有被回收,则调用抽象函数setImageDrawableInto(Drawable drawable, View view)去向View设置图片。

(3). setImageBitmap(Bitmap bitmap)

如果当前操作在主线程并且 View 没有被回收,则调用抽象函数setImageBitmapInto(Bitmap bitmap, View view)去向View设置图片。

4.2.11 ImageViewAware.java

封装 Android ImageView 来显示图片的ImageAware,继承了ViewAware,利用Reference来 Warp View 防止内存泄露。
如果getWidth()函数小于等于 0,会利用反射获取mMaxWidth的值作为宽。
如果getHeight()函数小于等于 0,会利用反射获取mMaxHeight的值作为高。

4.2.12 NonViewAware.java

仅包含处理图片相关信息却没有需要显示图片的 View 的ImageAware,实现了ImageAware接口。常用于加载图片后调用回调接口而不是显示的情况。

4.2.13 DisplayImageOptions.java

图片显示的配置项。比如加载前、加载中、加载失败应该显示的占位图片,图片是否需要在磁盘缓存,是否需要在 memory 缓存等。

主要属性及含义:

(1). int imageResOnLoading

图片正在加载中的占位图片的 resource id,优先级比下面的imageOnLoading高,当存在时,imageOnLoading不起作用。

(2). int imageResForEmptyUri

空 uri 时的占位图片的 resource id,优先级比下面的imageForEmptyUri高,当存在时,imageForEmptyUri不起作用。

(3). int imageResOnFail

加载失败时的占位图片的 resource id,优先级比下面的imageOnFail高,当存在时,imageOnFail不起作用。

(4). Drawable imageOnLoading

加载中的占位图片的 drawabled 对象,默认为 null。

(5). Drawable imageForEmptyUri

空 uri 时的占位图片的 drawabled 对象,默认为 null。

(6). Drawable imageOnFail

加载失败时的占位图片的 drawabled 对象,默认为 null。

(7). boolean resetViewBeforeLoading

在加载前是否重置 view,通过 Builder 构建的对象默认为 false。

(8). boolean cacheInMemory

是否缓存在内存中,通过 Builder 构建的对象默认为 false。

(9). boolean cacheOnDisk

是否缓存在磁盘中,通过 Builder 构建的对象默认为 false。

(10). ImageScaleType imageScaleType

图片的缩放类型,通过 Builder 构建的对象默认为IN_SAMPLE_POWER_OF_2

(11). Options decodingOptions;

为 BitmapFactory.Options,用于BitmapFactory.decodeStream(imageStream, null, decodingOptions)得到图片尺寸等信息。

(12). int delayBeforeLoading

设置在开始加载前的延迟时间,单位为毫秒,通过 Builder 构建的对象默认为 0。

(13). boolean considerExifParams

是否考虑图片的 EXIF 信息,通过 Builder 构建的对象默认为 false。

(14). Object extraForDownloader

下载器需要的辅助信息。下载时传入ImageDownloader.getStream(String, Object)的对象,方便用户自己扩展,默认为 null。

(15). BitmapProcessor preProcessor

缓存在内存之前的处理程序,默认为 null。

(16). BitmapProcessor postProcessor

缓存在内存之后的处理程序,默认为 null。

(17). BitmapDisplayer displayer

图片的显示方式,通过 Builder 构建的对象默认为SimpleBitmapDisplayer

(18). Handler handler

handler 对象,默认为 null。

(19). boolean isSyncLoading

是否同步加载,通过 Builder 构建的对象默认为 false。

4.2.14 DisplayImageOptions.Builder.java 静态内部类

Builder 模式,用于构造参数繁多的DisplayImageOptions
其属性与DisplayImageOptions类似,函数多是属性设置函数。

4.2.15 ImageLoadingListener.java

图片加载各种时刻的回调接口,可在图片加载的某些点做监听。
包括开始加载(onLoadingStarted)、加载失败(onLoadingFailed)、加载成功(onLoadingComplete)、取消加载(onLoadingCancelled)四个回调函数。

4.2.16 SimpleImageLoadingListener.java

实现ImageLoadingListener接口,不过各个函数都是空实现,表示不在 Image 加载过程中做任何回调监听。
ImageLoader.displayImage(…)函数中当入参listener为空时的默认值。

4.2.17 ImageLoadingProgressListener.java

Image 加载进度的回调接口。其中抽象函数

void onProgressUpdate(String imageUri, View view, int current, int total)

会在获取图片存储到文件系统时被回调。其中total表示图片总大小,为网络请求结果Response Headercontent-length字段,如果不存在则为 -1。

4.2.18 DisplayBitmapTask.java

显示图片的Task,实现了Runnable接口,必须在主线程调用。

主要函数:

(1) run()

首先判断imageAware是否被 GC 回收,如果是直接调用取消加载回调接口ImageLoadingListener.onLoadingCancelled(…)
否则判断imageAware是否被复用,如果是直接调用取消加载回调接口ImageLoadingListener.onLoadingCancelled(…)
否则调用displayer显示图片,并将imageAware从正在加载的 map 中移除。调用加载成功回调接口ImageLoadingListener.onLoadingComplete(…)

对于 ListView 或是 GridView 这类会缓存 Item 的 View 来说,单个 Item 中如果含有 ImageView,在滑动过程中可能因为异步加载及 View 复用导致图片错乱,这里对imageAware是否被复用的判断就能很好的解决这个问题。原因类似:Android ListView 滑动过程中图片显示重复错位闪烁问题原因及解决方案

4.2.19 ProcessAndDisplayImageTask.java

处理并显示图片的Task,实现了Runnable接口。

主要函数:

(1) run()

主要通过 imageLoadingInfo 得到BitmapProcessor处理图片,并用处理后的图片和配置新建一个DisplayBitmapTaskImageAware中显示图片。

4.2.20 LoadAndDisplayImageTask.java

加载并显示图片的Task,实现了Runnable接口,用于从网络、文件系统或内存获取图片并解析,然后调用DisplayBitmapTaskImageAware中显示图片。

主要函数:

(1) run()

获取图片并显示,核心代码如下:

bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null || bmp.isRecycled()) {
    bmp = tryLoadBitmap();
    ...
    ...
    ...
    if (bmp != null && options.isCacheInMemory()) {
        L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
        configuration.memoryCache.put(memoryCacheKey, bmp);
    }
}
……
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);

从上面代码段中可以看到先是从内存缓存中去读取 bitmap 对象,若 bitmap 对象不存在,则调用 tryLoadBitmap() 函数获取 bitmap 对象,获取成功后若在 DisplayImageOptions.Builder 中设置了 cacheInMemory(true), 同时将 bitmap 对象缓存到内存中。
最后新建DisplayBitmapTask显示图片。

函数流程图如下:
Load and Display Image Task Flow Chart

  1. 判断图片的内存缓存是否存在,若存在直接执行步骤 8;
  2. 判断图片的磁盘缓存是否存在,若存在直接执行步骤 5;
  3. 从网络上下载图片;
  4. 将图片缓存在磁盘上;
  5. 将图片 decode 成 bitmap 对象;
  6. 根据DisplayImageOptions配置对图片进行预处理(Pre-process Bitmap);
  7. 将 bitmap 对象缓存到内存中;
  8. 根据DisplayImageOptions配置对图片进行后处理(Post-process Bitmap);
  9. 执行DisplayBitmapTask将图片显示在相应的控件上。
    流程图可以参见3. 流程图
(2) tryLoadBitmap()

从磁盘缓存或网络获取图片,核心代码如下:

File imageFile = configuration.diskCache.get(uri);
if (imageFile != null && imageFile.exists()) {
    ...
    bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
}
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
    ...
    String imageUriForDecoding = uri;
    if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
        imageFile = configuration.diskCache.get(uri);
        if (imageFile != null) {
            imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
        }
    }
    checkTaskNotActual();
    bitmap = decodeImage(imageUriForDecoding);
    ...
}

首先根据 uri 看看磁盘中是不是已经缓存了这个文件,如果已经缓存,调用 decodeImage 函数,将图片文件 decode 成 bitmap 对象;
如果 bitmap 不合法或缓存文件不存在,判断是否需要缓存在磁盘,需要则调用tryCacheImageOnDisk()函数去下载并缓存图片到本地磁盘,再通过decodeImage(imageUri)函数将图片文件 decode 成 bitmap 对象,否则直接通过decodeImage(imageUriForDecoding)下载图片并解析。

(3) tryCacheImageOnDisk()

下载图片并存储在磁盘内,根据磁盘缓存图片最长宽高的配置处理图片。

    loaded = downloadImage();

主要就是这一句话,调用下载器下载并保存图片。
如果你在ImageLoaderConfiguration中还配置了maxImageWidthForDiskCache或者maxImageHeightForDiskCache,还会调用resizeAndSaveImage()函数,调整图片尺寸,并保存新的图片文件。

(4) downloadImage()

下载图片并存储在磁盘内。调用getDownloader()得到ImageDownloader去下载图片。

(4) resizeAndSaveImage(int maxWidth, int maxHeight)

从磁盘缓存中得到图片,重新设置大小及进行一些处理后保存。

(5) getDownloader()

根据ImageLoaderEngine配置得到下载器。
如果不允许访问网络,则使用不允许访问网络的图片下载器NetworkDeniedImageDownloader;如果是慢网络情况,则使用慢网络情况下的图片下载器SlowNetworkImageDownloader;否则直接使用ImageLoaderConfiguration中的downloader

4.2.21 ImageLoadingInfo.java

加载和显示图片任务需要的信息。
String uri 图片 url。
String memoryCacheKey 图片缓存 key。
ImageAware imageAware 需要加载图片的对象。
ImageSize targetSize 图片的显示尺寸。
DisplayImageOptions options 图片显示的配置项。
ImageLoadingListener listener 图片加载各种时刻的回调接口。
ImageLoadingProgressListener progressListener 图片加载进度的回调接口。
ReentrantLock loadFromUriLock 图片加载中的重入锁。

4.2.22 ImageDownloader.java

图片下载接口。待实现函数

getStream(String imageUri, Object extra)

表示通过 uri 得到 InputStream。
通过内部定义的枚举Scheme, 可以看出 UIL 支持哪些图片来源。

HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN("");
4.2.23 BaseImageDownloader.java

ImageDownloader的具体实现类。得到上面各种Scheme对应的图片 InputStream。

主要函数

(1). getStream(String imageUri, Object extra)

getStream(…)函数内根据不同Scheme类型获取图片输入流。

@Override
public InputStream getStream(String imageUri, Object extra) throws IOException {
    switch (Scheme.ofUri(imageUri)) {
        case HTTP:
        case HTTPS:
            return getStreamFromNetwork(imageUri, extra);
        case FILE:
            return getStreamFromFile(imageUri, extra);
        case CONTENT:
            return getStreamFromContent(imageUri, extra);
        case ASSETS:
            return getStreamFromAssets(imageUri, extra);
        case DRAWABLE:
            return getStreamFromDrawable(imageUri, extra);
        case UNKNOWN:
        default:
            return getStreamFromOtherSource(imageUri, extra);
    }
}

具体见下面各函数介绍。

(2). getStreamFromNetwork(String imageUri, Object extra)

通过HttpURLConnection从网络获取图片的InputStream。支持 response code 为 3xx 的重定向。这里有个小细节代码如下:

try {
    imageStream = conn.getInputStream();
} catch (IOException e) {
    // Read all data to allow reuse connection (http://bit.ly/1ad35PY)
    IoUtils.readAndCloseStream(conn.getErrorStream());
    throw e;
}

在发生异常时会调用conn.getErrorStream()继续读取 Error Stream,这是为了利于网络连接回收及复用。但有意思的是在 Froyo(2.2) 之前,HttpURLConnection 有个重大 Bug,调用 close() 函数会影响连接池,导致连接复用失效,不少库通过在 2.3 之前使用 AndroidHttpClient 解决这个问题。

(3). getStreamFromFile(String imageUri, Object extra)

从文件系统获取图片的InputStream。如果 uri 是 video 类型,则需要单独得到 video 的缩略图返回,否则按照一般读取文件操作返回。

(4). getStreamFromContent(String imageUri, Object extra)

从 ContentProvider 获取图片的InputStream
如果是 video 类型,则先从MediaStore得到 video 的缩略图返回;
如果是联系人类型,通过ContactsContract.Contacts.openContactPhotoInputStream(res, uri)读取内容返回。
否则通过 ContentResolver.openInputStream(…) 读取内容返回。

(5). getStreamFromAssets(String imageUri, Object extra)

从 Assets 中获取图片的InputStream

(6). getStreamFromDrawable(String imageUri, Object extra)

从 Drawable 资源中获取图片的InputStream

(7). getStreamFromOtherSource(String imageUri, Object extra)

UNKNOWN(自定义)类型的处理,目前是直接抛出不支持的异常。

4.2.24 MemoryCache.java

Bitmap 内存缓存接口,需要实现的接口包括 get(…)、put(…)、remove(…)、clear()、keys()。

4.2.25 BaseMemoryCache.java

实现了MemoryCache主要函数的抽象类,以 Map\<string, reference\<bitmap\=””>> softMap 做为缓存池,利于虚拟机在内存不足时回收缓存对象。提供抽象函数:

protected abstract Reference<Bitmap> createReference(Bitmap value)

表示根据 Bitmap 创建一个 Reference 做为缓存对象。Reference 可以是 WeakReference、SoftReference 等。

4.2.26 WeakMemoryCache.java

WeakReference<Bitmap>做为缓存 value 的内存缓存,实现了BaseMemoryCache
实现了BaseMemoryCachecreateReference(Bitmap value)函数,直接返回一个new WeakReference<Bitmap>(value)做为缓存 value。

4.2.27 LimitedMemoryCache.java

限制总字节大小的内存缓存,继承自BaseMemoryCache的抽象类。
会在 put(…) 函数中判断总体大小是否超出了上限,是则循环删除缓存对象直到小于上限。删除顺序由抽象函数

protected abstract Bitmap removeNext()

决定。抽象函数

protected abstract int getSize(Bitmap value)

表示每个元素大小。

4.2.28 LargestLimitedMemoryCache.java

限制总字节大小的内存缓存,会在缓存满时优先删除 size 最大的元素,继承自LimitedMemoryCache
实现了LimitedMemoryCache缓存removeNext()函数,总是返回当前缓存中 size 最大的元素。

4.2.29 UsingFreqLimitedMemoryCache.java

限制总字节大小的内存缓存,会在缓存满时优先删除使用次数最少的元素,继承自LimitedMemoryCache
实现了LimitedMemoryCache缓存removeNext()函数,总是返回当前缓存中使用次数最少的元素。

4.2.30 LRULimitedMemoryCache.java

限制总字节大小的内存缓存,会在缓存满时优先删除最近最少使用的元素,继承自LimitedMemoryCache
通过new LinkedHashMap<String, Bitmap>(10, 1.1f, true)作为缓存池。LinkedHashMap 第三个参数表示是否需要根据访问顺序(accessOrder)排序,true 表示根据accessOrder排序,最近访问的跟最新加入的一样放到最后面,false 表示根据插入顺序排序。这里为 true 且缓存满时始终删除第一个元素,即始终删除最近最少访问的元素。
实现了LimitedMemoryCache缓存removeNext()函数,总是返回第一个元素,即最近最少使用的元素。

4.2.31 FIFOLimitedMemoryCache.java

限制总字节大小的内存缓存,会在缓存满时优先删除先进入缓存的元素,继承自LimitedMemoryCache
实现了LimitedMemoryCache缓存removeNext()函数,总是返回最先进入缓存的元素。

以上所有LimitedMemoryCache子类都有个问题,就是 Bitmap 虽然通过WeakReference<Bitmap>包装,但实际根本不会被虚拟机回收,因为他们子类中同时都保留了 Bitmap 的强引用。大都是 UIL 早期实现的版本,不推荐使用。

4.2.32 LruMemoryCache.java

限制总字节大小的内存缓存,会在缓存满时优先删除最近最少使用的元素,实现了MemoryCache。LRU(Least Recently Used) 为最近最少使用算法。

new LinkedHashMap<String, Bitmap>(0, 0.75f, true)作为缓存池。LinkedHashMap 第三个参数表示是否需要根据访问顺序(accessOrder)排序,true 表示根据accessOrder排序,最近访问的跟最新加入的一样放到最后面,false 表示根据插入顺序排序。这里为 true 且缓存满时始终删除第一个元素,即始终删除最近最少访问的元素。

put(…)函数中通过trimToSize(int maxSize)函数判断总体大小是否超出了上限,是则删除第缓存池中第一个元素,即最近最少使用的元素,直到总体大小小于上限。

LruMemoryCache功能上与LRULimitedMemoryCache类似,不过在实现上更加优雅。用简单的实现接口方式,而不是不断继承的方式。

4.2.33 LimitedAgeMemoryCache.java

限制了对象最长存活周期的内存缓存。
MemoryCache的装饰者,相当于为MemoryCache添加了一个特性。以一个MemoryCache内存缓存和一个 maxAge 做为构造函数入参。在 get(…) 时判断如果对象存活时间已经超过设置的最长时间,则删除。

4.2.34 FuzzyKeyMemoryCache.java

可以将某些原本不同的 key 看做相等,在 put 时删除这些相等的 key。
MemoryCache的装饰者,相当于为MemoryCache添加了一个特性。以一个MemoryCache内存缓存和一个 keyComparator 做为构造函数入参。在 put(…) 时判断如果 key 与缓存中已有 key 经过Comparator比较后相等,则删除之前的元素。

4.2.35 FileNameGenerator.java

根据 uri 得到文件名的接口。

4.2.36 HashCodeFileNameGenerator.java

以 uri 的 hashCode 作为文件名。

4.2.37 Md5FileNameGenerator.java

以 uri 的 MD5 值作为文件名。

4.2.38 DiskCache.java

图片的磁盘缓存接口。

主要函数:

(1) File get(String imageUri)

根据原始图片的 uri 去获取缓存图片的文件。

(2) boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener)

保存 imageStream 到磁盘中,listener 表示保存进度且可在其中取消某些段的保存。

(3) boolean save(String imageUri, Bitmap bitmap)

保存图片到磁盘。

(4) boolean remove(String imageUri)

根据图片 uri 删除缓存图片。

(5) void close()

关闭磁盘缓存,并释放资源。

(6) void clear()

清空磁盘缓存。

(7) File getDirectory()

得到磁盘缓存的根目录。

4.2.39 BaseDiskCache.java

一个无大小限制的本地图片缓存,实现了DiskCache主要函数的抽象类。
图片缓存在cacheDir文件夹内,当cacheDir不可用时,则使用备库reserveCacheDir

主要函数:

(1). save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener)

先根据imageUri得到目标文件,将imageStream先写入与目标文件同一文件夹的 .tmp 结尾的临时文件内,若未被listener取消且写入成功则将临时文件重命名为目标文件并返回 true,否则删除临时文件并返回 false。

(2). save(String imageUri, Bitmap bitmap)

先根据imageUri得到目标文件,通过Bitmap.compress(…)函数将bitmap先写入与目标文件同一文件夹的 .tmp 结尾的临时文件内,若写入成功则将临时文件重命名为目标文件并返回 true,否则删除临时文件并返回 false。

(3). File getFile(String imageUri)

根据 imageUri 和 fileNameGenerator得到文件名,返回cacheDir内该文件,若cacheDir不可用,则使用备库reserveCacheDir

4.2.40 LimitedAgeDiskCache.java

限制了缓存对象最长存活周期的磁盘缓存,继承自BaseDiskCache
在 get(…) 时判断如果缓存对象存活时间已经超过设置的最长时间,则删除。在 save(…) 时保存当存时间作为对象的创建时间。

4.2.41 UnlimitedDiskCache.java

一个无大小限制的本地图片缓存。与BaseDiskCache无异,只是用了个意思明确的类名。

4.2.42 DiskLruCache.java

限制总字节大小的内存缓存,会在缓存满时优先删除最近最少使用的元素。

通过缓存目录下名为journal的文件记录缓存的所有操作,并在缓存open时读取journal的文件内容存储到LinkedHashMap<String, Entry> lruEntries中,后面get(String key)获取缓存内容时,会先从lruEntries中得到图片文件名返回文件。

LRU 的实现跟上面内存缓存类似,lruEntriesnew LinkedHashMap<String, Entry>(0, 0.75f, true),LinkedHashMap 第三个参数表示是否需要根据访问顺序(accessOrder)排序,true 表示根据accessOrder排序,最近访问的跟最新加入的一样放到最后面,false 表示根据插入顺序排序。这里为 true 且缓存满时trimToSize()函数始终删除第一个元素,即始终删除最近最少访问的文件。

来源于 JakeWharton 的开源项目 DiskLruCache,具体分析请等待 DiskLruCache 源码解析 完成。

4.2.43 LruDiskCache.java

限制总字节大小的内存缓存,会在缓存满时优先删除最近最少使用的元素,实现了DiskCache
内部有个DiskLruCache cache属性,缓存的存、取操作基本都是由该属性代理完成。

4.2.44 StrictLineReader.java

通过readLine()函数从InputStream中读取一行,目前仅用于磁盘缓存操作记录文件journal的解析。

4.2.45 Util.java

工具类。
String readFully(Reader reader)读取 reader 中内容。
deleteContents(File dir)递归删除文件夹内容。

4.2.46 ContentLengthInputStream.java

InputStream的装饰者,可通过available()函数得到 InputStream 对应数据源的长度(总字节数)。主要用于计算文件存储进度即图片下载进度时的总进度。

4.2.47 FailReason.java

图片下载及显示时的错误原因,目前包括:
IO_ERROR 网络连接或是磁盘存储错误。
DECODING_ERROR decode image 为 Bitmap 时错误。
NETWORK_DENIED 当图片不在缓存中,且设置不允许访问网络时的错误。
OUT_OF_MEMORY 内存溢出错误。
UNKNOWN 未知错误。

4.2.48 FlushedInputStream.java

为了解决早期 Android 版本BitmapFactory.decodeStream(…)在慢网络情况下 decode image 异常的 Bug。
主要通过重写FilterInputStream的 skip(long n) 函数解决,确保 skip(long n) 始终跳过了 n 个字节。如果返回结果即跳过的字节数小于 n,则不断循环直到 skip(long n) 跳过 n 字节或到达文件尾。

4.2.49 ImageScaleType.java

Image 的缩放类型,目前包括:
NONE不缩放。
NONE_SAFE根据需要以整数倍缩小图片,使得其尺寸不超过 Texture 可接受最大尺寸。
IN_SAMPLE_POWER_OF_2根据需要以 2 的 n 次幂缩小图片,使其尺寸不超过目标大小,比较快的缩小方式。
IN_SAMPLE_INT根据需要以整数倍缩小图片,使其尺寸不超过目标大小。
EXACTLY根据需要缩小图片到宽或高有一个与目标尺寸一致。
EXACTLY_STRETCHED根据需要缩放图片到宽或高有一个与目标尺寸一致。

4.2.50 ViewScaleType.java

ImageAware的 ScaleType。
将 ImageView 的 ScaleType 简化为两种FIT_INSIDECROP两种。FIT_INSIDE表示将图片缩放到至少宽度和高度有一个小于等于 View 的对应尺寸,CROP表示将图片缩放到宽度和高度都大于等于 View 的对应尺寸。

4.2.51 ImageSize.java

表示图片宽高的类。
scaleDown(…) 等比缩小宽高。
scale(…) 等比放大宽高。

4.2.52 LoadedFrom.java

图片来源枚举类,包括网络、磁盘缓存、内存缓存。

4.2.53 ImageDecoder.java

将图片转换为 Bitmap 的接口,抽象函数:

Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException;

表示根据ImageDecodingInfo信息得到图片并根据参数将其转换为 Bitmap。

4.2.54 BaseImageDecoder.java

实现了ImageDecoder。调用ImageDownloader获取图片,然后根据ImageDecodingInfo或图片 Exif 信息处理图片转换为 Bitmap。

主要函数:

(1). decode(ImageDecodingInfo decodingInfo)

调用ImageDownloader获取图片,再调用defineImageSizeAndRotation(…)函数得到图片的相关信息,调用prepareDecodingOptions(…)得到图片缩放的比例,调用BitmapFactory.decodeStream将 InputStream 转换为 Bitmap,最后调用considerExactScaleAndOrientatiton(…)根据参数将图片放大、翻转、旋转为合适的样子返回。

(2). defineImageSizeAndRotation(InputStream imageStream, ImageDecodingInfo decodingInfo)

得到图片真实大小以及 Exif 信息(设置考虑 Exif 的条件下)。

(3). defineExifOrientation(String imageUri)

得到图片 Exif 信息中的翻转以及旋转角度信息。

(4). prepareDecodingOptions(ImageSize imageSize, ImageDecodingInfo decodingInfo)

得到图片缩放的比例。

  1. 如果scaleType等于ImageScaleType.NONE,则缩放比例为 1;
  2. 如果scaleType等于ImageScaleType.NONE_SAFE,则缩放比例为 (int)Math.ceil(Math.max((float)srcWidth / maxWidth, (float)srcHeight / maxHeight))
  3. 否则,调用ImageSizeUtils.computeImageSampleSize(…)计算缩放比例。
    在 computeImageSampleSize(…) 中
  4. 如果viewScaleType等于ViewScaleType.FIT_INSIDE
    1.1 如果scaleType等于ImageScaleType.IN_SAMPLE_POWER_OF_2,则缩放比例从 1 开始不断 *2 直到宽或高小于最大尺寸;
    1.2 否则取宽和高分别与最大尺寸比例中较大值,即Math.max(srcWidth / targetWidth, srcHeight / targetHeight)
  5. 如果scaleType等于ViewScaleType.CROP
    2.1 如果scaleType等于ImageScaleType.IN_SAMPLE_POWER_OF_2,则缩放比例从 1 开始不断 *2 直到宽和高都小于最大尺寸。
    2.2 否则取宽和高分别与最大尺寸比例中较小值,即Math.min(srcWidth / targetWidth, srcHeight / targetHeight)
  6. 最后判断宽和高是否超过最大值,如果是 *2 或是 +1 缩放。
(5). considerExactScaleAndOrientatiton(Bitmap subsampledBitmap, ImageDecodingInfo decodingInfo, int rotation, boolean flipHorizontal)

根据参数将图片放大、翻转、旋转为合适的样子返回。

4.2.55 ImageDecodingInfo.java

Image Decode 需要的信息。
String imageKey 图片。
String imageUri 图片 uri,可能是缓存文件的 uri。
String originalImageUri 图片原 uri。
ImageSize targetSize 图片的显示尺寸。
imageScaleType 图片的 ScaleType。
ImageDownloader downloader 图片的下载器。
Object extraForDownloader 下载器需要的辅助信息。
boolean considerExifParams 是否需要考虑图片 Exif 信息。
Options decodingOptions 图片的解码信息,为 BitmapFactory.Options。

4.2.56 BitmapDisplayer.java

ImageAware中显示 bitmap 对象的接口。可在实现中对 bitmap 做一些额外处理,比如加圆角、动画效果。

4.2.57 FadeInBitmapDisplayer.java

图片淡入方式显示在ImageAware中,实现了BitmapDisplayer接口。

4.2.58 RoundedBitmapDisplayer.java

为图片添加圆角显示在ImageAware中,实现了BitmapDisplayer接口。主要通过BitmapShader实现。

4.2.59 RoundedVignetteBitmapDisplayer.java

为图片添加渐变效果的圆角显示在ImageAware中,实现了BitmapDisplayer接口。主要通过RadialGradient实现。

4.2.60 SimpleBitmapDisplayer.java

直接将图片显示在ImageAware中,实现了BitmapDisplayer接口。

4.2.61 BitmapProcessor.java

图片处理接口。可用于对图片预处理(Pre-process Bitmap)和后处理(Post-process Bitmap)。抽象函数:

public interface BitmapProcessor {
    Bitmap process(Bitmap bitmap);
}

用户可以根据自己需求去实现它。比如你想要为你的图片添加一个水印,那么可以自己去实现 BitmapProcessor 接口,在DisplayImageOptions中配置 Pre-process 阶段预处理图片,这样设置后存储在文件系统以及内存缓存中的图片都是加了水印后的。如果只希望在显示时改变不动原图片,可以在BitmapDisplayer中处理。

4.2.62 PauseOnScrollListener.java

可在 View 滚动过程中暂停图片加载的 Listener,实现了 OnScrollListener 接口。
它的好处是防止滚动中不必要的图片加载,比如快速滚动不希望滚动中的图片加载。在 ListView 或 GridView 中 item 加载图片最好使用它,简单的一行代码:

gridView.setOnScrollListener(new PauseOnScrollListener(ImageLoader.getInstance(), false, true));

主要的成员变量:
pauseOnScroll 触摸滑动(手指依然在屏幕上)过程中是否暂停图片加载。
pauseOnFling 甩指滚动(手指已离开屏幕)过程中是否暂停图片加载。
externalListener 自定义的 OnScrollListener 接口,适用于 View 原来就有自定义 OnScrollListener 情况设置。

实现原理:
重写onScrollStateChanged(…)函数判断不同的状态下暂停或继续图片加载。
OnScrollListener.SCROLL_STATE_IDLE表示 View 处于空闲状态,没有在滚动,这时候会加载图片。
OnScrollListener.SCROLL_STATE_TOUCH_SCROLL表示 View 处于触摸滑动状态,手指依然在屏幕上,通过pauseOnScroll变量确定是否需要暂停图片加载。这种时候大都属于慢速滚动浏览状态,所以建议继续图片加载。
OnScrollListener.SCROLL_STATE_FLING表示 View 处于甩指滚动状态,手指已离开屏幕,通过pauseOnFling变量确定是否需要暂停图片加载。这种时候大都属于快速滚动状态,所以建议暂停图片加载以节省资源。

4.2.63 QueueProcessingType.java

任务队列的处理类型,包括FIFO先进先出、LIFO后进先出。

4.2.64 LIFOLinkedBlockingDeque.java

后进先出阻塞队列。重写LinkedBlockingDequeoffer(…)函数如下:

@Override
public boolean offer(T e) {
    return super.offerFirst(e);
}

LinkedBlockingDeque插入总在最前,而remove()本身始终删除第一个元素,所以就变为了后进先出阻塞队列。
实际一般情况只重写offer(…)函数是不够的,但因为ThreadPoolExecutor默认只用到了BlockingQueueoffer(…)函数,所以这种简单重写后做为ThreadPoolExecutor的任务队列没问题。

LIFOLinkedBlockingDeque.java包下的LinkedBlockingDeque.javaBlockingDeque.javaDeque.java都是 Java 1.6 源码中的,这里不做分析。

4.2.65 DiskCacheUtils.java

磁盘缓存工具类,可用于查找或删除某个 uri 对应的磁盘缓存。

4.2.66 MemoryCacheUtils.java

内存缓存工具类。可用于根据 uri 生成内存缓存 key,缓存 key 比较,根据 uri 得到所有相关的 key 或图片,删除某个 uri 的内存缓存。
generateKey(String imageUri, ImageSize targetSize)
根据 uri 生成内存缓存 key,key 规则为[imageUri]_[width]x[height]

4.2.67 StorageUtils.java

得到图片 SD 卡缓存目录路径。
缓存目录优先选择/Android/data/[app_package_name]/cache;若无权限或不可用,则选择 App 在文件系统的缓存目录context.getCacheDir();若无权限或不可用,则选择/data/data/[app_package_name]/cache

如果缓存目录选择了/Android/data/[app_package_name]/cache,则新建.nomedia文件表示不允许类似 Galley 这些应用显示此文件夹下图片。不过在 4.0 系统有 Bug 这种方式不生效。

4.2.68 ImageSizeUtils.java

用于计算图片尺寸、缩放比例相关的工具类。

4.2.69 IoUtils.java

IO 相关工具类,包括 stream 拷贝,关闭等。

4.2.70 L.java

Log 工具类。

5. 杂谈

聊聊 LRU

UIL 的内存缓存默认使用了 LRU 算法。
LRU: Least Recently Used 近期最少使用算法, 选用了基于链表结构的 LinkedHashMap 作为存储结构。
假设情景:内存缓存设置的阈值只够存储两个 bitmap 对象,当 put 第三个 bitmap 对象时,将近期最少使用的 bitmap 对象移除。
图 1: 初始化 LinkedHashMap, 并按使用顺序来排序, accessOrder = true;
图 2: 向缓存池中放入 bitmap1 和 bitmap2 两个对象。
图 3: 继续放入第三个 bitmap3,根据假设情景,将会超过设定缓存池阈值。
图 4: 释放对 bitmap1 对象的引用。
图 5: bitmap1 对象被 GC 回收。




Android应用程序窗口设计框架介绍

 

Android系统中,一个Activity对应一个应用程序窗口,任何一个Activity的启动都是由AMS服务和应用程序进程相互配合来完成的。AMS服务统一调度系统中所有进程的Activity启动,而每个Activity的启动过程则由其所属进程来完成。AMS服务通过realStartActivityLocked函数来通知应用程序进程启动某个Activity:

frameworksbaseservicesjavacomandroidserveram ActivityStack.java

  1. final boolean realStartActivityLocked(ActivityRecord r,
  2.         ProcessRecord app, boolean andResume, boolean checkConfig)
  3.         throws RemoteException {
  4.     …
  5.     //系统参数发送变化,通知Activity
  6.     if (checkConfig) {
  7.         ①Configuration config = mService.mWindowManager.updateOrientationFromAppTokens(mService.mConfiguration,
  8.                 r.mayFreezeScreenLocked(app) ? r.appToken : null);
  9.         mService.updateConfigurationLocked(config, r, falsefalse);
  10.     }
  11.     //将进程描述符设置到启动的Activity描述符中
  12.     r.app = app;
  13.     app.waitingToKill = null;
  14.     //将启动的Activity添加到进程启动的Activity列表中
  15.     int idx = app.activities.indexOf(r);
  16.     if (idx < 0) {
  17.         app.activities.add(r);
  18.     }
  19.     mService.updateLruProcessLocked(app, truetrue);
  20.     try {
  21.         …
  22.         //通知应用程序进程加载Activity
  23.         ②app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
  24.                 System.identityHashCode(r), r.info,
  25.                 new Configuration(mService.mConfiguration),
  26.                 r.compat, r.icicle, results, newIntents, !andResume,
  27.                 mService.isNextTransitionForward(), profileFile, profileFd,
  28.                 profileAutoStop);
  29.         …
  30.     } catch (RemoteException e) {
  31.         …
  32.     }
  33.     if (mMainStack) {
  34.         mService.startSetupActivityLocked();
  35.     }
  36.     return true;
  37. }

AMS通过realStartActivityLocked函数来调度应用程序进程启动一个Activity,参数r为即将启动的Activity在AMS服务中的描述符,参数app为Activity运行所在的应用程序进程在AMS服务中的描述符。函数通过IApplicationThread代理对象ApplicationThreadProxy通知应用程序进程启动r对应的Activity,应用程序进程完成Activity的加载等准备工作后,AMS最后启动该Activity。启动Activity的创建等工作是在应用程序进程中完成的,AMS是通过IApplicationThread接口和应用程序进程通信的。r.appToken
在AMS服务端的类型为Token,是IApplicationToken的Binder本地对象。

frameworksbasecorejavaandroidapp ActivityThread.java

  1. public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
  2.         ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
  3.         Bundle state, List<ResultInfo> pendingResults,
  4.         List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
  5.         String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) {
  6.     //将AMS服务传过来的参数封装为ActivityClientRecord对象
  7.     ActivityClientRecord r = new ActivityClientRecord();
  8.     r.token = token;
  9.     r.ident = ident;
  10.     r.intent = intent;
  11.     r.activityInfo = info;
  12.     r.compatInfo = compatInfo;
  13.     r.state = state;
  14.     r.pendingResults = pendingResults;
  15.     r.pendingIntents = pendingNewIntents;
  16.     r.startsNotResumed = notResumed;
  17.     r.isForward = isForward;
  18.     r.profileFile = profileName;
  19.     r.profileFd = profileFd;
  20.     r.autoStopProfiler = autoStopProfiler;
  21.     updatePendingConfiguration(curConfig);
  22.     //使用异步消息方式实现Activity的启动
  23.     queueOrSendMessage(H.LAUNCH_ACTIVITY, r);
  24. }

参数token从AMS服务端经过Binder传输到应用程序进程后,变为IApplicationToken的Binder代理对象,类型为IApplicationToken.Proxy,这是因为AMS和应用程序运行在不同的进程中。

通过queueOrSendMessage函数将Binder跨进程调用转换为应用程序进程中的异步消息处理

frameworksbasecorejavaandroidapp ActivityThread.java

  1. private class H extends Handler {
  2.  public void handleMessage(Message msg) {
  3.     switch (msg.what) {
  4.             case LAUNCH_ACTIVITY: {
  5.                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “activityStart”);
  6.                 ActivityClientRecord r = (ActivityClientRecord)msg.obj;
  7.                 r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
  8.                 handleLaunchActivity(r, null);
  9.                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  10.             } break;
  11.         }
  12.     }
  13. }

LAUNCH_ACTIVITY消息在应用程序主线程消息循环中得到处理,应用程序通过handleLaunchActivity函数来启动Activity。到此AMS服务就完成了Activity的调度任务,将Activity的启动过程完全交给了应用程序进程来完成。

frameworksbasecorejavaandroidapp ActivityThread.java

  1. private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  2.     //主线程空闲时会定时执行垃圾回收,主线程当前要完成启动Activity的任务,因此这里先暂停GC
  3.     unscheduleGcIdler();
  4.     if (r.profileFd != null) {
  5.         mProfiler.setProfiler(r.profileFile, r.profileFd);
  6.         mProfiler.startProfiling();
  7.         mProfiler.autoStopProfiler = r.autoStopProfiler;
  8.     }
  9.     // Make sure we are running with the most recent config.
  10.     ①handleConfigurationChanged(nullnull);
  11.     //创建Activity
  12.     ②Activity a = performLaunchActivity(r, customIntent);
  13.     if (a != null) {
  14.         r.createdConfig = new Configuration(mConfiguration);
  15.         Bundle oldState = r.state;
  16.         //启动Activity
  17.         ③handleResumeActivity(r.token, false, r.isForward);
  18.         …
  19.     }else{
  20.         …
  21.     }
  22. }

performLaunchActivity

应用程序进程通过performLaunchActivity函数将即将要启动的Activity加载到当前进程空间来,同时为启动Activity做准备。

frameworksbasecorejavaandroidapp ActivityThread.java

  1. private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  2.     ActivityInfo aInfo = r.activityInfo;
  3.     if (r.packageInfo == null) {
  4.         //通过Activity所在的应用程序信息及该Activity对应的CompatibilityInfo信息从PMS服务中查询当前Activity的包信息
  5.         r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,Context.CONTEXT_INCLUDE_CODE);
  6.     }
  7.     //获取当前Activity的组件信息
  8.     ComponentName component = r.intent.getComponent();
  9.     if (component == null) {
  10.         component = r.intent.resolveActivity(mInitialApplication.getPackageManager());
  11.         r.intent.setComponent(component);
  12.     }
  13.     if (r.activityInfo.targetActivity != null) {
  14.         //packageName为启动Activity的包名,targetActivity为Activity的类名
  15.         component = new ComponentName(r.activityInfo.packageName,
  16.                 r.activityInfo.targetActivity);
  17.     }
  18.     //通过类反射方式加载即将启动的Activity
  19.     Activity activity = null;
  20.     try {
  21.         java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
  22.         ①activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
  23.         StrictMode.incrementExpectedActivityCount(activity.getClass());
  24.         r.intent.setExtrasClassLoader(cl);
  25.         if (r.state != null) {
  26.             r.state.setClassLoader(cl);
  27.         }
  28.     } catch (Exception e) {
  29.         …
  30.     }
  31.     try {
  32.         //通过单例模式为应用程序进程创建Application对象
  33.         ②Application app = r.packageInfo.makeApplication(false, mInstrumentation);
  34.         if (activity != null) {
  35.             //为当前Activity创建上下文对象ContextImpl
  36.             ContextImpl appContext = new ContextImpl();
  37.             //上下文初始化
  38.             ③appContext.init(r.packageInfo, r.token, this);
  39.             appContext.setOuterContext(activity);
  40.             CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
  41.             …
  42.             Configuration config = new Configuration(mCompatConfiguration);
  43.             //将当前启动的Activity和上下文ContextImpl、Application绑定
  44.             ④activity.attach(appContext, this, getInstrumentation(), r.token,
  45.                     r.ident, app, r.intent, r.activityInfo, title, r.parent,
  46.                     r.embeddedID, r.lastNonConfigurationInstances, config);
  47.             …
  48.             //调用Activity的OnCreate函数
  49.             ⑤mInstrumentation.callActivityOnCreate(activity, r.state);
  50.             …
  51.             //将Activity保存到ActivityClientRecord中,ActivityClientRecord为Activity在应用程序进程中的描述符
  52.             r.activity = activity;
  53.             …
  54.         }
  55.         r.paused = true;
  56.         //ActivityThread的成员变量mActivities保存了当前应用程序进程中的所有Activity的描述符
  57.         mActivities.put(r.token, r);
  58.     } catch (SuperNotCalledException e) {
  59.         …
  60.     }
  61.     return activity;
  62. }

在该函数中,首先通过PMS服务查找到即将启动的Activity的包名信息,然后通过类反射方式创建一个该Activity实例,同时为应用程序启动的每一个Activity创建一个LoadedApk实例对象,应用程序进程中创建的所有LoadedApk对象保存在ActivityThread的成员变量mPackages中。接着通过LoadedApk对象的makeApplication函数,使用单例模式创建Application对象,因此在android应用程序进程中有且只有一个Application实例。然后为当前启动的Activity创建一个ContextImpl上下文对象,并初始化该上下文,到此我们可以知道,启动一个Activity需要以下对象:

1)      XXActivity对象,需要启动的Activity;

2)      LoadedApk对象,每个启动的Activity都拥有属于自身的LoadedApk对象;

3)      ContextImpl对象,每个启动的Activity都拥有属于自身的ContextImpl对象;

4)      Application对象,应用程序进程中有且只有一个实例,和Activity是一对多的关系;

加载Activity类

  1. public Activity newActivity(ClassLoader cl, String className,
  2.         Intent intent)
  3.         throws InstantiationException, IllegalAccessException,
  4.         ClassNotFoundException {
  5.     return (Activity)cl.loadClass(className).newInstance();
  6. }

这里通过类反射的方式来加载要启动的Activity实例对象。

LoadedApk构造过程

首先介绍一下LoadedApk对象的构造过程:

frameworksbasecorejavaandroidapp ActivityThread.java

  1. public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
  2.         int flags) {
  3.     synchronized (mPackages) {
  4.         //通过Activity的包名从对应的成员变量中查找LoadedApk对象
  5.         WeakReference<LoadedApk> ref;
  6.         if ((flags&Context.CONTEXT_INCLUDE_CODE) != 0) {
  7.             ref = mPackages.get(packageName);
  8.         } else {
  9.             ref = mResourcePackages.get(packageName);
  10.         }
  11.         LoadedApk packageInfo = ref != null ? ref.get() : null;
  12.         if (packageInfo != null && (packageInfo.mResources == null
  13.                 || packageInfo.mResources.getAssets().isUpToDate())) {
  14.             …
  15.             return packageInfo;
  16.         }
  17.     }
  18.     //如果没有,则为当前Activity创建对应的LoadedApk对象
  19.     ApplicationInfo ai = null;
  20.     try {
  21.         //通过包名在PMS服务中查找应用程序信息
  22.         ai = getPackageManager().getApplicationInfo(packageName,
  23.                 PackageManager.GET_SHARED_LIBRARY_FILES, UserId.myUserId());
  24.     } catch (RemoteException e) {
  25.         // Ignore
  26.     }
  27.     //使用另一个重载函数创建LoadedApk对象
  28.     if (ai != null) {
  29.         return getPackageInfo(ai, compatInfo, flags);
  30.     }
  31.     return null;
  32. }

 

  1. public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo,
  2.         int flags) {
  3.     boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0;
  4.     boolean securityViolation = includeCode && ai.uid != 0
  5.             && ai.uid != Process.SYSTEM_UID && (mBoundApplication != null
  6.                     ? !UserId.isSameApp(ai.uid, mBoundApplication.appInfo.uid)
  7.                     : true);
  8.     if ((flags&(Context.CONTEXT_INCLUDE_CODE|Context.CONTEXT_IGNORE_SECURITY))
  9.             == Context.CONTEXT_INCLUDE_CODE) {
  10.         …
  11.     }
  12.     return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode);
  13. }

 

  1. private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
  2.         ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {
  3.     //再次从对应的成员变量中查找LoadedApk实例
  4.     synchronized (mPackages) {
  5.         WeakReference<LoadedApk> ref;
  6.         if (includeCode) {
  7.             ref = mPackages.get(aInfo.packageName);
  8.         } else {
  9.             ref = mResourcePackages.get(aInfo.packageName);
  10.         }
  11.         LoadedApk packageInfo = ref != null ? ref.get() : null;
  12.         if (packageInfo == null || (packageInfo.mResources != null
  13.                 && !packageInfo.mResources.getAssets().isUpToDate())) {
  14.             …
  15.             //构造一个LoadedApk对象
  16.             packageInfo =new LoadedApk(this, aInfo, compatInfo, this, baseLoader,
  17.                         securityViolation, includeCode &&
  18.                         (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);
  19.             //保存LoadedApk实例到ActivityThread的相应成员变量中
  20.             if (includeCode) {
  21.                 mPackages.put(aInfo.packageName,
  22.                         new WeakReference<LoadedApk>(packageInfo));
  23.             } else {
  24.                 mResourcePackages.put(aInfo.packageName,
  25.                         new WeakReference<LoadedApk>(packageInfo));
  26.             }
  27.         }
  28.         return packageInfo;
  29.     }
  30. }

 

frameworksbasecorejavaandroidappLoadedApk.java

  1. public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
  2.         CompatibilityInfo compatInfo,
  3.         ActivityThread mainThread, ClassLoader baseLoader,
  4.         boolean securityViolation, boolean includeCode) {
  5.     mActivityThread = activityThread;
  6.     mApplicationInfo = aInfo;
  7.     mPackageName = aInfo.packageName;
  8.     mAppDir = aInfo.sourceDir;
  9.     final int myUid = Process.myUid();
  10.     mResDir = aInfo.uid == myUid ? aInfo.sourceDir
  11.             : aInfo.publicSourceDir;
  12.     if (!UserId.isSameUser(aInfo.uid, myUid) && !Process.isIsolated()) {
  13.         aInfo.dataDir = PackageManager.getDataDirForUser(UserId.getUserId(myUid),
  14.                 mPackageName);
  15.     }
  16.     mSharedLibraries = aInfo.sharedLibraryFiles;
  17.     mDataDir = aInfo.dataDir;
  18.     mDataDirFile = mDataDir != null ? new File(mDataDir) : null;
  19.     mLibDir = aInfo.nativeLibraryDir;
  20.     mBaseClassLoader = baseLoader;
  21.     mSecurityViolation = securityViolation;
  22.     mIncludeCode = includeCode;
  23.     mCompatibilityInfo.set(compatInfo);
  24.     if (mAppDir == null) {
  25.         //为应用程序进程创建一个ContextImpl上下文
  26.         if (ActivityThread.mSystemContext == null) {
  27.             ActivityThread.mSystemContext =
  28.                 ContextImpl.createSystemContext(mainThread);
  29.             ActivityThread.mSystemContext.getResources().updateConfiguration(
  30.                      mainThread.getConfiguration(),
  31.                      mainThread.getDisplayMetricsLocked(compatInfo, false),
  32.                      compatInfo);
  33.         }
  34.         mClassLoader = ActivityThread.mSystemContext.getClassLoader();
  35.         mResources = ActivityThread.mSystemContext.getResources();
  36.     }
  37. }

从以上LoadedApk的构造函数可以看出,LoadedApk类记录了Activity运行所在的ActivityThread、Activity所在的应用程序信息、Activity的包名、Activity的资源路径、Activity的库路径、Activity的数据存储路径、类加载器和应用程序所使用的资源等信息。

Application构造过程

当Activity为应用程序进程启动的第一个Activity,因此需要构造一个Application对象

frameworksbasecorejavaandroidappLoadedApk.java

  1. public Application makeApplication(boolean forceDefaultAppClass,
  2.         Instrumentation instrumentation) {
  3.     //在应用程序进程空间以单例模式创建Application对象
  4.     if (mApplication != null) {
  5.         return mApplication;
  6.     }
  7.     Application app = null;
  8.     //得到应用程序的Application类名
  9.     String appClass = mApplicationInfo.className;
  10.     //如果应用程序没用重写Application,则使用Android默认的Application类
  11.     if (forceDefaultAppClass || (appClass == null)) {
  12.         appClass = “android.app.Application”;
  13.     }
  14.     try {
  15.         java.lang.ClassLoader cl = getClassLoader();
  16.         //为Application实例创建一个上下文对象ContextImpl
  17.         ①ContextImpl appContext = new ContextImpl();
  18.         //初始化上下文
  19.         ②appContext.init(thisnull, mActivityThread);
  20.         //创建Application实例对象
  21.         ③app = mActivityThread.mInstrumentation.newApplication(
  22.                 cl, appClass, appContext);
  23.         appContext.setOuterContext(app);
  24.     } catch (Exception e) {
  25.         …
  26.     }
  27.     mActivityThread.mAllApplications.add(app);
  28.     mApplication = app;
  29.     if (instrumentation != null) {
  30.         try {
  31.             //调用Application的OnCreate函数
  32.             ④instrumentation.callApplicationOnCreate(app);
  33.         } catch (Exception e) {
  34.             …
  35.         }
  36.     }
  37.     return app;
  38. }

在应用程序开发过程中,当我们重写了Application类后,应用程序加载运行的是我们定义的Application类,否则就加载运行默认的Application类。从Application对象的构造过程就可以解释为什么应用程序启动后首先执行的是Application的OnCreate函数。在实例化Application对象时,同样创建并初始化了一个ContextImpl上下文对象。

ContextImpl构造过程

前面我们介绍了,每一个Activity拥有一个上下文对象ContextImpl,每一个Application对象也拥有一个ContextImpl上下文对象,那么ContextImpl对象又是如何构造的呢?

frameworksbasecorejavaandroidapp ContextImpl.java

  1. ContextImpl() {
  2.     mOuterContext = this;
  3. }

ContextImpl的构造过程什么也没干,通过调用ContextImpl的init函数进行初始化

  1. final void init(LoadedApk packageInfo,IBinder activityToken, ActivityThread mainThread) {
  2.     init(packageInfo, activityToken, mainThread, nullnull);
  3. }

 

  1. final void init(LoadedApk packageInfo,IBinder activityToken, ActivityThread mainThread,
  2.             Resources container, String basePackageName) {
  3.     mPackageInfo = packageInfo;
  4.     mBasePackageName = basePackageName != null ? basePackageName : packageInfo.mPackageName;
  5.     mResources = mPackageInfo.getResources(mainThread);
  6.     if (mResources != null && container != null
  7.             && container.getCompatibilityInfo().applicationScale !=
  8.                     mResources.getCompatibilityInfo().applicationScale) {
  9.         mResources = mainThread.getTopLevelResources(
  10.                 mPackageInfo.getResDir(), container.getCompatibilityInfo());
  11.     }
  12.     mMainThread = mainThread;
  13.     mContentResolver = new ApplicationContentResolver(this, mainThread);
  14.     setActivityToken(activityToken);
  15. }

从ContextImpl的初始化函数中可以知道,ContextImpl记录了应用程序的包名信息、应用程序的资源信息、应用程序的主线程、ContentResolver及Activity对应的IApplicationToken.Proxy,当然对应Application对象所拥有的ContextImpl上下文就没有对应的Token了。通过前面的分析我们可以知道各个对象之间的关系:

对象Attach过程

Activity所需要的对象都创建好了,就需要将Activity和Application对象、ContextImpl对象绑定在一起。

frameworksbasecorejavaandroidapp Activity.java

  1. final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token,
  2.         Application application, Intent intent, ActivityInfo info, CharSequence title,
  3.         Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances,
  4.         Configuration config) {
  5.     attach(context, aThread, instr, token, 0, application, intent, info, title, parent, id,
  6.         lastNonConfigurationInstances, config);
  7. }

context:Activity的上下文对象,就是前面创建的ContextImpl对象;

aThread:Activity运行所在的主线程描述符ActivityThread;

instr:用于监控Activity运行状态的Instrumentation对象;

token:用于和AMS服务通信的IApplicationToken.Proxy代理对象;

application:Activity运行所在进程的Application对象;

parent:启动当前Activity的Activity;

  1. final void attach(Context context, ActivityThread aThread,
  2.         Instrumentation instr, IBinder token, int ident,
  3.         Application application, Intent intent, ActivityInfo info,
  4.         CharSequence title, Activity parent, String id,
  5.         NonConfigurationInstances lastNonConfigurationInstances,
  6.         Configuration config) {
  7.     //将上下文对象ContextImpl保存到Activity的成员变量中
  8.     attachBaseContext(context);
  9.     //每个Activity都拥有一个FragmentManager,这里就是将当前Activity设置到FragmentManager中管理
  10.     mFragments.attachActivity(this);
  11.     //创建窗口对象
  12.     ①mWindow = PolicyManager.makeNewWindow(this);
  13.     mWindow.setCallback(this);
  14.     mWindow.getLayoutInflater().setPrivateFactory(this);
  15.     if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
  16.         mWindow.setSoftInputMode(info.softInputMode);
  17.     }
  18.     if (info.uiOptions != 0) {
  19.         mWindow.setUiOptions(info.uiOptions);
  20.     }
  21.     //记录应用程序的UI线程
  22.     mUiThread = Thread.currentThread();
  23.     //记录应用程序的ActivityThread对象
  24.     mMainThread = aThread;
  25.     mInstrumentation = instr;
  26.     mToken = token;
  27.     mIdent = ident;
  28.     mApplication = application;
  29.     mIntent = intent;
  30.     mComponent = intent.getComponent();
  31.     mActivityInfo = info;
  32.     mTitle = title;
  33.     mParent = parent;
  34.     mEmbeddedID = id;
  35.     mLastNonConfigurationInstances = lastNonConfigurationInstances;
  36.     //为Activity所在的窗口创建窗口管理器
  37.     ②mWindow.setWindowManager(null, mToken, mComponent.flattenToString(),
  38.             (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
  39.     if (mParent != null) {
  40.         mWindow.setContainer(mParent.getWindow());
  41.     }
  42.     mWindowManager = mWindow.getWindowManager();
  43.     mCurrentConfig = config;
  44. }

在该attach函数中主要做了以下几件事:

1)        将Activity设置到FragmentManager中;

2)        根据参数初始化Activity的成员变量;

3)        为Activity创建窗口Window对象;

4)        为Window创建窗口管理器;

到此为止应用程序进程为启动的Activity对象创建了以下不同的实例对象,它们之间的关系如下:

应用程序窗口创建过程

frameworksbasecorejavacomandroidinternalpolicy PolicyManager.java

  1. public static Window makeNewWindow(Context context) {
  2.     return sPolicy.makeNewWindow(context);
  3. }

通过Policy类的makeNewWindow函数来创建一个应用程序窗口

  1. private static final String POLICY_IMPL_CLASS_NAME =
  2.         “com.android.internal.policy.impl.Policy”;
  3. private static final IPolicy sPolicy;
  4. static {
  5.     try {
  6.         Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
  7.         sPolicy = (IPolicy)policyClass.newInstance();
  8.     } catch (ClassNotFoundException ex) {
  9.         …
  10.     }
  11. }

frameworksbasepolicysrccomandroidinternalpolicyimpl Policy.java

  1. public Window makeNewWindow(Context context) {
  2.     return new PhoneWindow(context);
  3. }

应用程序窗口的创建过程其实就是构造一个PhoneWindow对象。PhoneWindow类是通过静态方式加载到应用程序进程空间的。

  1. private static final String[] preload_classes = {
  2.     “com.android.internal.policy.impl.PhoneLayoutInflater”,
  3.     “com.android.internal.policy.impl.PhoneWindow”,
  4.     “com.android.internal.policy.impl.PhoneWindow$1”,
  5.     “com.android.internal.policy.impl.PhoneWindow$ContextMenuCallback”,
  6.     “com.android.internal.policy.impl.PhoneWindow$DecorView”,
  7.     “com.android.internal.policy.impl.PhoneWindow$PanelFeatureState”,
  8.     “com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState”,
  9. };
  1. static {
  2.     for (String s : preload_classes) {
  3.         try {
  4.             Class.forName(s);
  5.         } catch (ClassNotFoundException ex) {
  6.             Log.e(TAG, “Could not preload class for phone policy: “ + s);
  7.         }
  8.     }
  9. }

PhoneWindow的构造过程

  1. public PhoneWindow(Context context) {
  2.     super(context);
  3.     mAlternativePanelStyle=getContext().getResources().getBoolean(com.android.internal.R.bool.config_alternativePanelStyle);
  4.     mLayoutInflater = LayoutInflater.from(context);
  5. }

构造过程比较简单,只是得到布局加载服务对象。

窗口管理器创建过程

通过前面的分析我们可以知道,在Activity启动过程中,会为Activity创建一个窗口对象PhoneWindow,应用程序有了窗口那就需要有一个窗口管理器来管理这些窗口,因此在Activity启动过程中还会创建一个WindowManager对象。

frameworksbasecorejavaandroidview Window.java

  1. public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
  2.         boolean hardwareAccelerated) {
  3.     mAppToken = appToken;// IApplicationToken.Proxy代理对象
  4.     mAppName = appName;
  5.     //得到WindowManagerImpl实例,
  6.     if (wm == null) {
  7.         wm = WindowManagerImpl.getDefault();
  8.     }
  9.     //为每个启动的Activity创建一个轻量级的窗口管理器LocalWindowManager
  10.     mWindowManager = new LocalWindowManager(wm, hardwareAccelerated);
  11. }

WindowManagerImpl为重量级的窗口管理器,应用程序进程中有且只有一个WindowManagerImpl实例,它管理了应用程序进程中创建的所有PhoneWindow窗口。Activity并没有直接引用WindowManagerImpl实例,Android系统为每一个启动的Activity创建了一个轻量级的窗口管理器LocalWindowManager,每个Activity通过LocalWindowManager来访问WindowManagerImpl,它们三者之间的关系如下图所示:

WindowManagerImpl以单例模式创建,应用程序进程中有且只有一个WindowManagerImpl实例

frameworksbasecorejavaandroidview WindowManagerImpl.java

  1. private final static WindowManagerImpl sWindowManager = new WindowManagerImpl();
  2. public static WindowManagerImpl getDefault() {
  3.     return sWindowManager;
  4. }

应用程序进程会为每一个Activity创建一个LocalWindowManager实例对象

frameworksbasecorejavaandroidview Window.java

  1. LocalWindowManager(WindowManager wm, boolean hardwareAccelerated) {
  2.     super(wm, getCompatInfo(mContext));
  3.     mHardwareAccelerated = hardwareAccelerated ||
  4.             SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
  5. }

frameworksbasecorejavaandroidview WindowManagerImpl.java

  1. CompatModeWrapper(WindowManager wm, CompatibilityInfoHolder ci) {
  2.     mWindowManager = wm instanceof CompatModeWrapper
  3.             ? ((CompatModeWrapper)wm).mWindowManager : (WindowManagerImpl)wm;
  4.     if (ci == null) {
  5.         mDefaultDisplay = mWindowManager.getDefaultDisplay();
  6.     } else {
  7.         mDefaultDisplay = Display.createCompatibleDisplay(
  8.                 mWindowManager.getDefaultDisplay().getDisplayId(), ci);
  9.     }
  10.     mCompatibilityInfo = ci;
  11. }

  1. public Display getDefaultDisplay() {
  2.     return new Display(Display.DEFAULT_DISPLAY, null);
  3. }

frameworksbasecorejavaandroidviewDisplay.java

  1. Display(int display, CompatibilityInfoHolder compatInfo) {
  2.     synchronized (sStaticInit) {
  3.         if (!sInitialized) {
  4.             nativeClassInit();
  5.             sInitialized = true;
  6.         }
  7.     }
  8.     mCompatibilityInfo = compatInfo != null ? compatInfo : new CompatibilityInfoHolder();
  9.     mDisplay = display;
  10.     init(display);
  11. }

构造Display对象时需要初始化该对象。

frameworksbasecorejniandroid_view_Display.cpp

  1. static void android_view_Display_init(
  2.         JNIEnv* env, jobject clazz, jint dpy)
  3. {
  4.     DisplayInfo info;
  5.     if (headless) {
  6.         // initialize dummy display with reasonable values
  7.         info.pixelFormatInfo.format = 1// RGB_8888
  8.         info.fps = 60;
  9.         info.density = 160;
  10.         info.xdpi = 160;
  11.         info.ydpi = 160;
  12.     } else {
  13.         status_t err = SurfaceComposerClient::getDisplayInfo(DisplayID(dpy), &info);
  14.         if (err < 0) {
  15.             jniThrowException(env, “java/lang/IllegalArgumentException”, NULL);
  16.             return;
  17.         }
  18.     }
  19.     env->SetIntField(clazz, offsets.pixelFormat,info.pixelFormatInfo.format);
  20.     env->SetFloatField(clazz, offsets.fps,      info.fps);
  21.     env->SetFloatField(clazz, offsets.density,  info.density);
  22.     env->SetFloatField(clazz, offsets.xdpi,     info.xdpi);
  23.     env->SetFloatField(clazz, offsets.ydpi,     info.ydpi);
  24. }

Display的初始化过程很简单,就是通过SurfaceComposerClient请求SurfaceFlinger得到显示屏的基本信息。

frameworksnativelibsgui SurfaceComposerClient.cpp

  1. status_t SurfaceComposerClient::getDisplayInfo(
  2.         DisplayID dpy, DisplayInfo* info)
  3. {
  4.     if (uint32_t(dpy)>=NUM_DISPLAY_MAX)
  5.         return BAD_VALUE;
  6.     volatile surface_flinger_cblk_t const * cblk = get_cblk();
  7.     volatile display_cblk_t const * dcblk = cblk->displays + dpy;
  8.     info->w              = dcblk->w;
  9.     info->h              = dcblk->h;
  10.     info->orientation      = dcblk->orientation;
  11.     info->xdpi           = dcblk->xdpi;
  12.     info->ydpi           = dcblk->ydpi;
  13.     info->fps            = dcblk->fps;
  14.     info->density        = dcblk->density;
  15.     return getPixelFormatInfo(dcblk->format, &(info->pixelFormatInfo));
  16. }

我们知道在SurfaceFlinger启动过程中,创建了一块匿名共享内存来保存显示屏的基本信息,这里就是通过访问这块匿名共享内存来读取显示屏信息。到此一个Activity所需要的窗口对象就创建完成了,在应用程序窗口的创建过程中一共创建了以下几个对象:

Activity视图对象的创建过程

在Activity的attach函数中完成应用程序窗口的创建后,通过Instrumentation回调Activity的OnCreate函数来为当前Activity加载布局文件,进一步创建视图对象。

frameworksbasecorejavaandroidappInstrumentation.java

  1. public void callActivityOnCreate(Activity activity, Bundle icicle) {
  2.     …
  3.     activity.performCreate(icicle);
  4.     …
  5. }

frameworksbasecorejavaandroidappActivity.java

  1. final void performCreate(Bundle icicle) {
  2.     onCreate(icicle);
  3.     mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
  4.             com.android.internal.R.styleable.Window_windowNoDisplay, false);
  5.     mFragments.dispatchActivityCreated();
  6. }

我们知道在应用程序开发中,需要重写Activity的OnCreate函数:

Packagesappsxxxsrccomxxx xxxActivity.java

  1. public void onCreate(Bundle savedInstanceState) {
  2.     super.onCreate(savedInstanceState);
  3.     setContentView(R.layout.main_activity);
  4.     …
  5. }

在OnCreate函数中通过setContentView来设置Activity的布局文件,就是生成该Activity的所有视图对象。

frameworksbasecorejavaandroidappActivity.java

  1. public void setContentView(View view, ViewGroup.LayoutParams params) {
  2.     getWindow().setContentView(view, params);
  3.     //初始化动作条
  4.     initActionBar();
  5. }

getWindow()函数得到前面创建的窗口对象PhoneWindow,通过PhoneWindow来设置Activity的视图。

frameworksbasepolicysrccomandroidinternalpolicyimplPhoneWindow.java

  1. public void setContentView(int layoutResID) {
  2.     //如果窗口顶级视图对象为空,则创建窗口视图对象
  3.     if (mContentParent == null) {
  4.         installDecor();
  5.     } else {//否则只是移除该视图对象中的其他视图
  6.         mContentParent.removeAllViews();
  7.     }
  8.     //加载布局文件,并将布局文件中的所有视图对象添加到mContentParent容器中
  9.     mLayoutInflater.inflate(layoutResID, mContentParent);
  10.     final Callback cb = getCallback();
  11.     if (cb != null && !isDestroyed()) {
  12.         cb.onContentChanged();
  13.     }
  14. }

PhoneWindow的成员变量mContentParent的类型为ViewGroup,是窗口内容存放的地方

frameworksbasepolicysrccomandroidinternalpolicyimplPhoneWindow.java

  1. private void installDecor() {
  2.     if (mDecor == null) {
  3.         ①mDecor = generateDecor();
  4.         mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
  5.         mDecor.setIsRootNamespace(true);
  6.         if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
  7.             mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
  8.         }
  9.     }
  10.     if (mContentParent == null) {
  11.         ②mContentParent = generateLayout(mDecor);
  12.         mDecor.makeOptionalFitsSystemWindows();
  13.         //应用程序窗口标题栏
  14.         mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
  15.         if (mTitleView != null) {
  16.             …
  17.         } else {
  18.             //应用程序窗口动作条
  19.             mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
  20.             if (mActionBar != null) {
  21.                 …
  22.             }
  23.         }
  24.     }
  25. }

通过函数generateDecor()来创建一个DecorView对象

  1. protected DecorView generateDecor() {
  2.     return new DecorView(getContext(), –1);
  3. }

接着通过generateLayout(mDecor)来创建视图对象容器mContentParent

  1. protected ViewGroup generateLayout(DecorView decor) {
  2.     //通过读取属性配置文件设置窗口风格
  3.     if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBarOverlay, false)) {
  4.         requestFeature(FEATURE_ACTION_BAR_OVERLAY);
  5.     }
  6.     …
  7.     //通过读取属性配置文件设置窗口标志
  8.     if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {
  9.     setFlags(FLAG_FULLSCREEN,FLAG_FULLSCREEN&(~getForcedWindowFlags()));
  10.     }
  11.     …
  12.     WindowManager.LayoutParams params = getAttributes();
  13.     …
  14.     mDecor.startChanging();
  15.     //根据窗口主题风格选择不同的布局文件layoutResource
  16.     …
  17.     //加载布局文件
  18.     ①View in = mLayoutInflater.inflate(layoutResource, null);
  19.     //添加到DecorView中
  20.     ②decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
  21.     //从窗口视图中找出窗口内容视图对象
  22.     ③ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
  23.     …
  24.     mDecor.finishChanging();
  25.     return contentParent;
  26. }

到此Activity的所有视图对象都已经创建完毕,DecorView是Activity的顶级视图,由窗口PhoneWindow对象持有,在DecorView视图对象中添加了一个ViewGroup容器组件contentParent,所有用户定义视图组件将被添加到该容器中。

handleResumeActivity

performLaunchActivity函数完成了两件事:

1)        Activity窗口对象的创建,通过attach函数来完成;

2)        Activity视图对象的创建,通过setContentView函数来完成;

这些准备工作完成后,就可以显示该Activity了,应用程序进程通过调用handleResumeActivity函数来启动Activity的显示过程。

frameworksbasecorejavaandroidapp ActivityThread.java

  1. final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
  2.     unscheduleGcIdler();
  3.     ActivityClientRecord r;
  4.     try {
  5.         ①r = performResumeActivity(token, clearHide);
  6.     } catch (Exception e) {
  7.         …
  8.     }
  9.     if (r != null) {
  10.         final Activity a = r.activity;
  11.         …
  12.         if (r.window == null && !a.mFinished && willBeVisible) {
  13.             //获得为当前Activity创建的窗口PhoneWindow对象
  14.             r.window = r.activity.getWindow();
  15.             //获取为窗口创建的视图DecorView对象
  16.             View decor = r.window.getDecorView();
  17.             decor.setVisibility(View.INVISIBLE);
  18.             //在attach函数中就为当前Activity创建了WindowManager对象
  19.             ViewManager wm = a.getWindowManager();
  20.             //得到该视图对象的布局参数
  21.             ②WindowManager.LayoutParams l = r.window.getAttributes();
  22.             //将视图对象保存到Activity的成员变量mDecor中
  23.             a.mDecor = decor;
  24.             l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
  25.             if (r.intent.hasCategory(Intent.CATEGORY_HOME)) {
  26.                 l.idleScreenAvailable = true;
  27.             } else {
  28.                 l.idleScreenAvailable = false;
  29.             }
  30.             l.softInputMode |= forwardBit;
  31.             if (a.mVisibleFromClient) {
  32.                 a.mWindowAdded = true;
  33.                 //将创建的视图对象DecorView添加到Activity的窗口管理器中
  34.                 ③wm.addView(decor, l);
  35.             }
  36.         } else if (!willBeVisible) {
  37.             …
  38.         }
  39.         …
  40.         if (!r.onlyLocalRequest) {
  41.             r.nextIdle = mNewActivities;
  42.             mNewActivities = r;
  43.             Looper.myQueue().addIdleHandler(new Idler());
  44.         }
  45.         …
  46.     } else {
  47.         …
  48.     }
  49. }

我们知道,在前面的performLaunchActivity函数中完成Activity的创建后,会将当前当前创建的Activity在应用程序进程端的描述符ActivityClientRecord以键值对的形式保存到ActivityThread的成员变量mActivities中:mActivities.put(r.token, r),r.token就是Activity的身份证,即是IApplicationToken.Proxy代理对象,也用于与AMS通信。上面的函数首先通过performResumeActivity从mActivities变量中取出Activity的应用程序端描述符ActivityClientRecord,然后取出前面为Activity创建的视图对象DecorView和窗口管理器WindowManager,最后将视图对象添加到窗口管理器中。

我们知道Activity引用的其实是轻量级的窗口管理器LocalWindowManager

frameworksbasecorejavaandroidview Window.java

  1. public final void addView(View view, ViewGroup.LayoutParams params) {
  2.     WindowManager.LayoutParams wp = (WindowManager.LayoutParams)params;
  3.     CharSequence curTitle = wp.getTitle();
  4.     //应用程序窗口
  5.     if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
  6.         wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
  7.         if (wp.token == null) {
  8.             View decor = peekDecorView();
  9.             if (decor != null) {
  10.                 // LayoutParams 的token设置为W本地Binder对象
  11.                 wp.token = decor.getWindowToken();
  12.             }
  13.         }
  14.         if (curTitle == null || curTitle.length() == 0) {
  15.             //根据窗口类型设置不同的标题
  16.             …
  17.             if (mAppName != null) {
  18.                 title += “:” + mAppName;
  19.             }
  20.             wp.setTitle(title);
  21.         }
  22.     } else {//系统窗口
  23.         if (wp.token == null) {
  24.             wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
  25.         }
  26.         if ((curTitle == null || curTitle.length() == 0)
  27.                 && mAppName != null) {
  28.             wp.setTitle(mAppName);
  29.         }
  30.     }
  31.     if (wp.packageName == null) {
  32.         wp.packageName = mContext.getPackageName();
  33.     }
  34.     if (mHardwareAccelerated) {
  35.         wp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
  36.     }
  37.     super.addView(view, params);
  38. }

LocalWindowManager的addView函数对不同类型窗口的布局参数进行相应的设置,比如布局参数中的token设置,如果是应用程序窗口,则设置token为W本地Binder对象。如果不是应用程序窗口,同时当前窗口没有父窗口,则设置token为当前窗口的IApplicationToken.Proxy代理对象,否则设置为父窗口的IApplicationToken.Proxy代理对象。最后视图组件的添加工作交给其父类来完成。LocalWindowManager继承于CompatModeWrapper,是WindowManagerImpl的内部类。

frameworksbasecorejavaandroidviewWindowManagerImpl.java

  1. public void addView(View view, android.view.ViewGroup.LayoutParams params) {
  2.     mWindowManager.addView(view, params, mCompatibilityInfo);
  3. }

前面我们介绍了,每一个Activity拥有一个轻量级窗口管理器,通过轻量级窗口管理器LocalWindowManager来访问重量级窗口管理器WindowManagerImpl,因此视图组件的添加过程又转交给了WindowManagerImpl来实现。

  1. public void addView(View view, ViewGroup.LayoutParams params, CompatibilityInfoHolder cih) {
  2.     addView(view, params, cih, false);
  3. }

该函数又调用WindowManagerImpl的另一个重载函数来添加视图组件

  1. private void addView(View view, ViewGroup.LayoutParams params,
  2.         CompatibilityInfoHolder cih, boolean nest) {
  3.     …
  4.     final WindowManager.LayoutParams wparams= (WindowManager.LayoutParams)params;
  5.     ViewRootImpl root;
  6.     View panelParentView = null;
  7.     synchronized (this) {
  8.         …
  9.         //从mViews中查找当前添加的View
  10.         int index = findViewLocked(view, false);
  11.         //如果已经存在,直接返回
  12.         if (index >= 0) {
  13.             …
  14.             return;
  15.         }
  16.         //尚未添加当前View
  17.         if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
  18.                 wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
  19.             final int count = mViews != null ? mViews.length : 0;
  20.             for (int i=0; i<count; i++) {
  21.                 if (mRoots[i].mWindow.asBinder() == wparams.token) {
  22.                     panelParentView = mViews[i];
  23.                 }
  24.             }
  25.         }
  26.         //为Activity创建一个ViewRootImpl对象
  27.         ①root = new ViewRootImpl(view.getContext());
  28.         …
  29.         //设置视图组件的布局参数
  30.         view.setLayoutParams(wparams);
  31.         if (mViews == null) {
  32.             index = 1;
  33.             mViews = new View[1];
  34.             mRoots = new ViewRootImpl[1];
  35.             mParams = new WindowManager.LayoutParams[1];
  36.         } else {
  37.             //动态增加mViews数组长度
  38.             index = mViews.length + 1;
  39.             Object[] old = mViews;
  40.             mViews = new View[index];
  41.             System.arraycopy(old, 0, mViews, 0, index-1);
  42.             //动态增加mRoots数组长度
  43.             old = mRoots;
  44.             mRoots = new ViewRootImpl[index];
  45.             System.arraycopy(old, 0, mRoots, 0, index-1);
  46.             //动态增加mParams数组长度
  47.             old = mParams;
  48.             mParams = new WindowManager.LayoutParams[index];
  49.             System.arraycopy(old, 0, mParams, 0, index-1);
  50.         }
  51.         index–;
  52.         ②mViews[index] = view;
  53.         mRoots[index] = root;
  54.         mParams[index] = wparams;
  55.     }
  56.     try {
  57.         ③root.setView(view, wparams, panelParentView);
  58.     } catch (RuntimeException e) {
  59.         …
  60.     }
  61. }

到此我们知道,当应用程序向窗口管理器中添加一个视图对象时,首先会为该视图对象创建一个ViewRootImpl对象,并且将视图对象、ViewRootImpl对象、视图布局参数分别保存到窗口管理器WindowManagerImpl得mViews、mRoots、mParams数组中,如下图所示:

最后通过ViewRootImpl对象来完成视图的显示过程。

ViewRootImpl构造过程

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. public ViewRootImpl(Context context) {
  2.     …
  3.     ①getWindowSession(context.getMainLooper());
  4.     mThread = Thread.currentThread();
  5.     mLocation = new WindowLeaked(null);
  6.     mLocation.fillInStackTrace();
  7.     mWidth = –1;
  8.     mHeight = –1;
  9.     mDirty = new Rect();
  10.     mTempRect = new Rect();
  11.     mVisRect = new Rect();
  12.     mWinFrame = new Rect();
  13.     ②mWindow = new W(this);
  14.     mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
  15.     mInputMethodCallback = new InputMethodCallback(this);
  16.     mViewVisibility = View.GONE;
  17.     mTransparentRegion = new Region();
  18.     mPreviousTransparentRegion = new Region();
  19.     mFirst = true// true for the first time the view is added
  20.     mAdded = false;
  21.     mAccessibilityManager = AccessibilityManager.getInstance(context);
  22.     mAccessibilityInteractionConnectionManager =
  23.         new AccessibilityInteractionConnectionManager();
  24.     mAccessibilityManager.addAccessibilityStateChangeListener(
  25.             mAccessibilityInteractionConnectionManager);
  26.     ③mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, mHandler, this);
  27.     mViewConfiguration = ViewConfiguration.get(context);
  28.     mDensity = context.getResources().getDisplayMetrics().densityDpi;
  29.     mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context);
  30.     mProfileRendering = Boolean.parseBoolean(
  31.             SystemProperties.get(PROPERTY_PROFILE_RENDERING, “false”));
  32.     ④mChoreographer = Choreographer.getInstance();
  33.     PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
  34.     mAttachInfo.mScreenOn = powerManager.isScreenOn();
  35.     loadSystemProperties();
  36. }

在ViewRootImpl的构造函数中初始化了一些成员变量,ViewRootImpl创建了以下几个主要对象:

1)        通过getWindowSession(context.getMainLooper())得到IWindowSession的代理对象,该对象用于和WMS通信。

2)        创建了一个W本地Binder对象,用于WMS通知应用程序进程。

3)        采用单例模式创建了一个Choreographer对象,用于统一调度窗口绘图。

4)        创建ViewRootHandler对象,用于处理当前视图消息。

5)        构造一个AttachInfo对象;

6)        创建Surface对象,用于绘制当前视图,当然该Surface对象的真正创建是由WMS来完成的,只不过是WMS传递给应用程序进程的。

  1. private final Surface mSurface = new Surface();
  2. final ViewRootHandler mHandler = new ViewRootHandler();

IWindowSession代理获取过程

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. public static IWindowSession getWindowSession(Looper mainLooper) {
  2.     synchronized (mStaticInit) {
  3.         if (!mInitialized) {
  4.             try {
  5.                 //获取输入法管理器
  6.                 InputMethodManager imm = InputMethodManager.getInstance(mainLooper);
  7.                 //获取窗口管理器
  8.                 IWindowManager windowManager = Display.getWindowManager();
  9.                 //得到IWindowSession代理对象
  10.                 sWindowSession = windowManager.openSession(imm.getClient(), imm.getInputContext());
  11.                 float animatorScale = windowManager.getAnimationScale(2);
  12.                 ValueAnimator.setDurationScale(animatorScale);
  13.                 mInitialized = true;
  14.             } catch (RemoteException e) {
  15.             }
  16.         }
  17.         return sWindowSession;
  18.     }
  19. }

以上函数通过WMS的openSession函数创建应用程序与WMS之间的连接通道,即获取IWindowSession代理对象,并将该代理对象保存到ViewRootImpl的静态成员变量sWindowSession中

  1. static IWindowSession sWindowSession;

因此在应用程序进程中有且只有一个IWindowSession代理对象。

frameworksbaseservicesjavacomandroidserverwmWindowManagerService.java

  1. public IWindowSession openSession(IInputMethodClient client,
  2.         IInputContext inputContext) {
  3.     if (client == nullthrow new IllegalArgumentException(“null client”);
  4.     if (inputContext == nullthrow new IllegalArgumentException(“null inputContext”);
  5.     Session session = new Session(this, client, inputContext);
  6.     return session;
  7. }

在WMS服务端构造了一个Session实例对象。

AttachInfo构造过程

frameworksbasecorejavaandroidview View.java

  1. AttachInfo(IWindowSession session, IWindow window,
  2.         ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
  3.     mSession = session;//IWindowSession代理对象,用于与WMS通信
  4.     mWindow = window;//W对象
  5.     mWindowToken = window.asBinder();//W本地Binder对象
  6.     mViewRootImpl = viewRootImpl;//ViewRootImpl实例
  7.     mHandler = handler;//ViewRootHandler对象
  8.     mRootCallbacks = effectPlayer;//ViewRootImpl实例
  9. }

 

创建Choreographer对象

Android Project Butter分析中介绍了Android4.1引入VSYNC、Triple Buffer和Choreographer来改善Android先天存在的UI流畅性差问题,有关Choreographer的实现过程请参看Android系统Choreographer机制实现过程

视图View添加过程

窗口管理器WindowManagerImpl为当前添加的窗口创建好各种对象后,调用ViewRootImpl的setView函数向WMS服务添加一个窗口对象。

  1. public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
  2.     synchronized (this) {
  3.         if (mView == null) {
  4.             //将DecorView保存到ViewRootImpl的成员变量mView中
  5.             mView = view;
  6.             mFallbackEventHandler.setView(view);
  7.             mWindowAttributes.copyFrom(attrs);
  8.             attrs = mWindowAttributes;
  9.             mClientWindowLayoutFlags = attrs.flags;
  10.             setAccessibilityFocus(nullnull);
  11.             //DecorView实现了RootViewSurfaceTaker接口
  12.             if (view instanceof RootViewSurfaceTaker) {
  13.                 mSurfaceHolderCallback =
  14.                         ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
  15.                 if (mSurfaceHolderCallback != null) {
  16.                     mSurfaceHolder = new TakenSurfaceHolder();
  17.                     mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
  18.                 }
  19.             }
  20.             …
  21.             //同时将DecorView保存到mAttachInfo中
  22.             mAttachInfo.mRootView = view;
  23.             mAttachInfo.mScalingRequired = mTranslator != null;
  24.             mAttachInfo.mApplicationScale = mTranslator == null ? 1.0f : mTranslator.applicationScale;
  25.             if (panelParentView != null) {
  26.                 mAttachInfo.mPanelParentWindowToken
  27.                         = panelParentView.getApplicationWindowToken();
  28.             }
  29.             …
  30.             //在添加窗口前进行UI布局
  31.             ①requestLayout();
  32.             if ((mWindowAttributes.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
  33.                 mInputChannel = new InputChannel();
  34.             }
  35.             try {
  36.                 mOrigWindowType = mWindowAttributes.type;
  37.                 mAttachInfo.mRecomputeGlobalAttributes = true;
  38.                 collectViewAttributes();
  39.                 //将窗口添加到WMS服务中,mWindow为W本地Binder对象,通过Binder传输到WMS服务端后,变为IWindow代理对象
  40.                 ②res = sWindowSession.add(mWindow, mSeq, mWindowAttributes,
  41.                         getHostVisibility(), mAttachInfo.mContentInsets,
  42.                         mInputChannel);
  43.             } catch (RemoteException e) {
  44.                 …
  45.             }
  46.             …
  47.             //建立窗口消息通道
  48.             if (view instanceof RootViewSurfaceTaker) {
  49.                 mInputQueueCallback =
  50.                     ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
  51.             }
  52.             if (mInputChannel != null) {
  53.                 if (mInputQueueCallback != null) {
  54.                     mInputQueue = new InputQueue(mInputChannel);
  55.                     mInputQueueCallback.onInputQueueCreated(mInputQueue);
  56.                 } else {
  57.                     mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,Looper.myLooper());
  58.                 }
  59.             }
  60.             …
  61.         }
  62.     }
  63. }

通过前面的分析可以知道,用户自定义的UI作为一个子View被添加到DecorView中,然后将顶级视图DecorView添加到应用程序进程的窗口管理器中,窗口管理器首先为当前添加的View创建一个ViewRootImpl对象、一个布局参数对象ViewGroup.LayoutParams,然后将这三个对象分别保存到当前应用程序进程的窗口管理器WindowManagerImpl中,最后通过ViewRootImpl对象将当前视图对象注册到WMS服务中。

ViewRootImpl的setView函数向WMS服务添加一个窗口对象过程:

1)         requestLayout()在应用程序进程中进行窗口UI布局;

2)         WindowSession.add()向WMS服务注册一个窗口对象;

3)         注册应用程序进程端的消息接收通道;

窗口UI布局过程

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. public void requestLayout() {
  2.     //检查当前线程是否是UI线程
  3.     checkThread();
  4.     //标识当前正在请求UI布局
  5.     mLayoutRequested = true;
  6.     scheduleTraversals();
  7. }

窗口布局过程必须在UI线程中进行,因此该函数首先检查调用requestLayout()函数的线程是否为创建ViewRootImpl对象的线程。然后调用scheduleTraversals()函数启动Choreographer的Callback遍历过程。

  1. void scheduleTraversals() {
  2.     if (!mTraversalScheduled) {
  3.         mTraversalScheduled = true;
  4.         //暂停UI线程消息队列对同步消息的处理
  5.         mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
  6.         //向Choreographer注册一个类型为CALLBACK_TRAVERSAL的回调,用于处理UI绘制
  7.         mChoreographer.postCallback(
  8.                 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
  9.         //向Choreographer注册一个类型为CALLBACK_INPUT的回调,用于处理输入事件
  10.         scheduleConsumeBatchedInput();
  11.     }
  12. }

关于Choreographer的postCallback()用法在前面进行了详细的介绍,当Vsync事件到来时,mTraversalRunnable对象的run()函数将被调用。

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  2. final class TraversalRunnable implements Runnable {
  3.         @Override
  4.         public void run() {
  5.             doTraversal();
  6.         }
  7. }

mTraversalRunnable对象的类型为TraversalRunnable,该类实现了Runnable接口,在其run()函数中调用了doTraversal()函数来完成窗口布局。

  1. void doTraversal() {
  2.     if (mTraversalScheduled) {
  3.         mTraversalScheduled = false;
  4.         mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
  5.         if (mProfile) {
  6.             Debug.startMethodTracing(“ViewAncestor”);
  7.         }
  8.         Trace.traceBegin(Trace.TRACE_TAG_VIEW, “performTraversals”);
  9.         try {
  10.             performTraversals();
  11.         } finally {
  12.             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  13.         }
  14.         if (mProfile) {
  15.             Debug.stopMethodTracing();
  16.             mProfile = false;
  17.         }
  18.     }
  19. }

performTraversals函数相当复杂,其主要实现以下几个重要步骤:

1.执行窗口测量;

2.执行窗口注册;

3.执行窗口布局;

4.执行窗口绘图;

  1. private void performTraversals() {
  2.     // cache mView since it is used so much below…
  3.     final View host = mView;
  4.     if (host == null || !mAdded)
  5.         return;
  6.     mWillDrawSoon = true;
  7.     boolean windowSizeMayChange = false;
  8.     boolean newSurface = false;
  9.     boolean surfaceChanged = false;
  10.     WindowManager.LayoutParams lp = mWindowAttributes;
  11.     int desiredWindowWidth;
  12.     int desiredWindowHeight;
  13.     final View.AttachInfo attachInfo = mAttachInfo;
  14.     final int viewVisibility = getHostVisibility();
  15.     boolean viewVisibilityChanged = mViewVisibility != viewVisibility
  16.             || mNewSurfaceNeeded;
  17.     WindowManager.LayoutParams params = null;
  18.     if (mWindowAttributesChanged) {
  19.         mWindowAttributesChanged = false;
  20.         surfaceChanged = true;
  21.         params = lp;
  22.     }
  23.     …
  24.     /****************执行窗口测量******************/
  25.     boolean layoutRequested = mLayoutRequested && !mStopped;
  26.     if (layoutRequested) {
  27.         …
  28.         // Ask host how big it wants to be
  29.         windowSizeMayChange |= measureHierarchy(host, lp, res,
  30.                 desiredWindowWidth, desiredWindowHeight);
  31.     }
  32.     …
  33.     /****************向WMS服务添加窗口******************/
  34.     if (mFirst || windowShouldResize || insetsChanged ||
  35.             viewVisibilityChanged || params != null) {
  36.         …
  37.         try {
  38.             final int surfaceGenerationId = mSurface.getGenerationId();
  39.             relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
  40.             …
  41.         } catch (RemoteException e) {
  42.         }
  43.         …
  44.         if (!mStopped) {
  45.             boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
  46.                     (relayoutResult&WindowManagerImpl.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
  47.             if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
  48.                     || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
  49.                 …
  50.                  // Ask host how big it wants to be
  51.                 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  52.                 …
  53.             }
  54.         }
  55.     }
  56.     /****************执行窗口布局******************/
  57.     final boolean didLayout = layoutRequested && !mStopped;
  58.     boolean triggerGlobalLayoutListener = didLayout
  59.             || attachInfo.mRecomputeGlobalAttributes;
  60.     if (didLayout) {
  61.         performLayout();
  62.         …
  63.     }
  64.     …
  65.     /****************查找窗口焦点******************/
  66.     boolean skipDraw = false;
  67.     if (mFirst) {
  68.         // handle first focus request
  69.         if (DEBUG_INPUT_RESIZE) Log.v(TAG, “First: mView.hasFocus()=”
  70.                 + mView.hasFocus());
  71.         if (mView != null) {
  72.             if (!mView.hasFocus()) {
  73.                 mView.requestFocus(View.FOCUS_FORWARD);
  74.                 mFocusedView = mRealFocusedView = mView.findFocus();
  75.                 if (DEBUG_INPUT_RESIZE) Log.v(TAG, “First: requested focused view=”
  76.                         + mFocusedView);
  77.             } else {
  78.                 mRealFocusedView = mView.findFocus();
  79.                 if (DEBUG_INPUT_RESIZE) Log.v(TAG, “First: existing focused view=”
  80.                         + mRealFocusedView);
  81.             }
  82.         }
  83.         if ((relayoutResult&WindowManagerImpl.RELAYOUT_RES_ANIMATING) != 0) {
  84.             // The first time we relayout the window, if the system is
  85.             // doing window animations, we want to hold of on any future
  86.             // draws until the animation is done.
  87.             mWindowsAnimating = true;
  88.         }
  89.     } else if (mWindowsAnimating) {
  90.         skipDraw = true;
  91.     }
  92.     /****************执行窗口绘制******************/
  93.     mFirst = false;
  94.     mWillDrawSoon = false;
  95.     mNewSurfaceNeeded = false;
  96.     mViewVisibility = viewVisibility;
  97.     …
  98.     boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() ||
  99.             viewVisibility != View.VISIBLE;
  100.     if (!cancelDraw && !newSurface) {
  101.         if (!skipDraw || mReportNextDraw) {
  102.             if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
  103.                 for (int i = 0; i < mPendingTransitions.size(); ++i) {
  104.                     mPendingTransitions.get(i).startChangingAnimations();
  105.                 }
  106.                 mPendingTransitions.clear();
  107.             }
  108.             performDraw();
  109.         }
  110.     } else {
  111.         if (viewVisibility == View.VISIBLE) {
  112.             // Try again
  113.             scheduleTraversals();
  114.         } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
  115.             for (int i = 0; i < mPendingTransitions.size(); ++i) {
  116.                 mPendingTransitions.get(i).endChangingAnimations();
  117.             }
  118.             mPendingTransitions.clear();
  119.         }
  120.     }
  121. }
performMeasure

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
  2.  Trace.traceBegin(Trace.TRACE_TAG_VIEW, “measure”);
  3.  try {
  4.   mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  5.  } finally {
  6.   Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  7.  }
  8. }
relayoutWindow

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
  2.         boolean insetsPending) throws RemoteException {
  3.     …
  4.     int relayoutResult = sWindowSession.relayout(
  5.             mWindow, mSeq, params,
  6.             (int) (mView.getMeasuredWidth() * appScale + 0.5f),
  7.             (int) (mView.getMeasuredHeight() * appScale + 0.5f),
  8.             viewVisibility, insetsPending ? WindowManagerImpl.RELAYOUT_INSETS_PENDING : 0,
  9.             mWinFrame, mPendingContentInsets, mPendingVisibleInsets,
  10.             mPendingConfiguration, mSurface);
  11.     …
  12.     return relayoutResult;
  13. }

这里通过前面获取的IWindowSession代理对象请求WMS服务执行窗口布局,mSurface是ViewRootImpl的成员变量

  1. private final Surface mSurface = new Surface();

frameworksbasecorejavaandroidview Surface.java

  1. public Surface() {
  2.     checkHeadless();
  3.     if (DEBUG_RELEASE) {
  4.         mCreationStack = new Exception();
  5.     }
  6.     mCanvas = new CompatibleCanvas();
  7. }

该Surface构造函数仅仅创建了一个CompatibleCanvas对象,并没有对该Surface进程native层的初始化,到此我们知道应用程序进程为每个窗口对象都创建了一个Surface对象。并且将该Surface通过跨进程方式传输给WMS服务进程,我们知道,在Android系统中,如果一个对象需要在不同进程间传输,必须实现Parcelable接口,Surface类正好实现了Parcelable接口。ViewRootImpl通过IWindowSession接口请求WMS的完整过程如下:

frameworksbasecorejavaandroidviewIWindowSession.java$ Proxy

  1. public int relayout(android.view.IWindow window, int seq,
  2.         android.view.WindowManager.LayoutParams attrs, int requestedWidth,
  3.         int requestedHeight, int viewVisibility, int flags,
  4.         android.graphics.Rect outFrame,
  5.         android.graphics.Rect outOverscanInsets,
  6.         android.graphics.Rect outContentInsets,
  7.         android.graphics.Rect outVisibleInsets,
  8.         android.content.res.Configuration outConfig,
  9.         android.view.Surface outSurface) throws android.os.RemoteException {
  10.     android.os.Parcel _data = android.os.Parcel.obtain();
  11.     android.os.Parcel _reply = android.os.Parcel.obtain();
  12.     int _result;
  13.     try {
  14.         _data.writeInterfaceToken(DESCRIPTOR);
  15.         _data.writeStrongBinder((((window != null)) ? (window.asBinder()): (null)));
  16.         _data.writeInt(seq);
  17.         if ((attrs != null)) {
  18.             _data.writeInt(1);
  19.             attrs.writeToParcel(_data, 0);
  20.         } else {
  21.             _data.writeInt(0);
  22.         }
  23.         _data.writeInt(requestedWidth);
  24.         _data.writeInt(requestedHeight);
  25.         _data.writeInt(viewVisibility);
  26.         _data.writeInt(flags);
  27.         mRemote.transact(Stub.TRANSACTION_relayout, _data, _reply, 0);
  28.         _reply.readException();
  29.         _result = _reply.readInt();
  30.         if ((0 != _reply.readInt())) {
  31.             outFrame.readFromParcel(_reply);
  32.         }
  33.         if ((0 != _reply.readInt())) {
  34.             outOverscanInsets.readFromParcel(_reply);
  35.         }
  36.         if ((0 != _reply.readInt())) {
  37.             outContentInsets.readFromParcel(_reply);
  38.         }
  39.         if ((0 != _reply.readInt())) {
  40.             outVisibleInsets.readFromParcel(_reply);
  41.         }
  42.         if ((0 != _reply.readInt())) {
  43.             outConfig.readFromParcel(_reply);
  44.         }
  45.         if ((0 != _reply.readInt())) {
  46.             outSurface.readFromParcel(_reply);
  47.         }
  48.     } finally {
  49.         _reply.recycle();
  50.         _data.recycle();
  51.     }
  52.     return _result;
  53. }

从该函数的实现可以看出,应用程序进程中创建的Surface对象并没有传递到WMS服务进程,只是读取WMS服务进程返回来的Surface。那么WMS服务进程是如何响应应用程序进程布局请求的呢?
frameworksbasecorejavaandroidviewIWindowSession.java$ Stub

  1. public boolean onTransact(int code, android.os.Parcel data,
  2.         android.os.Parcel reply, int flags)throws android.os.RemoteException {
  3.     switch (code) {
  4.     case TRANSACTION_relayout: {
  5.         data.enforceInterface(DESCRIPTOR);
  6.         android.view.IWindow _arg0;
  7.         _arg0 = android.view.IWindow.Stub.asInterface(data.readStrongBinder());
  8.         int _arg1;
  9.         _arg1 = data.readInt();
  10.         android.view.WindowManager.LayoutParams _arg2;
  11.         if ((0 != data.readInt())) {
  12.             _arg2 = android.view.WindowManager.LayoutParams.CREATOR
  13.                     .createFromParcel(data);
  14.         } else {
  15.             _arg2 = null;
  16.         }
  17.         int _arg3;
  18.         _arg3 = data.readInt();
  19.         int _arg4;
  20.         _arg4 = data.readInt();
  21.         int _arg5;
  22.         _arg5 = data.readInt();
  23.         int _arg6;
  24.         _arg6 = data.readInt();
  25.         android.graphics.Rect _arg7;
  26.         _arg7 = new android.graphics.Rect();
  27.         android.graphics.Rect _arg8;
  28.         _arg8 = new android.graphics.Rect();
  29.         android.graphics.Rect _arg9;
  30.         _arg9 = new android.graphics.Rect();
  31.         android.graphics.Rect _arg10;
  32.         _arg10 = new android.graphics.Rect();
  33.         android.content.res.Configuration _arg11;
  34.         _arg11 = new android.content.res.Configuration();
  35.         android.view.Surface _arg12;
  36.         _arg12 = new android.view.Surface();
  37.         int _result = this.relayout(_arg0, _arg1, _arg2, _arg3, _arg4,
  38.                 _arg5, _arg6, _arg7, _arg8, _arg9, _arg10, _arg11, _arg12);
  39.         reply.writeNoException();
  40.         reply.writeInt(_result);
  41.         if ((_arg7 != null)) {
  42.             reply.writeInt(1);
  43.             _arg7.writeToParcel(reply,
  44.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  45.         } else {
  46.             reply.writeInt(0);
  47.         }
  48.         if ((_arg8 != null)) {
  49.             reply.writeInt(1);
  50.             _arg8.writeToParcel(reply,
  51.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  52.         } else {
  53.             reply.writeInt(0);
  54.         }
  55.         if ((_arg9 != null)) {
  56.             reply.writeInt(1);
  57.             _arg9.writeToParcel(reply,
  58.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  59.         } else {
  60.             reply.writeInt(0);
  61.         }
  62.         if ((_arg10 != null)) {
  63.             reply.writeInt(1);
  64.             _arg10.writeToParcel(reply,
  65.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  66.         } else {
  67.             reply.writeInt(0);
  68.         }
  69.         if ((_arg11 != null)) {
  70.             reply.writeInt(1);
  71.             _arg11.writeToParcel(reply,
  72.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  73.         } else {
  74.             reply.writeInt(0);
  75.         }
  76.         if ((_arg12 != null)) {
  77.             reply.writeInt(1);
  78.             _arg12.writeToParcel(reply,
  79.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  80.         } else {
  81.             reply.writeInt(0);
  82.         }
  83.         return true;
  84.     }
  85.     }
  86. }

该函数可以看出,WMS服务在响应应用程序进程请求添加窗口时,首先在当前进程空间创建一个Surface对象,然后调用Session的relayout()函数进一步完成窗口添加过程,最后将WMS服务中创建的Surface返回给应用程序进程。

到目前为止,在应用程序进程和WMS服务进程分别创建了一个Surface对象,但是他们调用的都是Surface的无参构造函数,在该构造函数中并未真正初始化native层的Surface,那native层的Surface是在那里创建的呢?
frameworksbaseservicesjavacomandroidserverwm Session.java

  1. public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
  2.         int requestedWidth, int requestedHeight, int viewFlags,
  3.         int flags, Rect outFrame, Rect outContentInsets,
  4.         Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
  5.     int res = mService.relayoutWindow(this, window, seq, attrs,
  6.             requestedWidth, requestedHeight, viewFlags, flags,
  7.             outFrame, outContentInsets, outVisibleInsets,
  8.             outConfig, outSurface);
  9.     return res;
  10. }

frameworksbaseservicesjavacomandroidserverwm WindowManagerService.java

  1. public int relayoutWindow(Session session, IWindow client, int seq,
  2.         WindowManager.LayoutParams attrs, int requestedWidth,
  3.         int requestedHeight, int viewVisibility, int flags,
  4.         Rect outFrame, Rect outContentInsets,
  5.         Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
  6.     …
  7.     synchronized(mWindowMap) {
  8.         // TODO(cmautner): synchronize on mAnimator or win.mWinAnimator.
  9.         WindowState win = windowForClientLocked(session, client, false);
  10.         if (win == null) {
  11.             return 0;
  12.         }
  13.         …
  14.         if (viewVisibility == View.VISIBLE &&
  15.                 (win.mAppToken == null || !win.mAppToken.clientHidden)) {
  16.             …
  17.             try {
  18.                 if (!win.mHasSurface) {
  19.                     surfaceChanged = true;
  20.                 }
  21.                 //创建Surface
  22.                 Surface surface = winAnimator.createSurfaceLocked();
  23.                 if (surface != null) {
  24.                     outSurface.copyFrom(surface);
  25.                 } else {
  26.                     outSurface.release();
  27.                 }
  28.             } catch (Exception e) {
  29.                 …
  30.             }
  31.             …
  32.         }
  33.         …
  34.     }
  35.     …
  36. }

frameworksbaseservicesjavacomandroidserverwmWindowStateAnimator.java

  1. Surface createSurfaceLocked() {
  2.     if (mSurface == null) {
  3.         …
  4.         try {
  5.             …
  6.             if (DEBUG_SURFACE_TRACE) {
  7.                 mSurface = new SurfaceTrace(
  8.                         mSession.mSurfaceSession, mSession.mPid,
  9.                         attrs.getTitle().toString(),
  10.                         0, w, h, format, flags);
  11.             } else {
  12.                 mSurface = new Surface(
  13.                     mSession.mSurfaceSession, mSession.mPid,
  14.                     attrs.getTitle().toString(),
  15.                     0, w, h, format, flags);
  16.             }
  17.             mWin.mHasSurface = true;
  18.         } catch (Surface.OutOfResourcesException e) {
  19.             …
  20.         }
  21.         Surface.openTransaction();
  22.         …
  23.     }
  24.     return mSurface;
  25. }

Surface创建过程
frameworksbasecorejavaandroidviewSurface.java

  1. public Surface(SurfaceSession s,int pid, String name, int display, int w, int h, int format, int flags)
  2.     throws OutOfResourcesException {
  3.     checkHeadless();
  4.     if (DEBUG_RELEASE) {
  5.         mCreationStack = new Exception();
  6.     }
  7.     mCanvas = new CompatibleCanvas();
  8.     init(s,pid,name,display,w,h,format,flags);
  9.     mName = name;
  10. }

frameworksbasecorejni android_view_Surface.cpp

  1. static void Surface_init(
  2.         JNIEnv* env, jobject clazz,
  3.         jobject session,
  4.         jint, jstring jname, jint dpy, jint w, jint h, jint format, jint flags)
  5. {
  6.     if (session == NULL) {
  7.         doThrowNPE(env);
  8.         return;
  9.     }
  10.     SurfaceComposerClient* client =
  11.             (SurfaceComposerClient*)env->GetIntField(session, sso.client);
  12.     sp<SurfaceControl> surface;
  13.     if (jname == NULL) {
  14.         surface = client->createSurface(dpy, w, h, format, flags);
  15.     } else {
  16.         const jchar* str = env->GetStringCritical(jname, 0);
  17.         const String8 name(str, env->GetStringLength(jname));
  18.         env->ReleaseStringCritical(jname, str);
  19.         surface = client->createSurface(name, dpy, w, h, format, flags);
  20.     }
  21.     if (surface == 0) {
  22.         jniThrowException(env, OutOfResourcesException, NULL);
  23.         return;
  24.     }
  25.     setSurfaceControl(env, clazz, surface);
  26. }

到此才算真正创建了一个可用于绘图的Surface,从上面的分析我们可以看出,在WMS服务进程端,其实创建了两个Java层的Surface对象,第一个Surface使用了无参构造函数,仅仅构造一个Surface对象而已,而第二个Surface却使用了有参构造函数,参数指定了图象宽高等信息,这个Java层Surface对象还会在native层请求SurfaceFlinger创建一个真正能用于绘制图象的native层Surface。最后通过浅拷贝的方式将第二个Surface复制到第一个Surface中,最后通过writeToParcel方式写回到应用程序进程。

到目前为止,应用程序和WMS一共创建了3个Java层Surface对象,如上图所示,而真正能用于绘图的Surface只有3号,那么3号Surface与2号Surface之间是什么关系呢?outSurface.copyFrom(surface)
frameworksbasecorejni android_view_Surface.cpp

  1. static void Surface_copyFrom(JNIEnv* env, jobject clazz, jobject other)
  2. {
  3.     if (clazz == other)
  4.         return;
  5.     if (other == NULL) {
  6.         doThrowNPE(env);
  7.         return;
  8.     }
  9.     //得到当前Surface所引用的SurfaceControl对象
  10.     const sp<SurfaceControl>& surface = getSurfaceControl(env, clazz);
  11.     //得到源Surface所引用的SurfaceControl对象
  12.     const sp<SurfaceControl>& rhs = getSurfaceControl(env, other);
  13.     //如果它们引用的不是同一个SurfaceControl对象
  14.     if (!SurfaceControl::isSameSurface(surface, rhs)) {
  15.         setSurfaceControl(env, clazz, rhs);
  16.     }
  17. }

2号Surface引用到了3号Surface的SurfaceControl对象后,通过writeToParcel()函数写会到应用程序进程。
frameworksbasecorejni android_view_Surface.cpp

  1. static void Surface_writeToParcel(
  2.         JNIEnv* env, jobject clazz, jobject argParcel, jint flags)
  3. {
  4.     Parcel* parcel = (Parcel*)env->GetIntField(
  5.             argParcel, no.native_parcel);
  6.     if (parcel == NULL) {
  7.         doThrowNPE(env);
  8.         return;
  9.     }
  10.     const sp<SurfaceControl>& control(getSurfaceControl(env, clazz));
  11.     if (control != NULL) {
  12.         SurfaceControl::writeSurfaceToParcel(control, parcel);
  13.     } else {
  14.         sp<Surface> surface(Surface_getSurface(env, clazz));
  15.         if (surface != NULL) {
  16.             Surface::writeToParcel(surface, parcel);
  17.         } else {
  18.             SurfaceControl::writeSurfaceToParcel(NULL, parcel);
  19.         }
  20.     }
  21.     if (flags & PARCELABLE_WRITE_RETURN_VALUE) {
  22.         setSurfaceControl(env, clazz, NULL);
  23.         setSurface(env, clazz, NULL);
  24.     }
  25. }

由于2号Surface引用的SurfaceControl对象不为空,因此这里就将SurfaceControl对象写会给应用程序进程
frameworksnativelibsgui Surface.cpp

  1. status_t SurfaceControl::writeSurfaceToParcel(
  2.         const sp<SurfaceControl>& control, Parcel* parcel)
  3. {
  4.     sp<ISurface> sur;
  5.     uint32_t identity = 0;
  6.     if (SurfaceControl::isValid(control)) {
  7.         sur = control->mSurface;
  8.         identity = control->mIdentity;
  9.     }
  10.     parcel->writeStrongBinder(sur!=0 ? sur->asBinder() : NULL);
  11.     parcel->writeStrongBinder(NULL);  // NULL ISurfaceTexture in this case.
  12.     parcel->writeInt32(identity);
  13.     return NO_ERROR;
  14. }

写入Parcel包裹的对象顺序如下:

应用程序进程中的1号Surface通过readFromParcel()函数读取从WMS服务进程写回的Binder对象。
frameworksbasecorejni android_view_Surface.cpp

  1. static void Surface_readFromParcel(
  2.         JNIEnv* env, jobject clazz, jobject argParcel)
  3. {
  4.     Parcel* parcel = (Parcel*)env->GetIntField( argParcel, no.native_parcel);
  5.     if (parcel == NULL) {
  6.         doThrowNPE(env);
  7.         return;
  8.     }
  9.     sp<Surface> sur(Surface::readFromParcel(*parcel));
  10.     setSurface(env, clazz, sur);
  11. }

frameworksnativelibsgui Surface.cpp

  1. sp<Surface> Surface::readFromParcel(const Parcel& data) {
  2.     Mutex::Autolock _l(sCachedSurfacesLock);
  3.     sp<IBinder> binder(data.readStrongBinder());
  4.     sp<Surface> surface = sCachedSurfaces.valueFor(binder).promote();
  5.     if (surface == 0) {
  6.        surface = new Surface(data, binder);
  7.        sCachedSurfaces.add(binder, surface);
  8.     } else {
  9.         // The Surface was found in the cache, but we still should clear any
  10.         // remaining data from the parcel.
  11.         data.readStrongBinder();  // ISurfaceTexture
  12.         data.readInt32();         // identity
  13.     }
  14.     if (surface->mSurface == NULL && surface->getISurfaceTexture() == NULL) {
  15.         surface = 0;
  16.     }
  17.     cleanCachedSurfacesLocked();
  18.     return surface;
  19. }

应用程序进程中的1号Surface按相反顺序读取WMS服务端返回过来的Binder对象等数据,并构造一个native层的Surface对象。

  1. Surface::Surface(const Parcel& parcel, const sp<IBinder>& ref)
  2.     : SurfaceTextureClient()
  3. {
  4.     mSurface = interface_cast<ISurface>(ref);
  5.     sp<IBinder> st_binder(parcel.readStrongBinder());
  6.     sp<ISurfaceTexture> st;
  7.     if (st_binder != NULL) {
  8.         st = interface_cast<ISurfaceTexture>(st_binder);
  9.     } else if (mSurface != NULL) {
  10.         st = mSurface->getSurfaceTexture();
  11.     }
  12.     mIdentity   = parcel.readInt32();
  13.     init(st);
  14. }

每个Activity可以有一个或多个Surface,默认情况下一个Activity只有一个Surface,当Activity中使用SurfaceView时,就存在多个Surface。Activity默认surface是在relayoutWindow过程中由WMS服务创建的,然后回传给应用程序进程,我们知道一个Surface其实就是应用程序端的本地窗口,关于Surface的初始化过程这里就不在介绍。

performLayout

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. private void performLayout() {
  2.     mLayoutRequested = false;
  3.     mScrollMayChange = true;
  4.     final View host = mView;
  5.     if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
  6.         Log.v(TAG, “Laying out “ + host + ” to (“ +
  7.                 host.getMeasuredWidth() + “, “ + host.getMeasuredHeight() + “)”);
  8.     }
  9.     Trace.traceBegin(Trace.TRACE_TAG_VIEW, “layout”);
  10.     try {
  11.         host.layout(00, host.getMeasuredWidth(), host.getMeasuredHeight());
  12.     } finally {
  13.         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  14.     }
  15. }

performDraw

frameworksbasecorejavaandroidview ViewRootImpl.java

  1. private void performDraw() {
  2.     if (!mAttachInfo.mScreenOn && !mReportNextDraw) {
  3.         return;
  4.     }
  5.     final boolean fullRedrawNeeded = mFullRedrawNeeded;
  6.     mFullRedrawNeeded = false;
  7.     mIsDrawing = true;
  8.     Trace.traceBegin(Trace.TRACE_TAG_VIEW, “draw”);
  9.     try {
  10.         draw(fullRedrawNeeded);
  11.     } finally {
  12.         mIsDrawing = false;
  13.         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  14.     }
  15.     …
  16. }

 

  1. private void draw(boolean fullRedrawNeeded) {
  2.     Surface surface = mSurface;
  3.     if (surface == null || !surface.isValid()) {
  4.         return;
  5.     }
  6.     …
  7.     if (!dirty.isEmpty() || mIsAnimating) {
  8.         //使用硬件渲染
  9.         if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
  10.             // Draw with hardware renderer.
  11.             mIsAnimating = false;
  12.             mHardwareYOffset = yoff;
  13.             mResizeAlpha = resizeAlpha;
  14.             mCurrentDirty.set(dirty);
  15.             mCurrentDirty.union(mPreviousDirty);
  16.             mPreviousDirty.set(dirty);
  17.             dirty.setEmpty();
  18.             if (attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
  19.                     animating ? null : mCurrentDirty)) {
  20.                 mPreviousDirty.set(00, mWidth, mHeight);
  21.             }
  22.         //使用软件渲染
  23.         } else if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
  24.             return;
  25.         }
  26.     }
  27.     …
  28. }

窗口添加过程

frameworksbaseservicesjavacomandroidserverwmSession.java

  1. public int add(IWindow window, int seq, WindowManager.LayoutParams attrs,
  2.         int viewVisibility, Rect outContentInsets, InputChannel outInputChannel) {
  3.     return mService.addWindow(this, window, seq, attrs, viewVisibility, outContentInsets,
  4.             outInputChannel);
  5. }

frameworksbaseservicesjavacomandroidserverwmWindowManagerService.java

  1. public int addWindow(Session session, IWindow client, int seq,
  2.         WindowManager.LayoutParams attrs, int viewVisibility,
  3.         Rect outContentInsets, InputChannel outInputChannel) {
  4.     //client为IWindow的代理对象,是Activity在WMS服务中的唯一标示
  5.     int res = mPolicy.checkAddPermission(attrs);
  6.     if (res != WindowManagerImpl.ADD_OKAY) {
  7.         return res;
  8.     }
  9.     boolean reportNewConfig = false;
  10.     WindowState attachedWindow = null;
  11.     WindowState win = null;
  12.     long origId;
  13.     synchronized(mWindowMap) {
  14.         if (mDisplay == null) {
  15.             throw new IllegalStateException(“Display has not been initialialized”);
  16.         }
  17.         //判断窗口是否已经存在
  18.         if (mWindowMap.containsKey(client.asBinder())) {
  19.             Slog.w(TAG, “Window “ + client + ” is already added”);
  20.             return WindowManagerImpl.ADD_DUPLICATE_ADD;
  21.         }
  22.         //如果添加的是应用程序窗口
  23.         if (attrs.type >= FIRST_SUB_WINDOW && attrs.type <= LAST_SUB_WINDOW) {
  24.             //根据attrs.token从mWindowMap中取出应用程序窗口在WMS服务中的描述符WindowState
  25.             attachedWindow = windowForClientLocked(null, attrs.token, false);
  26.             if (attachedWindow == null) {
  27.                 Slog.w(TAG, “Attempted to add window with token that is not a window: “
  28.                       + attrs.token + “.  Aborting.”);
  29.                 return WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN;
  30.             }
  31.             if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
  32.                     && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
  33.                 Slog.w(TAG, “Attempted to add window with token that is a sub-window: “
  34.                         + attrs.token + “.  Aborting.”);
  35.                 return WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN;
  36.             }
  37.         }
  38.         boolean addToken = false;
  39.         //根据attrs.token从mWindowMap中取出应用程序窗口在WMS服务中的描述符WindowState
  40.         WindowToken token = mTokenMap.get(attrs.token);
  41.         if (token == null) {
  42.             …
  43.             ①token = new WindowToken(this, attrs.token, –1false);
  44.             addToken = true;
  45.         }
  46.         //应用程序窗口
  47.         else if (attrs.type >= FIRST_APPLICATION_WINDOW
  48.                 && attrs.type <= LAST_APPLICATION_WINDOW) {
  49.             AppWindowToken atoken = token.appWindowToken;
  50.             …
  51.         }
  52.         //输入法窗口
  53.         else if (attrs.type == TYPE_INPUT_METHOD) {
  54.             …
  55.         }
  56.         //墙纸窗口
  57.         else if (attrs.type == TYPE_WALLPAPER) {
  58.             …
  59.         }
  60.         //Dream窗口
  61.         else if (attrs.type == TYPE_DREAM) {
  62.             …
  63.         }
  64.         //为Activity窗口创建WindowState对象
  65.         ②win = new WindowState(this, session, client, token,
  66.                 attachedWindow, seq, attrs, viewVisibility);
  67.         …
  68.         if (outInputChannel != null && (attrs.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
  69.             String name = win.makeInputChannelName();
  70.             InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
  71.             win.setInputChannel(inputChannels[0]);
  72.             inputChannels[1].transferTo(outInputChannel);
  73.             mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
  74.         }
  75.         …
  76.         //以键值对<IWindow.Proxy/Token,WindowToken>形式保存到mTokenMap表中
  77.         if (addToken) {
  78.             ③mTokenMap.put(attrs.token, token);
  79.         }
  80.         ④win.attach();
  81.         //以键值对<IWindow的代理对象,WindowState>形式保存到mWindowMap表中
  82.         ⑤mWindowMap.put(client.asBinder(), win);
  83.         …
  84.     }
  85.     …
  86.     return res;
  87. }

我们知道当应用程序进程添加一个DecorView到窗口管理器时,会为当前添加的窗口创建ViewRootImpl对象,同时构造了一个W本地Binder对象,无论是窗口视图对象DecorView还是ViewRootImpl对象,都只是存在于应用程序进程中,在添加窗口过程中仅仅将该窗口的W对象传递给WMS服务,经过Binder传输后,到达WMS服务端进程后变为IWindow.Proxy代理对象,因此该函数的参数client的类型为IWindow.Proxy。参数attrs的类型为WindowManager.LayoutParams,在应用程序进程启动Activity时,handleResumeActivity()函数通过WindowManager.LayoutParams
l = r.window.getAttributes();来得到应用程序窗口布局参数,由于WindowManager.LayoutParams实现了Parcelable接口,因此WindowManager.LayoutParams对象可以跨进程传输,WMS服务的addWindow函数中的attrs参数就是应用程序进程发送过来的窗口布局参数。在LocalWindowManager的addView函数中为窗口布局参数设置了相应的token,如果是应用程序窗口,则布局参数的token设为W本地Binder对象。如果不是应用程序窗口,同时当前窗口没有父窗口,则设置token为当前窗口的IApplicationToken.Proxy代理对象,否则设置为父窗口的IApplicationToken.Proxy代理对象,由于应用程序和WMS分属于两个不同的进程空间,因此经过Binder传输后,布局参数的令牌attrs.token就转变为IWindow.Proxy或者Token。以上函数首先根据布局参数的token等信息构造一个WindowToken对象,然后在构造一个WindowState对象,并将添加的窗口信息记录到mTokenMap和mWindowMap哈希表中。


在WMS服务端创建了所需对象后,接着调用了WindowState的attach()来进一步完成窗口添加。
frameworksbaseservicesjavacomandroidserverwmWindowState.java

  1. void attach() {
  2.     if (WindowManagerService.localLOGV) Slog.v(
  3.         TAG, “Attaching “ + this + ” token=” + mToken
  4.         + “, list=” + mToken.windows);
  5.     mSession.windowAddedLocked();
  6. }

frameworksbaseservicesjavacomandroidserverwmSession.java

  1. void windowAddedLocked() {
  2.     if (mSurfaceSession == null) {
  3.         mSurfaceSession = new SurfaceSession();
  4.         if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(
  5.                 WindowManagerService.TAG, ”  NEW SURFACE SESSION “ + mSurfaceSession);
  6.         mService.mSessions.add(this);
  7.     }
  8.     mNumWindow++;//记录对应的某个应用程序添加的窗口数量
  9. }

到此一个新的应用程序窗口就添加完成了。总结一下:

应用程序通过IWindowSession接口请求WMS服务添加一个应用程序窗口,WMS服务首先在自己服务进程为应用程序创建创建一个对应的WindowState描述符,然后保存到成员变量mWindowMap中。如果还没有为应用程序进程创建连接SurfaceFlinger的会话,就接着创建该会话通道SurfaceSession,我们知道,Activity中的视图所使用的画布Surface是在WMS服务进程中创建的,但是该画布所使用的图形buffer确实在SurfaceFlinger进程中分配管理的,而图形的绘制确是在应用程序进程中完成的,所以Activity的显示过程需要三个进程的配合才能完成。应用程序进程只与WMS服务进程交互,并不直接和SurfaceFlinger进程交互,而是由WMS服务进程同SurfaceFlinger进程配合。前面我们介绍了应用程序进程是通过IWindowSession接口与WMS服务进程通信的,那WMS服务是如何与SurfaceFlinger进程通信的呢,这就是windowAddedLocked函数要完成的工作。

在windowAddedLocked函数中使用单例模式创建一个SurfaceSession对象,在构造该对象时,通过JNI在native层创建一个与SurfaceFlinger进程的连接。

frameworksbasecorejavaandroidviewSurfaceSession.java

  1. public SurfaceSession() {
  2.     init();
  3. }

该init()函数是一个native函数,其JNI实现如下:

frameworksbasecorejni android_view_Surface.cpp

  1. static void SurfaceSession_init(JNIEnv* env, jobject clazz)
  2. {
  3.     sp<SurfaceComposerClient> client = new SurfaceComposerClient;
  4.     client->incStrong(clazz);
  5.     env->SetIntField(clazz, sso.client, (int)client.get());
  6. }

该函数构造了一个SurfaceComposerClient对象,在第一次强引用该对象时,会请求SurfaceFlinger创建一个专门处理当前应用程序进程请求的Client会话。

每个应用程序进程都持有一个与WMS服务会话通道IWindowSession,而服务端的Session有且只有一个SurfaceSession对象。系统中创建的所有IWindowSession都被记录到WMS服务的mSessions成员变量中,这样WMS就可以知道自己正在处理那些应用程序的请求。到此我们来梳理一下在WMS服务端都创建了那些对象:

1)        WindowState对象,是应用程序窗口在WMS服务端的描述符;

2)        Session对象,应用程序进程与WMS服务会话通道;

3)        SurfaceSession对象,应用程序进程与SurfaceFlinger的会话通道;

 

Android中View绘制流程以及invalidate()等相关方法分析

转载请注明出处:http://blog.csdn.net/qinjuning

 前言: 本文是我读《Android内核剖析》第13章—-View工作原理总结而成的,在此膜拜下作者 。同时真挚地向渴望了解  Android 框架层网友,推荐这本书,希望你们能够在Android开发里学到更多的知识 。 

            整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为之前状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘 (draw),其框架过程如下:

                                                                                                   步骤其实为host.layout() 

           

 

      接下来温习一下整个View树的结构,对每个具体View对象的操作,其实就是个递归的实现。

 

 

   关于这个 DecorView 根视图的说明,可以参考我的这篇博客:

    《Android中将布局文件/View添加至窗口过程分析 —- 从setContentView()谈起》

 

  流程一:      mesarue()过程

        主要作用:为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。

     具体的调用链如下

          ViewRoot根对象的属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:    

         1、设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性:  mMeasuredHeight)和宽(对应属性:mMeasureWidth)   ;

         2 、如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程。

                             2.1  对每个子视图的measure()过程,是通过调用父类ViewGroup.java类里的measureChildWithMargins()方法去 实现,该方法内部只是简单地调用了View对象的measure()方法。(由于measureChildWithMargins()方法只是一个过渡 层更简单的做法是直接调用View对象的measure()方法)。

      整个measure调用流程就是个树形的递归过程

  measure函数原型为 View.java 该函数不能被重载

  1. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  2.     //….
  3.     //回调onMeasure()方法  
  4.     onMeasure(widthMeasureSpec, heightMeasureSpec);
  5.     //more
  6. }

 

 为了大家更好的理解,采用“二B程序员”的方式利用伪代码描述该measure流程

 

  1. //回调View视图里的onMeasure过程
  2. private void onMeasure(int height , int width){
  3.  //设置该view的实际宽(mMeasuredWidth)高(mMeasuredHeight)
  4.  //1、该方法必须在onMeasure调用,否者报异常。
  5.  setMeasuredDimension(h , l) ;
  6.  //2、如果该View是ViewGroup类型,则对它的每个子View进行measure()过程
  7.  int childCount = getChildCount() ;
  8.  for(int i=0 ;i<childCount ;i++){
  9.   //2.1、获得每个子View对象引用
  10.   View child = getChildAt(i) ;
  11.   //整个measure()过程就是个递归过程
  12.   //该方法只是一个过滤器,最后会调用measure()过程 ;或者 measureChild(child , h, i)方法都
  13.   measureChildWithMargins(child , h, i) ;
  14.   //其实,对于我们自己写的应用来说,最好的办法是去掉框架里的该方法,直接调用view.measure(),如下:
  15.   //child.measure(h, l)
  16.  }
  17. }
  18. //该方法具体实现在ViewGroup.java里 。
  19. protected  void measureChildWithMargins(View v, int height , int width){
  20.  v.measure(h,l)
  21. }

 

流程二、 layout布局过程:

 

     主要作用 :为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。

 

     具体的调用链如下:

       host.layout()开始View树的布局,继而回调给View/ViewGroup类中的layout()方法。具体流程如下

  

        1 、layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)

  接下来回调onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局) ;

       

       2、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值。

 

layout函数原型为 ,位于View.java

  1. /* final 标识符 , 不能被重载 , 参数为每个视图位于父视图的坐标轴
  2.  * @param l Left position, relative to parent
  3.  * @param t Top position, relative to parent
  4.  * @param r Right position, relative to parent
  5.  * @param b Bottom position, relative to parent
  6.  */
  7. public final void layout(int l, int t, int r, int b) {
  8.     boolean changed = setFrame(l, t, r, b); //设置每个视图位于父视图的坐标轴
  9.     if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
  10.         if (ViewDebug.TRACE_HIERARCHY) {
  11.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
  12.         }
  13.         onLayout(changed, l, t, r, b);//回调onLayout函数 ,设置每个子视图的布局
  14.         mPrivateFlags &= ~LAYOUT_REQUIRED;
  15.     }
  16.     mPrivateFlags &= ~FORCE_LAYOUT;
  17. }

 

 同样地, 将上面layout调用流程,用伪代码描述如下: 

  1. // layout()过程  ViewRoot.java
  2. // 发起layout()的”发号者”在ViewRoot.java里的performTraversals()方法, mView.layout()
  3. private void  performTraversals(){
  4.     //…
  5.     View mView  ;
  6.        mView.layout(left,top,right,bottom) ;
  7.     //….
  8. }
  9. //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现
  10. private void onLayout(int left , int top , right , bottom){
  11.  //如果该View不是ViewGroup类型
  12.  //调用setFrame()方法设置该控件的在父视图上的坐标轴
  13.  setFrame(l ,t , r ,b) ;
  14.  //————————–
  15.  //如果该View是ViewGroup类型,则对它的每个子View进行layout()过程
  16.  int childCount = getChildCount() ;
  17.  for(int i=0 ;i<childCount ;i++){
  18.   //2.1、获得每个子View对象引用
  19.   View child = getChildAt(i) ;
  20.   //整个layout()过程就是个递归过程
  21.   child.layout(l, t, r, b) ;
  22.  }
  23. }

 

 

   流程三、 draw()绘图过程

     由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不 会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。

 

   调用流程 :

     mView.draw()开始绘制,draw()方法实现的功能如下:

          1 、绘制该View的背景

          2 、为显示渐变框做一些准备操作(见5,大多数情况下,不需要改渐变框)          

          3、调用onDraw()方法绘制视图本身   (每个View都需要重载该方法,ViewGroup不需要实现该方法)

          4、调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)

值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类

  函数实现具体的功能。

 

            4.1 dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个 

地方“需要重绘”的视图才会调用draw()方法)。值得说明的是,ViewGroup类已经为我们重写了dispatchDraw()的功能

实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。

    

     5、绘制滚动条

 

  于是,整个调用链就这样递归下去了。

    

     同样地,使用伪代码描述如下:

 

  1. // draw()过程     ViewRoot.java
  2. // 发起draw()的”发号者”在ViewRoot.java里的performTraversals()方法, 该方法会继续调用draw()方法开始绘图
  3. private void  draw(){
  4.     //…
  5.  View mView  ;
  6.     mView.draw(canvas) ;
  7.     //….
  8. }
  9. //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现
  10. private void draw(Canvas canvas){
  11.  //该方法会做如下事情
  12.  //1 、绘制该View的背景
  13.  //2、为绘制渐变框做一些准备操作
  14.  //3、调用onDraw()方法绘制视图本身
  15.  //4、调用dispatchDraw()方法绘制每个子视图,dispatchDraw()已经在Android框架中实现了,在ViewGroup方法中。
  16.       // 应用程序程序一般不需要重写该方法,但可以捕获该方法的发生,做一些特别的事情。
  17.  //5、绘制渐变框  
  18. }
  19. //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法
  20. @Override
  21. protected void dispatchDraw(Canvas canvas) {
  22.  // 
  23.  //其实现方法类似如下:
  24.  int childCount = getChildCount() ;
  25.  for(int i=0 ;i<childCount ;i++){
  26.   View child = getChildAt(i) ;
  27.   //调用drawChild完成
  28.   drawChild(child,canvas) ;
  29.  }
  30. }
  31. //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法
  32. protected void drawChild(View child,Canvas canvas) {
  33.  // ….
  34.  //简单的回调View对象的draw()方法,递归就这么产生了。
  35.  child.draw(canvas) ;
  36.  //………
  37. }
   关于绘制背景图片详细的过程,请参考我的另外的博客:

           

              <<Android中View(视图)绘制不同状态背景图片原理深入分析以及StateListDrawable使用详解>>

 

    强调一点的就是,在这三个流程中,Google已经帮我们把draw()过程框架已经写好了,自定义的ViewGroup只需要实现

 measure()过程和layout()过程即可 。

 

     这三种情况,最终会直接或间接调用到三个函数,分别为invalidate(),requsetLaytout()以及requestFocus() ,接着

这三个函数最终会调用到ViewRoot中的schedulTraversale()方法,该函数然后发起一个异步消息,消息处理中调用

performTraverser()方法对整个View进行遍历。

 

 

    invalidate()方法 :

 

   说明:请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”

视图,即谁(View的话,只绘制该View ;ViewGroup,则绘制整个ViewGroup)请求invalidate()方法,就绘制该视图。

 

     一般引起invalidate()操作的函数如下:

            1、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。

            2、setSelection()方法 :请求重新draw(),但只会绘制调用者本身。

            3、setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,

                     继而绘制该View。

            4 、setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。

 

    requestLayout()方法 :会导致调用measure()过程 和 layout()过程 。

 

           说明:只是对View树重新布局layout过程包括measure()和layout()过程,不会调用draw()过程,但不会重新绘制

任何视图包括该调用者本身。

 

    一般引起invalidate()操作的函数如下:

         1、setVisibility()方法:

             当View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。

    同时,由于整个个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要“重新绘制”的视图。

 

    requestFocus()函数说明:

 

          说明:请求View树的draw()过程,但只绘制“需要重绘”的视图。

 

 

    下面写个简单的小Demo吧,主要目的是给大家演示绘图的过程以及每个流程里该做的一些功能。截图如下:

 

                                                

 

 

 

1、    MyViewGroup.java  自定义ViewGroup类型

   

  1. /**
  2.  * @author http://http://blog.csdn.net/qinjuning
  3.  */
  4. //自定义ViewGroup 对象
  5. public class MyViewGroup  extends ViewGroup{
  6.     private static String TAG = “MyViewGroup” ;
  7.     private Context mContext ;
  8.     public MyViewGroup(Context context) {
  9.         super(context);
  10.         mContext = context ;
  11.         init() ;
  12.     }
  13.     //xml定义的属性,需要该构造函数
  14.     public MyViewGroup(Context context , AttributeSet attrs){
  15.         super(context,attrs) ;
  16.         mContext = context ;
  17.         init() ;
  18.     }
  19.     //为MyViewGroup添加三个子View
  20.     private void init(){
  21.         //调用ViewGroup父类addView()方法添加子View
  22.         //child 对象一 : Button
  23.         Button btn= new Button(mContext) ;
  24.         btn.setText(“I am Button”) ;
  25.         this.addView(btn) ;
  26.         //child 对象二 : ImageView 
  27.         ImageView img = new ImageView(mContext) ;
  28.         img.setBackgroundResource(R.drawable.icon) ;
  29.         this.addView(img) ;
  30.         //child 对象三 : TextView
  31.         TextView txt = new TextView(mContext) ;
  32.         txt.setText(“Only Text”) ;
  33.         this.addView(txt) ;
  34.         //child 对象四 : 自定义View
  35.         MyView myView = new MyView(mContext) ;
  36.         this.addView(myView) ;
  37.     }
  38.     @Override
  39.     //对每个子View进行measure():设置每子View的大小,即实际宽和高
  40.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
  41.         //通过init()方法,我们为该ViewGroup对象添加了三个视图 , Button、 ImageView、TextView
  42.         int childCount = getChildCount() ;
  43.         Log.i(TAG, “the size of this ViewGroup is —-> “ + childCount) ;
  44.         Log.i(TAG, “**** onMeasure start *****”) ;
  45.         //获取该ViewGroup的实际长和宽  涉及到MeasureSpec类的使用
  46.         int specSize_Widht = MeasureSpec.getSize(widthMeasureSpec) ;
  47.         int specSize_Heigth = MeasureSpec.getSize(heightMeasureSpec) ;
  48.         Log.i(TAG, “**** specSize_Widht “ + specSize_Widht+ ” * specSize_Heigth   *****” + specSize_Heigth) ;
  49.         //设置本ViewGroup的宽高
  50.         setMeasuredDimension(specSize_Widht , specSize_Heigth) ;
  51.         for(int i=0 ;i<childCount ; i++){
  52.             View child = getChildAt(i) ;   //获得每个对象的引用
  53.             child.measure(5050) ;   //简单的设置每个子View对象的宽高为 50px , 50px  
  54.             //或者可以调用ViewGroup父类方法measureChild()或者measureChildWithMargins()方法
  55.             //this.measureChild(child, widthMeasureSpec, heightMeasureSpec) ;
  56.         }
  57.     }
  58.     @Override
  59.     //对每个子View视图进行布局
  60.     protected void onLayout(boolean changed, int l, int t, int r, int b) {
  61.         // TODO Auto-generated method stub
  62.         //通过init()方法,我们为该ViewGroup对象添加了三个视图 , Button、 ImageView、TextView
  63.         int childCount = getChildCount() ;
  64.         int startLeft = 0 ;//设置每个子View的起始横坐标 
  65.         int startTop = 10 ; //每个子View距离父视图的位置 , 简单设置为10px吧 。 可以理解为 android:margin=10px ;
  66.         Log.i(TAG, “**** onLayout start ****”) ;
  67.         for(int i=0 ;i<childCount ; i++){
  68.             View child = getChildAt(i) ;   //获得每个对象的引用
  69.             child.layout(startLeft, startTop, startLeft+child.getMeasuredWidth(), startTop+child.getMeasuredHeight()) ;
  70.             startLeft =startLeft+child.getMeasuredWidth() + 10;  //校准startLeft值,View之间的间距设为10px ;
  71.             Log.i(TAG, “**** onLayout startLeft ****” +startLeft) ;
  72.         }
  73.     }
  74.     //绘图过程Android已经为我们封装好了 ,这儿只为了观察方法调用程
  75.     protected void dispatchDraw(Canvas canvas){
  76.         Log.i(TAG, “**** dispatchDraw start ****”) ;
  77.         super.dispatchDraw(canvas) ;
  78.     }
  79.     protected boolean drawChild(Canvas canvas , View child, long drawingTime){
  80.         Log.i(TAG, “**** drawChild start ****”) ;
  81.         return super.drawChild(canvas, child, drawingTime) ;
  82.     }
  83. }

   

          2、MyView.java 自定义View类型,重写onDraw()方法 ,

 

  1. //自定义View对象
  2.     public class MyView extends View{
  3.         private Paint paint  = new Paint() ;
  4.         public MyView(Context context) {
  5.             super(context);
  6.             // TODO Auto-generated constructor stub
  7.         }
  8.         public MyView(Context context , AttributeSet attrs){
  9.             super(context,attrs);
  10.         }
  11.         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
  12.             //设置该View大小为 80 80
  13.             setMeasuredDimension(50 , 50) ;
  14.         }
  15.         //存在canvas对象,即存在默认的显示区域
  16.         @Override
  17.         public void onDraw(Canvas canvas) {
  18.             // TODO Auto-generated method stub
  19.             super.onDraw(canvas);
  20.             Log.i(“MyViewGroup”“MyView is onDraw “) ;
  21.             //加粗
  22.             paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
  23.             paint.setColor(Color.RED);
  24.             canvas.drawColor(Color.BLUE) ;
  25.             canvas.drawRect(003030, paint);
  26.             canvas.drawText(“MyView”1040, paint);
  27.         }
  28.     }

 

 

  主Activity只是显示了该xml文件,在此也不罗嗦了。 大家可以查看该ViewGroup的Log仔细分析下View的绘制流程以及

相关方法的使用。第一次启动后捕获的Log如下,网上找了些资料,第一次View树绘制过程会走几遍,具体原因可能是某些

View 发生了改变,请求重新绘制,但这根本不影响我们的界面显示效果 。

 

        总的来说: 整个绘制过程还是十分十分复杂地,每个具体方法的实现都是我辈难以立即的,感到悲剧啊。对Android提

 供的一些ViewGroup对象,比如LinearLayout、RelativeLayout布局对象的实现也很有压力。 本文重在介绍整个View树的绘制

流程,希望大家在此基础上,多接触源代码进行更深入地扩展。

      

       示例DEMO下载地址:http://download.csdn.net/detail/qinjuning/3982468

Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

版权声明:本文出自郭霖的博客,转载必须注明出处。

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9153761

记得在前面的文章中,我带大家一起从源码的角度分析了Android中View的事件分发机制,相信阅读过的朋友对View的事件分发已经有比较深刻的理解了。

还未阅读过的朋友,请先参考 Android事件分发机制完全解析,带你从源码的角度彻底理解(上) 。

那么今天我们将继续上次未完成的话题,从源码的角度分析ViewGroup的事件分发。

首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别?

顾名思义,ViewGroup就是一组View的集合,它包含很多的子View和子VewGroup,是Android中所有布局的父类或间接父类,像LinearLayout、RelativeLayout等都是继承自ViewGroup的。但ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。ViewGroup继承结构示意图如下所示:

可以看到,我们平时项目里经常用到的各种布局,全都属于ViewGroup的子类。

简单介绍完了ViewGroup,我们现在通过一个Demo来演示一下Android中VewGroup的事件分发流程吧。

首先我们来自定义一个布局,命名为MyLayout,继承自LinearLayout,如下所示:

  1. public class MyLayout extends LinearLayout {
  2.     public MyLayout(Context context, AttributeSet attrs) {
  3.         super(context, attrs);
  4.     }
  5. }

然后,打开主布局文件activity_main.xml,在其中加入我们自定义的布局:

  1. <com.example.viewgrouptouchevent.MyLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  2.     xmlns:tools=“http://schemas.android.com/tools”
  3.     android:id=“@+id/my_layout”
  4.     android:layout_width=“match_parent”
  5.     android:layout_height=“match_parent”
  6.     android:orientation=“vertical” >
  7.     <Button
  8.         android:id=“@+id/button1”
  9.         android:layout_width=“match_parent”
  10.         android:layout_height=“wrap_content”
  11.         android:text=“Button1” />
  12.     <Button
  13.         android:id=“@+id/button2”
  14.         android:layout_width=“match_parent”
  15.         android:layout_height=“wrap_content”
  16.         android:text=“Button2” />
  17. </com.example.viewgrouptouchevent.MyLayout>

可以看到,我们在MyLayout中添加了两个按钮,接着在MainActivity中为这两个按钮和MyLayout都注册了监听事件:

  1. myLayout.setOnTouchListener(new OnTouchListener() {
  2.     @Override
  3.     public boolean onTouch(View v, MotionEvent event) {
  4.         Log.d(“TAG”“myLayout on touch”);
  5.         return false;
  6.     }
  7. });
  8. button1.setOnClickListener(new OnClickListener() {
  9.     @Override
  10.     public void onClick(View v) {
  11.         Log.d(“TAG”“You clicked button1”);
  12.     }
  13. });
  14. button2.setOnClickListener(new OnClickListener() {
  15.     @Override
  16.     public void onClick(View v) {
  17.         Log.d(“TAG”“You clicked button2”);
  18.     }
  19. });

我们在MyLayout的onTouch方法,和Button1、Button2的onClick方法中都打印了一句话。现在运行一下项目,效果图如下所示:

分别点击一下Button1、Button2和空白区域,打印结果如下所示:

你会发现,当点击按钮的时候,MyLayout注册的onTouch方法并不会执行,只有点击空白区域的时候才会执行该方法。你可以先理解成Button的onClick方法将事件消费掉了,因此事件不会再继续向下传递。

那就说明Android中的touch事件是先传递到View,再传递到ViewGroup的?现在下结论还未免过早了,让我们再来做一个实验。

查阅文档可以看到,ViewGroup中有一个onInterceptTouchEvent方法,我们来看一下这个方法的源码:

  1. /**
  2.  * Implement this method to intercept all touch screen motion events.  This
  3.  * allows you to watch events as they are dispatched to your children, and
  4.  * take ownership of the current gesture at any point.
  5.  *
  6.  * <p>Using this function takes some care, as it has a fairly complicated
  7.  * interaction with {@link View#onTouchEvent(MotionEvent)
  8.  * View.onTouchEvent(MotionEvent)}, and using it requires implementing
  9.  * that method as well as this one in the correct way.  Events will be
  10.  * received in the following order:
  11.  *
  12.  * <ol>
  13.  * <li> You will receive the down event here.
  14.  * <li> The down event will be handled either by a child of this view
  15.  * group, or given to your own onTouchEvent() method to handle; this means
  16.  * you should implement onTouchEvent() to return true, so you will
  17.  * continue to see the rest of the gesture (instead of looking for
  18.  * a parent view to handle it).  Also, by returning true from
  19.  * onTouchEvent(), you will not receive any following
  20.  * events in onInterceptTouchEvent() and all touch processing must
  21.  * happen in onTouchEvent() like normal.
  22.  * <li> For as long as you return false from this function, each following
  23.  * event (up to and including the final up) will be delivered first here
  24.  * and then to the target’s onTouchEvent().
  25.  * <li> If you return true from here, you will not receive any
  26.  * following events: the target view will receive the same event but
  27.  * with the action {@link MotionEvent#ACTION_CANCEL}, and all further
  28.  * events will be delivered to your onTouchEvent() method and no longer
  29.  * appear here.
  30.  * </ol>
  31.  *
  32.  * @param ev The motion event being dispatched down the hierarchy.
  33.  * @return Return true to steal motion events from the children and have
  34.  * them dispatched to this ViewGroup through onTouchEvent().
  35.  * The current target will receive an ACTION_CANCEL event, and no further
  36.  * messages will be delivered here.
  37.  */
  38. public boolean onInterceptTouchEvent(MotionEvent ev) {
  39.     return false;
  40. }

如果不看源码你还真可能被这注释吓到了,这么长的英文注释看得头都大了。可是源码竟然如此简单!只有一行代码,返回了一个false!

好吧,既然是布尔型的返回,那么只有两种可能,我们在MyLayout中重写这个方法,然后返回一个true试试,代码如下所示:

  1. public class MyLayout extends LinearLayout {
  2.     public MyLayout(Context context, AttributeSet attrs) {
  3.         super(context, attrs);
  4.     }
  5.     @Override
  6.     public boolean onInterceptTouchEvent(MotionEvent ev) {
  7.         return true;
  8.     }
  9. }

现在再次运行项目,然后分别Button1、Button2和空白区域,打印结果如下所示:

你会发现,不管你点击哪里,永远都只会触发MyLayout的touch事件了,按钮的点击事件完全被屏蔽掉了!这是为什么呢?如果Android中的touch事件是先传递到View,再传递到ViewGroup的,那么MyLayout又怎么可能屏蔽掉Button的点击事件呢?

看来只有通过阅读源码,搞清楚Android中ViewGroup的事件分发机制,才能解决我们心中的疑惑了,不过这里我想先跟你透露一句,Android中touch事件的传递,绝对是先传递到ViewGroup,再传递到View的。记得在Android事件分发机制完全解析,带你从源码的角度彻底理解(上) 中我有说明过,只要你触摸了任何控件,就一定会调用该控件的dispatchTouchEvent方法。这个说法没错,只不过还不完整而已。实际情况是,当你点击了某个控件,首先会去调用该控件所在布局的dispatchTouchEvent方法,然后在布局的dispatchTouchEvent方法中找到被点击的相应控件,再去调用该控件的dispatchTouchEvent方法。如果我们点击了MyLayout中的按钮,会先去调用MyLayout的dispatchTouchEvent方法,可是你会发现MyLayout中并没有这个方法。那就再到它的父类LinearLayout中找一找,发现也没有这个方法。那只好继续再找LinearLayout的父类ViewGroup,你终于在ViewGroup中看到了这个方法,按钮的dispatchTouchEvent方法就是在这里调用的。修改后的示意图如下所示:

那还等什么?快去看一看ViewGroup中的dispatchTouchEvent方法的源码吧!代码如下所示:

  1. public boolean dispatchTouchEvent(MotionEvent ev) {
  2.     final int action = ev.getAction();
  3.     final float xf = ev.getX();
  4.     final float yf = ev.getY();
  5.     final float scrolledXFloat = xf + mScrollX;
  6.     final float scrolledYFloat = yf + mScrollY;
  7.     final Rect frame = mTempRect;
  8.     boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
  9.     if (action == MotionEvent.ACTION_DOWN) {
  10.         if (mMotionTarget != null) {
  11.             mMotionTarget = null;
  12.         }
  13.         if (disallowIntercept || !onInterceptTouchEvent(ev)) {
  14.             ev.setAction(MotionEvent.ACTION_DOWN);
  15.             final int scrolledXInt = (int) scrolledXFloat;
  16.             final int scrolledYInt = (int) scrolledYFloat;
  17.             final View[] children = mChildren;
  18.             final int count = mChildrenCount;
  19.             for (int i = count – 1; i >= 0; i–) {
  20.                 final View child = children[i];
  21.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
  22.                         || child.getAnimation() != null) {
  23.                     child.getHitRect(frame);
  24.                     if (frame.contains(scrolledXInt, scrolledYInt)) {
  25.                         final float xc = scrolledXFloat – child.mLeft;
  26.                         final float yc = scrolledYFloat – child.mTop;
  27.                         ev.setLocation(xc, yc);
  28.                         child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
  29.                         if (child.dispatchTouchEvent(ev))  {
  30.                             mMotionTarget = child;
  31.                             return true;
  32.                         }
  33.                     }
  34.                 }
  35.             }
  36.         }
  37.     }
  38.     boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
  39.             (action == MotionEvent.ACTION_CANCEL);
  40.     if (isUpOrCancel) {
  41.         mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
  42.     }
  43.     final View target = mMotionTarget;
  44.     if (target == null) {
  45.         ev.setLocation(xf, yf);
  46.         if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
  47.             ev.setAction(MotionEvent.ACTION_CANCEL);
  48.             mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
  49.         }
  50.         return super.dispatchTouchEvent(ev);
  51.     }
  52.     if (!disallowIntercept && onInterceptTouchEvent(ev)) {
  53.         final float xc = scrolledXFloat – (float) target.mLeft;
  54.         final float yc = scrolledYFloat – (float) target.mTop;
  55.         mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
  56.         ev.setAction(MotionEvent.ACTION_CANCEL);
  57.         ev.setLocation(xc, yc);
  58.         if (!target.dispatchTouchEvent(ev)) {
  59.         }
  60.         mMotionTarget = null;
  61.         return true;
  62.     }
  63.     if (isUpOrCancel) {
  64.         mMotionTarget = null;
  65.     }
  66.     final float xc = scrolledXFloat – (float) target.mLeft;
  67.     final float yc = scrolledYFloat – (float) target.mTop;
  68.     ev.setLocation(xc, yc);
  69.     if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
  70.         ev.setAction(MotionEvent.ACTION_CANCEL);
  71.         target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
  72.         mMotionTarget = null;
  73.     }
  74.     return target.dispatchTouchEvent(ev);
  75. }

这个方法代码比较长,我们只挑重点看。首先在第13行可以看到一个条件判断,如果disallowIntercept和!onInterceptTouchEvent(ev)两者有一个为true,就会进入到这个条件判断中。disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。那么当第一个值为false的时候就会完全依赖第二个值来决定是否可以进入到条件判断的内部,第二个值是什么呢?竟然就是对onInterceptTouchEvent方法的返回值取反!也就是说如果我们在onInterceptTouchEvent方法中返回false,就会让第二个值为true,从而进入到条件判断的内部,如果我们在onInterceptTouchEvent方法中返回true,就会让第二个值为false,从而跳出了这个条件判断。

这个时候你就可以思考一下了,由于我们刚刚在MyLayout中重写了onInterceptTouchEvent方法,让这个方法返回true,导致所有按钮的点击事件都被屏蔽了,那我们就完全有理由相信,按钮点击事件的处理就是在第13行条件判断的内部进行的!

那我们重点来看下条件判断的内部是怎么实现的。在第19行通过一个for循环,遍历了当前ViewGroup下的所有子View,然后在第24行判断当前遍历的View是不是正在点击的View,如果是的话就会进入到该条件判断的内部,然后在第29行调用了该View的dispatchTouchEvent,之后的流程就和 Android事件分发机制完全解析,带你从源码的角度彻底理解(上) 中讲解的是一样的了。我们也因此证实了,按钮点击事件的处理确实就是在这里进行的。

然后需要注意一下,调用子View的dispatchTouchEvent后是有返回值的。我们已经知道,如果一个控件是可点击的,那么点击该控件时,dispatchTouchEvent的返回值必定是true。因此会导致第29行的条件判断成立,于是在第31行给ViewGroup的dispatchTouchEvent方法直接返回了true。这样就导致后面的代码无法执行到了,也是印证了我们前面的Demo打印的结果,如果按钮的点击事件得到执行,就会把MyLayout的touch事件拦截掉。

那如果我们点击的不是按钮,而是空白区域呢?这种情况就一定不会在第31行返回true了,而是会继续执行后面的代码。那我们继续往后看,在第44行,如果target等于null,就会进入到该条件判断内部,这里一般情况下target都会是null,因此会在第50行调用super.dispatchTouchEvent(ev)。这句代码会调用到哪里呢?当然是View中的dispatchTouchEvent方法了,因为ViewGroup的父类就是View。之后的处理逻辑又和前面所说的是一样的了,也因此MyLayout中注册的onTouch方法会得到执行。之后的代码在一般情况下是走不到的了,我们也就不再继续往下分析。

再看一下整个ViewGroup事件分发过程的流程图吧,相信可以帮助大家更好地去理解:

       

现在整个ViewGroup的事件分发流程的分析也就到此结束了,我们最后再来简单梳理一下吧。

1. Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View的。

2. 在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认返回false。

3. 子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件。

好了,Android事件分发机制完全解析到此全部结束,结合上下两篇,相信大家对事件分发的理解已经非常深刻了。

 

Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

版权声明:本文出自郭霖的博客,转载必须注明出处。

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9097463

其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识。也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用ImageView?等等……对于这些问题,我并没有给出非常详细的回答,因为我知道如果想要彻底搞明白这些问题,掌握Android事件分发机制是必不可少的,而Android事件分发机制绝对不是三言两语就能说得清的。

在我经过较长时间的筹备之后,终于决定开始写这样一篇文章了。目前虽然网上相关的文章也不少,但我觉得没有哪篇写得特别详细的(也许我还没有找到),多数文章只是讲了讲理论,然后配合demo运行了一下结果。而我准备带着大家从源码的角度进行分析,相信大家可以更加深刻地理解Android事件分发机制。

阅读源码讲究由浅入深,循序渐进,因此我们也从简单的开始,本篇先带大家探究View的事件分发,下篇再去探究难度更高的ViewGroup的事件分发。

那我们现在就开始吧!比如说你当前有一个非常简单的项目,只有一个Activity,并且Activity中只有一个按钮。你可能已经知道,如果想要给这个按钮注册一个点击事件,只需要调用:

  1. button.setOnClickListener(new OnClickListener() {
  2.     @Override
  3.     public void onClick(View v) {
  4.         Log.d(“TAG”“onClick execute”);
  5.     }
  6. });

这样在onClick方法里面写实现,就可以在按钮被点击的时候执行。你可能也已经知道,如果想给这个按钮再添加一个touch事件,只需要调用:

  1. button.setOnTouchListener(new OnTouchListener() {
  2.     @Override
  3.     public boolean onTouch(View v, MotionEvent event) {
  4.         Log.d(“TAG”“onTouch execute, action “ + event.getAction());
  5.         return false;
  6.     }
  7. });

onTouch方法里能做的事情比onClick要多一些,比如判断手指按下、抬起、移动等事件。那么如果我两个事件都注册了,哪一个会先执行呢?我们来试一下就知道了,运行程序点击按钮,打印结果如下:

可以看到,onTouch是优先于onClick执行的,并且onTouch执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(你还可能会有多次ACTION_MOVE的执行,如果你手抖了一下)。因此事件传递的顺序是先经过onTouch,再传递到onClick。

细心的朋友应该可以注意到,onTouch方法是有返回值的,这里我们返回的是false,如果我们尝试把onTouch方法里的返回值改成true,再运行一次,结果如下:

我们发现,onClick方法不再执行了!为什么会这样呢?你可以先理解成onTouch方法返回true就认为这个事件被onTouch消费掉了,因而不会再继续向下传递。

如果到现在为止,以上的所有知识点你都是清楚的,那么说明你对Android事件传递的基本用法应该是掌握了。不过别满足于现状,让我们从源码的角度分析一下,出现上述现象的原理是什么。

首先你需要知道一点,只要你触摸到了任何一个控件,就一定会调用该控件的dispatchTouchEvent方法。那当我们去点击按钮的时候,就会去调用Button类里的dispatchTouchEvent方法,可是你会发现Button类里并没有这个方法,那么就到它的父类TextView里去找一找,你会发现TextView里也没有这个方法,那没办法了,只好继续在TextView的父类View里找一找,这个时候你终于在View里找到了这个方法,示意图如下:

然后我们来看一下View中dispatchTouchEvent方法的源码:

  1. public boolean dispatchTouchEvent(MotionEvent event) {
  2.     if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
  3.             mOnTouchListener.onTouch(this, event)) {
  4.         return true;
  5.     }
  6.     return onTouchEvent(event);
  7. }

这个方法非常的简洁,只有短短几行代码!我们可以看到,在这个方法内,首先是进行了一个判断,如果mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED和mOnTouchListener.onTouch(this, event)这三个条件都为真,就返回true,否则就去执行onTouchEvent(event)方法并返回。

先看一下第一个条件,mOnTouchListener这个变量是在哪里赋值的呢?我们寻找之后在View里发现了如下方法:

  1. public void setOnTouchListener(OnTouchListener l) {
  2.     mOnTouchListener = l;
  3. }

Bingo!找到了,mOnTouchListener正是在setOnTouchListener方法里赋值的,也就是说只要我们给控件注册了touch事件,mOnTouchListener就一定被赋值了。

第二个条件(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true。

第三个条件就比较关键了,mOnTouchListener.onTouch(this, event),其实也就是去回调控件注册touch事件时的onTouch方法。也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,从而整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。

现在我们可以结合前面的例子来分析一下了,首先在dispatchTouchEvent中最先执行的就是onTouch方法,因此onTouch肯定是要优先于onClick执行的,也是印证了刚刚的打印结果。而如果在onTouch方法里返回了true,就会让dispatchTouchEvent方法直接返回true,不会再继续往下执行。而打印结果也证实了如果onTouch返回true,onClick就不会再执行了。

根据以上源码的分析,从原理上解释了我们前面例子的运行结果。而上面的分析还透漏出了一个重要的信息,那就是onClick的调用肯定是在onTouchEvent(event)方法中的!那我们马上来看下onTouchEvent的源码,如下所示:

  1. public boolean onTouchEvent(MotionEvent event) {
  2.     final int viewFlags = mViewFlags;
  3.     if ((viewFlags & ENABLED_MASK) == DISABLED) {
  4.         // A disabled view that is clickable still consumes the touch
  5.         // events, it just doesn’t respond to them.
  6.         return (((viewFlags & CLICKABLE) == CLICKABLE ||
  7.                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
  8.     }
  9.     if (mTouchDelegate != null) {
  10.         if (mTouchDelegate.onTouchEvent(event)) {
  11.             return true;
  12.         }
  13.     }
  14.     if (((viewFlags & CLICKABLE) == CLICKABLE ||
  15.             (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
  16.         switch (event.getAction()) {
  17.             case MotionEvent.ACTION_UP:
  18.                 boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
  19.                 if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
  20.                     // take focus if we don’t have it already and we should in
  21.                     // touch mode.
  22.                     boolean focusTaken = false;
  23.                     if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
  24.                         focusTaken = requestFocus();
  25.                     }
  26.                     if (!mHasPerformedLongPress) {
  27.                         // This is a tap, so remove the longpress check
  28.                         removeLongPressCallback();
  29.                         // Only perform take click actions if we were in the pressed state
  30.                         if (!focusTaken) {
  31.                             // Use a Runnable and post this rather than calling
  32.                             // performClick directly. This lets other visual state
  33.                             // of the view update before click actions start.
  34.                             if (mPerformClick == null) {
  35.                                 mPerformClick = new PerformClick();
  36.                             }
  37.                             if (!post(mPerformClick)) {
  38.                                 performClick();
  39.                             }
  40.                         }
  41.                     }
  42.                     if (mUnsetPressedState == null) {
  43.                         mUnsetPressedState = new UnsetPressedState();
  44.                     }
  45.                     if (prepressed) {
  46.                         mPrivateFlags |= PRESSED;
  47.                         refreshDrawableState();
  48.                         postDelayed(mUnsetPressedState,
  49.                                 ViewConfiguration.getPressedStateDuration());
  50.                     } else if (!post(mUnsetPressedState)) {
  51.                         // If the post failed, unpress right now
  52.                         mUnsetPressedState.run();
  53.                     }
  54.                     removeTapCallback();
  55.                 }
  56.                 break;
  57.             case MotionEvent.ACTION_DOWN:
  58.                 if (mPendingCheckForTap == null) {
  59.                     mPendingCheckForTap = new CheckForTap();
  60.                 }
  61.                 mPrivateFlags |= PREPRESSED;
  62.                 mHasPerformedLongPress = false;
  63.                 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
  64.                 break;
  65.             case MotionEvent.ACTION_CANCEL:
  66.                 mPrivateFlags &= ~PRESSED;
  67.                 refreshDrawableState();
  68.                 removeTapCallback();
  69.                 break;
  70.             case MotionEvent.ACTION_MOVE:
  71.                 final int x = (int) event.getX();
  72.                 final int y = (int) event.getY();
  73.                 // Be lenient about moving outside of buttons
  74.                 int slop = mTouchSlop;
  75.                 if ((x < 0 – slop) || (x >= getWidth() + slop) ||
  76.                         (y < 0 – slop) || (y >= getHeight() + slop)) {
  77.                     // Outside button
  78.                     removeTapCallback();
  79.                     if ((mPrivateFlags & PRESSED) != 0) {
  80.                         // Remove any future long press/tap checks
  81.                         removeLongPressCallback();
  82.                         // Need to switch from pressed to not pressed
  83.                         mPrivateFlags &= ~PRESSED;
  84.                         refreshDrawableState();
  85.                     }
  86.                 }
  87.                 break;
  88.         }
  89.         return true;
  90.     }
  91.     return false;
  92. }

相较于刚才的dispatchTouchEvent方法,onTouchEvent方法复杂了很多,不过没关系,我们只挑重点看就可以了。

首先在第14行我们可以看出,如果该控件是可以点击的就会进入到第16行的switch判断中去,而如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。在经过种种判断之后,会执行到第38行的performClick()方法,那我们进入到这个方法里瞧一瞧:

  1. public boolean performClick() {
  2.     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
  3.     if (mOnClickListener != null) {
  4.         playSoundEffect(SoundEffectConstants.CLICK);
  5.         mOnClickListener.onClick(this);
  6.         return true;
  7.     }
  8.     return false;
  9. }

可以看到,只要mOnClickListener不是null,就会去调用它的onClick方法,那mOnClickListener又是在哪里赋值的呢?经过寻找后找到如下方法:

  1. public void setOnClickListener(OnClickListener l) {
  2.     if (!isClickable()) {
  3.         setClickable(true);
  4.     }
  5.     mOnClickListener = l;
  6. }

一切都是那么清楚了!当我们通过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值。然后每当控件被点击时,都会在performClick()方法里回调被点击控件的onClick方法。

这样View的整个事件分发的流程就让我们搞清楚了!不过别高兴的太早,现在还没结束,还有一个很重要的知识点需要说明,就是touch事件的层级传递。我们都知道如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。这里需要注意,如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。

说到这里,很多的朋友肯定要有巨大的疑问了。这不是在自相矛盾吗?前面的例子中,明明在onTouch事件里面返回了false,ACTION_DOWN和ACTION_UP不是都得到执行了吗?其实你只是被假象所迷惑了,让我们仔细分析一下,在前面的例子当中,我们到底返回的是什么。

参考着我们前面分析的源码,首先在onTouch事件里返回了false,就一定会进入到onTouchEvent方法中,然后我们来看一下onTouchEvent方法的细节。由于我们点击了按钮,就会进入到第14行这个if判断的内部,然后你会发现,不管当前的action是什么,最终都一定会走到第89行,返回一个true。

是不是有一种被欺骗的感觉?明明在onTouch事件里返回了false,系统还是在onTouchEvent方法中帮你返回了true。就因为这个原因,才使得前面的例子中ACTION_UP可以得到执行。

那我们可以换一个控件,将按钮替换成ImageView,然后给它也注册一个touch事件,并返回false。如下所示:

  1. imageView.setOnTouchListener(new OnTouchListener() {
  2.     @Override
  3.     public boolean onTouch(View v, MotionEvent event) {
  4.         Log.d(“TAG”“onTouch execute, action “ + event.getAction());
  5.         return false;
  6.     }
  7. });

运行一下程序,点击ImageView,你会发现结果如下:


在ACTION_DOWN执行完后,后面的一系列action都不会得到执行了。这又是为什么呢?因为ImageView和按钮不同,它是默认不可点击的,因此在onTouchEvent的第14行判断时无法进入到if的内部,直接跳到第91行返回了false,也就导致后面其它的action都无法执行了。

好了,关于View的事件分发,我想讲的东西全都在这里了。现在我们再来回顾一下开篇时提到的那三个问题,相信每个人都会有更深一层的理解。

1. onTouch和onTouchEvent有什么区别,又该如何使用?

从源码中可以看出,这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。

另外需要注意的是,onTouch能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。因此如果你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件,如果我们想要监听它的touch事件,就必须通过在该控件中重写onTouchEvent方法来实现。

2. 为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?

如果你阅读了Android滑动框架完全解析,教你如何一分钟实现滑动菜单特效 这篇文章,你应该会知道滑动菜单的功能是通过给ListView注册了一个touch事件来实现的。如果你在onTouch方法里处理完了滑动逻辑后返回true,那么ListView本身的滚动事件就被屏蔽了,自然也就无法滑动(原理同前面例子中按钮不能点击),因此解决办法就是在onTouch方法里返回false。

3. 为什么图片轮播器里的图片使用Button而不用ImageView?

提这个问题的朋友是看过了Android实现图片滚动控件,含页签功能,让你的应用像淘宝一样炫起来 这篇文章。当时我在图片轮播器里使用Button,主要就是因为Button是可点击的,而ImageView是不可点击的。如果想要使用ImageView,可以有两种改法。第一,在ImageView的onTouch方法里返回true,这样可以保证ACTION_DOWN之后的其它action都能得到执行,才能实现图片滚动的效果。第二,在布局文件里面给ImageView增加一个android:clickable=”true”的属性,这样ImageView变成可点击的之后,即使在onTouch里返回了false,ACTION_DOWN之后的其它action也是可以得到执行的。

今天的讲解就到这里了,相信大家现在对Android事件分发机制又有了进一步的认识,在后面的文章中我会再带大家一起探究Android中ViewGroup的事件分发机制,感兴趣的朋友请继续阅读 Android事件分发机制完全解析,带你从源码的角度彻底理解(下) 。