需求说明:系统自带Camera需要修改默认图片预览比例以及录像比例。
android版本:8.1
Camera的启动activity是CameraActivity.java
在启动之后在oncreate里开启了许多初始化相关的代码。
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
。。。。
mCameraDeviceCtrl = new CameraDeviceCtrl(this, mPreferences);
if (mPermissionManager.requestCameraLaunchPermissions()) {
mCameraDeviceCtrl.openCamera();
}
。。。。
ModuleCtrlImpl moduleCtrl = new ModuleCtrlImpl(this);
CameraPerformanceTracker.onEvent(TAG,
CameraPerformanceTracker.NAME_CAMERA_INIT_VIEW_MANAGER,
CameraPerformanceTracker.ISBEGIN);
mCameraAppUi = new CameraAppUiImpl(this);
mCameraAppUi.createCommonView();
initializeCommonManagers();
mCameraAppUi.initializeCommonView();
。。。
setContentView(R.layout.camera);
。。。
parseIntent();
CameraPerformanceTracker.onEvent(TAG,
CameraPerformanceTracker.NAME_CAMERA_CREATE_MODULE,
CameraPerformanceTracker.ISBEGIN);
mModuleManager = new ModuleManager(this, fileSaver, mCameraAppUi,
featureConfig, deviceManager, moduleCtrl, mISelfTimeManager);
mISettingCtrl = mModuleManager.getSettingController();
mCameraAppUi.setSettingCtrl(mISettingCtrl);
在setcontentview之前,创建了CameraDeviceCtrl,
在CameraDeviceCtrl的构造方法里
public CameraDeviceCtrl(CameraActivity activity, ComboPreferences preferences) {
mCameraActivity = activity;
mPreferences = preferences;
mIsFirstStartUp = true;
mMainHandler = new MainHandler(mCameraActivity.getMainLooper());
mCameraStartUpThread = new CameraStartUpThread();
mCameraStartUpThread.start();
HandlerThread ht = new HandlerThread("Camera Handler Thread");
ht.start();
mCameraHandler = new CameraHandler(ht.getLooper());
}
创建了CameraStartUpThread线程,并且启动了它。
在CameraStartUpThread的run方法里,就开始了各个参数的配置
@Override
public void run() {
while (mIsActive) {
if (mIsFirstStartUp) {
CameraPerformanceTracker.onEvent(TAG,
CameraPerformanceTracker.NAME_CAMERA_START_UP,
CameraPerformanceTracker.ISBEGIN);
int openResult = firstOpenCamera();
if (CAMERA_OPEN_SUCEESS != openResult) {
setCameraState(CameraState.STATE_CAMERA_CLOSED);
mIsFirstStartUp = false;
continue;
}
ModeChecker.updateModeMatrix(mCameraActivity, mCameraId);
mCurCameraDevice.setDisplayOrientation(true);
mCurCameraDevice.setPreviewSize();
// if open camera too quickly, here may be run with
// setCameraActor at the same time, this will make CameraStartUpThread
// pause forever, so we synchronize it for workaround.
synchronized (mCameraActorSync) {
if (mCameraActor == null) {
try {
mCameraActorSync.wait();
} catch (InterruptedException e) {
Log.e(TAG, "mCameraActorSync.wait with InterruptedException");
}
}
}
mCameraActor.onCameraOpenDone();
mModuleManager.onCameraOpen();
initializeFocusManager();
if (mCameraActivity.getCurrentMode() == ModePicker.MODE_PHOTO) {
mCurCameraDevice.setPhotoModeParameters(
mCameraActivity.isNonePickIntent());
}
if (mCancel) {
Log.d(TAG, "[mIsFirstStartUp.run] cancel after openCamera");
mIsFirstStartUp = false;
continue;
}
if (mCameraActivity.isVideoCaptureIntent()) {
initializeSettingController();
mModuleManager.setModeSettingValue(
mCameraActor.getCameraModeType(mCameraActor.getMode()), "on");
}
applyFirstParameters();
//Block startUp thread when not set surface to native
mSycForLaunch.block(500);
mCurCameraDevice.setOneShotPreviewCallback(mOneShotPreviewCallback);
mMainHandler.sendEmptyMessage(MSG_CAMERA_PARAMETERS_READY);
mMainHandler.sendEmptyMessage(MSG_CAMERA_PREVIEW_DONE);
mMainHandler.sendEmptyMessage(MSG_CAMERA_OPEN_DONE);
Storage.mkFileDir(Storage.getFileDirectory());
clearDeviceCallbacks();
applyDeviceCallbacks();
mCameraAppUi.clearViewCallbacks();
mCameraAppUi.applayViewCallbacks();
if (mCancel) {
Log.d(TAG, "[mIsFirstStartUp.run]" +
" cancel before initializeSettingController");
mIsFirstStartUp = false;
continue;
}
if (!mCameraActivity.isVideoCaptureIntent()) {
initializeSettingController();
mModuleManager.setModeSettingValue(
mCameraActor.getCameraModeType(mCameraActor.getMode()), "on");
}
if (mCancel) {
Log.d(TAG, "[mIsFirstStartUp.run] cancel before applySecondParameters");
mIsFirstStartUp = false;
continue;
}
applySecondParameters();
setCameraState(CameraState.STATE_CAMERA_OPENED);
mIsFirstStartUp = false;
CameraPerformanceTracker.onEvent(TAG,
CameraPerformanceTracker.NAME_CAMERA_START_UP,
CameraPerformanceTracker.ISEND);
continue;
}
。。。。。
}
这里做了个判断mIsFirstStartUp,接着走第一次启动的时候的初始化,
这里有两个关键点:
mCurCameraDevice.setPreviewSize() 是设置预览尺寸的地方,
initializeSettingController是进一步初始化其他设置属性
第一步:先看看setPreviewsize做了什么
可以发现,mCurCameraDevice是接口对象,而setPreviewSize是接口中的方法,找到是谁实现接口,发现
public interface ICameraDeviceExt
public class CameraDeviceExt implements ICameraDeviceExt
于是去看CameraDeviceExt的setPreviewSize方法里做了什么
@Override
public void setPreviewSize() {
String pictureRatio = mPreferences.getString(SettingConstants.KEY_PICTURE_RATIO,
null);
if (pictureRatio == null) {
List<String> supportedRatios = SettingUtils.buildPreviewRatios(mActivity,
mParametersExt);
if (supportedRatios != null && supportedRatios.size() > 0) {
SharedPreferences.Editor editor = mPreferences.edit();
String ratioString = supportedRatios.get(0);
editor.putString(SettingConstants.KEY_PICTURE_RATIO, ratioString);
editor.apply();
pictureRatio = ratioString;
}
}
SettingUtils.setPreviewSize(mActivity, mParametersExt, pictureRatio);
String pictureSize = mPreferences.getString(SettingConstants.KEY_PICTURE_SIZE,
null);
int limitedResolution = SettingUtils.getLimitResolution();
if (limitedResolution > 0) {
int index = pictureSize.indexOf('x');
int width = Integer.parseInt(pictureSize.substring(0, index));
int height = Integer.parseInt(pictureSize.substring(index + 1));
if (width * height > limitedResolution) {
pictureSize = null;
}
}
if (pictureSize == null) {
List<String> supportedSizes = SettingUtils
.buildSupportedPictureSizeByRatio(mParametersExt, pictureRatio);
SettingUtils.sortSizesInAscending(supportedSizes);
if (limitedResolution > 0) {
SettingUtils.filterLimitResolution(supportedSizes);
}
if (supportedSizes != null && supportedSizes.size() > 0) {
pictureSize = supportedSizes.get(supportedSizes.size() - 1);
SharedPreferences.Editor editor = mPreferences.edit();
editor.putString(SettingConstants.KEY_PICTURE_SIZE, pictureSize);
editor.apply();
}
}
Point ps = SettingUtils.getSize(pictureSize);
mParametersExt.setPictureSize(ps.x, ps.y);
}
可以发现,这里先是根据KEY_PICTURE_RATIO这个key从preference里获取ratio比率值,在还没有初始化完成前,这个ratio其实是空的,于是会去构造属性,SettingUtils.buildPreviewRatios会根据系统属性来获取系统支持的预览比例,得到一个数组,从数组里取出制定元素,存入SharedPreference里,然后再根据这个ratio也就是比如 4:3 = 1.333 16:9=17.777
从这些double值来匹配得到指定比例属性,然后再把这个picturesize设置给parameters去设置显示大小。
经过调试,发现supportedsizs这个列表的构造过程是这样的
public static List<String> buildPreviewRatios(Context context, Parameters parameters) {
List<String> supportedRatios = new ArrayList<String>();
String findString = null;
if (context != null && parameters != null) {
// Add standard preview ratio.
supportedRatios.add(getRatioString(4d / 3));
mCurrentFullScreenRatio = findFullscreenRatio(context);
List<String> fullScreenPictureSizes = buildSupportedPictureSizeByRatio(parameters,
getRatioString(mCurrentFullScreenRatio));
// Add full screen ratio if platform has full screen ratio picture sizes.
if (fullScreenPictureSizes.size() > 0) {
findString = getRatioString(mCurrentFullScreenRatio);
if (!supportedRatios.contains(findString)) {
supportedRatios.add(findString);
}
}
}
return supportedRatios;
}
第一个元素默认是标准比例 4:3, 接着就是系统的全屏比例,但是不论如何,列表的第一个是4:3=1.333 而其他的可能是16:9或者5:2等等,会根据搜索结果找到并添加到列表里。
然后客户的需要就是要设置成默认4:3,于是前面put放入preference的值,只能是get(0)第一个元素,经过验证,打开相机就是默认了4:3的预览模式,不是全屏。
搞定了照片预览模式,接着找video的预览比例设置。
第二步:initializeSettingController
private void initializeSettingController() {
if (!mISettingCtrl.isSettingsInitialized()) {
mISettingCtrl.initializeSettings(R.xml.camera_preferences, mPreferences.getGlobal(),
mPreferences.getLocal());
}
mISettingCtrl.updateSetting(mPreferences.getLocal());
// Workaround for hdr value is on in pip video mode when pause and resume camera
// again in pip video mode.
if (mCameraActor.getMode() == ModePicker.MODE_VIDEO_PIP
&& mISettingCtrl.getSetting(SettingConstants.KEY_HDR) != null) {
mISettingCtrl.onSettingChanged(SettingConstants.KEY_HDR, "off");
}
mMainHandler.sendEmptyMessage(MSG_CAMERA_PREFERENCE_READY);
}
mISettingCtrl的initializeSettings
@Override
public void initializeSettings(int preferenceRes, SharedPreferences globalPref,
SharedPreferences localPref) {
// Log.d(TAG, "[initializeSettings]...");
mGlobalPref = globalPref;
mLocalPrefs.put(mICameraDeviceManager.getCurrentCameraId(), localPref);
mPrefTransfer = new SharedPreferencesTransfer(globalPref, localPref);
mSettingGenerator = new SettingGenerator(mICameraContext, mPrefTransfer);
mSettingGenerator.createSettings(preferenceRes);
createRules();
mIsInitializedSettings = true;
}
这里有settingGenerator,并且还看到createSettings
public void createSettings(int preferenceRes) {
mPreferenceRes = preferenceRes;
mInflater = new PreferenceInflater(mContext, mPrefTransfer);
int currentCameraId = mICameraDeviceManager.getCurrentCameraId();
PreferenceGroup group = (PreferenceGroup) mInflater.inflate(preferenceRes);
mPreferencesGroupMap.put(currentCameraId, group);
createSettingItems();
createPreferences(group, currentCameraId);
}
createSettingItems
createPreferences
两步,创建设置的item并且创建属性存入preference
private void createPreferences(PreferenceGroup group, int cameraId) {
// Log.d(TAG, "[createPreferences], cameraId:" + cameraId + ", group:" + group);
ArrayList<ListPreference> preferences = mPreferencesMap.get(cameraId);
mSupportedImageProperties = new ArrayList<String>();
mSupportedFaceBeautyProperties = new ArrayList<String>();
if (preferences == null) {
preferences = new ArrayList<ListPreference>();
ArrayList<SettingItem> settingItems = mSettingItemsMap.get(cameraId);
for (int settingId = 0; settingId < SettingConstants.SETTING_COUNT; settingId++) {
String key = SettingConstants.getSettingKey(settingId);
ListPreference preference = group.findPreference(key);
preferences.add(preference);
SettingItem settingItem = settingItems.get(settingId);
settingItem.setListPreference(preference);
}
mPreferencesMap.put(cameraId, preferences);
}
// every camera maintain one setting item list.
filterPreferences(preferences, cameraId);
}
preference的group是在settingConstants类里定义好的,这里集合初步建完了
最后过滤一下, 因为列表里是有很多preference的item,但是他们是xml里和settingConstants预先就定义好了的数组和结构体,他们中的一些属性是默认就有,有些是没有的,有些是需要从底层获取硬件信息从parameter里得到,有的是需要从preference里得到,所以构建完了,就得过滤一下,得到真实的有效的默认值。filterPreferences
private void filterPreferences(ArrayList<ListPreference> preferences, int cameraId) {
ArrayList<SettingItem> settingItems = mSettingItemsMap.get(cameraId);
limitPreferencesByIntent();
for (int i = 0; i < preferences.size(); i++) {
// filter list preference.
ListPreference preference = preferences.get(i);
boolean isRemove = filterPreference(preference);
if (isRemove) {
preference = null;
preferences.set(i, null);
}
// update setting's value and default value.
SettingItem settingItem = settingItems.get(i);
updateSettingItem(settingItem, preference);
}
overrideSettingByIntent();
}
过滤过程,就是把每一个ListPreference给检查一遍。然后更新数据。
从源码里看,filterPreference是一个很长的switch条件选择方法,里面匹配了各个item的区别对待,
只看我想看到的
case SettingConstants.ROW_SETTING_VIDEO_QUALITY:// video
removePreference = filterUnsupportedOptions(preference,
getMTKSupportedVideoQuality(),
settingId);
break;
VIDEO_QUALITY 视频质量,就是那个拍摄的质量选项
这里看到 filterUnsupportedOptions 和getMTKSupportedVideoQuality
private boolean filterUnsupportedOptions(ListPreference pref, List<String> supported,
boolean resetFirst, int row) {
if (supported != null) {
pref.filterUnsupported(supported);
}
if (pref.getEntryValues().length == 1) {
SettingItem settingItem = getSettingItem(row);
CharSequence[] values = pref.getEntryValues();
settingItem.setDefaultValue(values[0].toString());
settingItem.setValue(values[0].toString());
}
// Remove the preference if the parameter is not supported or there is
// only one options for the settings.
if (supported == null || supported.size() <= 1) {
return true;
}
if (pref.getEntries().length <= 1) {
return true;
}
resetIfInvalid(pref, resetFirst);
return false;
}
就是从系统获取可用的值,来检测当前给出的值是否合理,如果不合理就默认用数组里的第一个。
private void resetIfInvalid(ListPreference pref, boolean first) {
// Set the value to the first entry if it is invalid.
String value = pref.getValue();
if (pref.findIndexOfValue(value) == NOT_FOUND) {
if (first) {
pref.setValueIndex(0);
} else if (pref.getEntryValues() != null && pref.getEntryValues().length > 0) {
pref.setValueIndex(pref.getEntryValues().length - 1);
}
}
}
很明显,如果系统初始化的时候,没有指定默认可用item就是用第一个可用的item。
在updatesettingitem
private void updateSettingItem(SettingItem settingItem, ListPreference preference) {
int settingId = settingItem.getSettingId();
int type = SettingConstants.getSettingType(settingId);
String defaultValue = settingItem.getDefaultValue();
switch (type) {
case SettingConstants.NEITHER_IN_PARAMETER_NOR_IN_PREFERENCE:
case SettingConstants.ONLY_IN_PARAMETER:
// set setting default value and value, the value is initialized to
// default value.
defaultValue = SettingDataBase.getDefaultValue(settingId);
if (!mIModuleCtrl.isNonePickIntent()) {
if (SettingConstants.ROW_SETTING_CAMERA_MODE == settingId) {
defaultValue = Integer.toString(Parameters.CAMERA_MODE_NORMAL);
}
}
settingItem.setDefaultValue(defaultValue);
settingItem.setValue(defaultValue);
break;
case SettingConstants.BOTH_IN_PARAMETER_AND_PREFERENCE:
case SettingConstants.ONLY_IN_PEFERENCE:
Parameters parameters = mICameraDevice.getParameters();
// if setting has preferences, its default value and value get from
// preference.
if (preference != null) {
preference.reloadValue();
if (defaultValue == null) {
defaultValue = generateDefaultValue(settingItem.getKey(),
mICameraDevice.getParameters(), preference);
}
settingItem.setDefaultValue(defaultValue);
settingItem.setValue(preference.getValue());
} else {
if(settingItem.getKey().equals(SettingConstants.KEY_PICTURE_RATIO)){
settingItem.setEnable(true);
}else{
settingItem.setEnable(false);
}
}
break;
default:
break;
}
}
settingItem设置默认的值是从preference的默认值来的,而preference的默认值又是可用item的第一个值,于是,如果我们想要给指定的属性值设置初始默认值就应该在把默认的preference的value改成我们自己的值。
这里有个ONLY_IN_PREFERNCE来自于SettingConstant的定义
static {
// setting only in preference
SETTING_TYPE[ROW_SETTING_DUAL_CAMERA] = ONLY_IN_PEFERENCE;
SETTING_TYPE[ROW_SETTING_IMAGE_PROPERTIES] = ONLY_IN_PEFERENCE;
SETTING_TYPE[ROW_SETTING_RECORD_LOCATION] = ONLY_IN_PEFERENCE;
SETTING_TYPE[ROW_SETTING_MICROPHONE] = ONLY_IN_PEFERENCE;
SETTING_TYPE[ROW_SETTING_AUDIO_MODE] = ONLY_IN_PEFERENCE;
SETTING_TYPE[ROW_SETTING_VIDEO_QUALITY] = ONLY_IN_PEFERENCE;
SETTING_TYPE[ROW_SETTING_PICTURE_RATIO] = ONLY_IN_PEFERENCE;
SETTING_TYPE[ROW_SETTING_VOICE] = ONLY_IN_PEFERENCE;
SETTING_TYPE[ROW_SETTING_STEREO_MODE] = ONLY_IN_PEFERENCE;
SETTING_TYPE[ROW_SETTING_FACEBEAUTY_PROPERTIES] = ONLY_IN_PEFERENCE;
SETTING_TYPE[ROW_SETTING_CAMERA_FACE_DETECT] = ONLY_IN_PEFERENCE;
SETTING_TYPE[ROW_SETTING_ASD] = ONLY_IN_PEFERENCE;
SETTING_TYPE[ROW_SETTING_DUAL_CAMERA_MODE] = ONLY_IN_PEFERENCE;
SETTING_TYPE[ROW_SETTING_DNG] = ONLY_IN_PEFERENCE;
。。。。
ROW_SETTING_VIDEO_QUALITY就是录像视频质量属性
于是应该在settingItem.setValue(preference.getValue());这一行之前做个拦截,就能解决video的默认视频质量值了。
Camera在启动的时候会有很多初始化,不像其他app直接从xml或者数组里直接取值,因为有些属性会根据实际的硬件camera来加载,但是大部分的属性值都在初始化的时候给设置了默认第一个或者最后一个,极少有自定义的。
这里在调试解决问题的时候,代码量太多,很容易走错,加上惯性思维,导致在其他地方设置item的属性时没有生效,解决问题还是得从源头找起,这是需要反思的。