资讯专栏INFORMATION COLUMN

浅谈Android O Notification声音播放流程

huhud / 1136人阅读

摘要:在线程中,在中创建线程在线程中通过播放饶了一大圈竟然也用来播放就是从传下来的需要注意,通知音是会申请焦点的。总结从创建到播放的流程基本就这样,至于声音的区分是否电话中,如果则使用播放,反之播放。

前言

我们在做Android开发的时候,免不了会使用到Notification,而且在android设备的设置中还可以设置通知音的优先级,以及播放的声音种类。那么通知音是如何播放的呢,今天我们就来谈谈这个。

Notification的使用

</>复制代码

  1. NotificationManager notificationManager=(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
  2. //重点:先创建通知渠道
  3. if(android.os.Build.VERSION.SDK_INT>=android.os.Build.VERSION_CODES.O){
  4. NotificationChannel mChannel=new NotificationChannel(getString(R.string.app_name),getString(R.string.app_name),NotificationManager.IMPORTANCE_MAX);
  5. NotificationChannel channel=new NotificationChannel(channelId,
  6. channelName,NotificationManager.IMPORTANCE_DEFAULT);
  7. channel.enableLights(true); //设置开启指示灯,如果设备有的话
  8. channel.setLightColor(Color.RED); //设置指示灯颜色
  9. channel.setShowBadge(true); //设置是否显示角标
  10. channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);//设置是否应在锁定屏幕上显示此频道的通知
  11. channel.setDescription(channelDescription);//设置渠道描述
  12. channel.setVibrationPattern(new long[]{100,200,300,400,500,600});//设置震动频率
  13. channel.setBypassDnd(true);//设置是否绕过免打扰模式
  14. notificationManager.createNotificationChannel(mChannel);
  15. }
  16. //再创建通知
  17. NotificationCompat.Builder builder=new NotificationCompat.Builder(this,getString(R.string.app_name));
  18. //设置通知栏大图标,上图中右边的大图
  19. builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
  20. // 设置状态栏和通知栏小图标
  21. .setSmallIcon(R.drawable.ic_launcher_background)
  22. // 设置通知栏应用名称
  23. .setTicker("通知栏应用名称")
  24. // 设置通知栏显示时间
  25. .setWhen(System.currentTimeMillis())
  26. // 设置通知栏标题
  27. .setContentTitle("通知栏标题")
  28. // 设置通知栏内容
  29. .setContentText("通知栏内")
  30. // 设置通知栏点击后是否清除,设置为true,当点击此通知栏后,它会自动消失
  31. .setAutoCancel(false)
  32. // 将Ongoing设为true 那么左滑右滑将不能删除通知栏
  33. .setOngoing(true)
  34. // 设置通知栏点击意图
  35. .setContentIntent(pendingIntent)
  36. // 铃声、闪光、震动均系统默认
  37. .setDefaults(Notification.DEFAULT_ALL)
  38. //设置通知时间
  39. .setWhen(System.currentTimeMillis())
  40. // 设置为public后,通知栏将在锁屏界面显示
  41. .setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
  42. //发送通知
  43. notificationManager.notify(10, builder.build());

ANdroid O主要增加了NotificationChannel,详细用法可参照其API。

正文

那么就从发送通知的notify开始入手

</>复制代码

  1. public void notify(String tag, int id, Notification notification)
  2. {
  3. //当我们调用Notification的notify()发送通知时,会继续调到notifyAsUser
  4. notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
  5. }
  6. public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
  7. {
  8. //得到NotificationManagerService
  9. INotificationManager service = getService();
  10. //…………
  11. ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
  12. boolean isLowRam = am.isLowRamDevice();
  13. final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam);
  14. try {
  15. //把Nofitication copy到了NofificationManagerservie中
  16. service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
  17. copy, user.getIdentifier());
  18. } catch (RemoteException e) {
  19. throw e.rethrowFromSystemServer();
  20. }
  21. }

而enqueueNotificationWithTag()又调用了 enqueueNotificationInternal()
在enqueueNotificationInternal()中需要注意下面这行代码:

</>复制代码

  1. final NotificationRecord r = new NotificationRecord(getContext(), n, channel);

我们来看下NotificationRecord的初始化,在NotificationRecoder的构造方法中

</>复制代码

  1. mAttributes = calculateAttributes();

mAttributes 是不是很熟悉,就是audio播放时需要传入的那个AudioAttributes,在

