您的位置:首页 > 移动开发 > Android开发

Android Gallery2流程分析

2015-06-06 18:51 956 查看
Android 系统的多媒体数据provider

packages/providers/MediaProvider/

含有类MediaProvider.java、MediaScannerService.java、MediaScannerReceiver.java,

编译后生成MediaProvider.apk。会在开机时扫描本机和sdcard上的媒体文件(图片、视频、音频),并在/data/data/com.android.providers.media/databases 目录下生成internal.db(/system/meida)和external-?.db(/sdcard)两个数据库文件。此后所有的多媒体信息都从这两个数据库中获取。

rameworks/base/core/java/android/provider/MediaStore.java

提供的多媒体数据库,所有多媒体数据信息都可以从这里提取。数据库的操作通过利用ContentResolver调用相关的接口实现。

frameworks/base/media/java/android/media/

提供了android上 多媒体应用层的操作接口。主要说明:

• MediaPlayer.java:提供了视频、音频、数据流的播放控制等操作的接口。

• MediaScanner*.java:提供了媒体扫描接口的支持,媒体扫描后加入数据库中,涉及MediaScannerConnection.java和MediaScannerConnectionClient.java。

gallery2的功能交互:



android 媒体文件类型:



以下转自: http://blog.csdn.net/shareviews/article/details/7953912
Android4.0图库Gallery2代码分析(二) 数据管理和数据加载

一 图库数据管理

Gallery2的数据管理 DataManager(职责:管理数据源)- MediaSource(职责:管理数据集) - MediaSet(职责:管理数据项)。DataManager中初始化所有的数据源(LocalSource, PicasaSource, MtpSource, ComboSource, ClusterSource, FilterSource, UriSource, SnailSource),将数据源放到一个Hash表中,提供存取操作。MediaSource负责管理数据集,以LoacalSource为例,从他的createMediaObject函数可以看出,根据路径他可以创建出LocalMediaSet,
LocalMedia, LocalImage, LocalVideo等。MediaSet负责管理数据项MediaItem. 图库数据管理简要图如图-1






图-1:图库数据管理简化图

二 数据项(MediaItem)的加载过程。

下面介绍一下,Albumpage加载缩略图列表的过程为例子。AlbumSetPage加载相册缩略图和PhotoPage加载大图的过程,请读者自行分析。

在相册页面(AlbumPage)和缩略图数据加载相关的有两个成员变量AlbumDataLoader和AlbumSlotRender。AlbumDataLoader负责维护要加载数据提供一个管理,将要加载的数据项MediaItem放在链表中进行存取操作,动态的增删改查操作。在AlbumPage类的Onresume函数中调用了AlbumDataLoader的resume,AlbumDataLoader的resume创建了一个线程,随时处理数据的变化(MediaItem的增删改查)。AlbumDataLoader的作用过程见图2。



图-2:AlbumDataLoader的作用过程

AlbumDataLoader负责数据模型的维护,AlbumSlotRender负责数据的缩略图的加载工作,为了提高性能,数据加载使用了【线程池】。AlbumSlotRender从AlbumDataLoader获取要加载的数据MediaItem, 根据每一个MediaItem的状态确定是是否Bitmap缩略图的是需要加载、回收、还是等待等。对于需要加载的缩略图,提交到线程池中。AlbumSlotRender的作用过程见图3。数据加载过程中,【同步问题】其实也是需要重点分析的,由于时间有限,这里就缩略了,见谅!



图-3:AlbumSlotRender的作用过程





以下转自: http://www.2cto.com/kf/201312/267018.html
gallery2 实例分析:
GalleryAppImpl 在androidManifest.xml中注册Application标签,应用创建时就会被初始化,维护应用内部全局数据。

主要看几个函数:initializeAsyncTask(),GalleryUtils.initialize(this),

private void initializeAsyncTask() {

// AsyncTask class needs to be loaded in UI thread.

// So we load it here to comply the rule.

try {

Class.forName(AsyncTask.class.getName());

} catch (ClassNotFoundException e) {

}

}

Class.forName(AsyncTask.class.getName())会返回AsyncTask这个类,并要求JVM查找并加载AsyncTask 类,也就是说JVM会执行AsyncTask类的静态代码段。

public static void initialize(Context context) {

DisplayMetrics metrics = new DisplayMetrics();

WindowManager wm = (WindowManager)

context.getSystemService(Context.WINDOW_SERVICE);

wm.getDefaultDisplay().getMetrics(metrics);

sPixelDensity = metrics.density;

Resources r = context.getResources();

TiledScreenNail.setPlaceholderColor(r.getColor(

R.color.bitmap_screennail_placeholder));

initializeThumbnailSizes(metrics, r);

}

GalleryUtil是Gallery的工具类,获得了屏幕参数,WindowManager,Resource等。

onCreate中由于mStitchingProgressManager赋值为null,所以暂时未调用getDataManager()。这个函数下面会介绍到,主要用来管理Gallery的数据路径层次。

Gallery 从launcher进入Gallery,图库应用会进入应用主入口Gallery.java。onCreate()中加载布局main.xml。

@Override

protected void onCreate(Bundle savedInstanceState) {

…...

setContentView(R.layout.main);

if (savedInstanceState != null) {

getStateManager().restoreFromState(savedInstanceState);

} else {

initializeByIntent();

}

}

savedInstanceState是在Activity在onPause时保存状态用的,这里暂时为null。

