两个或两个以上的 Android 应用可同时向同一输出流播放音频。系统会将所有音频流混合在一起。虽然这是一项出色的技术,但却会给用户带来很大的困扰。为了避免所有音乐应用同时播放,Android 引入了“音频焦点”的概念。 一次只能有一个应用获得音频焦点。
当您的应用需要输出音频时,它需要请求获得音频焦点,获得焦点后,就可以播放声音了。不过,在您获得音频焦点后,您可能无法将其一直持有到播放完成。其他应用可以请求焦点,从而占有您持有的音频焦点。如果发生这种情况,您的应用应暂停播放或降低音量,以便于用户听到新的音频源。
1).如果手机上安装了两个音频播放器,当一个正在播放的时候,打开第二个播放歌曲,有没有发现第一个自动暂停了。
2).如果你在听音频的同时,又去打开了其它视频APP,你会发现音频APP暂停播放了。
3).如果你正在听音频或者看视频时,来电话了,那么音视频便会暂停。挂了电话后音乐又继续播放,视频则需要点击按钮播放,是不是很奇怪
4).当你收到消息,比如微信消息,并且有消息声音的时候,那么听音频的那一瞬间,音频的声音会变小了,然后过会儿又恢复了。是不是很有意思。
别蒙圈,这个就叫做音频捕获和丢弃焦点。
如果不处理捕获与丢弃音频焦点的话,那么同时开几个音视频播放器,就会出现多个声音;
这是一种不好的体验。如下图:

图1

