網(wǎng)站首頁 編程語言 正文
前言
接到一個開發(fā)需求,需要定制化開發(fā)一個安全音量功能;此前有了解過為了符合歐盟等有關(guān)國家和地區(qū)的規(guī)定,原生Android是有自帶一個安全音量功能的,想要定制則先要了解這個功能原先長什么樣子,下面我們就從一個系統(tǒng)工程師的角度出發(fā)去探尋一下,原生Android的安全音量功能是如何實(shí)現(xiàn)的。
安全音量配置
安全音量的相關(guān)配置都在framework的config.xml里面,可以直接修改或者overlay配置修改其默認(rèn)值。
<!-- Whether safe headphone volume is enabled or not (country specific). --> <bool name="config_safe_media_volume_enabled">true</bool>
<!-- Safe headphone volume index. When music stream volume is below this index the SPL on headphone output is compliant to EN 60950 requirements for portable music players. --> <integer name="config_safe_media_volume_index">10</integer>
config_safe_media_volume_enabled是安全音量功能的總開關(guān),config_safe_media_volume_index則是表明觸發(fā)安全音量彈框的音量大小值。
安全音量相關(guān)流程
安全音量的主要流程都在AudioService里面,其大致流程如下圖所示:
onSystemReady 初始化
系統(tǒng)啟動過程略去不表,在系統(tǒng)啟動完成后會調(diào)用onSystemReady;在onSystemReady中,service會發(fā)送一個MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED的msg,強(qiáng)制配置安全音量。
public void onSystemReady() {
...
sendMsg(mAudioHandler,
MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED,
SENDMSG_REPLACE,
0,
0,
TAG,
SystemProperties.getBoolean("audio.safemedia.bypass", false) ?
0 : SAFE_VOLUME_CONFIGURE_TIMEOUT_MS);
...
}
發(fā)送的MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED會調(diào)用onConfigureSafeVolume()來進(jìn)行安全音量的配置
onConfigureSafeVolume() 安全音量配置
private void onConfigureSafeVolume(boolean force, String caller) {
synchronized (mSafeMediaVolumeStateLock) {
//Mobile contry code,國家代碼,主要用來區(qū)分不同國家,部分國家策略可能會不一致
int mcc = mContext.getResources().getConfiguration().mcc;
if ((mMcc != mcc) || ((mMcc == 0) && force)) {
//從config_safe_media_volume_index中獲取回來的安全音量觸發(fā)閾值
mSafeMediaVolumeIndex = mContext.getResources().getInteger(
com.android.internal.R.integer.config_safe_media_volume_index) * 10;
mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
//根據(jù)audio.safemedia.force屬性值或者value配置的值來決定是否使能安全音量
boolean safeMediaVolumeEnabled =
SystemProperties.getBoolean("audio.safemedia.force", false)
|| mContext.getResources().getBoolean(
com.android.internal.R.bool.config_safe_media_volume_enabled);
//確認(rèn)是否需要bypass掉安全音量功能
boolean safeMediaVolumeBypass =
SystemProperties.getBoolean("audio.safemedia.bypass", false);
// The persisted state is either "disabled" or "active": this is the state applied
// next time we boot and cannot be "inactive"
int persistedState;
if (safeMediaVolumeEnabled && !safeMediaVolumeBypass) {
persistedState = SAFE_MEDIA_VOLUME_ACTIVE; //這個值只能是disable或者active,不能是inactive,主要用于下次啟動。
// The state can already be "inactive" here if the user has forced it before
// the 30 seconds timeout for forced configuration. In this case we don't reset
// it to "active".
if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) {
if (mMusicActiveMs == 0) { //mMusicActiveMs主要用于計數(shù),當(dāng)安全音量彈框彈出時,如果按了確定,這個值便開始遞增,當(dāng)其達(dá)到UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX時,則重新使能安全音量
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
enforceSafeMediaVolume(caller);
} else {
//跑到這里則表示已經(jīng)彈過安全音量警示了,并且按了確定,所以把值設(shè)置為inactive
// We have existing playback time recorded, already confirmed.
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
}
}
} else {
persistedState = SAFE_MEDIA_VOLUME_DISABLED;
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
}
mMcc = mcc;
//持久化當(dāng)前安全音量的狀態(tài)
sendMsg(mAudioHandler,
MSG_PERSIST_SAFE_VOLUME_STATE,
SENDMSG_QUEUE,
persistedState,
0,
null,
0);
}
}
}
由上可知,onConfigureSafeVolume()主要用于配置和使能安全音量功能,并且通過發(fā)送MSG_PERSIST_SAFE_VOLUME_STATE來持久化安全音量配置的值,這個持久化的值只能是active或者disabled。
case MSG_PERSIST_SAFE_VOLUME_STATE:
onPersistSafeVolumeState(msg.arg1);
break;
....
....
private void onPersistSafeVolumeState(int state) {
Settings.Global.putInt(mContentResolver,
Settings.Global.AUDIO_SAFE_VOLUME_STATE,
state);
}
安全音量觸發(fā)
從實(shí)際操作可知,安全音量觸發(fā)條件是:音量增大到指定值。 從調(diào)節(jié)音量的代碼出發(fā),在調(diào)用mAudioManager.adjustStreamVolume和mAudioManager.setStreamVolume時,最終會調(diào)用到AudioService中的同名方法,在執(zhí)行該方法的內(nèi)部:
protected void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage, String caller, int uid) {
...
...
...
} else if ((direction == AudioManager.ADJUST_RAISE) &&
!checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);
mVolumeController.postDisplaySafeVolumeWarning(flags);
....
...
private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
String caller, int uid) {
....
....
if (!checkSafeMediaVolume(streamTypeAlias, index, device)) {
mVolumeController.postDisplaySafeVolumeWarning(flags);
mPendingVolumeCommand = new StreamVolumeCommand(
streamType, index, flags, device);
} else {
onSetStreamVolume(streamType, index, flags, device, caller);
index = mStreamStates[streamType].getIndex(device);
}
....
....
由以上代碼可以看出,其安全音量彈框警告的觸發(fā)地方就在checkSafeMediaVolume方法附近處,并且都是通過mVolumeController這個遠(yuǎn)程服務(wù)去調(diào)用UI顯示安全音量彈框警告,但兩種調(diào)節(jié)音量的方法,觸發(fā)效果略有不同:
- adjustStreamVolume:當(dāng)音量步進(jìn)方向是上升并且checkSafeMediaVolume返回false時,直接彈出警告框;由于警告框占據(jù)了焦點(diǎn),此時無法進(jìn)行UI操作,并且再按音量+鍵時,會繼續(xù)觸發(fā)這個彈框,導(dǎo)致無法實(shí)質(zhì)性地調(diào)整音量;
- setStreamVolume:當(dāng)傳入的音量形參大于安全音量閾值,會觸發(fā)checkSafeMediaVolume返回false,彈出安全音量警告框;并且會通過mPendingVolumeCommand保存設(shè)置的音量值,待關(guān)掉安全音量后再賦回來。
private boolean checkSafeMediaVolume(int streamType, int index, int device) {
synchronized (mSafeMediaVolumeStateLock) {
if ((mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) &&
(mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) &&
((device & mSafeMediaVolumeDevices) != 0) &&
(index > safeMediaVolumeIndex(device))) {
return false;
}
return true;
}
}
以上是安全音量判斷條件checkSafeMediaVolume,可以看出其判斷主要根據(jù)以下條件:
- mSafeMediaVolumeState是否為active,這個是安全音量功能的開關(guān)變量;
- 音頻流是否為STREAM_MUSIC,只針對該音頻流做安全音量;
- 設(shè)備類型,默認(rèn)mSafeMediaVolumeDevices值如下:
/*package*/ final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET
| AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
| AudioSystem.DEVICE_OUT_USB_HEADSET;
由上可知,只針對耳機(jī)播放或者USB耳機(jī)才做安全音量功能,如有需要系統(tǒng)工程師可自行配置其他設(shè)備;
- 音量大小,只有音量index超過safeMediaVolumeIndex獲取的值,才需要彈出安全音量警示框,而safeMediaVolumeIndex的值則是本文開頭在config.xml中配置的config_safe_media_volume_index所得出的;
UI部分
上面有提到,當(dāng)滿足安全音量警示框的觸發(fā)條件時,會通過mVolumeController這個遠(yuǎn)程服務(wù)去調(diào)用UI顯示安全音量彈框警告,其調(diào)用鏈條有點(diǎn)長,中途略過不表,其最終會走到VolumeDialogImpl.java的showSafetyWarningH,如下:
public class VolumeDialog {
...
private void showSafetyWarningH(int flags) {
if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0
|| mShowing) {
synchronized (mSafetyWarningLock) {
if (mSafetyWarning != null) {
return;
}
mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) {
@Override
protected void cleanUp() {
synchronized (mSafetyWarningLock) {
mSafetyWarning = null;
}
recheckH(null);
}
};
mSafetyWarning.show();
}
recheckH(null);
}
rescheduleTimeoutH();
}
...
}
UI配置部分主要在SafetyWarningDialog.java,代碼就不貼了,可自行查看,其本質(zhì)是一個對話框,在彈出時會搶占UI焦點(diǎn),如果不點(diǎn)擊確定或取消,則無法操作其他UI;
點(diǎn)擊確定后,會調(diào)用mAudioManager.disableSafeMediaVolume()來暫時關(guān)閉安全音量警告功能,但上面有提到,當(dāng)點(diǎn)擊確定之后其實(shí)是啟動了一個變量mMusicActiveMs的計數(shù),當(dāng)這個計數(shù)到達(dá)一定值(默認(rèn)是20個小時),安全音量會重新啟動;
但如果點(diǎn)擊了取消,再繼續(xù)調(diào)大音量時,安全音量彈框還是會繼續(xù)彈出;
disableSafeMediaVolume()
上面有提到,在安全音量彈框彈出后,點(diǎn)擊確定可以暫時關(guān)閉安全音量警告功能,其實(shí)最終會調(diào)用到AudioService中的disableSafeMediaVolume(),代碼如下:
public void disableSafeMediaVolume(String callingPackage) {
enforceVolumeController("disable the safe media volume");
synchronized (mSafeMediaVolumeStateLock) {
setSafeMediaVolumeEnabled(false, callingPackage);
if (mPendingVolumeCommand != null) {
onSetStreamVolume(mPendingVolumeCommand.mStreamType,
mPendingVolumeCommand.mIndex,
mPendingVolumeCommand.mFlags,
mPendingVolumeCommand.mDevice,
callingPackage);
mPendingVolumeCommand = null;
}
}
}
一方面是調(diào)用setSafeMediaVolumeEnabled來暫時關(guān)閉安全音量功能,另一方面會把此前臨時掛起的設(shè)置音量mPendingVolumeCommand重新設(shè)置回去。
小結(jié)
簡單來講,Android原生的安全音量功能默認(rèn)強(qiáng)制打開,在插入耳機(jī)后,音量調(diào)節(jié)到指定閾值時,會觸發(fā)音量警告彈框,該彈框會搶走焦點(diǎn),不點(diǎn)擊確定或取消無法進(jìn)行其他操作;在點(diǎn)擊確定后,默認(rèn)操作者本人允許設(shè)備音量繼續(xù)往上調(diào),但此時系統(tǒng)會開始一個默認(rèn)為20分鐘的倒計時,在這20分鐘內(nèi)音量隨意調(diào)節(jié)都不會觸發(fā)安全音量彈框,但20分鐘結(jié)束后,音量大于閾值時會繼續(xù)觸發(fā)安全音量彈框,提醒使用者注意。
原文鏈接:https://juejin.cn/post/7178817360810737722
相關(guān)推薦
- 2023-05-20 命令行傳遞參數(shù)argparse.ArgumentParser的使用解析_python
- 2024-02-01 前端訪問一個地址 地址上帶/api, 如何通過nginx 配置, 讓訪問后臺的時候是/而不是/api
- 2023-07-16 uni-app 自定義組件之星級評價分?jǐn)?shù)
- 2022-11-17 python?泛型函數(shù)--singledispatch的使用解讀_python
- 2023-10-11 在MyBatisPlus中添加分頁插件
- 2022-08-13 Docker(Windows版)安裝zookeeper+kafka
- 2021-12-21 Number的常見使用方法
- 2023-02-27 一文搞懂Golang?值傳遞還是引用傳遞_Golang
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支