Gallery2 的数据加载及渲染

来源:互联网 发布:淘宝投诉不成立怎么班 编辑:程序博客网 时间:2024/06/01 08:59

图库主要的显示界面

图库主要的显示界面包括:相册集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;进入其ReloadTaskrun方法中。

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值通常是0end值根据窗口的可见区域有个计算,竖屏最多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);

    }

}

 

Step7privatevoid 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 {

//直接从jpegexif库函数里面获取缩略图,

         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文件中。



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)和本地窗口系统间的一层接口。主要提供了一下功能:

 

创建rendering surfaces

Surface 通俗的讲就是能够承载图形的介质,如一张“画纸”。只有成功申请到Surface,应用程序才能真正“画图”到屏幕上。

创造图形环境(graphics context

OpenGL Es需要状态管理,这就是context的主要工作。

同步应用程序和本地平台渲染API

提供了对显示设备的访问

提供了对渲染配置的管理

 

简而言之,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的主要特性。

管理EGLDisplay,它表示一个显示屏。

管理Surface(本质就是一块内存区域)

会创建新的线程,使整个渲染过程不至于阻塞UI主线程。

用户可以自定义渲染方式,通过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暂停时需要调用GLSurfaceViewonPause(),恢复时在调用onResume()---这样才能使OpenGLES内部的线程做出正确的判断。

         综上,如果开发使用是基于GLSurfaceView,主要的工作就是Renderer的实现。

         SurfaceView继承自ViewView树会通过ViewRoot申请一个SurfaceSurfaceView中的SurfaceViewRootSurface不是同一个。下面看看SurfaceViewSurface是如何创建的。


SurfaceView中的Surface的申请过程:




Step1. ViewRoot成功AttachToWindow后,他需要将这个事件通知View树中的所有成员。这个过程在ViewRootImplperformTraversals()中。

         Step2. SurfaceView在收到AttachedToWindow成功的消息后,会通过getWindowSession()ViewRoot获取一个IWindowSession。这个是ViewRootWMS间的通信中介。

         Step3.SurfaceViewupdataWindow()时,将利用IWindowSession.relayout()重新申请一个Surface,其中最后一个参数mNewSurface就是wms生成的新Surface,这是一个出参。

         Step4.SurfaceView取得新Surface后,会transfermSurface变量中。还要把这一事件通知到注册了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 {

         //这里有2while循环,内循环,外循环

          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();

}

}

 

         整个函数很长,核心工作是:

事件队列是否有事件要处理,是就直接调出内循环,进入外循环处理。

根据状态看是否适合渲染、是否有事件通知应用程序。

如果确定需要渲染,跳出内循环。

处理事件,或者执行渲染。如此循环往复。

 

对其中核心代码分析如下:

1.       内循环中,首先判断mEventQueue.isEmpty是否为空 ---如果有event要处理,直接

跳出内循环;否则忍让运行在内循环中。

2.       接着基于一些全局变量展开,需要判断的情况有:

2.1是否要释放EGLSurface

 如当前的Activity已经处于pause状态。

2.2   是否丢失了Surface

异常情况下是可能丢掉的,要防止这种情况发生。变量mHasSurface表示当前有没有可用SurfacemWaitingForSurface表示是否在申请Surface的过程中。

2.3   是否需要放弃EGL Context

当调用requestReleaseEglContextLocked申请释放Context时,变量mShouldReleaseEglContext将被置为true,此时需要调用stopEglSurfaceLockedstopEglContextLocked来终止Context

3.       经过上一步判断后,进入渲染前的准备工作,即readyToDraw之间的代码,这段代码执行的前提是当前可以渲染(readyToDraw ==true),条件是:

3.1 程序当前不处于暂停状态

3.2已经成功获取到Surface

3.3有合适的尺寸,长宽大于0.

3.4处于自动渲染模式,或主动发起渲染请求。

接着确保EGL ContextEGLSurface存在且有效。

如果一切OK,会跳出内while循环,否则进入wait,直到有人唤醒它。

4.       跳出了内while循环

4.1 有事件处理,直接调用event.run()来执行具体工作,在事件处理结束进入下轮循环。

4.2 需要执行渲染,在一切准备就绪后,调用提供的Render对象执行真正的渲染工作:onDrawFramegl

4.3 最后,通过swap来把渲染结果显示到屏幕上。

 

run()的过程中,对EGL的操作都是通过EGLHelper来进行的。这是系统对EGL

的一层封装。

mEglHelper = newEglHelper(mGLSurfaceViewWeakRef);

EglHelper所做的工作,主要看它的start()放法。这里不再详述。


 

原创粉丝点击