private void initializeByIntent() {

Intent intent = getIntent();

String action = intent.getAction();

if (Intent.ACTION_GET_CONTENT.equalsIgnoreCase(action)){

startGetContent(intent);

} else if(Intent.ACTION_PICK.equalsIgnoreCase(action)) {

// We do NOT really support the PICKintent. Handle it as

// the GET_CONTENT. However, we needto translate the type

// in the intent here.

Log.w(TAG, "action PICK is notsupported");

String type =Utils.ensureNotNull(intent.getType());

if(type.startsWith("vnd.android.cursor.dir/")) {

if(type.endsWith("/image")) intent.setType("image/*");

if(type.endsWith("/video")) intent.setType("video/*");

}

startGetContent(intent);

} else if(Intent.ACTION_VIEW.equalsIgnoreCase(action)

||ACTION_REVIEW.equalsIgnoreCase(action)){

startViewAction(intent);

} else {

startDefaultPage();

}

}

从这个函数看,如果从相册应用图标进入,会走startDefaultPage流程,如果外部打开图片,会走startGetContent流程。先看默认的startDefaultPage流程吧。

public void startDefaultPage() {

PicasaSource.showSignInReminder(this);

Bundle data = new Bundle();

data.putString(AlbumSetPage.KEY_MEDIA_PATH,

getDataManager().getTopSetPath(DataManager.INCLUDE_ALL));

getStateManager().startState(AlbumSetPage.class,data);

mVersionCheckDialog =PicasaSource.getVersionCheckDialog(this);

if (mVersionCheckDialog != null) {

mVersionCheckDialog.setOnCancelListener(this);

}

}

data里面存了相册顶层路径:"/combo/{/mtp,/local/all,/picasa/all}"

StateManager和AbstractGalleryActivity

StateManager

StateManager中有个Stack mStack,类似于ActivityManager中的ActivityStack。用于控制相册界面的窗口堆栈管理,成员为StateEntry类。再看startState这个函数:

public void startState(Class klass,

Bundle data) {

Log.v(TAG, "startState " + klass);

ActivityState state = null;

try {

// 用窗口类创建一个ActivityState实例

state = klass.newInstance();

} catch (Exception e) {

throw new AssertionError(e);

}

// 堆栈非空

if (!mStack.isEmpty()) {

// 获取栈顶ActivityState

ActivityState top = getTopState();

top.transitionOnNextPause(top.getClass(), klass,

StateTransitionAnimation.Transition.Incoming);

// 调用栈顶ActivityState的onPause

if (mIsResumed) top.onPause();

}

// 初始化当前的ActivityState,这个和startActivity非常相似

state.initialize(mActivity, data);

// 初始化后入栈

mStack.push(new StateEntry(data, state));

Log.d(TAG, "startState:startState->onCreate");

// 调用新ActivityState实例的onCreate,这个和startActivity的流程又好相似

state.onCreate(data, null);

Log.d(TAG, "startState:startState->resume");

// 调用新ActivityState实例的onResume

if (mIsResumed) state.resume();

}

可以说这个函数和启动应用activity的流程非常相似,只是简化了流程而已。另外,startStateForResult也和startActivityForResult类似。

AbstractGalleryActivity 在介绍AlbumSetPage、AlbumPage、PhotoPage等页面前,必须先介绍AbstractGalleryActivity,因为以上三个页面的父类ActivityState中有成员变量AbstractGalleryActivity mActivity。相册的Activity实际上只有一个,可以说就是这个mActivity,通过StateManager和DataManager来控制显示不同的页面。

继承自Activity,其中有几个重要成员:GLRootView mGLRootView,StateManager mStateManager,DataManager,GalleryActionBar mActionBar,OrientationManager mOrientationManager。

其中GalleryActionBar就是相册各个界面顶部的ActionBar控件。按照Activity的生命周期,在onCreate中创建OrientationManager对象赋予mOrientationManager,调用toggleStatusBarByOrientation在屏幕横竖屏时对window的全屏标记做增加和清除。

// Shows status bar in portrait view, hide in landscape view

private void toggleStatusBarByOrientation() {

if (mDisableToggleStatusBar) return;

Window win = getWindow();

if (getResources().getConfiguration().orientation== Configuration.ORIENTATION_PORTRAIT) {

win.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

} else {

win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

}

}

在setContentView()中加载GLRootView。在onStart()中注册AlertDialog的onClick处理。在onResume(),onPause()中都会分别对StateManager,DataManager做对应的onResume, onPause操作。例如:

@Override

protected void onResume() {

super.onResume();

mGLRootView.lockRenderThread();

try {

getStateManager().resume();

getDataManager().resume();

} finally {

mGLRootView.unlockRenderThread();

}

mGLRootView.onResume();

mOrientationManager.resume();

}

其中 mGLRootView会调用 lockRenderThread(),在执行完xxx.resume()后会再调用unlockRenderThread()。下面是GLRootView的lockXXX()和unlockXXX()函数。

@Override

public void lockRenderThread() {

mRenderLock.lock();

}

@Override

public void unlockRenderThread() {

mRenderLock.unlock();

}

其中 mRenderLock是ReentrantLock对象。那么ReentrantLock(可重入锁)的作用是什么?

