Camera系統中設置picture大小菜單的流程分析

在Android平臺上,設置菜單有兩種方式:通過XML佈局來實現和通過Menu.add方法實現。Camera系統中的菜單可以說是通過XML佈局來實現的,但它的實現過程並非像一般文章裏介紹的那樣簡單。所以,在此我就以設置capture時picture大小爲例,將Camera系統菜單設置流程作以簡單介紹。

1、資源文件分析

在packages/apps/Camera/res/xml/有兩個xml文件,分別爲Camera_preferences.xml和Video_preferences.xml。這兩個文件中列舉了Camera和Video Camera兩種模式下的不同菜單信息。以Camera_preferences.xml爲例,其中關於picture大小菜單項信息爲:

<ListPreference camera:key="pref_camera_picturesize_key"

camera:title="@string/pref_camera_picturesize_title"

camera:entries="@array/pref_camera_picturesize_entries"

camera:entryValues="@array/pref_camera_picturesize_entryvalues" />

 

pref_camera_picturesize_title的定義在packages/apps/Camera/res/values/strings.xml中,其中有如下的定義:

- <!--

 Settings screen, Picture size title

  -->

  <item>@string/pref_camera_picturesize_entry_2592x1936</item>

  <item>@string/pref_camera_picturesize_entry_2592x1936</item>

  <item>@string/pref_camera_picturesize_entry_2048x1536</item>

  <item>@string/pref_camera_picturesize_entry_1600x1200</item>

  <item>@string/pref_camera_picturesize_entry_1024x768</item>

  <item>@string/pref_camera_picturesize_entry_320x240</item>

  </string-array>

pref_camera_picturesize_entryvalues的定義也在

packages/apps/Camera/res/values/arrays.xml中,其中有如下的定義:

-<string-array name="pref_camera_picturesize_entryvalues" translatable="false">

  <item>2592x1944</item>

  <item>2592x1936</item>

  <item>2048x1536</item>

  <item>1600x1200</item>

  <item>1024x768</item>

  <item>320x240</item>

  </string-array>

由這些定義,我們可以看出,Android的Camera應用程序支持五種圖片大小。但是硬件並非都支持這些大小。所以最終的菜單中只會顯示這五種大小當中底層硬件所支持的,如果硬件支持的大小與其中任何一種都不匹配,則不會顯示出“Picture size菜單

 

2、菜單的創建

在文件Packages/apps/camera/src/com/android/camera/Camera.java中,函數onCreateOptionsMenu()用來創建Camera系統菜單。其具體定義如下:

    @Override

    public boolean onCreateOptionsMenu(Menu menu) {

        super.onCreateOptionsMenu(menu);

 

        if (mIsImageCaptureIntent) {

            // No options menu for attach mode.

            return false;

        } else {

            addBaseMenuItems(menu);

        }

        return true;

}

在非Video Camera模式下,mIsImageCaptureIntent爲true,此處將不做任何處理。當mIsImageCaptureIntent爲false時,即Video Camera模式下,將調用函數addBaseMenuItems()來創建Video Camera的菜單。Picture大小菜單不會出現在Video Camera模式下,所以就不對addBaseMenuItems()函數分析了。

既然這裏沒有創建Camera模式下的菜單,那Camera模式下的菜單在哪裏創建的了?我們接着分析

在Camera.java文件中定義了類MainHandler,它裏面只有一個成員函數handleMessage(),它用來處理Camera應用程序中的message。

