Android音频管理之AudioManager(原创)

本文已独家授权 郭霖 ( guolin_blog ) 公众号发布!

本篇文章主要介绍的是Android应用(WebView加载H5的音频管理只是其中的一种)如何有效管理或定制音频的基本内容和一些思路。

事情的起因是这样的,最近接到个需求WebView加载H5游戏,嗯,霹雳巴拉一顿猛敲基本上就搞定了,针对8.1系统也做了一些适配。好了,验货的时间到了,产品用他那傲娇的手指一顿操作后不耐烦的说,这个应用为什么按下HOME键后(Back键禁用了 - - !)游戏还有声音???当时我在想,目标Activity生命周期 onResume()和onPause()不是写了对应的 myWebView.onResume();以及 myWebView.onPause()的啊啊!难不成那厮又在戏剧性耍我?

带着黑人小哥头上3个问号表情的我说到,按下HOME键没有声音的嘛,我先试试。产品瞬间反转谄媚一笑说到,下午可以搞定嘛?我说,这个声音可能是H5游戏引擎技术的问题,我先看下什么问题。然后,就是老套路编译代码打开应用插上耳机,进入游戏BGM时间,按下HOME键,MMP,声音真没消失!!!

好了,遇到问题后我跟大家的常规套路一样,打开浏览器,输入问题,接着search 结果真是惊呆了我&我的小伙伴,这都是些啥。

search - 1

怎么还出现了 中间home键按下有咯噔的声音怎么回事?可能是问题没有描述清楚,更加详细一点的。嗯,看上去貌似挺靠谱

逐个点进去看看,最后统计汇总下这些博客里面提供的办法,有使用反射(这个感觉怪怪的就没有试验)、有说什么要自定义webview,原生webview控制不住的......(我这本来就是用的第三方轮子)、有说针对生命周期下手的(这种特殊情况就是上面写的webview.onResume()等等,之前也说了生命周期没用)、有要我这边研究H5音频控件的......(喂喂喂、研究会花时间的好嘛?)、有说使用android与H5交互(也就是Android调H5界面关闭声音的方法,这个是我之前想到的一种办法)这个方法貌似可行,但是在后面的一些博客说到这种方案有些问题。。。更有甚者说,在Activity生命周期里面使用 myWebView.reload();这个函数,我也是醉了,拜托这是重新加载好嘛。

看来网上的一些办法不行(或者搜索姿势不对),我就带着问题(描述的很清楚写了生命周期没有作用)去各大Android技术群里面询问有经验的开发人员,结果这些朋友还是说要在生命周期里面做手脚( - - !),无奈,只得另寻他法。

接着找到了研究游戏的H5技术,我问他们这是什么情况,他们说不清楚。。。然后我说可不可以让我调取你们的JS脚本,他们说这样会有一些问题。。。你还是自己想想办法吧。

万般无奈,最后突然想到了,不管你是什么BGM,你都是声音,既然是声音那么Android肯定提供了一些API供我们调用,果不其然,在Android多媒体找到了AudioManager这个类。

AudioManager(audio翻译过来就是声音、音频):

AudioManager,音频管理类,它主要提供了丰富的API让开发者对应用的音量和铃声模式进行控制以及访问。主要内容涉及到音频流、声音、蓝牙、扩音器、耳机等等。

A:获取实例

由于音频管理涉及到多媒体,因此这个AudioManager获取实例的姿势是这样的:

AudioManager audio = (AudioManager)Context.getSystemService(Context.AUDIO_SERVICE);

B:丰富的API

音频管理类提供了大量的API,这些API是我们经常看到或者用到的,比如,调节音量,我相信对于很多人来说,调节音量这个姿势是很常见的,比如你打开某视频APP、某音乐APP其中肯定有调节音量大小的手势,那么调节音量内部的逻辑可以使用 adjustStreamVolume(int streamType, int direction, int flags)

参数预览:

streamType :要调整的音频流类型。类型有以下几种:

STREAM_VOICE_CALL(电话的音频流),

STREAM_SYSTEM(系统声音的音频流),

STREAM_RING(电话铃声的音频流),

STREAM_MUSIC(用于音乐播放的音频流)

STREAM_ALARM(警报的音频流)

区分流类型的目的是让用户能够单独地控制不同的种类的音频。但上述音频种类中,大多数都是被系统限制。除非应用需要做替换闹钟的铃声的操作,不然的话你只能通过STREAM_MUSIC来播放你的音频。也就是说我们最常见操作的就是STREAM_MUSIC这个类型。

direction :调整音量的方向。其中: ADJUST_LOWER(减少铃声音量),ADJUST_RAISE(增加铃声音量)或 ADJUST_SAME(保持之前的铃声音量)

flags :一个或多个标志。可能这里的标志不是很好理解,是这样,AudioManager提供了一些常量,我们可以将这些系统已经准备好的常量设置为这里的flags,比如:

FLAG_ALLOW_RINGER_MODES(更改音量时是否包括振铃模式作为可能的选项),

FLAG_PLAY_SOUND(是否在改变音量时播放声音),