因为Gallery的每个页面AlbumSet、AlbumPage、PhotoPage、PhotoView都是同一个Activity,界面的控制是通过StateManager以及DataManager,切换进入每个状态页面,都会获取不同的GLRootView,所以必须保证GLRootView是锁定情况下,才能对StateManager以及DataManager做处

以下转自: http://blog.csdn.net/buleideli/article/details/8491744 一、
1.1.1 进入gallery

进行如下关键操作。

先进入gallery’中处理。Gallery中initializeByIntent中最终会调用启动albumset,代码如下。

Bundledata = new Bundle();

data.putString(AlbumSetPage.KEY_MEDIA_PATH,

getDataManager().getTopSetPath(DataManager.INCLUDE_ALL));

getStateManager().startState(AlbumSetPage.class,data);

初始化view,创建albumsetview并注册其侦听者,该侦听者负责侦听用户点击触摸操作。

创建GLView类的对象,在该对象重载实现onlayout()以及render函数。其中onlayout()在每次进入这个主界面时调用,render()在每次该界面视图改变时调用,例如触摸等操作使得界面视图改变调用render来重绘。

创建界面上的其他元素。

创建过渡动画。

1.1.2 退出gallery

调用onbackpressed退出。

最终执行退出操作是由StateManager的finishState。该函数会先出栈一个activitystate,后再判断是否栈为空,为空才真正终止应用退出。

1.1.3 打开相册

在点击进入相册前进行如下操作。

若进入的相册下一级已经没有相册了,那就启动albumpage。

若进入的相册下一级仍有相册了,那就启动albumsetpage。

进入albumpage后,主要步骤跟进入gallery时差不多。

1.1.4 打开相片或视频

1)打开图片见3.1.5。

2)打开视频见下面分析。主要操作见下面。

ControllerOverlay用于播放控件的实现,诸如时间条,屏幕中间显示的播放状态按钮等等。

加载各种view,控件。

Movieplayer中的Bookmarker是个书签功能,看代码就是负责保存本次播放的位置,等挂起后重新进入gallery可以从上一次播放位置继续播放。

1.1.5 播放幻灯片

主要操作如下。

初始化数据以及view。

指定播放幻灯片按什么顺序播放,或是随机,或是顺序。

1.1.6 删除图片或视频

主要操作如下。

删除操作在三个地方有,gallery主界面、相册内界面、图片浏览界面,他们都是由界面上方的actionbar按钮触发。

Albumsetpage、albumpage均是创建ActionModeHandler来实现加载这个actionbar。Photopage是通过创建MenuExecutor来加载这个actionbar。另外这个actionbar同时包括actiobar的其他操作,诸如裁剪、编辑等等。

最终的删除操作是通过获取mediaobject对象,对其进行操作实现的。Mediaobject同时是mediaset、mediaitem的父类,多态性就能保证对相册或是相片进行删除操作。删除操作最终通过conresolver.delete实现。

1.1.7 开机与media相关的数据库流程

流程见下。

创建mediaprovider。Mediaprovider中oncreate主要做如下处理。

挂载内部存储器,创建内部存储器的数据库。

创建侦听者来侦听卸载存储器的事件,包括卸载内部存储器以及外部存储器。

挂载外部存储器,创建外部存储器的数据库。

创建线程,用于处理更新缩略图的请求。缩略图请求分为两种,一种是图片缩略图,处理原则是FIFO,先请求先处理;另一种是音乐专辑缩略图,处理原则是LIFO,后请求先处理。

与2.3一样,先通过mediascanerreceiver接收到Intent.ACTION_MEDIA_MOUNTED这个加载intent,调用scan对全盘媒体文件进行扫描。

接着调用到mediascannerservice。ServiceHandler中对指定路径进行扫描。调用到mediascannerservice.scan()。扫描前发送Intent.ACTION_MEDIA_SCANNER_STARTED广播,接着创建mediascanner进行扫描,扫描后发送Intent.ACTION_MEDIA_SCANNER_FINISHED。接收这两个广播的地方找不到。应该是用于提示外部的应用扫描的开始以及结束。

在扫描前会插入数据库一条记录,之后就删除该条记录。为什么要在之前插入一条记录,之后就删除这条记录,并且只是获取了uri,并没对这个uri进行操作,看不懂。代码如下。

UriscanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(),values);

//...

getContentResolver().delete(scanUri,null, null);

调用mediascanner.scanDirectories扫描所有目录。

mediascanner.scanDirectories中,有如下关键操作。

Initalize():初始化各个媒体uri。总共有如下uri。

mAudioUri

mVideoUri

mImagesUri

mThumbsUri

mFilesUri

Prescan():扫描mFilesUri所示的文件。写入filecache中。同时计算mImagesUri的文件数。

processDirectory():遍历扫描所有路径。具体进入c++代码,看不懂。

Postscan():删除掉文件系统中不存在但数据库中存在的文件项,此删除为删除数据库中记录项。

1.1.8 Filemanager中删除媒体文件

删除单个文件主要流程见下。

在Filemanager中,主要操作流程如下。

删除该文件。

FileMgrMenuUtil.notifyFileSystemChanged中发送Intent.ACTION_MEDIA_SCANNER_SCAN_FILE通知删除文件成功。Mediareceiver接收到就交给mediascannerservice进行scanfile()处理。

Mediascannerservice.scanfile(),最终调用到Mediascanner.scansinglefile()处理。主要进行如下处理。

初始化各种扫描需要的东西,如路径等。