類MainHandler的定義如下:

    /**

     * This Handler is used to post message back onto the main thread of the

     * application

     */

    private class MainHandler extends Handler {

        @Override

        public void handleMessage(Message msg) {

            switch (msg.what) {

                case RESTART_PREVIEW: {

                    restartPreview();

                    if (mJpegPictureCallbackTime != 0) {

                        long now = System.currentTimeMillis();

                        mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;

                        Log.v(TAG, "mJpegCallbackFinishTime = "

                                + mJpegCallbackFinishTime + "ms");

                        mJpegPictureCallbackTime = 0;

                    }

                    break;

                }

 

                case CLEAR_SCREEN_DELAY: {

                    getWindow().clearFlags(

                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

                    break;

                }

 

                case FIRST_TIME_INIT: {

                    initializeFirstTime();

                    break;

                }

 

                case SET_CAMERA_PARAMETERS_WHEN_IDLE: {

                    setCameraParametersWhenIdle(0);

                    break;

                }

            }

        }

 

Message FIRST_TIME_INIT應該在類Camera初始化時就會被處理,我是這樣猜的,只是還沒有找到具體的code。initializeFirstTime()的實現如下所示:

    // Snapshots can only be taken after this is called. It should be called

    // once only. We could have done these things in onCreate() but we want to

    // make preview screen appear as soon as possible.

    private void initializeFirstTime() {

    ……

        mFirstTimeInitialized = true;

        changeHeadUpDisplayState();

        addIdleHandler();

    }

在函數的結束部位有一個函數changeHeadUpDisplayState(),它的定義如下:

    private void changeHeadUpDisplayState() {

        // If the camera resumes behind the lock screen, the orientation

        // will be portrait. That causes OOM when we try to allocation GPU

        // memory for the GLSurfaceView again when the orientation changes. So,

        // we delayed initialization of HeadUpDisplay until the orientation

        // becomes landscape.

        Configuration config = getResources().getConfiguration();

        if (config.orientation == Configuration.ORIENTATION_LANDSCAPE

                && !mPausing && mFirstTimeInitialized) {

            if (mGLRootView == null) initializeHeadUpDisplay();

        } else if (mGLRootView != null) {

            finalizeHeadUpDisplay();

        }

    }

此時mFirstTimeInitialized已經爲true,當其他條件也滿足時將進入函數initializeHeadUpDisplay(),它的具體定義爲:

 

    private void initializeHeadUpDisplay() {

        FrameLayout frame = (FrameLayout) findViewById(R.id.frame);

        mGLRootView = new GLRootView(this);

        frame.addView(mGLRootView);

 

        mHeadUpDisplay = new CameraHeadUpDisplay(this);

        CameraSettings settings = new CameraSettings(this, mInitialParams);

        mHeadUpDisplay.initialize(this,

                settings.getPreferenceGroup(R.xml.camera_preferences));

        mHeadUpDisplay.setListener(new MyHeadUpDisplayListener());

        mHeadUpDisplay.setOrientation(mLastOrientation);

 

        if (mParameters.isZoomSupported()) {

            ……

        }

 

        updateSceneModeInHud();

 

        mGLRootView.setContentPane(mHeadUpDisplay);

    }

 

語句mHeadUpDisplay.initialize(this,                settings.getPreferenceGroup(R.xml.camera_preferences));中的R.xml.camera_preferences指的就是camera_preference.xml資源文件。

接下來我們將對settings.getPreferenceGroup(),mHeadUpDisplay.initialize()和MyHeadUpDisplayListener做以介紹。

函數settings.getPreferenceGroup()定義在文件Packages/apps/camera/src/com/android/camera/CameraSetting.java中。其具體定義爲:

    public PreferenceGroup getPreferenceGroup(int preferenceRes) {

        PreferenceInflater inflater = new PreferenceInflater(mContext);

        PreferenceGroup group =

                (PreferenceGroup) inflater.inflate(preferenceRes);

        initPreference(group);

        return group;

    }

函數inflater.inflate(preferenceRes)將camera_preference.xml文件中的菜單信息存儲到PreferenceGroup中。接着函數initPreference()對這些信息做了處理,其定義爲:

    private void initPreference(PreferenceGroup group) {

        ListPreference videoQuality = group.findPreference(KEY_VIDEO_QUALITY);

        ListPreference pictureSize = group.findPreference(KEY_PICTURE_SIZE);

        ListPreference whiteBalance =  group.findPreference(KEY_WHITE_BALANCE);

……

        // Since the screen could be loaded from different resources, we need

        // to check if the preference is available here

        if (videoQuality != null) {

            // Modify video duration settings.

            // The first entry is for MMS video duration, and we need to fill

            // in the device-dependent value (in seconds).

            CharSequence[] entries = videoQuality.getEntries();

            CharSequence[] values = videoQuality.getEntryValues();

            for (int i = 0; i < entries.length; ++i) {

                if (VIDEO_QUALITY_MMS.equals(values[i])) {

                    entries[i] = entries[i].toString().replace(

                            "30", Integer.toString(MMS_VIDEO_DURATION));

                    break;

                }

            }

        }

 

        // Filter out unsupported settings / options

        if (pictureSize != null) {

            filterUnsupportedOptions(group, pictureSize, sizeListToStringList(

                    mParameters.getSupportedPictureSizes()));

        }

        if (whiteBalance != null) {

            filterUnsupportedOptions(group,

                    whiteBalance, mParameters.getSupportedWhiteBalance());

        }

        ……

    }

 

其中,宏定義KEY_PICTURE_SIZE定義在文件

Packages/apps/camera/src/com/android/camera/CameraSetting.java中,具體定義爲:

public static final String KEY_PICTURE_SIZE = "pref_camera_picturesize_key";

該值與camera_preference.xml中的KEY值匹配。語句ListPreference pictureSize = group.findPreference(KEY_PICTURE_SIZE);將picture的大小信息存儲在了結構體ListPreference pictureSize中。

語句

        if (pictureSize != null) {

            filterUnsupportedOptions(group, pictureSize, sizeListToStringList(

                    mParameters.getSupportedPictureSizes()));

        }

mParameters.getSupportedPictureSizes()獲取了硬件Camera所支持的picture大小信息,我在Camera HAL層的實現中,在函數initDefaultParameters()中設置了硬件所支持的picture大小。CODE如下:

CameraParameters p;

p.set(“picture-size-values”, “1600x1200,1024x768,640x480”);

函數filterUnsupportedOptions()會將Camera應用和硬件支持的picture大小中兩者匹配的大小存儲在group中。

函數mHeadUpDisplay.initialize()定義在文件

Packages/apps/camera/src/com/android/camera/ui/HeadUpDisplay.java中,其具體定義爲:

    public void initialize(Context context, PreferenceGroup preferenceGroup) {

        mPreferenceGroup = preferenceGroup;

        mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);

        mSharedPrefs.registerOnSharedPreferenceChangeListener(

                mSharedPreferenceChangeListener);

        initializeIndicatorBar(context, preferenceGroup);

    }

其中函數initializeIndicatorBar()定義如下:

    protected void initializeIndicatorBar(

            Context context, PreferenceGroup group) {

        mIndicatorBar = new IndicatorBar();

 

        mIndicatorBar.setBackground(new NinePatchTexture(

                context, R.drawable.ic_viewfinder_iconbar));

        mIndicatorBar.setHighlight(new NinePatchTexture(

                context, R.drawable.ic_viewfinder_iconbar_highlight));

        addComponent(mIndicatorBar);

        mIndicatorBar.setOnItemSelectedListener(new IndicatorBarListener());

    }

函數addComponent()將group中的picture大小添加到了菜單中。至此,菜單的創建就告一段落。下面我們介紹一下菜單的事件。

 

3、菜單的監聽事件

菜單被點擊時,菜單的監聽事件就會監聽到該事件,並作出相應的處理。監聽事件定義在文件Packages/apps/camera/src/com/android/camera/Camera.java中。其具體定義爲:

    private class MyHeadUpDisplayListener implements HeadUpDisplay.Listener {

        // The callback functions here will be called from the GLThread. So,

        // we need to post these runnables to the main thread

        public void onSharedPreferencesChanged() {

            mHandler.post(new Runnable() {

                public void run() {

                    Camera.this.onSharedPreferenceChanged();

                }

            });

        }

 

        public void onRestorePreferencesClicked() {

            mHandler.post(new Runnable() {

                public void run() {

                    Camera.this.onRestorePreferencesClicked();

                }

            });

        }

 

        public void onPopupWindowVisibilityChanged(int visibility) {

        }

    }

函數onSharedPreferenceChanged()定義爲:

    private void onSharedPreferenceChanged() {

        // ignore the events after "onPause()"

        if (mPausing) return;

 

        boolean recordLocation;

 

        synchronized (mPreferences) {

            recordLocation = RecordLocationPreference.get(

                    mPreferences, getContentResolver());

            mQuickCapture = getQuickCaptureSettings();

        }

 

        if (mRecordLocation != recordLocation) {

            mRecordLocation = recordLocation;

            if (mRecordLocation) {

                startReceivingLocationUpdates();

            } else {

                stopReceivingLocationUpdates();

            }

        }

 

        setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);

    }