图2
Vehicle混音场景使用较频繁,常用的场景如下:
行为恰当的音频应用应根据以下一般准则来管理音频焦点:
运行的 Android 版本不同,音频焦点的处理方式也会不同:
获取音频焦点的第一个步骤是先向系统发出申请焦点的消息。注意这只是发出请求,并非直接获取。为了申请到音频聚焦,您必须向系统描述好您的意图。介绍四个常见音频焦点类型:
在音频焦点成功获取后,该方法会返回AUDIOFOCUS_REQUEST_GRANTED常量,否则,会返回AUDIOFOCUS_REQUEST_FAILED常量。
音频焦点处理逻辑:
1). Android O之前版本,需要用到 AudioFocusRequest,只需实现 AudioManager.OnAudioFocusChangeListener 接口。代码如下:
AudioManager mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
int focusRequest = mAudioManager.requestAudioFocus(
..., // Need to implement listener
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
switch (focusRequest) {
case AudioManager.AUDIOFOCUS_REQUEST_FAILED:
//请求焦点失败-->不允许播放
case AudioManager.AUDIOFOCUS_REQUEST_GRANTED:
//请求焦点成功->开始播放
}
2). Android O以及更新的版本上 必须使用 builder 来实例化一个 AudioFocusRequest 类。(在 builder 中必须指明请求的音频焦点类型),接口代码如下:
audioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
playbackAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_GAME)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(playbackAttributes)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(afChangeListener, handler)
.build();
mediaPlayer = new MediaPlayer();
final Object focusLock = new Object();
boolean playbackDelayed = false;
boolean playbackNowAuthorized = false;
// ...
int res = audioManager.requestAudioFocus(focusRequest);
synchronized(focusLock) {
if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
//请求焦点失败-->不允许播放
} else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
//请求焦点成功->开始播放
} else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
//延时焦点
}
}
// ...
延时焦点-场景案例:
假如当用户在通话中打开游戏,他们想玩游戏,不想听到游戏声音。但是当他们通话结束的时候他们想听到游戏声音(通话应用暂时持有音频焦点)。如果您的应用支持延迟音频聚焦,会发生如下情况:
一旦获得音频聚焦,您的应用要马上做出响应,因为它的状态可能在任何时间发生改变(丢失或重新获取),您可以实现 OnAudioFocusChangeListener 的来响应状态改变。
以下代码展示了 OnAudioFocusChangeListener 接口的实现,它处理了与 Google Assistant 应用协同工作的时候,音频焦点的各种状态的变化。
状态说明:
//焦点状态改变
@Override
public void onAudioFocusChange(int focusChange) {
int volume;
switch (focusChange) {
// 重新获得焦点
//如果通话结束,恢复播放;获取音量并且恢复音量。这个情景应该经常遇到
case AudioManager.AUDIOFOCUS_GAIN:
if (!willPlay() && isPausedByFocusLossTransient) {
// 通话结束,恢复播放
mPlayService.playPause();
}
//获取音量
volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
if (mVolumeWhenFocusLossTransientCanDuck > 0 && volume ==
mVolumeWhenFocusLossTransientCanDuck / 2) {
// 恢复音量
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
mVolumeWhenFocusLossTransientCanDuck, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
}
isPausedByFocusLossTransient = false;
mVolumeWhenFocusLossTransientCanDuck = 0;
break;
// 永久丢失焦点,如被其他播放器抢占,释放焦点
// 必须停止所有的audio播放,清理资源
case AudioManager.AUDIOFOCUS_LOSS:
if (willPlay()) {
forceStop();
}
break;
// 短暂丢失焦点,比如来了电话或者微信视频音频聊天等等
// 但是很快就会重新获得,在此状态应该暂停所有音频播放,但是不能清除资源
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
if (willPlay()) {
forceStop();
isPausedByFocusLossTransient = true;
}
break;
// 瞬间丢失焦点,如通知,导航
// 暂时失去 audio focus,但是允许持续播放音频(以很小的声音),不需要完全停止播放。
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// 音量减小为一半
volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
if (willPlay() && volume > 0) {
mVolumeWhenFocusLossTransientCanDuck = volume;
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
mVolumeWhenFocusLossTransientCanDuck / 2,
AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
}
break;
default:
break;
}
失去焦点的三种类型:
场景:
重新获得焦点场景:
播放完音频,记得使用 AudioManager.abandonAudioFocus(...) 来释放掉音频焦点。在前面的步骤中,我们遇到了一个应用暂停播放应该释放音频焦点的情况,但是这个应用依旧保留了音频焦点。
// Abandon audio focus when playback complete
audioManager.abandonAudioFocus(afChangeListener);

通过上述音频焦点AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK模式实现混音操作,或者在修改系统框架层,处理导航输出,压低媒体声音实现混音效果。
参考框架图:

如上硬件框架,软件设计如下:
处理方案如下:
方法:ActivityThread.currentPackageName();//获取该Class在哪个package调用
//AudioAttributes 参考修改
Public AudioAttributes build(){
...
String mCurName = ActivityThread.currentPackageName();
if(mCurName != null && mCurName .equals(“com.android.nav”))
{
//重置媒体ContentType
mContentType = AudioManager.STREAM_ALARM;
//重置mUsage 类型
mUsage = USAGE_ALARM;
}
...
}
音频焦点涉及相关代码路径:
frameworks/base/media/java/android/media/AudioFocusRequest.java
frameworks/base/media/java/android/media/AudioAttributes.java
frameworks/base/media/java/android/media/AudioManager.java
frameworks/base/media/java/android/media/AudioSystem.java
frameworks/base/media/java/android/media/AudioFocusInfo.java
frameworks/base/services/core/java/com/android/server/audio/AudioService.java
frameworks/base/services/core/java/com/android/server/audio/FocusRequest.java
frameworks/base/services/core/java/com/android/server/audio/MediaFocusControl.java
最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路
我想使用两种不同的protect_from_forgery策略构建一个Rails应用程序:一种用于Web应用程序,一种用于API。在我的应用程序Controller中,我有这行代码:protect_from_forgerywith::exception为了防止CSRF攻击,它工作得很好。在我的API命名空间中,我创建了一个继承self的应用程序Controller的api_controller,它是API命名空间中所有其他Controller的父类,我将上面的代码更改为:protect_from_forgery:null_session.遗憾的是,我在尝试发出POST请求时遇到错误:“
HTTP缓存是指浏览器或者代理服务器将已经请求过的资源保存到本地,以便下次请求时能够直接从缓存中获取资源,从而减少网络请求次数,提高网页的加载速度和用户体验。缓存分为强缓存和协商缓存两种模式。一.强缓存强缓存是指浏览器直接从本地缓存中获取资源,而不需要向web服务器发出网络请求。这是因为浏览器在第一次请求资源时,服务器会在响应头中添加相关缓存的响应头,以表明该资源的缓存策略。常见的强缓存响应头如下所述:Cache-ControlCache-Control响应头是用于控制强制缓存和协商缓存的缓存策略。该响应头中的指令如下:max-age:指定该资源在本地缓存的最长有效时间,以秒为单位。例如:Ca
本人是音乐爱好者,从小就特别喜欢那个随着音乐跳动的方框效果,就是这个:arduino上一大把对,我忍你很久了,我就想用mpy做,全网没有,行我自己研究。果然兴趣是最好的老师,我之前有篇博客专门讲音频,有兴趣的可以回顾一下。提到可视化频谱,必然绕不开fft,大学学过这玩意,当时一心玩,老师讲的一个字都么听进去,网上教程简略扫了一下,大该就是把时域转频域的工具,我大mpy居然没有fft函数,奶奶的,先放着。音频信息如何收集?第一种傻瓜式的ADC,模拟转数字,原始粗暴,第二种,I2S库,我之前博客有讲过,数据是PCM编码。然后又去学PCM编码,一学豁然开朗,舒服,以代码为例:audio_in=I2S
我正在开发一个将XML发布到某些网络服务的小型应用程序。这是使用Net::HTTP::Post::Post完成的。但是,服务提供商建议使用重新连接。类似于:第一个请求失败->2秒后重试第二个请求失败->5秒后重试第三次请求失败->10秒后重试...这样做的好方法是什么?简单地在循环中运行以下代码,捕获异常并在一定时间后再次运行?或者还有其他聪明的方法吗?也许Net包甚至有一些我不知道的内置功能?url=URI.parse("http://some.host")request=Net::HTTP::Post.new(url.path)request.body=xmlrequest.con
我是WATIR测试的新手(我喜欢它吗!)并且遇到了如何将我的WATIR脚本重新聚焦到新打开的窗口的问题。这是我的(简化的)脚本....#!/usr/bin/rubyrequire'rubygems'require'watir-webdriver'browser=Watir::Browser.newbrowser.goto("http://0:3050")browser.text_field(:name,"userkey300203830").set("OKJHNB")browser.button(:id,"interview48").clickputs"ExpectedResult:"
我有这个原始文本:________________________________________________________________________________________________________________________________PosCarCompetitor/TeamDriverVehicleCapCLLapsRace.TimeFastest...Lap16JasonClementsJasonClementsBMWM33200109:48.571030:57.3228*242DavidSkillenderDavidSkillenderHo
现在已经为此奋斗了一段时间,不确定为什么它不起作用。要点是希望将Devise与LDAP结合使用。除了身份验证外,我不需要做任何事情,所以除了自定义策略外,我不需要使用任何东西。我根据https://github.com/plataformatec/devise/wiki/How-To:-Authenticate-via-LDAP创建了一个据我所知,一切都应该正常工作,除了每当我尝试运行服务器(或rake路由)时,我得到一个NameErrorlib/devise/models.rb:88:in`const_get':uninitializedconstantDevise::Models:
我正在使用Grape和Rails创建RESTAPI。我有基本的架构,我正在寻找“清理”东西的地方。其中一个地方是错误处理/处理。我目前正在为整个API修复root.rb(GRAPE::API基类)文件中的错误。我格式化它们,然后通过rack_response发回错误。一切正常,但root.rb文件变得有点臃肿,所有错误都被修复,其中一些有需要完成的特殊解析。我想知道是否有人制定了一个好的错误处理策略,以便可以将其移出到它自己的模块中,并使root.rb(GRAPE::API基类)相当精简。我很想创建一个错误处理模块并为每种类型的错误定义方法,例如...moduleAPImoduleEr
运行有问题或需要源码请点赞关注收藏后评论区留言一、利用ContentResolver读写联系人在实际开发中,普通App很少会开放数据接口给其他应用访问。内容组件能够派上用场的情况往往是App想要访问系统应用的通讯数据,比如查看联系人,短信,通话记录等等,以及对这些通讯数据及逆行增删改查。首先要给AndroidMaifest.xml中添加响应的权限配置 下面是往手机通讯录添加联系人信息的例子效果如下分成三个步骤先查出联系人的基本信息,然后查询联系人号码,再查询联系人邮箱代码 ContactAddActivity类packagecom.example.chapter07;importandroid