Prescan():更新媒体文件的filecache。Filecache是一个索引表,存放媒体文件的关键数据,以提高访问这些文件的速度。

Doscanfile():在这里主要是置标志位表示已经在文件系统找到该文件。

均是调用mediaobject.delete()完成删除操作,其实最终调用的是mediaobject派生出的mediaset、mediaitem。而跟进代码最终是在mediaprovider.delete处理。

Mediaprovider.delete()主要是删除数据库中该条记录。

在Gallery中,主要需要注意流程如下。

均是调用mediaobject.delete()完成删除操作,其实最终调用的是mediaobject派生出的mediaset、mediaitem。而跟进代码最终是在mediaprovider.delete处理。

Mediaprovider.delete()主要是删除数据库中该条记录。

但是不能找到地方处理删除文件操作。

删除目录。

在filemanager中,流程稍有不同,主要见以下。

会调用到RecursiveDeleteTask线程进行递归删除目录下文件的处理。

FileMgrMenuUtil.notifyFileSystemChanged中发送Intent.ACTION_MEDIA_MOUNTED通知删除文件夹成功。Mediascannerservice进行scan()处理。其主要操作跟“开机与media相关的数据库流程”内的Mediascannerservice.scan()大同小异,区别在与扫描范围的不同。

在gallery中,操作见上面的删除单个文件操作。

1.1.9 Filemanager中移动媒体文件

移动单个文件。

在filemanager中,主要注意以下。

交由MoveTask线程处理,线程中通过file.renameto()来实现将文件移动到另一个地方,这个方法只是更改了原来文件路径。

FileMgrMenuUtil.notifyFileSystemChanged中发送Intent.ACTION_MEDIA_SCANNER_SCAN_FILE通知移动文件成功。接着Mediascannerservice同样进行单个文件扫描处理。

移动一整个目录。

在filemanager中,主要注意以下。

主要通过以下数据结构来存放待处理的文件列表。

private ArrayList<FileInfo>mCheckedFileList = newArrayList<FileInfo>();

同样还是MoveTask线程处理这些文件。

FileMgrMenuUtil.notifyFileSystemChanged中发送Intent.ACTION_MEDIA_MOUNTED通知移动文件夹成功。Mediascannerservice进行scan()处理。

1.1.10 Filemanager中重命名媒体文件

重命名单个文件与文件夹操作基本一致。

Filemanager中,主要操作流程如下。

同样使用file.renameto()进行重命名文件操作。

同样使用FileMgrMenuUtil.notifyFileSystemChanged发送intent消息通知重命名操作,使得Mediascannerservice再去扫描。这里发送的intent消息,重命名单个文件与重命名文件夹是不一样的,与之前区别一样。

1.1.11 Filemanager中复制媒体文件

复制单个文件。

在filemanager中,主要操作流程如下。

检查目的地址空间足够与否。

同样使用FileMgrMenuUtil.notifyFileSystemChanged发送intent消息通知重命名操作,使得Mediascannerservice再去扫描。这里发送的intent消息,重命名单个文件与重命名文件夹是不一样的,与之前区别一样。

复制目录。与单个文件操作基本一致。主要见以下。

递归实现copy。

1.1.12 增加媒体文件

在camera拍摄一张照片后,主要进行如下操作。

生成这个jpeg格式的图片。

ContentResolver中插入一条关于这照片的记录到对应数据库中。

二、

3.1.1 Gallery

gallery的主程序类。
其类图如下。



相关类说明

1)GalleryActionBar

用于在相册主界面出现的排序下拉框,如下。



3.1.2 AbstractGalleryActivity

实现基本的activity操作的类,作为某些类的基类。

其类图如下。



相关类说明

1)GalleryActivity

接口类,主要是一些获取AbstractGalleryActivity成员变量的方法。

public interface GalleryActivity extends GalleryContext {

public StateManager getStateManager();

public GLRoot getGLRoot();

public PositionRepository getPositionRepository();

public GalleryApp getGalleryApplication();

public GalleryActionBar getGalleryActionBar();

}

public interface GalleryContext {

public ImageCacheService getImageCacheService();

public DataManager getDataManager();

public Context getAndroidContext();

public Looper getMainLooper();

public Resources getResources();

public ContentResolver getContentResolver();

public ThreadPool getThreadPool();

}

2)StateManager

其负责管理activity的各种状态的启动挂起等。

通过管理一个栈,来维护所有activity的显示状态。

private Stack<StateEntry> mStack = new Stack<StateEntry>();

同时保存一个GalleryActivity的mcontext,用来方便对相应activity进行操作。

一般通过如下来启动一个activity。

mActivity.getStateManager().startState(ManageCachePage.class, data);

其相当于

context.startActivity()

或是通过如下启动一个需要处理结果的activity。

mActivity.getStateManager().startStateForResult(

PhotoPage.class, REQUEST_PHOTO, data);

其相当于

context.startActivityForResult()

以下是类图。



一个activity对应着多个显示状态。见如下定义。

private Stack<StateEntry> mStack = new Stack<StateEntry>();

private static class StateEntry {

public Bundle data;

public ActivityState activityState;

public StateEntry(Bundle data, ActivityState state) {

this.data = data;

this.activityState = state;

}

}

而显示状态通过StateEntry中的ActivityState变量来标识。

abstract public class ActivityState {

public static final int FLAG_HIDE_ACTION_BAR = 1;

public static final int FLAG_HIDE_STATUS_BAR = 2;

public static final int FLAG_SCREEN_ON = 3;

…}