其中函數setCameraParametersWhenIdle()定義爲:

    // If the Camera is idle, update the parameters immediately, otherwise

    // accumulate them in mUpdateSet and update later.

    private void setCameraParametersWhenIdle(int additionalUpdateSet) {

        mUpdateSet |= additionalUpdateSet;

        if (mCameraDevice == null) {

            // We will update all the parameters when we open the device, so

            // we don't need to do anything now.

            mUpdateSet = 0;

            return;

        } else if (isCameraIdle()) {

            setCameraParameters(mUpdateSet);

            mUpdateSet = 0;

        } else {

            if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {

                mHandler.sendEmptyMessageDelayed(

                        SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);

            }

        }

    }

函數setCameraParameters()定義爲:

    // We separate the parameters into several subsets, so we can update only

    // the subsets actually need updating. The PREFERENCE set needs extra

    // locking because the preference can be changed from GLThread as well.

    private void setCameraParameters(int updateSet) {

        mParameters = mCameraDevice.getParameters();

 

        if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {

            updateCameraParametersInitialize();

        }

 

        if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {

            updateCameraParametersZoom();

        }

 

        if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {

            synchronized (mPreferences) {

                updateCameraParametersPreference();

            }

        }

 

        mCameraDevice.setParameters(mParameters);

    }

