Android开发之Mediaplayer状态转换图及音频焦点

简介:

前言

  之前博客里已经将了MediaPlayer的简单应用,如何使用MediaPlayer在Android应用中播放音频。这篇博客在MediaPlayer使用的基础上,讲解一下MediaPlayer的一些高级功能的使用,以及它的状态转换。对MediaPlayer还不了解的朋友可以先看看之前那篇博客:Android--MP3播放器MediaPlayer。

   本篇博客主要内容如下:

  1. MediaPlayer的状态变换
  2. MediaPlayer的唤醒锁
  3. MediaPlayer的音频焦点

 

 

MediaPlayer的状态变换

  之前讲到,使用MediaPlayer播放音频,主要使用的是start()、pause()、stop()等方法操作MediaPlayer。但是除了开始、暂停、停止等,MediaPlayer还涉及到一些其他的状态切换,有些状态是可以双向转换的,有些只能单向环形转换。如果在某状态下,强行转换状态,会应发程序错误,例如在Preparing状态下切换到Started状态,是准备中强行开始播放,会出错。下图是官方文档上的图例,可以很清晰的表名MediaPlayer各个状态的转换情况。

  上图已经对MediaPlayer的各种状态转换有的清晰的介绍,这里不再详细讲解了,只是提一下需要注意的地方:

  • Started(开始)/Paused(暂停)到Stopped(停止)是单向转换,无法再从Stopped直接转换到Started,需要经历Prepared重新装载才可以重新播放。
  • Initialized(初始化)状态需要装载数据才可以进行start()播放,但是如果使用prepareAsync()方法异步准备,需要等待准备完成再开始播放,这里需要使用一个回调方法:setOnPreparedListener(),它会在异步装载完成后调用。
  • End(结束)状态是游离在其他状态之外的,在任何状态皆可切换,一般在不需要继续使用MediaPlayer的时候,才会使用release()回收资源。
  • Error(错误)状态是游离在其他状态之外的,只有在MediaPlayer发生错误的时候才会转换。为了保持应用的用户体验,一般我们回监听setOnErrorListener()回调方法,它会在MediaPlayer发生错误的时候被回调。

 

MediaPlayer的唤醒锁

  一般使用MediaPlayer播放音频流,推荐使用一个Service来承载MediaPlayer,而不是直接在Activity里使用。但是Android系统的功耗设计里,为了节约电池消耗,如果设备处于睡眠状态,系统将试图降低或者关闭一些没设备必须的特性,包括CUP和Wifi硬件,然后,如果是一个后台播放音乐的应用,降低CUP可能导致在后台运行的时候干扰音频的正常播放,关闭Wifi将可能导致网络音频流的获取出现错误。

  为了确保MediaPlayer的承载的服务在系统睡眠的时候继续正常运行下去,Android为我们提供了一种唤醒锁(wake locks)的机制。它可以在系统睡眠的,依然保持锁定硬件的正常工作。

  确保在MediaPlayer运行的时候,哪怕系统睡眠了CUP也能正常运行,需要使用MediaPlayer.setWakeMode()为MediaPlayer设定唤醒锁。下面是setWakMode()的签名:

    setWakeMode(Context context, int mode)

  第一个参数是当前上下文,第二个参数为需要加锁的状态,被设定为int类型的常量,定义在PowerManager这个final类中。PowerManager是专门用来管理Android功率消耗的锁定状态,与锁定CUP相关的,有四种,分别设定CUP、屏幕、键盘等的各种保持唤醒的状态,在这里只需要设定为PARTIAL_WAKE_LOCK即可。

1                 mediaPlayer = new MediaPlayer();
2                 // 设定CUP锁定
3                 mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

  一般对于锁而言,锁定了通常需要解锁,但是这里的唤醒说与MediaPlayer关联,所以只需要在使用完之后release()释放MediaPlayer即可,无需显式的为其解锁。在使用setWakeMode设定唤醒锁的时候,还必须为应用赋予相应的权限:

    <uses-permission android:name="android.permission.WAKE_LOCK"/>

 

  

  再来说说如何锁定wifi硬件在系统睡眠的时候保持正常运行。wifi锁通过WifiLock进行操作,而WifiLock通过WifiManager进行管理,通过WifiManager.createWifiLock()进行Wifi锁定。

    WifiManager.WifiLock createWifiLock(int lockType, String tag)

  这个方法有多个重载,这里介绍的这个,第一个参数设定锁的状态,为一个int类型的常量,定义在Context类中,这里的应用场景一般设定为WIFI_MODE_FULL即可。第二个参数为WifiLock的标志,用于确定wifiLock的。