calculateAttributes()中会取我们在创建channel时传入的AudioAttributes,如果没有则使用默认的,如果ANdroid O之前的版本,没有channel,则会使用Notification中默认的AudioAttributes,(NotificationChannel中的AudioAttributes是通过setSounde()方法设置下来的)。默认的AudioAttributes
是什么呢?就是

</>复制代码

  1. //通知中默认的AudioAttributes
  2. public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder()
  3. .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
  4. .setUsage(AudioAttributes.USAGE_NOTIFICATION)
  5. .build();

在enqueueNotificationInternal()中有将notificationRecord放到了EnqueueNotificationRunnable中线程运行代码如下:

</>复制代码

  1. mHandler.post(new EnqueueNotificationRunnable(userId, r));

而在EnqueueNotificationRunnable线程中又调用的PostNotificationRunnable线程中执行,代码如下

</>复制代码

  1. mHandler.post(new PostNotificationRunnable(r.getKey()));

在PostNotificationRunnable中 通过buzzBeepBlinkLocked(r)方法播放

</>复制代码

  1. if (hasValidSound) {
  2. mSoundNotificationKey = key;
  3. //如果电话中则playInCallNotification()
  4. if (mInCall) {
  5. playInCallNotification();
  6. beep = true;
  7. } else {
  8. //否则调用playSound(),
  9. beep = playSound(record, soundUri);
  10. }
  11. }

无论哪个方法方法都是一样的,只是 AudioAttributes不同,playInCallNotification()使用的mInCallNotificationAudioAttributes即

</>复制代码

  1. mInCallNotificationAudioAttributes = new AudioAttributes.Builder()
  2. .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
  3. .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
  4. .build();

而playSound()使用的AudioAttributes如果未通过channel传入,则使用上面提到的默认的,那么看看到底是如何播放的呢,通知音的播放是通过

</>复制代码

  1. final IRingtonePlayer player = mAudioManager.getRingtonePlayer();

来播放的,代码略过,简单说下RingTonePlayer的play()和playerAsync()这俩方法,还是有点区别的,play使用的Ringtone来播放的,源码:

</>复制代码

  1. client.mRingtone.setLooping(looping);
  2. client.mRingtone.setVolume(volume);
  3. client.mRingtone.play();

而playerAsync()是通过NotificationPlayer来播放的,对于NotificationPlayer的play()会通过enqueueLocked()创建CmdThread线程。在CmdThread线程中startSound(),在startSound()中创建CreationAndCompletionThread线程

</>复制代码

  1. mCompletionThread = new CreationAndCompletionThread(cmd);
  2. synchronized (mCompletionThread) {
  3. mCompletionThread.start();
  4. mCompletionThread.wait();
  5. }

在CreationAndCompletionThread线程中通过mediaplayer播放