ActivityState用于控制activity的显示模式,是否需要点亮显示、是否需要显示状态栏、是否需要显示动作按钮。

3)GLRootView

实现绘图的类。其类图如下。



小贴士

1)GLSurfaceView介绍

GLSurfaceView是一个视图,继承至SurfaceView,它内嵌的surface专门负责OpenGL渲染。

GLSurfaceView提供了下列特性:

1> 管理一个surface,这个surface就是一块特殊的内存,能直接排版到android的视图view上。

2> 管理一个EGL display,它能让opengl把内容渲染到上述的surface上。

3> 用户自定义渲染器(render)。

4> 让渲染器在独立的线程里运作,和UI线程分离。

5> 支持按需渲染(on-demand)和连续渲染(continuous)。

6> 一些可选工具,如调试。

使用GLSurfaceView

通常会继承GLSurfaceView,并重载一些和用户输入事件有关的方法。如果你不需要重载事件方法,GLSurfaceView也可以直接使用, 你可以使用set方法来为该类提供自定义的行为。例如,GLSurfaceView的渲染被委托给渲染器在独立的渲染线程里进行,这一点和普通视图不一 样,setRenderer(Renderer)设置渲染器。

初始化GLSurfaceView

初始化过程其实仅需要你使用setRenderer(Renderer)设置一个渲染器(render)。当然,你也可以修改GLSurfaceView一些默认配置。

* setDebugFlags(int)

* setEGLConfigChooser(boolean)

* setEGLConfigChooser(EGLConfigChooser)

* setEGLConfigChooser(int, int, int, int, int, int)

* setGLWrapper(GLWrapper)

定制android.view.Surface

GLSurfaceView默认会创建像素格式为PixelFormat.RGB_565的surface。如果需要透明效果,调用 getHolder().setFormat(PixelFormat.TRANSLUCENT)。透明(TRANSLUCENT)的surface的像 素格式都是32位,每个色彩单元都是8位深度,像素格式是设备相关的,这意味着它可能是ARGB、RGBA或其它。

选择EGL配置

Android设备往往支持多种EGL配置,可以使用不同数目的通道(channel),也可以指定每个通道具有不同数目的位(bits)深度。因此, 在渲染器工作之前就应该指定EGL的配置。GLSurfaceView默认EGL配置的像素格式为RGB_656,16位的深度缓存(depth buffer),默认不开启遮罩缓存(stencil buffer)。

如果你要选择不同的EGL配置,请使用setEGLConfigChooser方法中的一种。

调试行为

你可以调用调试方法setDebugFlags(int)或setGLWrapper(GLSurfaceView.GLWrapper)来自定义 GLSurfaceView一些行为。在setRenderer方法之前或之后都可以调用调试方法,不过最好是在之前调用,这样它们能立即生效。

设置渲染器

总之,你必须调用setRenderer(GLSurfaceView.Renderer)来注册一个GLSurfaceView.Renderer渲染器。渲染器负责真正的GL渲染工作。

渲染模式

渲染器设定之后,你可以使用setRenderMode(int)指定渲染模式是按需(on demand)还是连续(continuous)。默认是连续渲染。

Activity生命周期

Activity窗口暂停(pause)或恢复(resume)时,GLSurfaceView都会收到通知,此时它的onPause方法和 onResume方法应该被调用。这样做是为了让GLSurfaceView暂停或恢复它的渲染线程,以便它及时释放或重建OpenGL的资源。

事件处理

为了处理事件,一般都是继承GLSurfaceView类并重载它的事件方法。但是由于GLSurfaceView是多线程操作,所以需要一些特殊的处 理。由于渲染器在独立的渲染线程里,你应该使用Java的跨线程机制跟渲染器通讯。queueEvent(Runnable)方法就是一种相对简单的操 作,例如下面的例子。

class MyGLSurfaceView extends GLSurfaceView {

private MyRenderer mMyRenderer;

public void start() {

mMyRenderer = ...;

setRenderer(mMyRenderer);

}

public boolean onKeyDown(int keyCode, KeyEvent event) {

if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {

queueEvent(new Runnable() {

// 这个方法会在渲染线程里被调用

public void run() {

mMyRenderer.handleDpadCenter();

}});

return true;

}

return super.onKeyDown(keyCode, event);

}

}

(注:如果在UI线程里调用渲染器的方法,很容易收到“call to OpenGL ES API with no current context”的警告,典型的误区就是在键盘或鼠标事件方法里直接调用opengl es的API,因为UI事件和渲染绘制在不同的线程里。更甚者,这种情况下调用glDeleteBuffers这种释放资源的方法,可能引起程序的崩溃, 因为UI线程想释放它,渲染线程却要使用它。)

2)GLSurfaceView的非交互使用

使用GLSurfaceView开发3D应用时,如果是非交互式的应用,可以直接使用GLSurfaceView。如果需要交互式的行为,则需要继承

GLSurfaceView并重写一些方法。交互式应用示例见下篇。

import javax.microedition.khronos.egl.EGLConfig;

import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;

import android.opengl.GLSurfaceView;

import android.opengl.GLSurfaceView.Renderer;

import android.os.Bundle;

import android.provider.OpenableColumns;

