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