图库主要的显示界面
图库主要的显示界面包括:相册集AlbumSetPage,相册AlbumPage,图片预览PhotoPage,这些界面都有一个父类:ActivityState;界面的管理由StateManager负责。
public class AlbumSetPage extends ActivityState{}
public class StateManager {
//根据传入的界面的.class,进入到相应界面,比如:AlbumSetPage.class进入相册界面。
publicvoid startState(Class<? extends ActivityState> klass, Bundle data) {
ActivityStatestate = null;
state= klass.newInstance();
state.initialize(mActivity,data);
mStack.push(newStateEntry(data, state));
state.onCreate(data, null);
if(mIsResumed) state.resume();
}
}
图库中的各种数据源
图库中支持的数据源有:LocalSource(本地数据),PicasaSource(Gmail同步),ComboSource(混合类型数据),FilterSource(过滤后的数据), SecureSource(安全数据),UriSource(Uri数据)等,这些数据源都有一个父类: MediaSource,它负责管理数据集;
DataManager负责管理数据源,DataManager首先会初始化所有的数据源,然后放在一个 HashMap中,提供存取查询操作。
public class DataManager implementsStitchingChangeListener {
//这里是初始化所支持的数据源
public synchronized voidinitializeSourceMap() {
addSource(new LocalSource(mApplication));
addSource(new PicasaSource(mApplication));
addSource(new ComboSource(mApplication));
addSource(new ClusterSource(mApplication));
addSource(newFilterSource(mApplication));
addSource(new SecureSource(mApplication));
addSource(new UriSource(mApplication));
addSource(new SnailSource(mApplication));
if(mActiveCount > 0) {
for(MediaSource source : mSourceMap.values()) {
source.resume();
}
}
}
//将数据源保存到HashMap<String, MediaSource>mSourceMap中。
voidaddSource(MediaSource source) {
if(source == null) return;
mSourceMap.put(source.getPrefix(), source);
}
}
每类数据源包含的数据集
每类数据源包含相应的数据集,分别实现根据路径path的前缀匹配相应的数据及加载数据。
以LocalSource为例,含有的数据集有: LocalAlbumSet ,LocalAlbum, LocalMergeAlbum ,LocalImage, LocalVideo,这些数据集都有一个基类: MediaSet。
classLocalSource extends MediaSource {
//根据路径path的前缀,LocalSource中可以匹配到的数据集
public LocalSource(GalleryApp context) {
mMatcher = new PathMatcher();
mMatcher.add("/local/image",LOCAL_IMAGE_ALBUMSET);
mMatcher.add("/local/video",LOCAL_VIDEO_ALBUMSET);
mMatcher.add("/local/all",LOCAL_ALL_ALBUMSET);
mMatcher.add("/local/image/*", LOCAL_IMAGE_ALBUM);
mMatcher.add("/local/video/*", LOCAL_VIDEO_ALBUM);
mMatcher.add("/local/all/*",LOCAL_ALL_ALBUM);
mMatcher.add("/local/image/item/*", LOCAL_IMAGE_ITEM);
mMatcher.add("/local/video/item/*", LOCAL_VIDEO_ITEM);
}
}
//创建相应的数据集
publicMediaObject createMediaObject(Path path) {
switch (mMatcher.match(path)) {
caseLOCAL_ALL_ALBUMSET:
returnnew LocalAlbumSet(path, mApplication);
caseLOCAL_IMAGE_ITEM:
return new LocalImage(path, mApplication,mMatcher.getIntVar(0));
caseLOCAL_VIDEO_ITEM:
return new LocalVideo(path, mApplication, mMatcher.getIntVar(0));
……
}
}
Gallery的数据加载流程
包含有数据加载的界面有相册集(AlbumSetPage)和相册(Albumpage)这2个界面。进入图库的默认界面是AlbumSetPage,这个界面显示所有的相册,相册封面的缩略图,相册文件夹的名称;点击进入具体Albumpage,这个界面显示相册中所有图片的缩略图。单击图片或者从别的应用浏览图片加载的界面是:PhotoPage。
AlbumSetPage界面数据的加载
分析AlbumSetPage的代码,通常有列表显示的界面都会有一个适配器,这里的适配器是:AlbumSetDataLoader;而且适配器都是跟数据源相关联的,这里的数据源是:MediaSet。
有了数据之后,接下来就是显示真正的界面,界面的显示是由:AlbumSetSlotRenderer类来实现对接的。
数据加载-AlbumSetPage类-onCreate
数据源有很多种,这里以本地数据源LocalSource,LocalAlbumSet,LocalImage为例。
Step1,
首先从AlbumSetPage.java的onCreate()方法入手。
public voidonCreate(Bundle data, Bundle restoreState) {
//初始化View,AlbumSetSlotRenderer;及 SlotView的配置
initializeViews();
//得到媒体路径,创建数据源,创建适配器
initializeData(data);
}
Step2,
private void initializeData(Bundle data) {
//得到媒体路径,DataManager会根据路径创建相应的数据源。
String mediaPath =data.getString(AlbumSetPage.KEY_MEDIA_PATH,
mActivity.getDataManager().getTopSetPath(DataManager.INCLUDE_ALL));
//创建数据源
mMediaSet =mActivity.getDataManager().getMediaSet(mediaPath);
//创建适配器,同时绑定了数据源。
mAlbumSetDataAdapter = new AlbumSetDataLoader(
mActivity,mMediaSet, DATA_CACHE_SIZE)
//对接界面显示的AlbumSetSlotRenderer跟适配器绑定,也同时关联了数据源。
mAlbumSetView.setModel(mAlbumSetDataAdapter);
}
数据源MediaSet的生成
下面分析数据源生成的过程,即mMediaSet的生成:
Step1,首先获得DataManager对象时,会调用其initializeSourceMap()方法。
调用堆栈是:
getDataManager()@AbstractGalleryActivity.java à
getDataManager()@GalleryAppImpl.java à
initializeSourceMap()@DataManager.java .
Step2,然后进入到DataManager类中。
调用堆栈是:
getMediaSet()@DataManager.java à
//根据媒体路径,匹配数据源,这里以LocalSource为例。
getMediaObject()@DataManager.java à
//获取到相应的MediaSet,这里返回的是LocalAlbumSet
createMediaObject()@LocalSource.java à
new LocalAlbumSet(path, mApplication);
数据加载-AlbumSetPage类-onResume
Step1,AlbumSetPage.onCreate()之后,进入OnResume()方法中。
public void onResume() {
mAlbumSetDataAdapter.resume();
mAlbumSetView.resume();
}
Step2,接着调用适配器类mAlbumSetDataAdapter的resume()方法,这里新启一个线程,处理接下来的数据加载。进入线程的run()方法:
public void run() @ AlbumSetDataLoader {
//发送加载开始的消息,同时通知注册了监听数据加载的类,比如AlbumSetPage。
updateLoading(true);
//调用各种数据源的reload(),这里假设的是LocalAlbumSet。
long version =mSource.reload();
//检查相册是不是有更新
if (info.version!= version) {
info.version = version;
info.size =mSource.getSubMediaSetCount();
if(info.index >= info.size) {
info.index = INDEX_NONE;
}
}
//得到某个相册的标识,mSource是一个相册集。
info.item = mSource.getSubMediaSet(info.index);
//获取相册中第一张照片做封面,info.item是一个相册。
info.cover =info.item.getCoverMediaItem();
//获取相册中图片的张数。
info.totalCount =info.item.getTotalMediaItemCount();
//发送加载完成的消息。
updateLoading(false);
}
数据源localAlbumSet-相册加载
Step1,接着看数据源mSource(localAlbumSet)reload()方法。
public synchronized long reload()@LocalAlbumSet{
//这里新建了一个任务:AlbumsLoader,并添加到线程池,它是一个自定义的Job,类//似于Callable,跟Thread不同的是callable执行结束后有返回值。
mLoadTask =mApplication.getThreadPool().submit(new AlbumsLoader(), this);
// mLoadBuffer就是相册的集合,是AlbumsLoader这个callable执行结束后,在//onFutureDone()方法里,通过mLoadBuffer = future.get()返回的。
mAlbums =mLoadBuffer;
//每一个相册都加载照片。
for (MediaSet album : mAlbums) {
album.reload();
}
}
Step2,AlbumsLoader这个相册加载的Job,返回的是一组相册。他是一个内部类。
private class AlbumsLoader implementsThreadPool.Job<ArrayList<MediaSet>> {
publicArrayList<MediaSet> run(JobContext jc) {
//真正的数据查询在这里,从数据库加载数据,根据mType来区分Image和Video相册。
BucketEntry[]entries = BucketHelper.loadBucketEntries(
jc, mApplication.getContentResolver(),mType);
// BUCKET_ID字段为路径path的hash值,不同的BUCKET_ID代表不同的相册。
int index = findBucket(entries,MediaSetUtils.CAMERA_BUCKET_ID);
for(BucketEntry entry : entries) {
//得到相册的一组数据,添加到相册集albums中。
MediaSet album = getLocalAlbum(dataManager,
mType, mPath, entry.bucketId,entry.bucketName);
albums.add(album);
}
}
}
Step3 得到具体的相册
private MediaSetgetLocalAlbum @LocalAlbumSet (
DataManagermanager, int type, Path parent, int id, String name) {
switch (type) {
//包含图片,视频,或者是两者的集合
caseMEDIA_TYPE_IMAGE:
return newLocalAlbum(path, mApplication, id, true, name);
caseMEDIA_TYPE_VIDEO:
return newLocalAlbum(path, mApplication, id, false, name);
caseMEDIA_TYPE_ALL:
return newLocalMergeAlbum(path, comp, new MediaSet[] {
getLocalAlbum(manager,MEDIA_TYPE_IMAGE, PATH_IMAGE, id, name),
getLocalAlbum(manager,MEDIA_TYPE_VIDEO, PATH_VIDEO, id, name)}, id); }
}
到这里相册集的数据就加载完成了,这个流程是从:AlbumSetDataLoader.java类的ReloadTask的run()开始的。
AlbumPage界面数据的加载
AlbumPage数据的加载,跟AlbumSetPage的数据加载类似,不同的地方在于AlbumPage要把所有的图片都生成缩略图,而AlbumSetPage是取相册中的第一张图片作为封面。
AlbumPage的适配器类是AlbumDataLoader.java;进入其ReloadTask的run方法中。
private class ReloadTask extends Thread {
public void run(){
//得到相册中图片的张数
info.size =mSource.getMediaItemCount();
//从reloadStart开始,加载reloadCount张缩略图。
info.items =mSource.getMediaItem(info.reloadStart, info.reloadCount);
}
}
//这里一次最多加载64张缩略图,reloadStart通常是从0开始。
private class GetUpdateInfo implementsCallable<UpdateInfo>{}
private staticfinal int MAX_LOAD_COUNT = 64;
info.reloadStart= i;
info.reloadCount= Math.min(MAX_LOAD_COUNT, n - i);
}
对数据库变化的监听
Gallery不会去扫描sdcard,手机内存等存储设备;只是去查询数据库,及监听数据库的变化。
在DataManager中对具体的uri注册监听,在数据变化时会调用onChange(),然后调用数据源的notifyContentChanged();进一步调用会去唤醒处于等待状态的加载线程。
public void registerChangeNotifier(Uri uri,ChangeNotifier notifier)@ DataManager {
broker= new NotifyBroker(mDefaultMainHandler);
mApplication.getContentResolver()
.registerContentObserver(uri,true, broker);
}
生成数据源时,会定义监听那些uri,如:LocalAlbumSet.java中:
public class LocalAlbumSet {
//要监听的uri
privatestatic final Uri[] mWatchUris =
{Images.Media.EXTERNAL_CONTENT_URI,Video.Media.EXTERNAL_CONTENT_URI};
mNotifier= new ChangeNotifier(this, mWatchUris, application);
application.getDataManager().registerChangeNotifier(uris[i],this);
}
图片缩略图的生成
数据加载完之后,接下来就是显示数据,Gallery中的图片不是以一个控件View的形式来显示的,而是通过OpenGL ES把这些图片画出来的,可以提高浏览图片的效率。下面分析点击相册进去生成多张图片缩略图的过程。
数据源的导入和AlbumSlotRenderer的配置
AlbumSlotRenderer负责图片缩略图的生成工作,这里的数据处理同样使用了线程池。AlbumSlotRenderer是从AlbumDataLoader获取要处理的数据MediaItem的;根据每一个MediaItem的状态来确定Bitmap缩略图是需要加载,还是释放,对需要加载的缩略图,提交到线程池。
Step1,AlbumSlotRenderer的配置,查看AlbumPage.java
private void initializeViews() {
//相册界面的配置项,config定义了各个页面的配置,行、列、边距等。
Config.AlbumPageconfig = Config.AlbumPage.get(mActivity);
mSlotView= new SlotView(mActivity, config.slotViewSpec);
//对AlbumSlotRenderer进行配置
mAlbumView= new AlbumSlotRenderer(mActivity, mSlotView,
mSelectionManager,config.placeholderColor);
//添加相册页面到画布上
mRootPane.addComponent(mSlotView);
}
Step2,数据源的加载,查看AlbumPage.java
private void initializeData(Bundle data) {
//这里导入了数据源,mAlbumDataAdapter是加载数据的适配类对象。
mAlbumView.setModel(mAlbumDataAdapter);
}
Step3,进入setModel(),查看AlbumSlotRenderer.java
public void setModel(AlbumDataLoader model){
//创建缩略图窗口。
mDataWindow= new AlbumSlidingWindow(mActivity, model, CACHE_SIZE);
//对窗口添加监听。
mDataWindow.setListener(newMyDataModelListener());
}
Step4,我们点击某个相册,窗口会被创建、状态会发生变化,然后更新窗口的可见区域,会间接调用到MyDataModelListener. onVisibleRangeChanged()。
public void onVisibleRangeChanged(intvisibleStart, int visibleEnd) {
// mDataWindow是窗口显示相关的类
mDataWindow.setActiveWindow(visibleStart,visibleEnd);
}
Step5,进入setActiveWindow(),查看AlbumSlidingWindow.java
public void setActiveWindow(int start, intend)@ AlbumSlidingWindow {
//这里的start值通常是0,end值根据窗口的可见区域有个计算,竖屏最多16张,横//屏12张。
mActiveStart= start;
mActiveEnd= end;
//设置窗口,每张图片都对应一个窗口
setContentWindow(contentStart,contentEnd);
//更新所有图片生成缩略图的请求,即启动生成缩略图的任务。
updateAllImageRequests();
}
Step6,进入setContentWindow(),查看AlbumSlidingWindow.java
//设置包含的所有窗口
private void setContentWindow(int contentStart, intcontentEnd)@AlbumSlidingWindow {
mSource.setActiveWindow(contentStart,contentEnd);
//准备每个窗口的数据。
for (int i =contentStart; i < contentEnd; ++i) {
prepareSlotContent(i);
}
}
Step7,
private void prepareSlotContent(int slotIndex) @AlbumSlidingWindow{
//获取图片标识。
MediaItemitem = mSource.get(slotIndex);
//创建生成缩略图的任务。
entry.contentLoader= new ThumbnailLoader(slotIndex, entry.item);
}
开始生成缩略图- LocalImage $ ImageCacheRequest
Step1,回到前面提到的:updateAllImageRequests()方法,会更新缩略图的请求,并为每个MediaItem调用startLoad()方法,接着开始一个任务submitBitmapTask生成缩略图:
privateclass ThumbnailLoader extends BitmapLoader @AlbumSlidingWindow {
protected Future<Bitmap>submitBitmapTask(FutureListener<Bitmap> l) {
//开始生成缩略图的任务,
return mThreadPool.submit(
mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL),this);
}
}
Step2,这里会为每个数据源创建生成缩略图的的请求,并提交到线程池,这里假设是LocalImage本地数据的处理。
publicJob<Bitmap> requestImage(int type)@ LocalImage {
return newLocalImageRequest(mApplication, mPath, dateModifiedInSec,
type, filePath);
}
Step3,先看LocalImageRequest的父类ImageCacheRequest,是一个类似于callable的异步任务。
abstract classImageCacheRequest implements Job<Bitmap> @ImageCacheRequest{
public Bitmap run(JobContext jc) {
//先获取cache服务,从buffer中查询是否已经缓存过缩略图,因为生成过一次后会添
//加到缓存,下次不需要再生成。
ImageCacheService cacheService = mApplication.getImageCacheService();
BytesBuffer buffer =MediaItem.getBytesBufferPool().get();
booleanfound = cacheService.getImageData(mPath, mTimeModified, mType,
buffer);
}
//如果查到了,直接构建bitmap
Bitmapbitmap = DecodeUtils.decodeUsingPool(jc,
buffer.data,buffer.offset, buffer.length, options);
//没有查到,会调用子类的onDecodeOriginal()方法进行解码。
Bitmapbitmap = onDecodeOriginal(jc, mType);
//把缩略图添加到缓存,把bitmap压缩成byte数组,存储到cacheService。
if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
bitmap = BitmapUtils.resizeAndCropCenter(bitmap, mTargetSize, true);
} else {
bitmap = BitmapUtils.resizeDownBySideLength(bitmap, mTargetSize, true);
}
byte[]array = BitmapUtils.compressToBytes(bitmap);
cacheService.putImageData(mPath, mTimeModified, mType, array);
}
Step4,调用onDecodeOriginal()解码生成缩略图
public staticclass LocalImageRequest extends ImageCacheRequest@LocalImage {
//直接从jpeg的exif库函数里面获取缩略图,
ExifInterface exif = newExifInterface();
byte[] thumbData = null;
exif.readExif(mLocalFilePath);
thumbData = exif.getThumbnail();
//获取成功,就直接生成bitmap
Bitmapbitmap = DecodeUtils.decodeIfBigEnough(
jc, thumbData,options, targetSize);
//最后拿不到现成的,才调用方法进行解码。
DecodeUtils.decodeThumbnail(jc,mLocalFilePath, options, targetSize, type);
}
Step5,解码的方法调用:
decodeThumbnail()@DecodeUtils.java à
decodeFileDescriptor()@BitmapFactory.java à
nativeDecodeFileDescriptor()@BitmapFactory.cpp
这里是通过jni调用的本地方法。
图库的缩略图没插数据库,以下是图库中一张图片缩略图的生成过程:
onDecodeOriginal()@LocalImage.java -->
DecodeUtils.decodeThumbnail(jc, mLocalFilePath, options, targetSize, type); -->//mLocalFilePath这个地址图片在数据库中的地址
decodeThumbnail()@DecodeUtils.java --> // fis = new FileInputStream(filePath); 这里是读本地的数据,
//往后就是解析出bitmap,做scale,
Bitmap result = BitmapFactory.decodeFileDescriptor(fd, null, options);
result = BitmapUtils.resizeBitmapByScale(result, scale, true);
//这里写到buffer后,就返回了,整个过程没有看到再去写文件流
canvas.scale(scale, scale);
canvas.drawBitmap(bitmap,0, 0, paint);
//这里是往缓存写,是以ByteBuffer bufferl;buffer.array() ,bit数组的形式存到cache中的。
putImageData@ImageCacheService.java à
insert@BlobCache.java
通过BlobCache,最终写到了RandomAccessFile文件中。
在把图片存入缓存前,先通过Bitmap.compress()对图片数据做压缩,压缩的数据存储为byte[],然后把图片的path,timemodified,type合在一起生成一个key,实际就是把String转成一个byte[],最后把这个key,和图片的数据,一起存入一个ByteBuffer对象中,这个ByteBuffer对象会被写入到RandomAccessFile文件中。
在存入cache时,需要写入key-value对,这样才能方便查询,这里跟ByteBuffer对应的key是什么呢?它实际是把path,timemodified,type转成的byte[],在将其生成一个64位的CRC编码,来作为key。
Gallery的渲染机制
本节主要是介绍OpenGL ES的使用。前面提到Gallery中的图片显示界面是用OpenGL ES画出来的。Gallery使用OpenGL的方式是把GLSurfaceView添加到View树中。
gl_root_group.xml 被include到main.xml中
<mergexmlns:android="http://schemas.android.com/apk/res/android">
<com.android.gallery3d.ui.GLRootView
android:id="@+id/gl_root_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<Viewandroid:id="@+id/gl_root_cover"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"/>
</merge>
这里是自定义的渲染方式:GLRootView,它继承自:GLSurfaceView。
OpenGLES的使用
OpenGL是指定义了一个跨编程语言、跨平台的编程接口规格的专业的图形程序接口。它本身只是协议规范,而不是软件源码库。OpenGL ES(OpenGL for Embedded Systems) 是专门面向嵌入式领域的,是OpenGL(Open Graphics Library)API的子集,因为OpenGL的运行对设备要求比较高,所有才有了OpenGLES的诞生。
Android中的OpenGL ES简介源代码的路径:
l Java层SDK:frameworks\base\opengl
l JNI实现:frameworks\base\core\jni
l C++代码实现:frameworks\native\opengl
l Mesa 3D 图形处理软件库:external\mesa3d (兼容OpenGL协议)
在具体的实现中还有一部分比较重要的是EGL。它是介于本地窗口系统和RenderingAPI(这里只OpenGL ES)之间的一层接口。EGL主要负责图形环境管理、surface/buffer绑定、渲染同步等。
Android中OpenGL ES的框架图:
打个比方以帮助理解这些模块之间的关系。OpenGL ES就像打印机的标准一样,比如它规定了printLine(p1,p2)这个接口是打印一条从p1到p2的线段。加入打印机厂商(HP,EPSON)接受了这一协议,那么它们就需要在自己的设备中引入这样的实现。如此一来,无论是哪款遵循这一协议规定的打印机,只要向它发送printLine命令,最终得出的结果都应该是一样。Mesa 3D就是“打印机厂商”,并且他是开源的。
图形渲染API –EGL
EGL的功能
前面提到EGL,它是图形渲染API(OpenGL ES)和本地窗口系统间的一层接口。主要提供了一下功能:
l 创建rendering surfaces
Surface 通俗的讲就是能够承载图形的介质,如一张“画纸”。只有成功申请到Surface,应用程序才能真正“画图”到屏幕上。
l 创造图形环境(graphics context)
OpenGL Es需要状态管理,这就是context的主要工作。
l 同步应用程序和本地平台渲染API
l 提供了对显示设备的访问
l 提供了对渲染配置的管理
简而言之,EGL可以有效保证OpenGL ES的平台独立性。
加载实现库
图形渲染所采用的方式(硬件、软件)是在系统启动后动态确定的(具体源码文件是:frameworks/native/opengl/libs/egl/Loader.cpp),如果是在硬件加速的情况下,系统首先要加载相应的libhgl库;否则加载libagl软件库。
当前Android主流平台都是硬件支持OpenGL ES的,平台都把实现库放在system/lib/或者system/lib/egl目录。下图是手机的实现库存放的位置:
补充一点:这里并没有解析egl.cfg这个文件,尽管这个文件在手机试存在的,跟网上的一些介绍不太符合。应该说通过解析egl.cfg文件来选择渲染方式属于比较老的做法。目前的做法应该是直接精确加载实现库。这一点从代码注释大致可以看出来:
void*Loader::load_driver(const char* kind,
egl_connection_t* cnx, uint32_t mask)@Loader.cpp{
// first, we search for the exact name ofthe GLES userspace
// driver in both locations.
// i.e.:
// libGLES.so, or:
// libEGL.so, libGLESv1_CM.so, libGLESv2.so
for (size_t i=0 ; i<NELEM(searchPaths) ;i++) {
if(find(result, pattern, searchPaths[i], true)) {
return result;
}
}
}
EGL接口解析
整个显示框架中都贯穿着各种egl***和gl***的函数调用。所以egl接口是理解Android显示系统的关键。
以下接口的定义来源于:frameworks/native/opengl/include/egl/Egl.h
1. eglGetDisplay函数原型:
EGLAPIEGLDisplay EGLAPIENTRY eglGetDisplay(EGLNativeDisplayType display_id);
因为OpenGL ES和OpenGL都是系统无关的,所以在某一个特定平台上使用它时,就涉及如何对其进行“本地化”处理的问题。EGL为OpenGL ES与本地窗口系统提供了一个“中介”。
EGL面对的是各种各样的平台,而不同系统间的逻辑语义又存在或多或少的差异。所以,它需要一个机制来统一这些差异。接口eglGetDisplay得到的EGLDisplay就是一个与具体系统平台无关的对象。对于任何使用EGL的应用来说,首先就需要调用eglGetDisplay来取得设备的Display。
2. eglGetError函数原型:
EGLAPIEGLint EGLAPIENTRY eglGetError(void);
它返回当前EGL中已经发生的错误。因为大部分EGL函数只返回TURE或FALSE来表示成功和失败,所以开发人员要主动调用eglGetError来获取具体的失败原因。
3. eglInitialize函数原型
EGLAPIEGLBoolean EGLAPIENTRY eglInitialize(EGLDisplay dpy, EGLint *major, EGLint*minor);
成功执行了eglGetDisplay后,还需要对EGL进行初始化。这个函数对EGL的内部数据进行初始值设定,并返回当前的版本号。
4. eglGetConfigs函数原型:
EGLAPIEGLBoolean EGLAPIENTRY eglGetConfigs(EGLDisplay dpy, EGLConfig *configs,
EGLintconfig_size, EGLint *num_config);
初始化EGL完成后,下一步要获取一个最佳的Surface。方法有两种:其一通过查询当
前系统中所有Surface的配置,然后手动选择一个;其二是填写需求,然后由EGL推荐一个最佳匹配的Surface。
5. eglGetConfigAttrib函数原型:
EGLAPIEGLBoolean EGLAPIENTRY eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config,
EGLintattribute, EGLint *value);
EGLConfig包含了一个有效Surface的所有详细信息,如颜色数量、额外的缓冲区、
Surface类型等属性。可以通过eglGetConfigAttrib来指定需要查看的具体属性项。
6. eglChooseConfig函数原型:
EGLAPIEGLBoolean EGLAPIENTRY eglChooseConfig(EGLDisplay dpy, const EGLint
*attrib_list,EGLConfig *configs, EGLint config_size,EGLint *num_config);
前面提到,除了手动查阅EGLConfig,然后手动选择一个最佳配置外,还可以让EGL自动选择并直接返回匹配结果。使用这个函数要先填写一份需求。
7. eglCreateWindowSurface函数原型:
EGLAPIEGLSurface EGLAPIENTRY eglCreateWindowSurface(EGLDisplay dpy, EGLConfig
config,EGLNativeWindowTypewin,const EGLint *attrib_list);
一旦选择好最佳的EGLConfig,接下来就可以创建一个window了。
8. eglCreatePbufferSurface函数原型:
EGLAPIEGLSurface EGLAPIENTRY eglCreatePbufferSurface(EGLDisplay dpy, EGLConfig
config,constEGLint *attrib_list);
这个函数与上面的eglCreateWindowSurface一样都用于创建一个Surface,但是Surface的用途不同。前者生成的Surface可用于在物理屏幕上显示,也即是在某个窗口中显示使用。而PbufferSurface生成的结果则是“离屏”(off-screen)的渲染区,也就是要保存在显存中的帧。
9. eglCreateContext函数原型:
EGLAPIEGLContext EGLAPIENTRY eglCreateContext(EGLDisplay dpy, EGLConfig config,
EGLContextshare_context,const EGLint *attrib_list);
这个函数为OpenGL的运行提供了统一的环境,让我们可以依托于这个环境来更好地控
制OpenGL。
10. eglMakeCurrent函数原型:
EGLAPIEGLBoolean EGLAPIENTRY eglMakeCurrent(EGLDisplay dpy, EGLSurface draw,
EGLSurfaceread, EGLContext ctx);
一个进程中可能同时创建多个Context,必须选择其中一个作为当前的处理对象。
EGL实例
如下例子是Android显示系统—SurfaceFlinger通过EGL来搭建OpenGL ES的运行环
境。源码目录是:
Frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
具体在init()方法中:
voidSurfaceFlinger::init() {
// initialize EGL for the default display
mEGLDisplay =eglGetDisplay(EGL_DEFAULT_DISPLAY);//获取默认的显示屏
eglInitialize(mEGLDisplay,NULL, NULL); //初始化
//自动匹配一个最佳config,以下几个函数在:RenderEngine.cpp中
config= chooseEglConfig(display, hwcFormat);
//获取指定属性值
eglGetConfigAttrib(display,config,EGL_RENDERABLE_TYPE, &renderableType)
//创建EGL Context
EGLContextctxt = eglCreateContext(display, config, NULL, contextAttributes);
//获取一个默认的DisplayDevice,在生成一个DisplayDevice时,其构造函数会调用、、//eglCreateWindowSurface;
//设置当前的Context环境
getDefaultDisplayDevice()->makeCurrent(mEGLDisplay,mEGLContext);
}
可以看到,这段代码的处理过程和前面对egl接口的介绍基本一致。源码中以C/C++编写的类似的例子还有BootAnimation。
简化OpenGL ES使用–GLSurfaceView
上一节看到的是C/C++层对OpenGL ES的使用。接下来介绍应用程序如何使用OpenGL ES。和其他常用功能一样。Android在SDK中为开发人员封装了一套OpenGL ES的使用和管理机制。Java层可以通过两种方式来搭建OpenGL ES环境。
l 直接使用sdk提供的EGL,GLES类
与EGL相关的java类包括EGL10,EGL11,EGL14等;与GLES相关的java类包括GLES10,GLES11,GLES20,GLES30等。这些类讲间接通过jni调用C/C++层的EGL和GLES实现。
l GLSurfaceView
虽然可以通过EGL和GLES提供的java接口来使用OpenGL ES,但这种方式编写上较复
杂。所以Android系统特别封装了GLSurfaceView,从而精简了使用者的工作。
从上图GLSurfaceView的继承关系看,它继承自SurfaceView,意味着具备View类的所有功能和属性—特别是接收事件的能力。
GLSurfaceView的主要特性。
l 管理EGLDisplay,它表示一个显示屏。
l 管理Surface(本质就是一块内存区域)
l 会创建新的线程,使整个渲染过程不至于阻塞UI主线程。
l 用户可以自定义渲染方式,通过setRenderer()设置一个Renderer。
使用GLSurfaceView的基本步骤:
Step1.创建GLSurfaceView
因为GLSurfaceView也是一个View,可以通过布局文件的方式将它加入整个View树中。
Step2.初始化OpenGL ES环境
GLSurfaceView默认情况下已经为开发人员搭建好了OpenGL ES的运行环境,如果没有
特殊需求,不需要做额外工作。当然,所有的默认设置都可以被更改。
Step3.设置Render
SetRenderer()可以把自定义的一个Renderer加入到实际的渲染流程中。
Step4.设置Rendering Mode
默认采用的是连续的渲染方式,可以通过setRenderMode()来进行修改。
Step5.状态处理
使用GLSurfaceView也要注意程序的生命周期。Activity会有暂停和恢复状态,GLSurfaceView也要知道程序的当前状态。如当Activity暂停时需要调用GLSurfaceView的onPause(),恢复时在调用onResume()等---这样才能使OpenGLES内部的线程做出正确的判断。
综上,如果开发使用是基于GLSurfaceView,主要的工作就是Renderer的实现。
SurfaceView继承自View,View树会通过ViewRoot申请一个Surface,SurfaceView中的Surface跟ViewRoot中Surface不是同一个。下面看看SurfaceView中Surface是如何创建的。
SurfaceView中的Surface的申请过程:
Step1. 当ViewRoot成功AttachToWindow后,他需要将这个事件通知View树中的所有成员。这个过程在ViewRootImpl的performTraversals()中。
Step2. SurfaceView在收到AttachedToWindow成功的消息后,会通过getWindowSession()向ViewRoot获取一个IWindowSession。这个是ViewRoot与WMS间的通信中介。
Step3.SurfaceView在updataWindow()时,将利用IWindowSession.relayout()重新申请一个Surface,其中最后一个参数mNewSurface就是wms生成的新Surface,这是一个出参。
Step4.SurfaceView取得新Surface后,会transfer到mSurface变量中。还要把这一事件通知到注册了callback函数的对象,如GLSurfaceView。
GLSurfaceView在接收到事件回调时,执行surfaceCreated()方法:
/frameworks/base/opengl/java/android/opengl/GLSurfaceView.java
public voidsurfaceCreated(SurfaceHolder holder) {
mGLThread.surfaceCreated();
}
这里的mGLThread就是新启动的渲染线程。它在应用程序调用setRenderer()是启动,然后不断的等待和处理事件,同时负责Render工作。
接着看下这个线程的run()方法:
private void guardedRun() throws InterruptedException {
//这里有2个while循环,内循环,外循环
while (true) {
while (true) {
//1.从EventQueue中获取消息,取出这一消息,只是跳出内循环。
if (!mEventQueue.isEmpty()) {
event = mEventQueue.remove(0);
break;
}
//2.释放surface,比如Activity已经暂停了。
if(pausing && mHaveEglSurface) {
stopEglSurfaceLocked();
}
//3.判断Surface是否丢失,如果当前没有Surface,而且也不在等待Surface的创建,
//说明已经失去了Surface
if ((!mHasSurface) && (! mWaitingForSurface)) {
if (mHaveEglSurface) {
stopEglSurfaceLocked();
}
mWaitingForSurface= true;
mSurfaceIsBad= false;
}
if(readyToDraw()) {
//4.最核心的工作就是根据设置的Renderer来进行图形渲染处理。
}
//如果readyToDraw没有跳出内循环,就会执行wait
sGLThreadManager.wait();
}//内循环while结束
//程序走到这里,有两种可能:一是有事件要处理,一是要执行渲染工作。
//有事件处理,直接调用它的run()
if(event != null) {
event.run();
continue; //事件执行完毕后进入下一轮循环
}
//通知应用程序尺寸发生了变化
if(sizeChanged) {
view.mRenderer.onSurfaceChanged(gl, w,h);
}
//调用应用程序提供的render执行真正的渲染工作。
view.mRenderer.onDrawFrame(gl);
//通过swap把渲染结果显示到屏幕上
intswapError = mEglHelper.swap();
}
}
整个函数很长,核心工作是:
l 事件队列是否有事件要处理,是就直接调出内循环,进入外循环处理。
l 根据状态看是否适合渲染、是否有事件通知应用程序。
l 如果确定需要渲染,跳出内循环。
l 处理事件,或者执行渲染。如此循环往复。
对其中核心代码分析如下:
1. 内循环中,首先判断mEventQueue.isEmpty是否为空 ---如果有event要处理,直接
跳出内循环;否则忍让运行在内循环中。
2. 接着基于一些全局变量展开,需要判断的情况有:
2.1是否要释放EGLSurface。
如当前的Activity已经处于pause状态。
2.2 是否丢失了Surface。
异常情况下是可能丢掉的,要防止这种情况发生。变量mHasSurface表示当前有没有可用Surface;mWaitingForSurface表示是否在申请Surface的过程中。
2.3 是否需要放弃EGL Context
当调用requestReleaseEglContextLocked申请释放Context时,变量mShouldReleaseEglContext将被置为true,此时需要调用stopEglSurfaceLocked和stopEglContextLocked来终止Context。
3. 经过上一步判断后,进入渲染前的准备工作,即readyToDraw之间的代码,这段代码执行的前提是当前可以渲染(readyToDraw ==true),条件是:
3.1 程序当前不处于暂停状态
3.2已经成功获取到Surface
3.3有合适的尺寸,长宽大于0.
3.4处于自动渲染模式,或主动发起渲染请求。
接着确保EGL Context和EGLSurface存在且有效。
如果一切OK,会跳出内while循环,否则进入wait,直到有人唤醒它。
4. 跳出了内while循环
4.1 有事件处理,直接调用event.run()来执行具体工作,在事件处理结束进入下轮循环。
4.2 需要执行渲染,在一切准备就绪后,调用提供的Render对象执行真正的渲染工作:onDrawFrame(gl)
4.3 最后,通过swap来把渲染结果显示到屏幕上。
在run()的过程中,对EGL的操作都是通过EGLHelper来进行的。这是系统对EGL
的一层封装。
mEglHelper = newEglHelper(mGLSurfaceViewWeakRef);
EglHelper所做的工作,主要看它的start()放法。这里不再详述。
--------------
补充:
图片(相册集缩略图)渲染的过程,以时间轴界面为例,其他界面的渲染类似。
TimeLinePage.java
private void initializeViews() {
mSlotView = new TimeLineSlotView(mActivity, config.slotViewSpec);
mAlbumView = new TimeLineSlotRenderer(mActivity, mSlotView,
mSelectionManager, config.labelSpec, config.placeholderColor);
mSlotView.setSlotRenderer(mAlbumView);
mRootPane.addComponent(mSlotView);
}
时间轴界面的根布局是RootPane,类型是GLView,
private final GLView mRootPane = newGLView() {。。。}
TimeLinePage.java
private void initializeData(Bundle data) {
mAlbumDataAdapter = new TimeLineDataLoader(mActivity, mMediaSet);
mAlbumView.setModel(mAlbumDataAdapter);
}
TimeLinePage.java
protected void onResume() {
mAlbumView.resume();
}
TimeLineSlotRenderer.java
public void setModel(TimeLineDataLoader model) {
mDataWindow = new TimeLineSlidingWindow(mActivity, model, CACHE_SIZE, mLabelSpec,
mSlotView);
}
public void resume() {
mDataWindow.resume();
}
TimeLineSlidingWindow.java
public void resume() {
prepareSlotContent(i);
updateAllImageRequests();
}
private void prepareSlotContent(int slotIndex) {
AlbumEntry entry = new AlbumEntry();
MediaItem item = mSource.get(slotIndex); // item could be null;
entry.item = item;
entry.mediaType = (item == null)
? MediaItem.MEDIA_TYPE_UNKNOWN
: entry.item.getMediaType();
entry.path = (item == null) ? null : item.getPath();
entry.rotation = (item == null) ? 0 : item.getRotation();
entry.contentLoader = new ThumbnailLoader(slotIndex, entry.item);
mData[slotIndex % mData.length] = entry;
}
private class ThumbnailLoader extends BitmapLoader {
mItem以LocalImage为例
protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
return mThreadPool.submit( mItem.requestImage(
MediaItem.TYPE_MICROTHUMBNAIL), this);
}
private void updateAllImageRequests() {
requestNonactiveImages();
}
private void requestNonactiveImages() {
requestSlotImage(mActiveEnd + i);
}
entry.contentLoader的来源是prepareSlotContent中的赋值。
private boolean requestSlotImage(int slotIndex) {
entry.contentLoader.startLoad();
}
entry.contentLoader.startLoad()会调用prepareSlotContent的父类BitmapLoader的startLoad,然后调用子类submitBitmapTask方法,也就是:mThreadPool.submit(mItem.requestImage(
MediaItem.TYPE_MICROTHUMBNAIL), this);
间接调用LocalImage的requestImage,也就是LocalImageRequest,
LocalImage.java
public static class LocalImageRequest extends ImageCacheRequest {
public Bitmap onDecodeOriginal(JobContext jc, final int type) {
执行解码缩略图
}
}
LocalImageRequest是一个job,会先执行其父类ImageCacheRequest的run方法,
ImageCacheRequest.java
public Bitmap run(JobContext jc) {
先从缓存查询缩略图,如果找不到,才调用子类LocalImageRequest的onDecodeOriginal,缩略图解码后返回bitmap。
ImageCacheService cacheService = mApplication.getImageCacheService();
Bitmap bitmap = onDecodeOriginal(jc, mType);
return bitmap;
}
在缩略图解码结束后:
TimeLineSlidingWindow.java
private class ThumbnailLoader extends BitmapLoader {
job执行完,onFeatureDone中会调用onLoadComplete,通过msg,执行updateEntry。
protected void onLoadComplete(Bitmap bitmap) {
mHandler.obtainMessage(MSG_UPDATE_ENTRY, this).sendToTarget();
}
}
其中的getBitmap()获取的就是解码后的缩略图。
public void updateEntry() {
Bitmap bitmap = getBitmap();
if (bitmap == null) return; // error or recycled
AlbumEntry entry = mData[mSlotIndex % mData.length];
if (entry == null) return;
entry.bitmapTexture = new TiledTexture(bitmap);
entry.content = entry.bitmapTexture;
if (isActiveSlot(mSlotIndex)) {
mTileUploader.addTexture(entry.bitmapTexture);
--mActiveRequestCount;
if (mActiveRequestCount == 0) requestNonactiveImages();
if (mListener != null) mListener.onContentChanged();
} else {
mTileUploader.addTexture(entry.bitmapTexture);
}
}
先把bitmap封装成TiledTexture,TiledTexture类似于bitmaptexture,是一个可以绘制在glcanvas上的矩形图片,但是TiledTexture把bitmap分成了多片,这样做,可能会增加上传整个bitmap的时间,但是会减少上传每一片的时间,可以让动画更流畅,避免jank。
然后把TiledTexture对象,也就是entry.bitmapTexture,添加到一个列表中。
mTileUploader.addTexture(entry.bitmapTexture);
TiledTexture.java
public synchronized void addTexture(TiledTexture t) {
mTextures.addLast(t);
mGlRoot.addOnGLIdleListener(this);
}
其中mGlRoot.addOnGLIdleListener(this);会间接调用onGLIdle的执行,通过updateContent把数据上传到GPU的内存,也就是把bitmap上传到GL纹理中。
到这里缩略图数据就准备好了,下面是bitmap在GLSurfaceView中的绘制过程。
AbstractGalleryActivity.java
图库的根布局是GLRootView,
private GLRootView mGLRootView;
public class GLRootView extends GLSurfaceView
implements GLSurfaceView.Renderer, GLRoot {}
GLRootview继承自GLSurfaceView,渲染在GL Thread,事件处理在主线程。
渲染处理主要在GLSurfaceView.java的guardedRun方法中,也就是GLThread的run方法的核心函数。
GLSurfaceView.java
函数的大部分内容是做egl环境的检查及创建,实际的绘制操作是调用view.mRenderer.onSurfaceChanged, view.mRenderer.onDrawFrame。
private void guardedRun() throws InterruptedException {
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
view.mRenderer.onSurfaceChanged(gl, w, h);
view.mRenderer.onDrawFrame(gl);
}
这里的view是mGLSurfaceViewWeakRef这个弱引用指向的对象。
GLRootView.java
设置GLRootView的渲染器,
public GLRootView(Context context, AttributeSet attrs) {
其中的参数是GLRootView对象,同时GLRootView也实现GLSurfaceView.Renderer接口。
setRenderer(this);
}
GLSurfaceView.java
public void setRenderer(Renderer renderer) {
mRenderer = renderer;
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
}
所以GLThread中view,就是GLSurfaceView,mRenderer指的是GLRootView,guardeRun的中调用,实际执行的是GLRootView中的onSurfaceChanged,onDrawFrame。
重点关注drawframe。
GLRootView.java
public void onDrawFrame(GL10 gl) {
onDrawFrameLocked(gl);
}
private void onDrawFrameLocked(GL10 gl) {
mContentView.render(mCanvas);
}
函数中的mContentView就是timeLinepage界面中的mRootPane。所以接下来会调用GLView的render。
TimeLinePage.java
private final GLView mRootPane = new GLView() {
protected void render(GLCanvas canvas) {
直接调用了父类的render。
super.render(canvas);
}
}
GLView.java
protected void render(GLCanvas canvas) {
先渲染背景,然后渲染子view,对子view的渲染,也就是调用其render方法。
renderBackground(canvas);
for (int i = 0, n = getComponentCount(); i < n; ++i) {
renderChild(canvas, getComponent(i));
}
}
时间轴界面的子view,就是TimeLineSlotView。
TimeLineSlotView.java
protected void render(GLCanvas canvas) {
for (int i = mLayout.getVisibleEnd() - 1; i >= mLayout.getVisibleStart(); --i) {
int r = renderItem(canvas, i, 0, false);
}
}
private int renderItem(
GLCanvas canvas, int index, int pass, boolean paperActive) {
int result = mRenderer.renderSlot(
canvas, index, pass, rect.right - rect.left, rect.bottom - rect.top);
}
TimeLineSlotRenderer.java
public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height) {
这里的content就是缩略图bitmap,也就是new TiledTexture(bitmap);
Texture content = checkContentTexture(entry.content);
drawContent(canvas, content, width, height, entry.rotation);
if (entry.mediaType == MediaObject.MEDIA_TYPE_VIDEO) {
drawVideoOverlay(canvas, width, height, true, 0);
}
renderRequestFlags |= renderOverlay(canvas, index, entry, width, height);
}
AbstractSlotRenderer.java
protected void drawContent(GLCanvas canvas,
Texture content, int width, int height, int rotation) {
content.draw(canvas, 0, 0);
}
drawContent就是把bitmap画到GLCanvas中。到这里应用端的数据填充就完成了。