Android Gallery2流程分析

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
幻燈片播放時圖片數據改變等時的偵聽者。






發佈了37 篇原創文章 · 獲贊 22 · 訪問量 32萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章