/**

* 本示例演示OpenGL ES开发3D应用

* 该Activity直接使用了GLSurfaceView

* 这是因为GLSurfaceView可以直接使用,除非需要接受用户输入,和用户交互,才需要重写一些GLSurfaceView的方法

* 如果开发一个非交互式的OpenGL应用,可以直接使用GLSurfaceView。参照本示例

* @author Administrator

*

*/

public class NonInteractiveDemo extends Activity {

private GLSurfaceView mGLView;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

mGLView = new GLSurfaceView(this);

//这里需要指定一个自定义的渲染器

mGLView.setRenderer(new DemoRenderer());

setContentView(mGLView);

}

public void onPause(){

super.onPause();

mGLView.onPause(); //当Activity暂停时,告诉GLSurfaceView也停止渲染,并释放资源。

}

public void onResume(){

super.onResume();

mGLView.onResume(); //当Activity恢复时,告诉GLSurfaceView加载资源,继续渲染。

}

}

class DemoRenderer implements Renderer{

@Override

public void onDrawFrame(GL10 gl) {

//每帧都需要调用该方法进行绘制。绘制时通常先调用glClear来清空framebuffer。

//然后调用OpenGL ES其他接口进行绘制

gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);

}

@Override

public void onSurfaceChanged(GL10 gl, int w, int h) {

//当surface的尺寸发生改变时,该方法被调用,。往往在这里设置ViewPort。或者Camara等。

gl.glViewport(0, 0, w, h);

}

@Override

public void onSurfaceCreated(GL10 gl, EGLConfig config) {

// 该方法在渲染开始前调用,OpenGL ES的绘制上下文被重建时也会调用。

//当Activity暂停时,绘制上下文会丢失,当Activity恢复时,绘制上下文会重建。

//do nothing special

}

}

3)GLSurfaceView交互式

import javax.microedition.khronos.egl.EGLConfig;

import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;

import android.content.Context;

import android.opengl.GLSurfaceView;

import android.os.Bundle;

import android.view.MotionEvent;

/**

* 本示例演示OpenGL ES开发3D应用

* 该Activity使用了自定义的GLSurfaceView的子类

* 这样,我们可以开发出和用户交互的应用,比如游戏等。

* 需要注意的是:由于渲染对象是运行在一个独立的渲染线程中,所以

* 需要采用跨线程的机制来进行事件的处理。但是Android提供了一个简便的方法

* 我们只需要在事件处理中使用queueEvent(Runnable)就可以了.

*

* 对于大多数3D应用,如游戏、模拟等都是持续性渲染,但对于反应式应用来说,只有等用户进行了某个操作后再开始渲染。

* GLSurfaceView支持这两种模式。通过调用方法setRenderMode()方法设置。

* 调用requestRender()继续渲染。

*

*

* @author Administrator

*

*/

public class InteractiveDemo extends Activity {

private GLSurfaceView mGLView;

public void onCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

mGLView = new DemoGLSurfaceView(this); //这里使用的是自定义的GLSurfaceView的子类

setContentView(mGLView);

}

public void onPause(){

super.onPause();

mGLView.onPause();

}

public void onResume(){

super.onResume();

mGLView.onResume();

}

}

class DemoGLSurfaceView extends GLSurfaceView{

DemoRenderer2 mRenderer;

public DemoGLSurfaceView(Context context) {

super(context);

//为了可以激活log和错误检查,帮助调试3D应用,需要调用setDebugFlags()。

this.setDebugFlags(DEBUG_CHECK_GL_ERROR|DEBUG_LOG_GL_CALLS);

mRenderer = new DemoRenderer2();

this.setRenderer(mRenderer);

}

public boolean onTouchEvent(final MotionEvent event){

//由于DemoRenderer2对象运行在另一个线程中,这里采用跨线程的机制进行处理。使用queueEvent方法

//当然也可以使用其他像Synchronized来进行UI线程和渲染线程进行通信。

this.queueEvent(new Runnable() {

@Override

public void run() {

//TODO:

mRenderer.setColor(event.getX()/getWidth(), event.getY()/getHeight(), 1.0f);

}

});

return true;

}

}

/**

* 这个应用在每一帧中清空屏幕,当tap屏幕时,改变屏幕的颜色。

* @author Administrator

*

*/

class DemoRenderer2 implements GLSurfaceView.Renderer{

private float mRed;

private float mGreen;

private float mBlue;

@Override

public void onDrawFrame(GL10 gl) {

// TODO Auto-generated method stub

gl.glClearColor(mRed, mGreen, mBlue, 1.0f);

gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);

}

@Override

public void onSurfaceChanged(GL10 gl, int w, int h) {

// TODO Auto-generated method stub

gl.glViewport(0, 0, w, h);

}

@Override

public void onSurfaceCreated(GL10 gl, EGLConfig config) {

// TODO Auto-generated method stub

}

public void setColor(float r, float g, float b){

this.mRed = r;

this.mGreen = g;

this.mBlue = b;

}

}

GLRootView的构造函数中对其进行初始化操作,设置渲染器,设置EGL,设置背景图,使用RGB565作为像素格式。

三、
3.1.3 AlbumSetPage

负责处理刚进入相册显示的所有相册界面。

其类图如下。



相关类说明

1)SelectionManager.SelectionListener

负责对选择状态时的侦听。



2)SelectionManager

负责对选择状态的处理。

其类图如下。



mSourceMediaSet主要用于对相册数据进行管理,其具体作用待分析。MediaSet类请参见Mediaset。