FLAG_REMOVE_SOUND_AND_VIBRATE(删除可能在队列中或正在播放的任何声音/振动(与更改音量有关)),

FLAG_SHOW_UI(显示包含当前音量的吐司),

FLAG_VIBRATE(是否进入振动振铃模式时是否振动)

比如我现在想要增加音量,就可以这样写:

AudioManager . adjustStreamVolume (AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI);

这句代码的意思是指:指定调节类型为 音乐的音频,增大音量,显示音量图形示意。举一反三下面就是降低音量的代码:

AudioManager . adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, AudioManager.FLAG_PLAY_SOUND);

比如,我想要获取手机的音量,可以调取getStreamVolume(int streamType); 这里的streamType指获得手机的当前流类型的音量,最大值为7(笔者亲测 Android5.1系统 中兴)最小值为0。

setStreamVolume(int streamType, int index, int flags)这个API顾名思义就是根据音频流类型去设置音量大小的。主要这里的index不能超过最大索引,也就是7。

比如蓝牙相关的API:

isBluetoothA2dpOn() :这个方法是检查是否打开或关闭了到蓝牙耳机的A2DP音频路由。

isBluetoothScoOn(): 这个方法主要是检查通信是否使用蓝牙SCO。

startBluetoothSco(): 启动蓝牙SCO音频连接

setBluetoothScoOn(boolean on): 请求使用蓝牙SCO耳机进行通信。(设置true 代表用于通信的蓝牙SCO; 设置false 即不使用蓝牙SCO进行通信)

void stopBluetoothSco(): 停止蓝牙SCO音频连接。

麦克风相关:

setMicrophoneMute(boolean on):设置麦克风静音开启或关闭。( 设置true 关闭麦克风也就是麦克风静音; 设置false,即关闭静音打开麦克风)

setSpeakerphoneOn(boolean on):这个方法主要是判断是否打开扩音器(设置true,即打开免提电话; false将其关闭)

isMicrophoneMute():判断麦克风是否静音或是否打开(如果麦克风静音则为true,否则为false)

isMusicActive():判断是否有音乐处于活跃状态(如果任何音乐曲目有效,则为true)

等等,具体的更丰富更全面的API可以参考 AudioManager官方文档

综上,笔者的这个问题就可以通过AudioManager去进行操作:

首先,写一个监听HOME键的代码(我这里使用的是广播),在广播里面进行操作,如果按下HOME键以后,广播通知AudioManage关闭声音,然后打开页面回到目标Activity,在对应的生命周期里面进行音量的设置

代码如下:

首先是监听HOME键的广播:

public class InnerRecevier extends BroadcastReceiver {

private AudioManager mAudioManager;

    final String SYSTEM_DIALOG_REASON_KEY ="reason";

    final String SYSTEM_DIALOG_REASON_RECENT_APPS ="recentapps";

    final String SYSTEM_DIALOG_REASON_HOME_KEY ="homekey";

    @Override

    public void onReceive(Context context, Intent intent) {

String action = intent.getAction();

        mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);

        if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {

String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);

            if (reason !=null) {

if (reason.equals(SYSTEM_DIALOG_REASON_HOME_KEY)) {

//按下HOEM键后,设置音频流类型为STREAM_MUSIC,Volume为0(也就是没有声音)

mAudioManager.setStreamVolume(STREAM_MUSIC,0,0);

//                    Toast.makeText(context, "点击了Home键", Toast.LENGTH_SHORT).show();

                }else if (reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {

//                    Toast.makeText(context, "多任务键被监听", Toast.LENGTH_SHORT).show();

                }

}

}

}

}

接着,在 目标Activity 注册广播,(别忘了清单文件注册):

void initHomeBroadCast() {

   //创建广播

    InnerRecevier innerReceiver =new InnerRecevier();

    //动态注册广播

    IntentFilter intentFilter =new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);

    //启动广播

    registerReceiver(innerReceiver, intentFilter);

}

最后,对生命周期onRestart()方法里面,通过AudioManage设置音量即可

@Override

protected void onRestart() {

    super.onRestart();

    //设置音量  音频流类型为STREAM_MUSIC,Volume为6 第二个索引不能超过最大索引

    mAudioManager.setStreamVolume(STREAM_MUSIC, 6, 0);

}

测试:成功解决了WebView加载H5按下HOME键的声音问题。

哦~这个产品貌似忘了电源键也是可以关闭界面的吧 ~ ~ ~

进一步思考:

我们知道,一款手机可能会有多个应用去播放音频,(手机安装多款音视频播放器这个是很常见的现象、同时打开多个音视频播放器也是很正常的)

试想如果不有效的处理应用的音频,会出现什么情况?我们在听歌的同时可能还会听到啪啪啪的声音。(注:这里的啪啪啪指观看羽毛球视频)为了防止多个音乐播放应用同时播放音频,

谷歌技术团队使用音频焦点(Audio Focus)来控制音频的播放。也就是,当且仅当apk获取到音频焦点成功以后,才可以播放音频!

首先,如何获取音频焦点?获取音频焦点通过( AudioManager类 )这个方法:

  public int  requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint)

来获取音频焦点,这个方法有三个参数,下面仔细分析下这三个参数