Picture大小設置屬於UPDATE_PARAM_PREFERENCE,所以會調到函數updateCameraParametersPreference(),其定義爲:

   private void updateCameraParametersPreference() {

        // Set picture size.

        String pictureSize = mPreferences.getString(

                CameraSettings.KEY_PICTURE_SIZE, null);

        if (pictureSize == null) {

            CameraSettings.initialCameraPictureSize(this, mParameters);

        } else {

            List<Size> supported = mParameters.getSupportedPictureSizes();

            CameraSettings.setCameraPictureSize(

                    pictureSize, supported, mParameters);

        }

 

        // Set the preview frame aspect ratio according to the picture size.

        Size size = mParameters.getPictureSize();

        PreviewFrameLayout frameLayout =

                (PreviewFrameLayout) findViewById(R.id.frame_layout);

        frameLayout.setAspectRatio((double) size.width / size.height);

 

        // Set a preview size that is closest to the viewfinder height and has

        // the right aspect ratio.

        List<Size> sizes = mParameters.getSupportedPreviewSizes();

        Size optimalSize = getOptimalPreviewSize(

                sizes, (double) size.width / size.height);

        if (optimalSize != null) {

            mParameters.setPreviewSize(optimalSize.width, optimalSize.height);

        }

……

       

    }

函數CameraSettings.initialCameraPictureSize()的定義如下:

    public static void initialCameraPictureSize(

            Context context, Parameters parameters) {

        // When launching the camera app first time, we will set the picture

        // size to the first one in the list defined in "arrays.xml" and is also

        // supported by the driver.

        List<Size> supported = parameters.getSupportedPictureSizes();

        if (supported == null) return;

        for (String candidate : context.getResources().getStringArray(

                R.array.pref_camera_picturesize_entryvalues)) {

            if (setCameraPictureSize(candidate, supported, parameters)) {

                SharedPreferences.Editor editor = PreferenceManager

                        .getDefaultSharedPreferences(context).edit();

                editor.putString(KEY_PICTURE_SIZE, candidate);

                editor.commit();

                return;

            }

        }

        Log.e(TAG, "No supported picture size found");

    }

函數setCameraPictureSize()定義如下:

    public static boolean setCameraPictureSize(

            String candidate, List<Size> supported, Parameters parameters) {

        int index = candidate.indexOf('x');

        if (index == NOT_FOUND) return false;

        int width = Integer.parseInt(candidate.substring(0, index));

        int height = Integer.parseInt(candidate.substring(index + 1));

        for (Size size: supported) {

            if (size.width == width && size.height == height) {

                parameters.setPictureSize(width, height);

                return true;

            }

        }

        return false;

    }

 

監聽事件中的onRestorePreferencesClicked()定義如下:

    protected void onRestorePreferencesClicked() {

        if (mPausing) return;

        Runnable runnable = new Runnable() {

            public void run() {

                mHeadUpDisplay.restorePreferences(mParameters);

            }

        };

        MenuHelper.confirmAction(this,

                getString(R.string.confirm_restore_title),

                getString(R.string.confirm_restore_message),

                runnable);

    }

 

其中restorePreferences()函數定義如下:

    public void restorePreferences(final Parameters param) {

        getGLRootView().runInGLThread(new Runnable() {

            public void run() {

                OnSharedPreferenceChangeListener l =

                        mSharedPreferenceChangeListener;

                // Unregister the listener since "upgrade preference" will

                // change bunch of preferences. We can handle them with one

                // onSharedPreferencesChanged();

                mSharedPrefs.unregisterOnSharedPreferenceChangeListener(l);

                Context context = getGLRootView().getContext();

                synchronized (mSharedPrefs) {

                    Editor editor = mSharedPrefs.edit();

                    editor.clear();

                    editor.commit();

                }

                CameraSettings.upgradePreferences(mSharedPrefs);

                CameraSettings.initialCameraPictureSize(context, param);

                reloadPreferences();

                if (mListener != null) {

                    mListener.onSharedPreferencesChanged();

                }

                mSharedPrefs.registerOnSharedPreferenceChangeListener(l);

            }

        });

    }

函數reloadPreferences()定義如下:

    public void reloadPreferences() {

        mPreferenceGroup.reloadValue();

        mIndicatorBar.reloadPreferences();

    }

}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章