</>复制代码

  1. private final class CreationAndCompletionThread extends Thread {
  2. public Command mCmd;
  3. public CreationAndCompletionThread(Command cmd) {
  4. super();
  5. mCmd = cmd;
  6. }
  7. public void run() {
  8. Looper.prepare();
  9. // ok to modify mLooper as here we are
  10. // synchronized on mCompletionHandlingLock due to the Object.wait() in startSound(cmd)
  11. mLooper = Looper.myLooper();
  12. if (DEBUG) Log.d(mTag, "in run: new looper " + mLooper);
  13. synchronized(this) {
  14. AudioManager audioManager =
  15. (AudioManager) mCmd.context.getSystemService(Context.AUDIO_SERVICE);
  16. try {
  17. //饶了一大圈竟然也用mediaplayer来播放
  18. MediaPlayer player = new MediaPlayer();
  19. //attributes 就是从NotificationChannel传下来的attributes
  20. if (mCmd.attributes == null) {
  21. mCmd.attributes = new AudioAttributes.Builder()
  22. .setUsage(AudioAttributes.USAGE_NOTIFICATION)
  23. .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
  24. .build();
  25. }
  26. player.setAudioAttributes(mCmd.attributes);
  27. player.setDataSource(mCmd.context, mCmd.uri);
  28. player.setLooping(mCmd.looping);
  29. player.setOnCompletionListener(NotificationPlayer.this);
  30. player.setOnErrorListener(NotificationPlayer.this);
  31. player.prepare();
  32. if ((mCmd.uri != null) && (mCmd.uri.getEncodedPath() != null)
  33. && (mCmd.uri.getEncodedPath().length() > 0)) {
  34. if (!audioManager.isMusicActiveRemotely()) {
  35. synchronized (mQueueAudioFocusLock) {
  36. if (mAudioManagerWithAudioFocus == null) {
  37. if (DEBUG) Log.d(mTag, "requesting AudioFocus");
  38. int focusGain = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
  39. if (mCmd.looping) {
  40. focusGain = AudioManager.AUDIOFOCUS_GAIN;
  41. }
  42. mNotificationRampTimeMs = audioManager.getFocusRampTimeMs(
  43. focusGain, mCmd.attributes);
  44. //需要注意i,通知音是会申请焦点的。
  45. audioManager.requestAudioFocus(null, mCmd.attributes,
  46. focusGain, 0);
  47. mAudioManagerWithAudioFocus = audioManager;
  48. } else {
  49. if (DEBUG) Log.d(mTag, "AudioFocus was previously requested");
  50. }
  51. }
  52. }
  53. }
  54. // FIXME Having to start a new thread so we can receive completion callbacks
  55. // is wrong, as we kill this thread whenever a new sound is to be played. This
  56. // can lead to AudioFocus being released too early, before the second sound is
  57. // done playing. This class should be modified to use a single thread, on which
  58. // command are issued, and on which it receives the completion callbacks.
  59. if (DEBUG) { Log.d(mTag, "notification will be delayed by "
  60. + mNotificationRampTimeMs + "ms"); }
  61. try {
  62. Thread.sleep(mNotificationRampTimeMs);
  63. player.start();
  64. } catch (InterruptedException e) {
  65. Log.e(mTag, "Exception while sleeping to sync notification playback"
  66. + " with ducking", e);
  67. }
  68. if (DEBUG) { Log.d(mTag, "player.start"); }
  69. if (mPlayer != null) {
  70. if (DEBUG) { Log.d(mTag, "mPlayer.release"); }
  71. mPlayer.release();
  72. }
  73. mPlayer = player;
  74. }
  75. catch (Exception e) {
  76. Log.w(mTag, "error loading sound for " + mCmd.uri, e);
  77. }
  78. this.notify();
  79. }
  80. Looper.loop();
  81. }
  82. };

到此over,代码逻辑很复杂,涉及的类也比较多,有兴趣的可以去看看源码,我就不多说了。

总结

Notification从创建到播放的流程基本就这样,至于声音的区分是否电话中,如果incall则使用RingTone播放,反之mediaplayer播放。
而使用的attributes也区分是否incall。

以上。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/72166.html

相关文章

  • 浅谈Android O Touch声音播放流程

    摘要:前言当我们点击屏幕按键时,就会听到音,那么音是如何播放起来的呢,由于最近项目需求顺便熟悉下了音的逻辑。完成的绘制过程,包括过程。向分发收到的用户发起的事件,如按键,触屏等事件。总结音的流程就简单分析到这里,欢迎大家交流指正。 前言 当我们点击屏幕按键时,就会听到touch音,那么touch音是如何播放起来的呢,由于最近项目需求顺便熟悉下了touch音的逻辑。 正文 谈touch逻辑首先...

    XiNGRZ 评论0 收藏0
  • Notification通知栏

    摘要:当向系统发出通知时,它将先以图标的形式显示在通知栏中。通知栏和抽屉式通知栏均是由系统控制,用户可以随时查看。更新通知跟发送通知使用相同的方式。创建返回栈添加返回栈代码默认情况下,从通知启动一个,按返回键会回到主屏幕。 目录介绍 1.Notification简单概述 2.Notification通知用途 3.Notification的基本操作 3.1 Notification创建必要的...

    FWHeart 评论0 收藏0
  • iNotify.js 2 实现浏览器的title闪烁滚动声音提示,弹出通知

    摘要:实现浏览器的闪烁滚动声音提示等系统弹出通知。它没有依赖,压缩只有只有,实例预览。下载使用有消息了。文字的方向它的值可以是自动从左到右从右到左。一个图片的,将被用于显示通知的图标。当用户关闭通知时被触发。 JS 实现浏览器的 title 闪烁、滚动、声音提示、chrome、Firefox、Safari等系统弹出通知。它没有依赖,压缩只有只有4.66kb(gzipped: 1.70kb),...

    fantix 评论0 收藏0

发表评论

0条评论

huhud

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<