Gallery2 的数据加载及渲染

图库主要的显示界面

图库主要的显示界面包括:相册集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值通常是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)和本地窗口系统间的一层接口。主要提供了一下功能:

 

创建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()放法。这里不再详述。


--------------

补充:

图片(相册集缩略图)渲染的过程,以时间轴界面为例,其他界面的渲染类似。

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中。到这里应用端的数据填充就完成了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值