CameraAPP的设置项管理是CameraAPP中客户需求比较偏重的一部分,一般客户要添加新功能,都需要提供一个设置项开关出来。今天我们来整理下设置项的逻辑,先来看下设置项的截图:
我们知道,不同Module下设置项的个数和种类也是不同的,现在就开始我们的代码追踪之旅吧。
DataModuleBasic系列是管理设置项的基类,在它内部定义了几个常用的内部类。
DataSPAndPath
关键成员变量:String mPath
int position
SharedPreferences mSharePreferences
根据名称就可以猜到,此类是与SharePreference,保存数据相关的。
DataStorageStruct
关键成员变量:String mKey;
int mStorePosition;
String mDefaultValue;
CharSequence[] mEntryValues;
CharSequence[] mEntries;
String mRestorageValue;
数据存储结构类,此类是与设置项的xml配置是一致的,用来装从xml中读取的设置项数据。
还有一个不在DataModuleBasic中定义的类 :
DataStructSetting
关键成员变量:String mCategory;//photo/video/camera
boolean mIsFront; // front/back camera
int mMode;
int mCameraID;
此类是用来描述一个Module的信息
回到DataModuleBasic中,其关键方法initializeData,此方法通过DreamSettingUtil来从xml中获取配置的设置项数据
1,DreamSettingUtil .getSupportDataResourceID
public void initializeData(DataStructSetting dataSetting) {
mDataSetting = dataSetting;
// generate support configuration data resourceID
int supportdataResourceID = DreamSettingUtil
.getSupportDataResourceID(dataSetting);
// generate support data map
if (supportdataResourceID != -1) {
Log.d(TAG, "initializeData -- generateSupportDataList supportdataResourceID:" + supportdataResourceID);
generateSupportDataList(supportdataResourceID);
}
// generate mutex data resourceID
int mutexDataResourceID = DreamSettingUtil
.getMutexDataResourceID(dataSetting);
// generate mutex data map
if (mutexDataResourceID != -1) {
generateMutexDataList(mutexDataResourceID);
}
// initialize show item resourceID
int showItemSetID = DreamSettingUtil
.getPreferenceUIConfigureID(dataSetting);
// generate show item data
if (showItemSetID != -1) {
generateShowItemList(showItemSetID);
}
// setEntryAndEntryValues for list
fillEntriesAndSummaries();
}
流程转到 DreamSettingUtil 中:我们以后摄photo模式为例追踪代码,其他模式是类似的
getSupportDataBackPhoto中就会根据传过来的不同Module来读取对应的xml配置的array数组数据,这也是我们会看到在不同Module下,设置项不一样的原因。
private static int getSupportDataBackPhoto(int mode , int id) {
int resourceID = id;
switch (mode) {
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_AUTO:
resourceID = R.array.photo_back_auto_setting;
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_MANUAL:
resourceID = R.array.photo_back_manual_setting;
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_CONTINUE_PICTURE:
resourceID = R.array.photo_back_continue_pic_setting;
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_PANORAMA:
resourceID = R.array.photo_back_panorama_setting;
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_SCENE:
resourceID = R.array.photo_back_scene_setting;
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_UCAM_FILTER:
resourceID = getResourceIdBackUcamFilter(resourceID);
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_INTENT_CAPTURE:
resourceID = R.array.photo_back_mode_intent_capture_setting;
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_QRCODE:
resourceID = R.array.photo_back_mode_qrcode_setting;
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_SOUND_PICTURE:
resourceID = R.array.photo_back_mode_sound_setting;
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_PIP_VIV:
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_REFOCUS:
resourceID = getResourceIdBackRefocus(resourceID);
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_3DNR:
resourceID = R.array.photo_back_3dnr_setting;
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_3DNR_PRO:
resourceID = R.array.photo_back_3dnr_pro_setting;
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_ULTRA_WIDE_ANGLE:
resourceID = R.array.photo_back_ultra_wide_angle_setting;
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_PORTRAIT:
resourceID = R.array.photo_back_portrait_setting;
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_HIGH_RESOLUTION:
resourceID = R.array.photo_back_high_resolution_photo_setting;
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_IR:
resourceID = R.array.photo_back_ir_photo_setting;
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_FOCUS_LENGTH_FUSION:
resourceID = R.array.photo_back_focus_length_fusion_setting;
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_MACRO:
resourceID = R.array.photo_back_macro_photo_setting;
break;
case DataConfig.PhotoModeType.PHOTO_MODE_BACK_PORTRAIT_BACKGROUND_REPLACEMENT:
resourceID = R.array.photo_back_mode_protrait_background_replacement_photo_setting;
break;
default:
break;
}
return resourceID;
}
以后摄photo模式为例,来看下 R.array.photo_back_auto_setting内容
<integer-array name="photo_back_auto_setting">
<item>@array/pref_camera_picturesize_back_key_array</item>
<item>@array/pref_camera_jpeg_quality_key_array</item>
<item>@array/pref_camera_composition_line_key_array</item>
<item>@array/pref_camera_face_detect_key_array</item>
<item>@array/pref_camera_antibanding_key_array</item>
<item>@array/pref_auto3dnr_param_key_array</item>
<item>@array/pref_camera_ai_scene_detect_key_array</item>
<item>@array/pref_ai_detect_smile_key_array</item>
<item>@array/pref_ai_detect_face_attributes_key_array</item>
<item>@array/pref_camera_hdr_normal_pic_key_array</item>
<item>@array/pref_auto_add_logowatermark_key_array</item>
<item>@array/pref_auto_add_timewatermark_key_array</item>
<item>@array/pref_add_level_key_array</item>
<item>@array/pref_camera_touching_photograph_key_array</item>
<item>@array/pref_camera_time_stamp_key_array</item>
<item>@array/pref_camera_zsl_key_array</item>
<item>@array/pref_auto_tracking_key_array</item>
<item>@array/pref_camera_back_beauty_entered_key_array</item>
<item>@array/pref_make_up_display_key_array</item>
<item>@array/pref_camera_ai_beauty_entered_key_back_array</item>
<item>@array/pref_camera_flashmode_key_array</item>
<item>@array/pref_camera_countdown_duration_key_array</item>
<item>@array/pref_camera_refocus_key_array</item>
<item>@array/pref_camera_hdr_key_array</item>
<item>@array/pref_eois_dc_back_key_array</item>
<item>@array/pref_camera_metering_key_array</item>
<item>@array/pref_camera_zoom_enable_true_key_array</item>
<item>@array/pref_camera_ae_lock_key_array</item>
<item>@array/pref_camera_ultra_wide_angle_key_array</item>
<item>@array/pref_camera_montionphoto_key_array</item>
</integer-array>
我们看到photo_back_auto_setting数组的内部每一项又都是数组,以pref_auto_add_logowatermark_key_array为例看下
<integer-array name="pref_auto_add_logowatermark_key_array">
<item>@string/pref_auto_add_logowatermark_key</item>
<item>@integer/storage_position_category_bf</item>
<item>@string/preference_switch_item_default_value_false</item>
<item>@array/preference_camera_switch_entryvalues</item>
<item>@array/preference_camera_switch_entryvalues</item>
</integer-array>
logoWaterMark的设置项包含:key、position、defaultValue、entry、entryValues值。
还记得上面在DataModuleBasic中的内部类DataStorageStruct吗,没错,每个设置项的配置与DataStorageStruct的结构是一致的,这在后面会有用处的。
现在我们回到DreamSettingUtil.getSupportDataResourceID,它是返回对应Module下的设置项数组,对于后摄Photo模式就是返回R.array.photo_back_auto_setting,然后再返回到DataModuleBasic的initializeData方法中。
2,generateSupportDataList(supportdataResourceID);
private void generateSupportDataList(int resourceID) {
synchronized (mLock) {
Log.e(TAG, "======== support data list start ========== resourceID:" + resourceID);
TypedArray types = mContext.getResources().obtainTypedArray(resourceID);
mSupportDataMap.clear();
if (types != null) {
for (int i = 0; i < types.length(); i++) {
TypedArray type = mContext.getResources().obtainTypedArray(
types.getResourceId(i, -1));
if (type != null) {
DataStorageStruct data = new DataStorageStruct(type);
mSupportDataMap.put(data.mKey, data);
data.mRestorageValue = getString(data.mKey);
Log.e(TAG, data.toString());
type.recycle();
}
}
}
types.recycle();
Log.e(TAG, "===== support data list end =====");
}
}
根据上一步返回的array数组,循环遍历,将array数据填充到DataStorageStruct类型的类中,然后构建一个以设置项key值为key,设置项数据填充的DataStorageStruct对象为value的map,得到mSupportDataMap数据。
到此得到了一个关键map,mSupportDataMap,在后续代码追踪中,我们会经常看到这个map的出现,现在我们要记住,这个map装的就是xml中配置的所有设置项的数据。
在回到 DataModuleBasic的initializeData方法中,在完成了generateSupportDataList后,后续其又generateMutexDataList、generateShowItemList。流程与generateSupportDataList是类似的,结果都是在java中通过一定的数据结构保存相应xml配置的数据。
其中的generateShowItemList需要注意下,我们来看下后摄Photo模式的xml配置。
<integer-array name="photo_back_mode_auto_setting_display">
<item>@string/pref_camera_picturesize_back_key</item>
<item>@string/pref_camera_jpeg_quality_key</item>
<item>@string/pref_camera_composition_line_key</item>
<item>@string/pref_ai_detect_smile_key</item>
<item>@string/pref_ai_detect_face_attributes_key</item>
<item>@string/pref_auto_add_logowatermark_key</item>
<item>@string/pref_add_level_key</item>
<item>@string/pref_auto_add_timewatermark_key</item>
<item>@string/pref_camera_antibanding_key</item>
<item>@string/pref_auto3dnr_param_key</item>
<item>@string/pref_camera_ai_scene_detect_key</item>
<item>@string/pref_camera_hdr_normal_pic_key</item>
<item>@string/pref_camera_touching_photograph_key</item>
<item>@string/pref_camera_time_stamp_key</item>
<item>@string/pref_camera_flashmode_key</item>
<item>@string/pref_camera_countdown_duration_key</item>
<item>@string/pref_camera_refocus_key</item>
<item>@string/pref_camera_hdr_key</item>
<item>@string/pref_eois_dc_back_key</item>
<item>@string/pre_ae_lock_key</item>
<item>@string/pref_camera_ultra_wide_angle_key</item>
<item>@string/pref_auto_tracking_key</item>
<item>@string/pref_key_montionphoto</item>
</integer-array>
这个数据的内部项不在是数组了,而是简单的String,并且如果你细心的看,这些String就是我们上面看到的 R.array.photo_back_auto_setting数组中每个设置项(也为array)的key值。并且这两个数组的名称也很有关联:
R.array.photo_back_auto_setting 和 R.array.photo_back_mode_auto_setting_display。后面随着我们继续的追踪,就会发现这两个数组之间确实是有关联的。
上面介绍的是DataModuleBasic将xml内容转换成map集合了,那么有了集合,就少不了对集合的数据操作,也就是用户更改这些设置项后,DataModuleBasic还要负责将用户更改的数据更新到集合并且保存下来,以便用户退出后,下次再进来,用户的设置仍然在。
数据的持久化保存就与前面介绍的内部类DataSPAndPath有关了,因为这里面有SharePreference。所以在DataModuleBasic中提供了一些set和get数据的方法:
public abstract boolean isSet(int position, String key);
public abstract void set(int position, String key, String value);
public abstract String getString(int position, String key,String defaultValue);
DataModuleBasic并没有实现这些方法,其子类DataModuleInterfacePV实现了。这里在补充一个注意点:Camera中通过SharePreference保存数据,有position的概念,不同的position对应不同的SharePreference文件,即设置项的数据是保存在不同的SP文件中的。DataConfig中定义了如下Position:
public static final int POSITION_ERROR = 0x10000000;
public static final int POSITION_CAMERA_PUBLIC = 0x00000010;
public static final int POSITION_CATEGORY = 0x00000001;
public static final int POSITION_CATEGORY_BF = 0x00000002;
public static final int POSITION_CATEGORY_BF_MODULE = 0x00000004;
public static final int POSITION_CATEGORY_BF_MODULE_ID = 0x00000008;
继续看下 DataModuleInterfacePV中set、get的实现:
private DataSPAndPath getStorageHandler(int position) {
switch (position) {
case SettingStoragePosition.POSITION_CATEGORY:
return mCategorySPB;
case SettingStoragePosition.POSITION_CATEGORY_BF:
return mCategoryFBSPB;
case SettingStoragePosition.POSITION_CATEGORY_BF_MODULE:
return mCategoryFBModuleSPB;
case SettingStoragePosition.POSITION_CATEGORY_BF_MODULE_ID:
return mCategoryFBModuleCIDSPB;
default:
return null;
}
}
@Override
public boolean isSet(int position, String key) {
DataSPAndPath spb = getStorageHandler(position);
return ((spb != null) ? spb.isSet(key) : false); // Bug 1159255 (NULL_RETURNS)
}
@Override
public void set(int position, String key, String value) {
DataSPAndPath spb = getStorageHandler(position);
if (spb != null) // Bug 1159255 (NULL_RETURNS)
spb.set(key, value);
}
@Override
public String getString(int position, String key, String defaultValue) {
DataSPAndPath spb = getStorageHandler(position);
return spb == null ? defaultValue : spb.getString(key, defaultValue);
}
DataModuleInterfacePV从名称上看,是一个接口Public的实现,应该还有类在继续继承的。
搜索发现,确实有 DataModulePhoto、DataModuleVideo extends DataModuleInterfacePV。
DataModulePhoto和DataModuleVideo从名称就可以看出来,一个是负责管理photo的,一个是负责管理video的。
以Photo的为例,简单看下其内容
@Override
protected void setMutex(String key, Object newValue, Set<String> keyList) {
String entryValue = getString(key);
switch (key) {
case Keys.KEY_EXPOSURE_SHUTTERTIME:
setMutexShutterTime(key, entryValue, keyList);
break;
case Keys.KEY_AUTO_3DNR_PARAMETER:
setMutexAuto3Dnr(key, entryValue, keyList);
break;
case Keys.KEY_CAMERA_FACE_DATECT:
setMutexAIDetect(key, entryValue, keyList);
break;
case Keys.KEY_CAMERA_COLOR_EFFECT:
setMutexColorEffect(key, entryValue, keyList);
break;
case Keys.KEY_FLASH_MODE:
setMutexFlash(key, entryValue, keyList);
break;
case Keys.KEY_CAMERA_HDR:
setMutexHDR(key, entryValue, keyList);
break;
//case Keys.KEY_CAMERA_HDR_NORMAL_PIC:
//setMutexNormalHDR(key, entryValue, keyList);
//break;
case Keys.KEY_SCENE_MODE:
setMutexSceneMode(key, entryValue, keyList);
break;
case Keys.KEY_CAMERA_BEAUTY_ENTERED:
setMutexBeauty(key, entryValue, keyList);
break;
case Keys.KEY_CAMERA_AI_BEAUTY_ENTERED:
setMutexAiBeauty(key, entryValue, keyList);
break;
case Keys.KEY_CAMERA_ZSL:
setMutexZSL(key, entryValue, keyList);
break;
case Keys.KEY_CAMERA_FILTER_TYPE:
setMutexFilterType(key, entryValue, keyList);
break;
case Keys.KEY_CAMERA_PORTRAITBACKGROUNDREPLACEMENT_TYPE:
setMutexPortraitBackgroundPeplacementType(key, entryValue, keyList);
break;
case Keys.KEY_DREAM_FLASH_GIF_PHOTO_MODE:
setMutexGifPhotoFlash(key, entryValue, keyList);
break;
case Keys.KEY_CAMERA_AI_SCENE_DATECT:
setMutexAISceneDetect(key, entryValue, keyList);
break;
case Keys.KEY_CAMERA_TOUCHING_PHOTOGRAPH:
setMutexTouchingPhoto(key, entryValue, keyList);
break;
case Keys.KEY_AUTO_TRACKING:
setMutexAutoTracking(key, entryValue, keyList);
break;
case Keys.KEY_MAKE_UP_DISPLAY:
setMutexMakeUpDisplay(key, entryValue, keyList);
break;
case Keys.KEY_LIGHT_PORTIAIT_DISPLAY:
setMutexLightPortraitDisplay(key, entryValue, keyList);
break;
case Keys.KEY_AE_LOCK:
setMutexAELock(key, entryValue, keyList);
break;
case Keys.KEY_LIGHT_PORTIAIT:
if(CameraUtil.isPortraitAndRefocusMutex()){
setMutexLightPortrait(key, entryValue, keyList);
}
break;
case Keys.KEY_PORTRAIT_REFOCUS_KEY:
if(CameraUtil.isPortraitAndRefocusMutex()){
setMutexPortraitRefocus(key, entryValue, keyList);
}
break;
/* @} */
default:
break;
}
}
主要是在处理mutex,互斥的操作。
至此,DataModuleBasic的流程就介绍结束了。这块主要是管理设置项数据,那么设置项view的处理逻辑呢?
View直接相关的是 DreamUIPreferenceSettingLayout 和 DreamUIPreferenceSettingFragment,加载的xml文件是dream_camera_preferences,xml文件中分为三类:
preference_key_category_camera_root —》DreamUISettingPartCamera
preference_key_category_photo_root —》DreamUISettingPartPhoto
preference_key_category_video_root —》DreamUISettingPartVideo
这三个自定义view均是继承的DreamUISettingPartBasic,又是basic类,basic类的关键代码逻辑是:
public void changContent() {
//mDataModule.addListener(this);
updatePreItemAccordingConfig(this);
// update UI display according properties config
updatePreItemsAccordingProperties();
//update UI display according currentModule settings
updatePreItemsAccordingSettings(this);
// setEntryAndEntryValues for list
fillEntriesAndSummaries(this);
// set data for each preference
initializeData(this);
}
private void updatePreItemsAccordingSettings(PreferenceGroup group){//移除不在 mSupportDataMap 中的设置项
ArrayList<String> keyListui = getAllPreKeyList(group);
Set<String> keySetsettings = mDataModule.getSupportSettingKeys();
for(int i = 0; i < keyListui.size(); i++){
if(!keySetsettings.contains(keyListui.get(i))){
Preference pref = group.findPreference(keyListui.get(i));
group.removePreference(pref);
}
}
}
private void updatePreItemAccordingConfig(PreferenceGroup group) {//移除不在 mShowItemsSet 中的设置项
ArrayList<String> keyList = getAllPreKeyList(group);
keyList.removeAll(mDataModule.getShowItemsSet());
for (int i = 0; i < keyList.size(); i++) {
Log.e(TAG, "remove key = " + keyList.get(i));
Preference pref = group.findPreference(keyList.get(i));
if (pref != null) {
group.removePreference(pref);
}
}
}
注释写的很清楚,获取我们前面介绍的 mDataModuleBasic对象,根据其维护的map数据来确定设置项layout最终应该显示出来的设置项。
注意:这里用到了mShowItemsSet ,就是我们前面说的R.array.photo_back_auto_setting 和 R.array.photo_back_mode_auto_setting_display 后面这个数组,这样看来一个设置项要显示出来,不仅要在R.array.photo_back_auto_setting 中配置,还要在 R.array.photo_back_mode_auto_setting_display里面也配置。
DreamUISettingPartBasic系列,我们以DreamUISettingPartPhoto为例,来看下内容:
@Override
protected void updatePreItemsAccordingProperties() {
// update visibility of picturesize
updateVisibilityPictureSizes();
// update visibility of EIOS
updateVisibilityEOIS();
// update visibility of mirror
updateVisibilityMirror();
updateVisibilityTouchPhotograph();
updateVisibilityAIDetect();
updateVisibilitySensorSelfShot();
updateVisibilityNormalHdr();
updateVisibilityAntiFlicker();
updateVisibilityAiSceneDetect();
updateVisibilityHDR();
updateVisibilityAutoTracking();
updateVisibilityFaceAttributeDetect();
updateVisibilityFDR();
}
private void updateVisibilityAutoTracking() {
if (!CameraUtil.isAutoChasingSupport()) {
recursiveDelete(this, findPreference(Keys.KEY_AUTO_TRACKING));
}
}
private void updateVisibilityHDR() {
if(!CameraUtil.isIsMotionPhotoEnabled() ){
recursiveDelete(this, findPreference(Keys.KEY_CAMERA_HDR));
return;
}
if((mDataModule.getDataSetting().mMode == SettingsScopeNamespaces.REFOCUS
|| mDataModule.getDataSetting().mMode == SettingsScopeNamespaces.FRONT_BLUR)
&&!CameraUtil.isHdrBlurSupported()){
recursiveDelete(this, findPreference(Keys.KEY_CAMERA_HDR));
}
}
我们看到有比较多的 updateVisibility开头的方法,用来二次控制设置项是否能显示出来。这里比较多的条件是与硬件相关的。也就是说一个设置项要能显示出来,不仅需要在xml中配置,还需要硬件支持,java文件的二次判断相当于又多加了一次拦截,把zml配置的支持的设置项在过滤一遍,避免出现不支持的设置项被显示出来了。因此,如果我们发现一个设置项不显示,不仅要检查xml是否配置,还要考虑可能是java文件做了二次拦截。
好了,到这里我们已经将CamereAPP的设置项流程(数据和layout)全部追踪完成了。