mClickedSet主要用于对选中的媒体文件的路径进行管理。

mListener就是侦听者。

mDataManager主要用于管理相册数据。

mPressedPath存放当前点中的媒体数据的路径。

选择分为以下几种状态。

SelectionManager.ENTER_SELECTION_MODE

SelectionManager.LEAVE_SELECTION_MODE

SelectionManager.SELECT_ALL_MODE

进入退出全选,对应每种状态都要相应处理。

3)GalleryActionBar.ClusterRunner

主要实现doCluster函数。该函数用于实现按模式排序时的activity跳转。

通过GalleryActionBar实现排序,原理就是打包bundle值,重新开启一个activity,新的activity接受到这个bundle值。

4)EyePosition.EyePositionListener

人眼定位的侦听者,主要实现onEyePositionChanged,用于对人眼位置改变的侦听。未研究。

5)EyePosition

处理人眼定位的类。未研究。

6)MediaSet.SyncListener

处理异步加载mediaset的结果侦听者。实现onSyncDone()函数。

该函数处理异步加载的结果。

7)StaticBackground

管理背景图图片。

8)AlbumSetView

负责绘制所有相册的view。

如下是类图。



8.1)SLotView

将GLView再封装。未研究。

8.2)GLView

关于GLView类介绍的注释如下。通过其可以知道GLView大致是用来将内容渲染到GLCanvas画布上的,同时处理一些触摸事件。未研究。

// GLView is a UI component. It can render to a GLCanvas and accept touch

// events. A GLView may have zero or more child GLView and they form a tree

// structure. The rendering and event handling will pass through the tree

// structure.

//

// A GLView tree should be attached to a GLRoot before event dispatching and

// rendering happens. GLView asks GLRoot to re-render or re-layout the

// GLView hierarchy using requestRender() and requestLayoutContentPane().

//

// The render() method is called in a separate thread. Before calling

// dispatchTouchEvent() and layout(), GLRoot acquires a lock to avoid the

// rendering thread running at the same time. If there are other entry points

// from main thread (like a Handler) in your GLView, you need to call

// lockRendering() if the rendering thread should not run at the same time.

8.3)AlbumSetSlidingWindow

负责滑动显示图片相关。

9)AlbumSetDataAdapter

数据适配器。

10)ActionModeHandler

对操作栏上的操作进行管理。

目前有如下操作。

private static final int SUPPORT_MULTIPLE_MASK = MediaObject.SUPPORT_DELETE

| MediaObject.SUPPORT_ROTATE | MediaObject.SUPPORT_SHARE

| MediaObject.SUPPORT_CACHE | MediaObject.SUPPORT_IMPORT;

3.1.4 AlbumPage

其基本结构与albumsetpage差不多,里面的view变成albumview。

四、
3.1.5 PhotoPage

其类图如下。



一般创建流程如下。顺序无先后

1)绘制视图,添加需要用到的控件或各种view。

2)为需要侦听者的控件或view加侦听者。

3)绑定相应model,例如这里绑定photopage.model。

4)创建渲染对象,GLView。各个视图用到的view都是加载在其上的,通过GLView.addComponent()实现。

其他主要page,诸如albumsetpage、albumpage。

相关类说明

1)PhotoView.PhotoTapListener

主要是处理onSingleTapUp事件。用于处理点击媒体文件时的动作。

2)PhotoView

主要处理photo的view。

其类图如下。



2.1)ScreenNailEntry

主要保存上下两个缩略图的显示的数据,显示这两个缩略图。

手势识别相关

ScaleGestureDetector、GestureDetector、DownUpDetector、PositionController都是跟手势识别相关的。例如左滑动、右滑动切换图片,手势放大图片等,以及过渡动画。

2.2)TileImageView

负责每个图片的处理流程。

存在一个状态机,标示每种decode相关的状态。如下图:



Tile作为一个绘制的单位,可以理解为,其封装了解码后的图片数据。TileQueue是存放tile的队列,这些tile就是等待最终调用canvas绘制的数据。GLCanvas就是封装了canvas的类。

Tileimageview.render就是绘制图片的开始。里面会调用到queueForDecode()将绘制的tile丢入tilequeue中等待被处理。一旦轮到该tile被绘制,则调用decodetile将此tile解码。

ImageCacheRequest.run调用DecodeUtils. requestDecode完成最终的解码操作。

存在三个队列,如下:

Decodequeue:存放等待decode的tile。

Uploadqueue:存放等待upload的tile。Upload就是将解码的数据送到CAVANS绘制出来,因此都是从decode中出来的。

Recycledqueue:存放要回收的tile。

mActiveTiles用于存放已激活的tile的map。

2.3)EdgeView

缩略图中每张图的蓝色外框。

2.4)Model

用于沟通UI层与数据层的桥梁。

其定义如下。

public static interface Model extendsTileImageView.Model {

public void next();

public void previous();

public void jumpTo(int index);

public int getImageRotation();

// Return null if the specified imageis unavailable.

public ImageDatagetNextImage();

public ImageData getPreviousImage();

}

2.5)ProgressSpinner

进度圆圈。

2.6)手势识别处理相关

private final ScaleGestureDetector mScaleDetector;//两点触摸放大缩小等处理

private final GestureDetector mGestureDetector;

private final DownUpDetector mDownUpDetector;//点击处理相关

private PhotoTapListener mPhotoTapListener;