1         wifiLock= ((WifiManager) getSystemService(this.WIFI_SERVICE))
2                 .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
3         wifiLock.acquire();

 

  当然,在应用中把Wifi锁定之后,还需要在MediaPlayer.release()的时候为wifi硬件解锁,为避免意外关闭的情况,最好在Android组件的onDestory()里对其进行释放,释放Wifi锁使用WifiLock.release()。  

复制代码
 1     /**
 2      * 停止播放
 3      */
 4     protected void stop() {
 5         if (mediaPlayer != null && mediaPlayer.isPlaying()) {
 6             mediaPlayer.stop();
 7             mediaPlayer.release();
 8             mediaPlayer = null;
 9             // 释放wifi锁
10             wifiLock.acquire();
11             btn_play.setEnabled(true);
12             Toast.makeText(this, "停止播放", 0).show();
13         }
14 
15     }
16 
17     @Override
18     protected void onDestroy() {
19         // 在activity结束的时候回收资源
20         if (mediaPlayer != null && mediaPlayer.isPlaying()) {
21             mediaPlayer.stop();
22             mediaPlayer.release();
23             mediaPlayer = null;
24             // 释放wifi锁
25             wifiLock.acquire();
26         }
27         super.onDestroy();
28     }
复制代码

 

MediaPlayer的音频焦点

  众所周知,Android是一个多任务的操作系统,所以对于音频的播放,也许有几个不同的媒体服务会同时播放,这样可能导致一个比较杂乱的声音环境,而错过一些重要的声音提醒。在Android2.2之后,Android提供了一种应用协商使用设备音频输出的机制,这种机制称为音频焦点。

  当应用程序需要输出音频或通知的时候,需要请求音频焦点,当请求得到音频焦点之后,监听音频焦点的变换,当音频焦点变换了,根据返回回来的音频焦点码进行相应的处理。音频焦点的注册使用音频管理器的AudioManager.requestAudioFocus()方法设定。它的签名如下:

    int requestAudioFocus(AudioManager.OnAudioFocusChangeListener l, int streamType, int durationHint)

  这个方法的返回值是int类型,其含义被定义在AudioManager中以常量表示AUDIOFOCUS_REQUEST_FAILED(获取音频焦点成功)AUDIOFOCUS_REQUEST_GRANTED(获取音频焦点失败)。其中重要的是第一个参数,为音频焦点变化的回调函数,在其中可以设定如果音频焦点变换了,当前应用如何管理MediaPlayer,第二个参数为媒体流的类型,第三个参数为持续的状态。

  AudioManager.OnAudioFocusChangeListener为音频焦点变换的监听器,其中需要实现一个方法:onAudioFocusChange(int focusChange)在音频焦点变换的时候回调。它有一个参数,为当前表示音频焦点对于当前应用的状态码,通过这个状态码指定对应的操作,有些时候音频状态改变了,并不一定需要停止音频的播放。

  focusChange有一下几种状态码:

  • AUDIOFOCUS_GAIN:获得音频焦点。
  • AUDIOFOCUS_LOSS:失去音频焦点,并且会持续很长时间。这是我们需要停止MediaPlayer的播放。
  • AUDIOFOCUS_LOSS_TRANSIENT:失去音频焦点,但并不会持续很长时间,需要暂停MediaPlayer的播放,等待重新获得音频焦点。
  • AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:暂时失去音频焦点,但是无需停止播放,只需降低声音方法。 
复制代码
 1                 audioManager = (AudioManager) getSystemService(this.AUDIO_SERVICE);
 2                 int result = audioManager.requestAudioFocus(
 3                         new OnAudioFocusChangeListener() {
 4 
 5                             @Override
 6                             public void onAudioFocusChange(int focusChange) {
 7                                 switch (focusChange) {
 8                                 case AudioManager.AUDIOFOCUS_GAIN:
 9                                     // 获得音频焦点
10                                     if (!mediaPlayer.isPlaying()) {
11                                         mediaPlayer.start();
12                                     }
13                                     // 还原音量
14                                     mediaPlayer.setVolume(1.0f, 1.0f);
15                                     break;
16 
17                                 case AudioManager.AUDIOFOCUS_LOSS:
18                                     // 长久的失去音频焦点,释放MediaPlayer
19                                     if (mediaPlayer.isPlaying())
20                                         mediaPlayer.stop();
21                                     mediaPlayer.release();
22                                     mediaPlayer = null;
23                                     break;
24 
25                                 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
26                                     // 展示失去音频焦点,暂停播放等待重新获得音频焦点
27                                     if (mediaPlayer.isPlaying())
28                                         mediaPlayer.pause();
29                                     break;
30                                 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
31                                     // 失去音频焦点,无需停止播放,降低声音即可
32                                     if (mediaPlayer.isPlaying()) {
33                                         mediaPlayer.setVolume(0.1f, 0.1f);
34                                     }
35                                     break;
36                                 }
37                             }
38                         }, AudioManager.STREAM_MUSIC,
39                         AudioManager.AUDIOFOCUS_GAIN);
复制代码

   

   源码下载

 

总结 

  以上就讲解了MediaPlayer的一些高级的内容,在掌握了MediaPlayer的使用之后,开发有关音乐播放类的应用的时候就可以得心应手了。从用户体验的方面出发,如果真实开发一款播放器类的软件,需要监听AUDIO_BECOMING_NOISY的广播,它会在音频输出源从其他输出源变换到设备扬声器的时候发出此广播,监听广播在音频输出源改变到设备扬声器的时候,停止播放,这样确保在耳机或额外的音频输出硬件与设备断开连接的时候,不至于重新从扬声器继续输出音频播放。

相关文章
|
7天前
|
编解码 Java Android开发
通义灵码:在安卓开发中提升工作效率的真实应用案例
本文介绍了通义灵码在安卓开发中的应用。作为一名97年的聋人开发者,我在2024年Google Gemma竞赛中获得了冠军,拿下了很多项目竞赛奖励,通义灵码成为我的得力助手。文章详细展示了如何安装通义灵码插件,并通过多个实例说明其在适配国际语言、多种分辨率、业务逻辑开发和编程语言转换等方面的应用,显著提高了开发效率和准确性。
|
6天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
19 5
|
5天前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!
|
6天前
|
缓存 数据库 Android开发
安卓开发中的性能优化技巧
【10月更文挑战第29天】在移动应用的海洋中,性能是船只能否破浪前行的关键。本文将深入探讨安卓开发中的性能优化策略,从代码层面到系统层面,揭示如何让应用运行得更快、更流畅。我们将以实际案例和最佳实践为灯塔,引领开发者避开性能瓶颈的暗礁。
18 3
|
8天前
|
存储 IDE 开发工具
探索Android开发之旅:从新手到专家
【10月更文挑战第26天】在这篇文章中,我们将一起踏上一段激动人心的旅程,探索如何在Android平台上从零开始,最终成为一名熟练的开发者。通过简单易懂的语言和实际代码示例,本文将引导你了解Android开发的基础知识、关键概念以及如何实现一个基本的应用程序。无论你是编程新手还是希望扩展你的技术栈,这篇文章都将为你提供价值和启发。让我们开始吧!
|
14天前
|
Java API Android开发
安卓应用程序开发的新手指南:从零开始构建你的第一个应用
【10月更文挑战第20天】在这个数字技术不断进步的时代,掌握移动应用开发技能无疑打开了一扇通往创新世界的大门。对于初学者来说,了解并学习如何从无到有构建一个安卓应用是至关重要的第一步。本文将为你提供一份详尽的入门指南,帮助你理解安卓开发的基础知识,并通过实际示例引导你完成第一个简单的应用项目。无论你是编程新手还是希望扩展你的技能集,这份指南都将是你宝贵的资源。
43 5
|
13天前
|
设计模式 IDE Java
探索安卓开发:从新手到专家的旅程
【10月更文挑战第22天】 在数字时代的浪潮中,移动应用开发如同一座金矿,吸引着无数探险者。本文将作为你的指南针,指引你进入安卓开发的广阔天地。我们将一起揭开安卓平台的神秘面纱,从搭建开发环境到掌握核心概念,再到深入理解安卓架构。无论你是初涉编程的新手,还是渴望进阶的开发者,这段旅程都将为你带来宝贵的知识和经验的财富。让我们开始吧!
|
3天前
|
移动开发 Java Android开发
探索Android与iOS开发的差异性与互联性
【10月更文挑战第32天】在移动开发的大潮中,Android和iOS两大平台各领风骚。本文将深入浅出地探讨这两个平台的开发差异,并通过实际代码示例,展示如何在各自平台上实现相似的功能。我们将从开发环境、编程语言、用户界面设计、性能优化等多个角度进行对比分析,旨在为开发者提供跨平台开发的实用指南。
20 0
|
13天前
|
搜索推荐 Android开发 UED
安卓开发中的自定义视图:打造个性化用户界面
【10月更文挑战第22天】在安卓应用的海洋中,如何让你的应用脱颖而出?一个独特且直观的用户界面(UI)至关重要。本文将引导你通过自定义视图来打造个性化的用户体验,从基础的视图绘制到触摸事件的处理,我们将一步步深入探讨。准备好了吗?让我们开始吧!
|
13天前
|
Android开发
我是一位Android工程师,用通义灵码的AS插件做开发工作助手,对比之前没有灵码,现在提效了60%
我是一位Android工程师,用通义灵码的AS插件做开发工作助手,对比之前没有灵码,现在提效了60%
29 0