流程一:
Android状态栏功能介绍
状态栏在Android手机中位于屏幕的最上方,通常在各种应用的最上方都会显示,也有个别,例如全屏的游戏将状态栏隐藏增加游戏的可视面积。状态栏的视图效果如下图:
如图所示,状态栏分为通知图标区域,状态图标区域,信号电量组合区域和时钟区域5个部分。
通知区域显示系统发出notification的ticker,有图标和文字的简短显示,类似于NBA中滚动的比分,不过此ticker的文字只在刚发送时显示一次。
状态图标区域显示系统设备的当前状态,例如打开了WIFI,蓝牙,GPS后,此区域都会显示相对应的图标。上图显示的就是打开的Wifi和SIM卡出错图标。
信号电量组合区域显示信号和电量的变化状态,当信号和电量发生变化后此处图标都会进行相应更新。在4.0中此区域信号部分修改为Wifi和手机信号的组合显示,用来节省状态栏的空间。
时钟区域按指定格式显示系统的当前时间。
状态栏的基本功能就是上面4大区域的更新显示,提示系统当前状况,显示当前系统存在的Notification消息。其中对Notification消息的处理比较特殊,除了在状态栏显示notification的ticker,还会在状态栏展开后,显示notification的详细信息,如下图:
多个notification会在此处列表显示,系统会根据notification产生的时间和优先级进行排序。每个notification还给我们提供了对产生notification应用的快捷操作,通过点击notification可以进入预先设定在notification中的点击事件,处理与此通知相关的事物。
个人认为状态栏是Android中设计非常出色的一个功能,通过简单明了的状态图标我们可以一目了然的观察到系统当前的状态,对于系统,其他应用发出的notification更是清楚简单的进行提示,并给出了对这些notification进一步处理的操作接口。
流程2:
SystemUI状态栏的启动过程:
状态栏的功能是以图标的简洁方式将设备的状态反馈在手机顶部的一小块条形区域。我们可以根据SystemUI的功能来猜想状态栏的运行方式,状态栏需要实时反馈系统状态,那么它就会长存系统中,在android中能长存系统运行的组件只有Service,状态栏就是一个长存系统进程空间运行的Service,它是一个系统服务。在android系统启动过程中,当ActivityManager这些系统服务启动完成后,在SystemServer中会启动SystemUIService(也就是状态栏服务),将状态栏服务在系统进程中长期运行。状态栏的代码在Android源码的路径是\frameworks\base\package\SystemUI, 我们看看SystemUI的类结构:
从类图中我们可以看到状态栏的核心类是StatusBar,它是一个抽象类,它的start方法定义了状态栏启动时的具体步骤。StatusBar继承自SystemUI,SystemUI是SystemUIService的子类(frameworks\base\packages\SystemUI\src\com\android\systemui),SystemUIService继承Service,所以StatusBar是一个Service。 StatusBar(frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar)实现了CommandQueue.CallBack接口,CommandQueue继承自IStatusBar.stub远程接口, 在CommandQueue中IStatusBar.stub接口的方法通过CommandQueue的Callback接口实现,StatusBar又是IStatusBar.stub远程接口的实现类。 因为StatusBar是抽象类,所以IStatusBar.stub接口方法的实现在StatusBar的两个子类PhoneStatusBar和TableteStatusBar中,这两个子类分别是手机和平板的状态栏操控类。
下面我们来看看SystemUI是怎么启动的。先看时序图:
一,我们知道Android的启动过程第二个init中是启动系统的框架服务,如我们熟悉的ActivityManagerService,PackageManagerService等,在这些服务启动完成后,SystemServer会启动SystemUI服务。在SystemServer中调用了startSystemUI()方法,此方法中通过context.start方法开始启动SystemUIService。
二,start启动service时会调用SystemUIService的onCreate()方法创建service
public void onCreate() {
// Pick status bar or system bar.
IWindowManager wm = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
try {
SERVICES[0] = wm.canStatusBarHide()
? R.string.config_statusBarComponent
: R.string.config_systemBarComponent;
} catch (RemoteException e) {
Slog.w(TAG, "Failing checking whether status bar can hide", e);
}
final int N = SERVICES.length;
mServices = new SystemUI[N];
for (int i=0; i
Slog.d(TAG, "loading: " + cl);
try {
mServices[i] = (SystemUI)cl.newInstance();
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
} catch (InstantiationException ex) {
throw new RuntimeException(ex);
}
mServices[i].mContext = this;
Slog.d(TAG, "running: " + mServices[i]);
mServices[i].start();
}
}
在创建Service时做了两步操作,一是判断设备是手机还是平板,确定具体的状态栏操控类。判断要使用的装态栏是通过wm.canStatusBarHide()获取,config_statusBarComponent的值是com.android.systemui.statusbar.phone.PhoneStatusBar,这是手机的状态栏,config_systemBarComponent的值是com.android.systemui.statusbar.tablet.TabletStatusBar,定义位于(frameworks\base\packages\SystemUI\res\values\),这是平板的状态栏。二是调用已确定SystemUI的start方法启动状态栏。
我们此处拿手机举例,所以下一步会调PhoneStatusBar的start方法。
三,调用PhoneStatusBar的start方法,此方法的主要操作是调用父类的start方法,因为状态栏的具体启动步骤在StatusBar的start方法中,这样也方便规定平板和手机按照同样的步骤启动状态栏,至于两种状态栏在操作中的不同都可以在平板和手机具体的状态栏实现类来实现。
@Override
public void start() {
mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
mWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
super.start();
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy = new PhoneStatusBarPolicy(mContext);
}
四,父类StatusBar的start方法
public void start() {
// First set up our views and stuff.
View sb = makeStatusBarView();
// Connect in to the status bar manager service
StatusBarIconList iconList = new StatusBarIconList();
ArrayList
ArrayList
mCommandQueue = new CommandQueue(this, iconList);
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
int[] switches = new int[7];
ArrayList
try {
mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
switches, binders);
} catch (RemoteException ex) {
// If the system process isn't there we're doomed anyway.
}
…………
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
height,
WindowManager.LayoutParams.TYPE_STATUS_BAR,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
PixelFormat.OPAQUE);
// the status bar should be in an overlay if possible
final Display defaultDisplay
= ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
if (ActivityManager.isHighEndGfx(defaultDisplay)) {
lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
lp.gravity = getStatusBarGravity();
lp.setTitle("StatusBar");
lp.packageName = mContext.getPackageName();
lp.windowAnimations = R.style.Animation_StatusBar;
WindowManagerImpl.getDefault().addView(sb, lp);
……
}
五,在StatusBar的start方法中首先会调用
// First set up our views and stuff.
View sb = makeStatusBarView();
这是一个抽象方法,在子类中实现,通过不同子类实现此方法,刚好实现平板和手机状态栏View的不同构造。这里我们启动的是手机,所以实现此方法的子类是phoneStatusBar.java。在PhoneStatusBar的makeStatusBar方法中除了构造手机状态栏的view还注册了一批系统的监听,用来监听系统的状态,这些监听都在每一种状态图标的控制类中初始化时完成注册。
protected View makeStatusBarView() {
…………
//初始化扩展状态栏,就是状态栏下拉后的那个View
ExpandedView expanded = (ExpandedView)View.inflate(context,
R.layout.status_bar_expanded, null);
expanded.mService = this;
…………
//获得状态栏的View
PhoneStatusBarView sb = (PhoneStatusBarView)View.inflate(context,
R.layout.status_bar, null);
sb.mService = this;
mStatusBarView = sb;
//初始化状态栏中的子View
// figure out which pixel-format to use for the status bar.
mPixelFormat = PixelFormat.OPAQUE;
mStatusIcons = (LinearLayout)sb.findViewById(R.id.statusIcons);
mNotificationIcons = (IconMerger)sb.findViewById(R.id.notificationIcons);
mIcons = (LinearLayout)sb.findViewById(R.id.icons);
mTickerView = sb.findViewById(R.id.ticker);
…………
//初始化位置,电量,网络,信号的控制类
// Other icons
mLocationController = new LocationController(mContext); // will post a notification
mBatteryController = new BatteryController(mContext);
mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery));
mNetworkController = new NetworkController(mContext);
final SignalClusterView signalCluster =
(SignalClusterView)sb.findViewById(R.id.signal_cluster);
mNetworkController.addSignalCluster(signalCluster);
signalCluster.setNetworkController(mNetworkController);
//启动保存近期打开任务的线程,开始在后台运行
// Recents Panel
mRecentTasksLoader = new RecentTasksLoader(context);
updateRecentsPanel();
return sb;
}
六,状态栏的View构造完成后,将sb的View返回给了父类StatusBar。接着会定义一些iconList ,notificationKeys,notifications容器,用来保存系统产生的状态图标StatusBarIcon和通知notification。还实例化了CommandQueue对象,在类的结构图那我们已说过,CommandQueue是一个IStatusBar.stub远程接口,用自身定义的CallBack接口实现IStatusBar.stub远程接口中的方法。而Callback接口是在StatusBar的子类中实现的,所以在实例化CommandQueue时需要将StatusBar的子类对象传入,在CommandQueue中保存,用来实现IStatusBar.stub远程接口方法。
//Statusbar中初始化传入的值
mCommandQueue = new CommandQueue(this, iconList);
//CommandQueue的构造方法
public CommandQueue(Callbacks callbacks, StatusBarIconList list) {
mCallbacks = callbacks;
mList = list;
}
七,注册StatusBar到StatusBarManagerService中。
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
int[] switches = new int[7];
ArrayList
try {
mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
switches, binders);
} catch (RemoteException ex) {
// If the system process isn't there we're doomed anyway.
}
StatusBarManagerService.java在framework的service包下面,是一个系统服务。具体注册方法大家可以到它的源码中去看。
在注册StatusBar时,主要是将我们上步实例化好的mCommandQueue对象付给StatusbarManagerService中的mBar属性,其他应用需要操作状态栏时,就可以获取StatusBarManagerService的远程代理IStatusBarService,通过AIDL进程间通信,调用StatusBarManagerService的方法,StatusBarManagerService中方法对状态栏的操作再通过mBar跨进程传给我们启动的PhoneStatusBar对象中。
注册成功之后,会调用WindowManagerImpl.getDefault().addView(sb, lp);将构造好的状态栏View添加到窗口上,这时我们就可以看见手机顶上的那条状态栏了。
八,启动PowerUI,它是集中了对电源状态的一些处理提示,继承于SystemUI。在构造SystemUIService时,SERVICES[]的初始值是
final Object[] SERVICES = new Object[] {
0, // system bar or status bar, filled in below.
com.android.systemui.power.PowerUI.class,
};
所以再启动完StatusBar的子类后,会启动PowerUI.java。PowerUI的start方法中注册了和电池相关的广播,在相应广播发生后,PowerUI会通过提示框提醒用户。
这样,SystemUIService就构造完成,开始运行于系统进程中,当手机状态发生改变时,在启动过程中注册的监听和广播捕捉到相关信息,就会更新状态栏上通标,以简单的方式通知用户。
流程三:
Notification的显示过程:
众所周知,notification是在状态栏上显示的可以定制声音,震动,Led灯,单击跳转,显示内容的通知。通常应用中要发送一个notification都是通过以下方式:
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
……//创建notification
manager.notify(1, notification);
我们在创建notification时指定notification的声音,震动,Led灯,单击跳转,显示内容,通过NotificationManager发送出去,最终在状态栏上显示,在显示过程中对声音,震动,Led灯做同步处理。
先来看看NotificationManager发送通知后的流程:
1, 在调用NotificationManager.notify后,NotificationManager通过内部的NotificationManagerService代理处理后续的发送任务,这里调用的是NotificationManagerService.enqueueNotification.
2,NotificationManagerService.enqueueNotification中反复调用了自己的几个方法,最终调用的是enqueueNotificationInternal发送,这个方法里完成了所有对notification发送的处理,也包括了对notification的更新。此类在Frameworks的services目录下面,方法如下:
public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
String tag, int id, int priority, Notification notification, int[] idOut)
{
checkIncomingCall(pkg);
// Limit the number of notifications that any given package except the android
// package can enqueue. Prevents DOS attacks and deals with leaks.
if (!"android".equals(pkg)) {
synchronized (mNotificationList) {
int count = 0;
final int N = mNotificationList.size();
for (int i=0; i= MAX_PACKAGE_NOTIFICATIONS) {
Slog.e(TAG, "Package has already posted " + count
+ " notifications. Not showing more. package=" + pkg);
return;
}
}
}
}
}
// This conditional is a dirty hack to limit the logging done on
// behalf of the download manager without affecting other apps.
if (!pkg.equals("com.android.providers.downloads")
|| Log.isLoggable("DownloadManager", Log.VERBOSE)) {
EventLog.writeEvent(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, tag,
notification.toString());
}
if (pkg == null || notification == null) {
throw new IllegalArgumentException("null not allowed: pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
if (notification.icon != 0) {
if (notification.contentView == null) {
throw new IllegalArgumentException("contentView required: pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
}
synchronized (mNotificationList) {
NotificationRecord r = new NotificationRecord(pkg, tag, id,
callingUid, callingPid,
priority,
notification);
NotificationRecord old = null;
int index = indexOfNotificationLocked(pkg, tag, id);
if (index < 0) {
mNotificationList.add(r);
} else {
old = mNotificationList.remove(index);
mNotificationList.add(index, r);
// Make sure we don't lose the foreground service state.
if (old != null) {
notification.flags |=
old.notification.flags&Notification.FLAG_FOREGROUND_SERVICE;
}
}
// Ensure if this is a foreground service that the proper additional
// flags are set.
if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) {
notification.flags |= Notification.FLAG_ONGOING_EVENT
| Notification.FLAG_NO_CLEAR;
}
if (notification.icon != 0) {
StatusBarNotification n = new StatusBarNotification(pkg, id, tag,
r.uid, r.initialPid, notification);
n.priority = r.priority;
if (old != null && old.statusBarKey != null) {
r.statusBarKey = old.statusBarKey;
long identity = Binder.clearCallingIdentity();
try {
mStatusBar.updateNotification(r.statusBarKey, n);
}
finally {
Binder.restoreCallingIdentity(identity);
}
} else {
long identity = Binder.clearCallingIdentity();
try {
r.statusBarKey = mStatusBar.addNotification(n);
mAttentionLight.pulse();
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
sendAccessibilityEvent(notification, pkg);
} else {
Slog.e(TAG, "Ignoring notification with icon==0: " + notification);
if (old != null && old.statusBarKey != null) {
long identity = Binder.clearCallingIdentity();
try {
mStatusBar.removeNotification(old.statusBarKey);
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
}
// If we're not supposed to beep, vibrate, etc. then don't.
if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
&& (!(old != null
&& (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
&& mSystemReady) {
final AudioManager audioManager = (AudioManager) mContext
.getSystemService(Context.AUDIO_SERVICE);
// sound
final boolean useDefaultSound =
(notification.defaults & Notification.DEFAULT_SOUND) != 0;
if (useDefaultSound || notification.sound != null) {
Uri uri;
if (useDefaultSound) {
uri = Settings.System.DEFAULT_NOTIFICATION_URI;
} else {
uri = notification.sound;
}
boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
int audioStreamType;
if (notification.audioStreamType >= 0) {
audioStreamType = notification.audioStreamType;
} else {
audioStreamType = DEFAULT_STREAM_TYPE;
}
mSoundNotification = r;
// do not play notifications if stream volume is 0
// (typically because ringer mode is silent).
if (audioManager.getStreamVolume(audioStreamType) != 0) {
long identity = Binder.clearCallingIdentity();
try {
mSound.play(mContext, uri, looping, audioStreamType);
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
}
// vibrate
final boolean useDefaultVibrate =
(notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
if ((useDefaultVibrate || notification.vibrate != null)
&& audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) {
mVibrateNotification = r;
mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN
: notification.vibrate,
((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
}
}
// this option doesn't shut off the lights
// light
// the most recent thing gets the light
mLights.remove(old);
if (mLedNotification == old) {
mLedNotification = null;
}
//Slog.i(TAG, "notification.lights="
// + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0));
if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
mLights.add(r);
updateLightsLocked();
} else {
if (old != null
&& ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
updateLightsLocked();
}
}
}
idOut[0] = id;
}
3.首先是对Notification的属性做了验证,同一个应用发送notification数量不能超过MAX_PACKAGE_NOTIFICATIONS的值50,其余的验证包括notification不为空,contentView 不为空的验证,条件都满足后先创建一个NotificationRecord
NotificationRecord r = new NotificationRecord(pkg, tag, id,
callingUid, callingPid,
priority,
notification);
通过indexOfNotificationLocked(pkg, tag, id)获取是否已经发送过此notification,如果有发送过,就获取oldNtificationRecord,后面走更新流程mStatusBar.updateNotification(r.statusBarKey, n);如果是新发送的notification就走新增流程r.statusBarKey = mStatusBar.addNotification(n); 如果notification的icon为空,并且存在旧的NotificationRecord,就调用mStatusBar.removeNotification(old.statusBarKey)取消这个notification.
if (notification.icon != 0) {
StatusBarNotification n = new StatusBarNotification(pkg, id, tag,
r.uid, r.initialPid, notification);
n.priority = r.priority;
if (old != null && old.statusBarKey != null) {
r.statusBarKey = old.statusBarKey;
long identity = Binder.clearCallingIdentity();
try {
mStatusBar.updateNotification(r.statusBarKey, n);
}
finally {
Binder.restoreCallingIdentity(identity);
}
} else {
long identity = Binder.clearCallingIdentity();
try {
r.statusBarKey = mStatusBar.addNotification(n);
mAttentionLight.pulse();
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
sendAccessibilityEvent(notification, pkg);
} else {
Slog.e(TAG, "Ignoring notification with icon==0: " + notification);
if (old != null && old.statusBarKey != null) {
long identity = Binder.clearCallingIdentity();
try {
mStatusBar.removeNotification(old.statusBarKey);
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
}
这里我们走notification的新增流程mStatusBar.addNotification(n),此mStatusBar是系统启动时创建NotificationManagerService中传的StatusBarManagerService。在上面说过StatusBarManagerService中的对statusBar的操作都是通过IStatusBar的接口实现类StatusBar完成的,所以addNotification最终是在StatusBar的子类PhoneStatusBar中完成。
4,PhoneStatusBar在SystemUI的包里面,在它的addNotification方法中完成了所有Notification在状态栏上的显示操作。对PhoneStatusBar.addNotification的调用是采用Handle的方式,此Handle的Message成功发送后,StatusBarManagerService的addNotification就返回了, 然后再返回到NotificationManagerService的调用处,接着处理后续的声音播放NotificationPlayer.play、震动Vibrator.vibrator、Led指示灯updateLightsLocked,这些都是和PhoneStatusBar的addNotification同时在进行,当Notification在显示的时候,声音,震动,Led的处理就同步完成了。
5,在PhoneStatusBar.addNotification中调用addNotificationView将notification的icon添加到statusBar,还有notification在ExpandedView(状态栏拉开的那个界面)中显示的View也是在此方法中创建。
接着调用tick方法,显示Notification的tick提示,就是notification显示时在状态栏上滚动的那个效果。
至此,Notification的显示就完成了。StatusBar上除了Notification的显示还有StatusIcon(状态图标),状态图标的显示比较简单。系统的状态图标都是在SystemUI中控制显示,通过注册监听,接收系统广播控制图标的显示。SystemUI在启动时系统设置的状态图标都会加入到状态栏的view中,但不会设置显示,当相应事件发生后,注册的监听或广播就会设置相应图标显示。要显示的状态图标都需要在/frameworks/base/core/values/config.xml中设置名称,否则系统无法显示状态图标。
至此,状态栏流程分析完毕。
状态栏中的状态图标的增加,删除及修改方法:
1.状态栏中状态图标申明的地方在config.xml (frameworks\base\core\res\res\values),必须在这里申明后,才能够在状态栏中显示出来。
2.在PhoneStatusBarPolicy.java(frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone)完成初始化并注册相应的receiver
来接受该广播。
3.添加图片资源(frameworks\base\packages\SystemUI\res)
状态栏中的Notification 的增加和删除:
1.NotificationManager : 是状态栏通知的管理类,负责发通知、清楚通知等。
2.Notification:是具体的状态栏通知对象,可以设置icon、文字、提示声音、振动、灯等等参数。
下面是设置一个通知需要的基本参数:
PendingIntent (点击通知执行页面跳转)
可选的设置:
Notification 的常用操作
一、创建Notification
通过NotificationManager 的 notify(int, Notification) 方法来启动Notification。
第一个参数唯一的标识该Notification,第二个参数就是Notification对象。
二、更新Notification
调用Notification的 setLatestEventInfo方法来更新内容,然后再调用NotificationManager的notify()方法即可。
三、删除Notification
通过NotificationManager 的cancel(int)方法,来清除某个通知。其中参数就是
Notification的唯一标识ID。
当然也可以通过 cancelAll() 来清除状态栏所有的通知。
参数实例化如下:
1. 添加声音
如果要采用默认声音,只要使用default就可以了。
baseNF.defaults = Notification.DEFAULT_SOUND;
如果要使用自定义声音,那么就要用到sound了。如下:
notification.sound = Uri.parse("file:///sdcard/notification/ringer.mp3");
上面这种方法,使用的是自己的铃声,如果想用系统自带的铃声,可以这样:
notification.sound = Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI, "6");
需要注意一点,如果default、sound同时出现,那么sound无效,会使用默认铃声。
默认情况下,通知的声音播放一遍就会结束。 如果你想让声音循环播放,需要为flags参数加上FLAG_INSISTENT。 这样声音会到用户响应才结束,比如下拉状态栏。
notification.flags |= notification.FLAG_INSISTENT;
2. 添加振动
如果是使用默认的振动方式,那么同样也是使用default。
2. notification.defaults |= Notification.DEFAULT_VIBRATE;
notification.defaults |= Notification.DEFAULT_VIBRATE;
当然也可以自己定义振动形式,这边需要用到Long型数组。
2. long[] vibrate = {0,100,200,300};
2. notification.vibrate = vibrate;
long[] vibrate = {0,100,200,300};
notification.vibrate = vibrate;
这边的Long型数组中,传递一个整型数组作为关闭和开启震动的持续时间,以毫秒为单位。第一个值表示等待震动开启的毫秒数,下一个值表示保持震动的毫秒数,这个序列值交替表示震动关闭和开启的毫秒数。
同样,如果default、vibrate同时出现时,会采用默认形式。
4. 闪光
使用默认的灯光,如下:
notification.defaults |= Notification.DEFAULT_LIGHTS;
自定义:
notification.ledARGB = 0xff00ff00;
notification.ledOnMS = 300;
notification.ledOffMS = 1000;
notification.flags |= Notification.FLAG_SHOW_LIGHTS;
其中ledARGB 表示灯光颜色、 ledOnMS 亮持续时间、ledOffMS 暗的时间。