private final PositionController mPositionController;

3)FilmStripView.Listener

显示图片下方的缩略图滚动条的处理侦听者。主要涉及点击跳转的侦听。如下

public interface Listener {

// Returns false if it cannot jump to the specified index at this time.

boolean onSlotSelected(int slotIndex);

}



4)FilmStripView

处理在这个 控件之上的操作,诸如触摸移动长按等动作。

五、
3.1.6 SlideshowPage

处理幻灯片动画显示。

其类图如下。



1)SlideshowView

负责处理幻灯片播放的view。

1.1)SlideshowAnimation

其继承自Animation,负责具体幻灯片切换的显示。这里用到两个对象,mPrevAnimation负责上一张图片动画显示,mCurrentAnimation负责当前图片动画显示,显示动画效果就是扩大,淡入淡出。

Animation类的基本用法如下:

1)使用前需要设置每张图片显示周期,使用setDuration()。

2)开始播放,使用start()。

3)calculate()返回单张图片是否在一个周期显示完毕。

1.2)FloatAnimation

与SlideshowAnimation配合,未研究。

1.3)BitmapTexture

存放显示的图片。这里也用到两个对象,mPrevTexture负责存放上一张图片数据,mCurrentTexture负责存放当前图片数据。

2)Slide

负责存放具体相片数据。

3)ShuffleSource

负责生成随机的图片序列。用于随机幻灯片显示图片。

4)SequentialSource

负责生成顺序的图片序列。用于顺序幻灯片显示图片。

3.1.7 PhotoDataAdapter

Photodataadapter属于photopage的数据层,负责在图片数据更新时请求刷新UI。

1)渲染显示各个界面元素的原理

Photopage界面上,显示的元素种类分为三种。完整画面显示的图片,位于图片下方的缩略图图片,可能出现的GIF图片。

Photopage界面上一般存在多个元素,一个完整图片以及很多张缩略图图片,要同时刷新这么多元素,只能多线程,而涉及的线程那么多,最好就是用线程池来管理。简单说,在需要渲染界面元素时,就将该渲染任务丢入线程池中,线程池中若有空闲线程就分配来处理之,没有则该任务挂起,等待一定时间。同时线程池容许存放的任务数有上限。如下图表示线程池处理渲染的原理图。



这里的发起更新图片请求通过updateImageRequests()来实现。

2)数据层与UI层的桥梁

该类保存了一个PhotoView,看其函数调用,很多都是notify开头的,可见该对象主要在这里扮演桥梁的角色。一旦数据更新了,就会经由它通知UI层进行更新,或是一旦UI层变化了了,就经由它通知数据层更新。

各种代码片段如下。

通知UI层更新。



通知数据层更新。



3)ReloadTask

继承自thread。该线程是是与SourceListener共同配合,处理当前显示图片检测到路径不存在时的情形。诸如,在文件管理器或是图库删除此图片时,就会进行该处理,通知界面各元素更新。

4)各种图片元素表示相关类

ImageEntry 类用于表示当前显示的图片。其结构如下:

private static class ImageEntry {

public int requestedBits = 0;

public int rotation;

public BitmapRegionDecoder fullImage;

public Bitmap screenNail;

public Future<Bitmap> screenNailTask;

public Future<BitmapRegionDecoder> fullImageTask;

public boolean failToLoad = false;

// the below members are added for Gif animation

public GifDecoder gifDecoder;

public Future<GifDecoder> gifDecoderTask;

public Bitmap currentGifFrame;

}

FullImageListener用于侦听全屏显示的图片的更新。

ScreenNailListener用于侦听缩略图显示的图片的更新。

ScreenNailJob用于缩略图更新时的具体处理,与ScreenNailListener配合使用。

GifDecoderListener用于侦听GIF图片的更新。

GifAnimation类用于表示当前显示的GIF图片,其结构如下:

private static class GifAnimation {

public ImageEntry entry;

public GifDecoder gifDecoder;

public int animatedIndex;

public int currentFrame;

public int totalFrameCount;

}

GifAnimationRunnable是负责GIF解码显示的runnale类。

5)图片缓存处理相关

要想在图片切换时速度加快,就应该建立一套缓存机制。Gallery 4.0主要通过在保存当前显示图片临近图片元素来实现。如前面所说,图片元素包括,完整画面显示的图片,位于图片下方的缩略图图片,可能出现的GIF图片。对于缩略图图片,IMAGE_CACHE_SIZE限定保存个数,这里是5,其余两种元素各保留一个。

保存图片元素使用ImageFetch,其类定义如下。

private static class ImageFetch {

int indexOffset;

int imageBit;

public ImageFetch(int offset, int bit) {

indexOffset = offset;

imageBit = bit;

}

}

6)图片裁切相关数据处理

相关类是TileImageViewAdapter。

7)DataManager

Mediaset以及mediaitem都用一个64bit的id来标识。高32bit用来标识其与父集的关系,低32bit用来标识本身,其作为私有id。对于mediaset,该私有id是唯一的。对于mediaitem,该私有id在其父集中是唯一的,全局来说并非唯一的。

父集的概念,应该就是文件夹的概念。Mediaitem不能作为其他元素的父集,而mediaset可以作为mediaitem的父集。

Datamanager用于管理mediaset以及mediaitem组成的树状数据结构的类。

8)DataListener

幻灯片播放时图片数据改变等时的侦听者。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: