摘要:在線程中,在中創建線程在線程中通過播放饒了一大圈竟然也用來播放就是從傳下來的需要注意,通知音是會申請焦點的。總結從創建到播放的流程基本就這樣,至于聲音的區分是否電話中,如果則使用播放,反之播放。
前言
我們在做Android開發的時候,免不了會使用到Notification,而且在android設備的設置中還可以設置通知音的優先級,以及播放的聲音種類。那么通知音是如何播放的呢,今天我們就來談談這個。
Notification的使用NotificationManager notificationManager=(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); //重點:先創建通知渠道 if(android.os.Build.VERSION.SDK_INT>=android.os.Build.VERSION_CODES.O){ NotificationChannel mChannel=new NotificationChannel(getString(R.string.app_name),getString(R.string.app_name),NotificationManager.IMPORTANCE_MAX); NotificationChannel channel=new NotificationChannel(channelId, channelName,NotificationManager.IMPORTANCE_DEFAULT); channel.enableLights(true); //設置開啟指示燈,如果設備有的話 channel.setLightColor(Color.RED); //設置指示燈顏色 channel.setShowBadge(true); //設置是否顯示角標 channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);//設置是否應在鎖定屏幕上顯示此頻道的通知 channel.setDescription(channelDescription);//設置渠道描述 channel.setVibrationPattern(new long[]{100,200,300,400,500,600});//設置震動頻率 channel.setBypassDnd(true);//設置是否繞過免打擾模式 notificationManager.createNotificationChannel(mChannel); } //再創建通知 NotificationCompat.Builder builder=new NotificationCompat.Builder(this,getString(R.string.app_name)); //設置通知欄大圖標,上圖中右邊的大圖 builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)) // 設置狀態欄和通知欄小圖標 .setSmallIcon(R.drawable.ic_launcher_background) // 設置通知欄應用名稱 .setTicker("通知欄應用名稱") // 設置通知欄顯示時間 .setWhen(System.currentTimeMillis()) // 設置通知欄標題 .setContentTitle("通知欄標題") // 設置通知欄內容 .setContentText("通知欄內") // 設置通知欄點擊后是否清除,設置為true,當點擊此通知欄后,它會自動消失 .setAutoCancel(false) // 將Ongoing設為true 那么左滑右滑將不能刪除通知欄 .setOngoing(true) // 設置通知欄點擊意圖 .setContentIntent(pendingIntent) // 鈴聲、閃光、震動均系統默認 .setDefaults(Notification.DEFAULT_ALL) //設置通知時間 .setWhen(System.currentTimeMillis()) // 設置為public后,通知欄將在鎖屏界面顯示 .setVisibility(NotificationCompat.VISIBILITY_PRIVATE); //發送通知 notificationManager.notify(10, builder.build());
ANdroid O主要增加了NotificationChannel,詳細用法可參照其API。
正文那么就從發送通知的notify開始入手
public void notify(String tag, int id, Notification notification) { //當我們調用Notification的notify()發送通知時,會繼續調到notifyAsUser notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId())); } public void notifyAsUser(String tag, int id, Notification notification, UserHandle user) { //得到NotificationManagerService INotificationManager service = getService(); //………… ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); boolean isLowRam = am.isLowRamDevice(); final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam); try { //把Nofitication copy到了NofificationManagerservie中 service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, copy, user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
而enqueueNotificationWithTag()又調用了 enqueueNotificationInternal()
在enqueueNotificationInternal()中需要注意下面這行代碼:
final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
我們來看下NotificationRecord的初始化,在NotificationRecoder的構造方法中
mAttributes = calculateAttributes();
mAttributes 是不是很熟悉,就是audio播放時需要傳入的那個AudioAttributes,在
calculateAttributes()中會取我們在創建channel時傳入的AudioAttributes,如果沒有則使用默認的,如果ANdroid O之前的版本,沒有channel,則會使用Notification中默認的AudioAttributes,(NotificationChannel中的AudioAttributes是通過setSounde()方法設置下來的)。默認的AudioAttributes
是什么呢?就是
//通知中默認的AudioAttributes public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_NOTIFICATION) .build();
在enqueueNotificationInternal()中有將notificationRecord放到了EnqueueNotificationRunnable中線程運行代碼如下:
mHandler.post(new EnqueueNotificationRunnable(userId, r));
而在EnqueueNotificationRunnable線程中又調用的PostNotificationRunnable線程中執行,代碼如下
mHandler.post(new PostNotificationRunnable(r.getKey()));
在PostNotificationRunnable中 通過buzzBeepBlinkLocked(r)方法播放
if (hasValidSound) { mSoundNotificationKey = key; //如果電話中則playInCallNotification() if (mInCall) { playInCallNotification(); beep = true; } else { //否則調用playSound(), beep = playSound(record, soundUri); } }
無論哪個方法方法都是一樣的,只是 AudioAttributes不同,playInCallNotification()使用的mInCallNotificationAudioAttributes即
mInCallNotificationAudioAttributes = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) .build();
而playSound()使用的AudioAttributes如果未通過channel傳入,則使用上面提到的默認的,那么看看到底是如何播放的呢,通知音的播放是通過
final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
來播放的,代碼略過,簡單說下RingTonePlayer的play()和playerAsync()這倆方法,還是有點區別的,play使用的Ringtone來播放的,源碼:
client.mRingtone.setLooping(looping); client.mRingtone.setVolume(volume); client.mRingtone.play();
而playerAsync()是通過NotificationPlayer來播放的,對于NotificationPlayer的play()會通過enqueueLocked()創建CmdThread線程。在CmdThread線程中startSound(),在startSound()中創建CreationAndCompletionThread線程
mCompletionThread = new CreationAndCompletionThread(cmd); synchronized (mCompletionThread) { mCompletionThread.start(); mCompletionThread.wait(); }
在CreationAndCompletionThread線程中通過mediaplayer播放
private final class CreationAndCompletionThread extends Thread { public Command mCmd; public CreationAndCompletionThread(Command cmd) { super(); mCmd = cmd; } public void run() { Looper.prepare(); // ok to modify mLooper as here we are // synchronized on mCompletionHandlingLock due to the Object.wait() in startSound(cmd) mLooper = Looper.myLooper(); if (DEBUG) Log.d(mTag, "in run: new looper " + mLooper); synchronized(this) { AudioManager audioManager = (AudioManager) mCmd.context.getSystemService(Context.AUDIO_SERVICE); try { //饒了一大圈竟然也用mediaplayer來播放 MediaPlayer player = new MediaPlayer(); //attributes 就是從NotificationChannel傳下來的attributes if (mCmd.attributes == null) { mCmd.attributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_NOTIFICATION) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build(); } player.setAudioAttributes(mCmd.attributes); player.setDataSource(mCmd.context, mCmd.uri); player.setLooping(mCmd.looping); player.setOnCompletionListener(NotificationPlayer.this); player.setOnErrorListener(NotificationPlayer.this); player.prepare(); if ((mCmd.uri != null) && (mCmd.uri.getEncodedPath() != null) && (mCmd.uri.getEncodedPath().length() > 0)) { if (!audioManager.isMusicActiveRemotely()) { synchronized (mQueueAudioFocusLock) { if (mAudioManagerWithAudioFocus == null) { if (DEBUG) Log.d(mTag, "requesting AudioFocus"); int focusGain = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK; if (mCmd.looping) { focusGain = AudioManager.AUDIOFOCUS_GAIN; } mNotificationRampTimeMs = audioManager.getFocusRampTimeMs( focusGain, mCmd.attributes); //需要注意i,通知音是會申請焦點的。 audioManager.requestAudioFocus(null, mCmd.attributes, focusGain, 0); mAudioManagerWithAudioFocus = audioManager; } else { if (DEBUG) Log.d(mTag, "AudioFocus was previously requested"); } } } } // FIXME Having to start a new thread so we can receive completion callbacks // is wrong, as we kill this thread whenever a new sound is to be played. This // can lead to AudioFocus being released too early, before the second sound is // done playing. This class should be modified to use a single thread, on which // command are issued, and on which it receives the completion callbacks. if (DEBUG) { Log.d(mTag, "notification will be delayed by " + mNotificationRampTimeMs + "ms"); } try { Thread.sleep(mNotificationRampTimeMs); player.start(); } catch (InterruptedException e) { Log.e(mTag, "Exception while sleeping to sync notification playback" + " with ducking", e); } if (DEBUG) { Log.d(mTag, "player.start"); } if (mPlayer != null) { if (DEBUG) { Log.d(mTag, "mPlayer.release"); } mPlayer.release(); } mPlayer = player; } catch (Exception e) { Log.w(mTag, "error loading sound for " + mCmd.uri, e); } this.notify(); } Looper.loop(); } };
到此over,代碼邏輯很復雜,涉及的類也比較多,有興趣的可以去看看源碼,我就不多說了。
總結Notification從創建到播放的流程基本就這樣,至于聲音的區分是否電話中,如果incall則使用RingTone播放,反之mediaplayer播放。
而使用的attributes也區分是否incall。
以上。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72166.html
摘要:前言當我們點擊屏幕按鍵時,就會聽到音,那么音是如何播放起來的呢,由于最近項目需求順便熟悉下了音的邏輯。完成的繪制過程,包括過程。向分發收到的用戶發起的事件,如按鍵,觸屏等事件。總結音的流程就簡單分析到這里,歡迎大家交流指正。 前言 當我們點擊屏幕按鍵時,就會聽到touch音,那么touch音是如何播放起來的呢,由于最近項目需求順便熟悉下了touch音的邏輯。 正文 談touch邏輯首先...
摘要:當向系統發出通知時,它將先以圖標的形式顯示在通知欄中。通知欄和抽屜式通知欄均是由系統控制,用戶可以隨時查看。更新通知跟發送通知使用相同的方式。創建返回棧添加返回棧代碼默認情況下,從通知啟動一個,按返回鍵會回到主屏幕。 目錄介紹 1.Notification簡單概述 2.Notification通知用途 3.Notification的基本操作 3.1 Notification創建必要的...
摘要:實現瀏覽器的閃爍滾動聲音提示等系統彈出通知。它沒有依賴,壓縮只有只有,實例預覽。下載使用有消息了。文字的方向它的值可以是自動從左到右從右到左。一個圖片的,將被用于顯示通知的圖標。當用戶關閉通知時被觸發。 JS 實現瀏覽器的 title 閃爍、滾動、聲音提示、chrome、Firefox、Safari等系統彈出通知。它沒有依賴,壓縮只有只有4.66kb(gzipped: 1.70kb),...
閱讀 2212·2021-11-22 13:52
閱讀 3847·2021-11-10 11:36
閱讀 1380·2021-09-24 09:47
閱讀 1088·2019-08-29 13:54
閱讀 3360·2019-08-29 13:46
閱讀 1942·2019-08-29 12:16
閱讀 2108·2019-08-26 13:26
閱讀 3471·2019-08-23 17:10