Android中Notification的framework层讲解【安卓源码解析四】
android的notificaiton的声音sound也是申请的AudioManager机制来播放声音的。最近让我找恢复出厂设置后,手机刚启动,接受短信没有声音,如果恢复出厂设置后,等一会儿,过个2分钟再接受短信,就有铃声了。下面我把我分析代码的方法写下来,给自己和读者一些启发:
日历也是用的是Notification,但是恢复出厂设置后,立马设置日历后,日历可以出声音,我看日历的代码,结果发现日历只是用了Notification的闪屏,真正声音是日历自己实现了Mediaplayer来出声音的。所以我又不得不老老实实地研究Notification.sound到底把声音传递到什么地方去了?
转载请标明出处:http://blog.csdn.net/wdaming1986/article/details/7081787
首先,我在短信的com.android.mms.transaction包中的MessagingNotification的573行的java代码:notification.sound = TextUtils.isEmpty(ringtoneStr) ? null : Uri.parse(ringtoneStr);打log查看,发现这个uri确实是存在的,我推测:就是说这个uri传给了framework一层,但是framework一层有没有执行完的动作,所以不响。为了验证是不是短信的错误,我自己单独写了个notification的例子,一个按钮,点击就发出声音。这个例子在正常情况下能正常发声。我就恢复出厂设置后,运行这个例子,结果发现没有声音,这就充分验证了我的猜测。我就去framework去着notificaion.sound = 的声音传递给framework做什么事情了??
接着,在Source Insight软件中导入framework整个工程,然后搜索,notificaiton.sounds,结果搜到了,在framework/base/core/java/android/app/Notification.java类。
- /*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package android.app;
- import java.util.Date;
- import android.app.PendingIntent;
- import android.content.Context;
- import android.content.Intent;
- import android.media.AudioManager;
- import android.net.Uri;
- import android.os.Parcel;
- import android.os.Parcelable;
- import android.text.TextUtils;
- import android.text.format.DateFormat;
- import android.text.format.DateUtils;
- import android.widget.RemoteViews;
- /**
- * A class that represents how a persistent notification is to be presented to
- * the user using the {@link android.app.NotificationManager}.
- *
- * <p>For a guide to creating notifications, see the
- * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Creating Status
- * Bar Notifications</a> document in the Dev Guide.</p>
- */
- public class Notification implements Parcelable
- {
- /**
- * Use all default values (where applicable).
- */
- public static final int DEFAULT_ALL = ~0;
- /**
- * Use the default notification sound. This will ignore any given
- * {@link #sound}.
- *
- * @see #defaults
- */
- public static final int DEFAULT_SOUND = 1;
- /**
- * Use the default notification vibrate. This will ignore any given
- * {@link #vibrate}. Using phone vibration requires the
- * {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
- *
- * @see #defaults
- */
- public static final int DEFAULT_VIBRATE = 2;
- /**
- * Use the default notification lights. This will ignore the
- * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or
- * {@link #ledOnMS}.
- *
- * @see #defaults
- */
- public static final int DEFAULT_LIGHTS = 4;
- /**
- * The timestamp for the notification. The icons and expanded views
- * are sorted by this key.
- */
- public long when;
- /**
- * The resource id of a drawable to use as the icon in the status bar.
- */
- public int icon;
- /**
- * The number of events that this notification represents. For example, in a new mail
- * notification, this could be the number of unread messages. This number is superimposed over
- * the icon in the status bar. If the number is 0 or negative, it is not shown in the status
- * bar.
- */
- public int number;
- /**
- * The intent to execute when the expanded status entry is clicked. If
- * this is an activity, it must include the
- * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
- * that you take care of task management as described in the <em>Activities and Tasks</em>
- * section of the <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application
- * Fundamentals</a> document.
- */
- public PendingIntent contentIntent;
- /**
- * The intent to execute when the status entry is deleted by the user
- * with the "Clear All Notifications" button. This probably shouldn't
- * be launching an activity since several of those will be sent at the
- * same time.
- */
- public PendingIntent deleteIntent;
- /**
- * An intent to launch instead of posting the notification to the status bar.
- * Only for use with extremely high-priority notifications demanding the user's
- * <strong>immediate</strong> attention, such as an incoming phone call or
- * alarm clock that the user has explicitly set to a particular time.
- * If this facility is used for something else, please give the user an option
- * to turn it off and use a normal notification, as this can be extremely
- * disruptive.
- */
- public PendingIntent fullScreenIntent;
- /**
- * Text to scroll across the screen when this item is added to
- * the status bar.
- */
- public CharSequence tickerText;
- /**
- * The view that will represent this notification in the expanded status bar.
- */
- public RemoteViews contentView;
- /**
- * If the icon in the status bar is to have more than one level, you can set this. Otherwise,
- * leave it at its default value of 0.
- *
- * @see android.widget.ImageView#setImageLevel
- * @see android.graphics.drawable#setLevel
- */
- public int iconLevel;
- /**
- * The sound to play.
- *
- * <p>
- * To play the default notification sound, see {@link #defaults}.
- * </p>
- */
- public Uri sound;
- /**
- * Use this constant as the value for audioStreamType to request that
- * the default stream type for notifications be used. Currently the
- * default stream type is STREAM_RING.
- */
- public static final int STREAM_DEFAULT = -1;
- /**
- * The audio stream type to use when playing the sound.
- * Should be one of the STREAM_ constants from
- * {@link android.media.AudioManager}.
- */
- public int audioStreamType = STREAM_DEFAULT;
- /**
- * The pattern with which to vibrate.
- *
- * <p>
- * To vibrate the default pattern, see {@link #defaults}.
- * </p>
- *
- * @see android.os.Vibrator#vibrate(long[],int)
- */
- public long[] vibrate;
- /**
- * The color of the led. The hardware will do its best approximation.
- *
- * @see #FLAG_SHOW_LIGHTS
- * @see #flags
- */
- public int ledARGB;
- /**
- * The number of milliseconds for the LED to be on while it's flashing.
- * The hardware will do its best approximation.
- *
- * @see #FLAG_SHOW_LIGHTS
- * @see #flags
- */
- public int ledOnMS;
- /**
- * The number of milliseconds for the LED to be off while it's flashing.
- * The hardware will do its best approximation.
- *
- * @see #FLAG_SHOW_LIGHTS
- * @see #flags
- */
- public int ledOffMS;
- /**
- * Specifies which values should be taken from the defaults.
- * <p>
- * To set, OR the desired from {@link #DEFAULT_SOUND},
- * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default
- * values, use {@link #DEFAULT_ALL}.
- * </p>
- */
- public int defaults;
- /**
- * Bit to be bitwise-ored into the {@link #flags} field that should be
- * set if you want the LED on for this notification.
- * <ul>
- * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB
- * or 0 for both ledOnMS and ledOffMS.</li>
- * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li>
- * <li>To flash the LED, pass the number of milliseconds that it should
- * be on and off to ledOnMS and ledOffMS.</li>
- * </ul>
- * <p>
- * Since hardware varies, you are not guaranteed that any of the values
- * you pass are honored exactly. Use the system defaults (TODO) if possible
- * because they will be set to values that work on any given hardware.
- * <p>
- * The alpha channel must be set for forward compatibility.
- *
- */
- public static final int FLAG_SHOW_LIGHTS = 0x00000001;
- /**
- * Bit to be bitwise-ored into the {@link #flags} field that should be
- * set if this notification is in reference to something that is ongoing,
- * like a phone call. It should not be set if this notification is in
- * reference to something that happened at a particular point in time,
- * like a missed phone call.
- */
- public static final int FLAG_ONGOING_EVENT = 0x00000002;
- /**
- * Bit to be bitwise-ored into the {@link #flags} field that if set,
- * the audio will be repeated until the notification is
- * cancelled or the notification window is opened.
- */
- public static final int FLAG_INSISTENT = 0x00000004;
- /**
- * Bit to be bitwise-ored into the {@link #flags} field that should be
- * set if you want the sound and/or vibration play each time the
- * notification is sent, even if it has not been canceled before that.
- */
- public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008;
- /**
- * Bit to be bitwise-ored into the {@link #flags} field that should be
- * set if the notification should be canceled when it is clicked by the
- * user.
- */
- public static final int FLAG_AUTO_CANCEL = 0x00000010;
- /**
- * Bit to be bitwise-ored into the {@link #flags} field that should be
- * set if the notification should not be canceled when the user clicks
- * the Clear all button.
- */
- public static final int FLAG_NO_CLEAR = 0x00000020;
- /**
- * Bit to be bitwise-ored into the {@link #flags} field that should be
- * set if this notification represents a currently running service. This
- * will normally be set for you by {@link Service#startForeground}.
- */
- public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
- public int flags;
- /**
- * Constructs a Notification object with everything set to 0.
- */
- public Notification()
- {
- this.when = System.currentTimeMillis();
- }
- /**
- * @deprecated use {@link #Notification(int,CharSequence,long)} and {@link #setLatestEventInfo}.
- * @hide
- */
- public Notification(Context context, int icon, CharSequence tickerText, long when,
- CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
- {
- this.when = when;
- this.icon = icon;
- this.tickerText = tickerText;
- setLatestEventInfo(context, contentTitle, contentText,
- PendingIntent.getActivity(context, 0, contentIntent, 0));
- }
- /**
- * Constructs a Notification object with the information needed to
- * have a status bar icon without the standard expanded view.
- *
- * @param icon The resource id of the icon to put in the status bar.
- * @param tickerText The text that flows by in the status bar when the notification first
- * activates.
- * @param when The time to show in the time field. In the System.currentTimeMillis
- * timebase.
- */
- public Notification(int icon, CharSequence tickerText, long when)
- {
- this.icon = icon;
- this.tickerText = tickerText;
- this.when = when;
- }
- /**
- * Unflatten the notification from a parcel.
- */
- public Notification(Parcel parcel)
- {
- int version = parcel.readInt();
- when = parcel.readLong();
- icon = parcel.readInt();
- number = parcel.readInt();
- if (parcel.readInt() != 0) {
- contentIntent = PendingIntent.CREATOR.createFromParcel(parcel);
- }
- if (parcel.readInt() != 0) {
- deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
- }
- if (parcel.readInt() != 0) {
- tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
- }
- if (parcel.readInt() != 0) {
- contentView = RemoteViews.CREATOR.createFromParcel(parcel);
- }
- defaults = parcel.readInt();
- flags = parcel.readInt();
- if (parcel.readInt() != 0) {
- sound = Uri.CREATOR.createFromParcel(parcel);
- }
- audioStreamType = parcel.readInt();
- vibrate = parcel.createLongArray();
- ledARGB = parcel.readInt();
- ledOnMS = parcel.readInt();
- ledOffMS = parcel.readInt();
- iconLevel = parcel.readInt();
- if (parcel.readInt() != 0) {
- fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel);
- }
- }
- public Notification clone() {
- Notification that = new Notification();
- that.when = this.when;
- that.icon = this.icon;
- that.number = this.number;
- // PendingIntents are global, so there's no reason (or way) to clone them.
- that.contentIntent = this.contentIntent;
- that.deleteIntent = this.deleteIntent;
- that.fullScreenIntent = this.fullScreenIntent;
- if (this.tickerText != null) {
- that.tickerText = this.tickerText.toString();
- }
- if (this.contentView != null) {
- that.contentView = this.contentView.clone();
- }
- that.iconLevel = that.iconLevel;
- that.sound = this.sound; // android.net.Uri is immutable
- that.audioStreamType = this.audioStreamType;
- final long[] vibrate = this.vibrate;
- if (vibrate != null) {
- final int N = vibrate.length;
- final long[] vib = that.vibrate = new long[N];
- System.arraycopy(vibrate, 0, vib, 0, N);
- }
- that.ledARGB = this.ledARGB;
- that.ledOnMS = this.ledOnMS;
- that.ledOffMS = this.ledOffMS;
- that.defaults = this.defaults;
- that.flags = this.flags;
- return that;
- }
- public int describeContents() {
- return 0;
- }
- /**
- * Flatten this notification from a parcel.
- */
- public void writeToParcel(Parcel parcel, int flags)
- {
- parcel.writeInt(1);
- parcel.writeLong(when);
- parcel.writeInt(icon);
- parcel.writeInt(number);
- if (contentIntent != null) {
- parcel.writeInt(1);
- contentIntent.writeToParcel(parcel, 0);
- } else {
- parcel.writeInt(0);
- }
- if (deleteIntent != null) {
- parcel.writeInt(1);
- deleteIntent.writeToParcel(parcel, 0);
- } else {
- parcel.writeInt(0);
- }
- if (tickerText != null) {
- parcel.writeInt(1);
- TextUtils.writeToParcel(tickerText, parcel, flags);
- } else {
- parcel.writeInt(0);
- }
- if (contentView != null) {
- parcel.writeInt(1);
- contentView.writeToParcel(parcel, 0);
- } else {
- parcel.writeInt(0);
- }
- parcel.writeInt(defaults);
- parcel.writeInt(this.flags);
- if (sound != null) {
- parcel.writeInt(1);
- sound.writeToParcel(parcel, 0);
- } else {
- parcel.writeInt(0);
- }
- parcel.writeInt(audioStreamType);
- parcel.writeLongArray(vibrate);
- parcel.writeInt(ledARGB);
- parcel.writeInt(ledOnMS);
- parcel.writeInt(ledOffMS);
- parcel.writeInt(iconLevel);
- if (fullScreenIntent != null) {
- parcel.writeInt(1);
- fullScreenIntent.writeToParcel(parcel, 0);
- } else {
- parcel.writeInt(0);
- }
- }
- /**
- * Parcelable.Creator that instantiates Notification objects
- */
- public static final Parcelable.Creator<Notification> CREATOR
- = new Parcelable.Creator<Notification>()
- {
- public Notification createFromParcel(Parcel parcel)
- {
- return new Notification(parcel);
- }
- public Notification[] newArray(int size)
- {
- return new Notification[size];
- }
- };
- /**
- * Sets the {@link #contentView} field to be a view with the standard "Latest Event"
- * layout.
- *
- * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
- * in the view.</p>
- * @param context The context for your application / activity.
- * @param contentTitle The title that goes in the expanded entry.
- * @param contentText The text that goes in the expanded entry.
- * @param contentIntent The intent to launch when the user clicks the expanded notification.
- * If this is an activity, it must include the
- * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
- * that you take care of task management as described in
- * <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">Application Fundamentals: Activities and Tasks</a>.
- */
- public void setLatestEventInfo(Context context,
- CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
- RemoteViews contentView = new RemoteViews(context.getPackageName(),
- com.android.internal.R.layout.status_bar_latest_event_content);
- if (this.icon != 0) {
- contentView.setImageViewResource(com.android.internal.R.id.icon, this.icon);
- }
- if (contentTitle != null) {
- contentView.setTextViewText(com.android.internal.R.id.title, contentTitle);
- }
- if (contentText != null) {
- contentView.setTextViewText(com.android.internal.R.id.text, contentText);
- }
- if (this.when != 0) {
- contentView.setLong(com.android.internal.R.id.time, "setTime", when);
- }
- this.contentView = contentView;
- this.contentIntent = contentIntent;
- }
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("Notification(vibrate=");
- if (this.vibrate != null) {
- int N = this.vibrate.length-1;
- sb.append("[");
- for (int i=0; i<N; i++) {
- sb.append(this.vibrate[i]);
- sb.append(',');
- }
- if (N != -1) {
- sb.append(this.vibrate[N]);
- }
- sb.append("]");
- } else if ((this.defaults & DEFAULT_VIBRATE) != 0) {
- sb.append("default");
- } else {
- sb.append("null");
- }
- sb.append(",sound=");
- if (this.sound != null) {
- sb.append(this.sound.toString());
- } else if ((this.defaults & DEFAULT_SOUND) != 0) {
- sb.append("default");
- } else {
- sb.append("null");
- }
- sb.append(",defaults=0x");
- sb.append(Integer.toHexString(this.defaults));
- sb.append(",flags=0x");
- sb.append(Integer.toHexString(this.flags));
- sb.append(")");
- return sb.toString();
- }
- }
在344行, sound = Uri.CREATOR.createFromParcel(parcel);回来经过打log发现,问题的关键不再这个类中。
再次,改变方向,换条思路走,找notification的服务类,看有什么新的发现,在framework/base/services/java/com/android/server/NotificationManagerService.java类中,真的找到了我需要的,
在871行有这句代码:uri = notification.sound;在886行有这句代码:mSound.play(mContext, uri, looping, audioStreamType);当时我就有点兴奋了,感觉这就是问题的关键,然后打log,发现这就是问题的关键,当notification正常发声音的时候,这个886行的代码走进来了,不发声音的时候这个代码没有走进来,所以我离问题的根源又进了一步。最后发现是包着这段代码的if语句中的判断引起的,所以我把if语句中的代码都打印出来,发现是mDisabledNotifications这个变量引起的,我就追踪这个mDisabledNotifications变量值的变化的地方。发现在467行有这段代码:
研究以上的注释,发现原来google故意这么设置的,至于google为什么要这么设置,我没有深究,暂时没有想明白,但是这个这个初始化的时候必须要tsetup wizard (设置向导)运行一次,所以导致了值不对,所以这个notification就不响了。
在264行有这段代码对mDisabledNotification进行改变的:
找到问题的根源了,太兴奋了,这个问题断断续续困扰了我3周,终于经过我3天地认真分析,把问题的根源找到了,要想解决就很简单了,可以在初始化的时候直接赋值为0就ok了!
最后、886行mSound.play(mContext, uri, looping, audioStreamType);是NotificationPlayer类中的一个方法,在play()方法中有一个线程, mThread = new CmdThread(); mThread.start();线程中的run方法中有:case PLAY:startSound(cmd);在startSound()方法中又有一个线程: mCompletionThread = new CreationAndCompletionThread(cmd);
在这个线程类中的run方法中:在这个线程中进行播放音乐的 ,真正的发声音也是通过Mediapaly来实现的:
希望给读者留下点启发,转载请标明出处:http://blog.csdn.net/wdaming1986/article/details/7081787