参数一:OnAudioFocusChangeListener(音频焦点发生改变时候的监听),这个OnAudioFocusChangeListener是AudioManager的一个内部接口,本质是监听 音频焦点的状态。

比如,是否获取了焦点、焦点是否失去、焦点暂时失去等状态 ,通过源码可以得知,它有四种状态,分别是:

状态一:AudioManager.AUDIOFOCUS_GAIN

状态二:AudioManager.AUDIOFOCUS_LOSS

状态三:AudioManager.AUDIOFOCUS_LOSS_TRANSIENT

状态四:AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK

以上四种状态分别代表的意思如下:

状态一:即应用获取了焦点,获取了焦点即正常播放音频

状态二:即应用彻底失去了焦点,不能播放音频

状态三:即应用暂时失去焦点(比如我们按下HOME键、电源键等)

状态四:应用暂时失去焦点,但是这个DUCK(DUCK翻译:鸭子、闪避、躲避)状态比较特殊,这种状态意味着其它应用可以继续播放,仅仅是在这一刻降低自己应用的音量,直到重新获取到音频焦点后恢复正常音量。应用场景有,比如在播放音乐的时候突然出现一个短暂的短信提示声音,仅仅是把歌曲的音量暂时调低,使得用户能够听到短信提示声,在此之后便立马恢复正常播放;再比如语言导航等等

注意:一旦结束了播放,需要确保调用abandonAudioFocus()方法。也就是告知系统我们不再需要获取焦点且注销AudioManager.OnAudioFocusChangeListener监听器。

因此我们可以有如下代码去监听状态:

    //焦点发生改变的监听

    private AudioManager.OnAudioFocusChangeListener afChangeListener = new AudioManager.OnAudioFocusChangeListener(){

        @Override

        public void onAudioFocusChange(int focusChange) {

            Log.i(TAG, "onAudioFocusChange: == "+focusChange);

            if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT){

            //暂停播放  停止播放

                Log.i(TAG, "onAudioFocusChange: == AUDIOFOCUS_LOSS_TRANSIENT 暂停播放 停止播放");

            }else if (focusChange == AUDIOFOCUS_GAIN){

            //获取焦点  继续播放

                Log.i(TAG, "onAudioFocusChange: == AUDIOFOCUS_GAIN 获取焦点  继续播放");

            }else if (focusChange == AUDIOFOCUS_LOSS){

            //彻底丢失焦点

                mAudioManager.abandonAudioFocus(afChangeListener);

                Log.i(TAG, "onAudioFocusChange: == AUDIOFOCUS_LOSS");

            }

        }

    };

参数二:streamType(流类型)这个上面也说了,一般设置为STREAM_MUSIC

参数三:durationHint (持续时间) 根据源码得知有以下几种固定写法:

A:AUDIOFOCUS_GAIN_TRANSIENT  焦点请求是临时

B: AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK  如果它的音频输出被暂停,那么之前成功获取焦点的就可以继续播放

C: AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 临时的(语音备忘记录或语音识别)

D:AUDIOFOCUS_GAIN 也就是焦点请求(这个用的较多)

另外源码可以得知,这个获取音频焦点的返回值只有两种:

一种是:AUDIOFOCUS_REQUEST_FAILED = 0

一种是:AUDIOFOCUS_REQUEST_GRANTED = 1,其中,AUDIOFOCUS_REQUEST_GRANTED代表的就是获取音频焦点成功

所以,获取音频焦点的写法如下:

        int result = mAudioManager.requestAudioFocus(afChangeListener,

                // Use the music stream.

                AudioManager.STREAM_MUSIC,

                // Request permanent focus.

                AudioManager.AUDIOFOCUS_GAIN);

//      如果请求结果AudioManager.AUDIOFOCUS_REQUEST_GRANTED 则表明 请求成功

        if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {

        }

思考:AudioManager的强大之处在于可以捕获音频流类型、蓝牙相关、麦克风相关等等等,怎么使用主观能动性全在开发者手中;另外通过音频焦点、生命周期以及主动设置音量,可以帮助我们有效主动管理音频。

如果这篇文章对您有开发or学习上的些许帮助,希望各位看官留下宝贵的star,谢谢。

Ps:著作权归作者所有,转载请注明作者, 商业转载请联系作者获得授权,非商业转载请注明出处(开头或结尾请添加转载出处,添加原文url地址),文章请勿滥用、开源项目仅供学习交流、也希望大家尊重笔者的劳动成果,谢谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 音频播放 音频播放声音分为MediaPlayer和AudioTrack两种方案的。MediaPlayer可以播放多...
    安仔夏天勤奋阅读 42,818评论 6 43
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • 我不说什么心灵鸡汤。我需要重塑我的新生。 脸蛋依旧重要,不支持不反对整容,但是搁我身上实现不了,因为我讨厌假的。我...
    沐未阅读 297评论 0 1
  • 这个世界上只分为两种人:为生命英勇奋战的人,和投降死去的人。无疑,《万物的签名》中的主角——阿尔玛是前一种人。 这...
    馒头橘子阅读 501评论 4 5