一辰企,研究背景:
起點Android客戶端670版本有個需求蓬戚,就是游戲下載在通知欄顯示。為了把其功能實現(xiàn)的優(yōu)雅滤祖,特此對此知識進(jìn)行一番深入的研究,希望達(dá)到的目的是實現(xiàn)多樣化的通知瓶籽,應(yīng)付各種先關(guān)需求變化。
二埂材,結(jié)構(gòu)圖:
Notification大家都不陌生塑顺,其實用起來也很簡單,有關(guān)工作原理如下:
通過以上簡單的結(jié)構(gòu)圖不難看出,這個Notification的生命周期其實是一個sevice的生命周期严拒,全權(quán)有NotificationManagerSerice來進(jìn)行管理扬绪。
下面會詳細(xì)分析到源碼。
三裤唠,運用介紹:
關(guān)于如何運用挤牛,我簡單的概括如下幾個步驟:
Step1:獲取NotificationManager對象
/** 獲取Notification管理器。*/
mNotificationManager= (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Step2: 創(chuàng)建Notification對象
(有兩種方式种蘸,一種直接new一個notificaiton對象墓赴,一種是創(chuàng)建一個builder中間對象,最后動過Bulder.build()方法達(dá)到目的航瞭,這個我覺得是看個人用法習(xí)慣)
builder=newNotification.Builder(this);
Step3:關(guān)聯(lián)intent對象
/*** 給Notification設(shè)置Intent诫硕,單擊Notification會發(fā)出這個Intent。*/
builder.setContentIntent(mPendingIntent);
這個intent我們一般會設(shè)置成PendingIntent刊侯,指的是當(dāng)觸發(fā)單個通知的時候發(fā)生的操作章办。提到PendingIntent就簡單說一下他和intent的區(qū)別吧,pendingintent顧名思義不會立即被當(dāng)前的activity所執(zhí)行滨彻,他算是intent的一個包裝藕届,保留了當(dāng)前app的上下文,也就是說不依賴于當(dāng)前的activity亭饵,即便是當(dāng)前的activty消失了休偶,外界的app也會通過保留的上下文來執(zhí)行pendingintent,通常使用他的場景就是鬧鐘和通知了冬骚。
Step4:執(zhí)行notify
/** 發(fā)送Notification提醒椅贱。*/
mNotificationManager.notify(0,builder.build());
以上四個步驟四最簡單不過的操作了,如果想更復(fù)雜點只冻,可以自定義自己的RemoteView等庇麦。
接下來我主要講notificaiton的新特性和notificaiton的源碼機(jī)制:
A.新特性:
5.0提出了MediaStyle這一設(shè)置,就是你經(jīng)诚驳拢看到鎖屏狀態(tài)也能收到某些app的通知界面山橄,這些通知會直接展現(xiàn)在手機(jī)界面上。MediaStyle實現(xiàn)了這一功能舍悯,可以將你通過Notification.Builder.addAction()添加的動作按鈕航棱,緊湊的呈現(xiàn)出來。?通過設(shè)置setSession可以配 置notification控制指定的MediaSession萌衬。通 過.setVisibility(Notification.VISIBILITY_PUBLIC)以使其出現(xiàn)在鎖屏界面上饮醇。
如何實現(xiàn),其實很簡單:
private voidshow() {
MediaSession mediaSession =newMediaSession(this,"My Special Notification!");
builder.setVisibility(Notification.VISIBILITY_PUBLIC);// 設(shè)置能看的權(quán)限秕豫,這個代表鎖屏狀態(tài)下也能看
builder.setSmallIcon(R.drawable.about_icon);
builder.setContentTitle("My Notification朴艰!");
builder.setContentText("Test-test-test");
mIntent=newIntent(this,QDTestActivity.class);
PendingIntent p1 = PendingIntent.getActivity(this,0,mIntent,0);
builder.addAction(R.drawable.about_icon,"Move",p1);// 可以設(shè)置不同的pendingintent
builder.addAction(R.drawable.about_icon,"Pause",p1);
builder.addAction(R.drawable.about_icon,"Stop",p1);
builder.setStyle(newNotification.MediaStyle().setMediaSession(mediaSession.getSessionToken()));
mNotificationManager.notify(0,builder.build());
}
備注:5.0一下的機(jī)型是不支持的观蓄,否則會報以下的錯誤:
Process: com.qidian.liyongli.mynotificationtest, PID: 22064
java.lang.NoClassDefFoundError: android.media.session.MediaSession
B.Notificaiton機(jī)制:
我們知道應(yīng)用程序如果要在通知欄彈一個消息,看起來只有幾行代碼祠墅,實際上有兩個比較大的框架在里面侮穿。一個是通過 PendingIntent的靜態(tài)函數(shù)getActivity()獲取一個PendingIntent對象;一個是獲取 NotificationManagerService的服務(wù)代理對象調(diào)用notify()來post一個消息出去毁嗦。
通過PendingIntent的靜態(tài)函數(shù)getActivity()獲取一個PendingIntent對象亲茅,這個里面其實做了幾件隱蔽的事情:
其一是調(diào)用 了AMS的getIntentSender()函數(shù),在AMS中創(chuàng)建了一個PendingIntentRecord記錄塊并保存在 mIntentSenderRecords中狗准;
其二是:PendingIntentRecord繼承 IIntentSender.Stub克锣,getIntentSender()函數(shù)返回了PendingIntentRecord的binder引 用,binder引用保存在PendingIntent.mTarget變量中驶俊;
其三是上層應(yīng)用的創(chuàng)建的Intent保存在 PendingIntentRecord中供后續(xù)觸發(fā)娶耍。
Notification的發(fā)送也很簡單,首先將消息發(fā)送到系統(tǒng)層饼酿,NotificationManagerService會為之創(chuàng)建一個 NotificationRecord榕酒,保存在mNotificationList、mNotificationsByKey.put中故俐,然后把 ManagedServices.services中所有對象取出來(第一部分中為通知欄的INotificationListenerWrapper的 binder引用創(chuàng)建了一個ManagedServiceInfo)想鹰,然后調(diào)用onNotificationPosted()將通知發(fā)送給通知欄。
接下來就直接走進(jìn)源碼吧药版,撿關(guān)鍵的說辑舷,直接從notify方法入手:
public voidnotify(intid,Notification notification)
{
notify(null,id,notification);// 調(diào)用notificaiton的地方
}
public voidnotify(String tag, intid,Notification notification) {
int[] idOut =new int[1];
INotificationManager service = getService();// 拿到真正的service INotificationManager是一個通信接口,是一個aidl文件
String pkg = mContext.getPackageName();
if(notification.sound!=null) {
notification.sound= notification.sound.getCanonicalUri();
if(StrictMode.vmFileUriExposureEnabled()) {
notification.sound.checkFileUriExposed("Notification.sound");
}
}
if(localLOGV) Log.v(TAG,pkg +": notify("+ id +", "+ notification +")");
Notification stripped = notification.clone();
Builder.stripForDelivery(stripped);
try{
service.enqueueNotificationWithTag(pkg,mContext.getOpPackageName(),tag,id,
stripped,idOut,UserHandle.myUserId());// 實際上是先對notificaiton進(jìn)行一個系統(tǒng)保存
if(id != idOut[0]) {
Log.w(TAG,"notify: id corrupted: sent "+ id +", got back "+ idOut[0]);
}
}catch(RemoteException e) {}
由于源碼太多槽片,下面我就根據(jù)邏輯流程撿重點的說何缓。
首先你消息如何保存。
答:NotificaitonRecord还栓,用它創(chuàng)建一個arraylist碌廓,所有的消息都暫存于此。但是創(chuàng)建一個這個對象時先判斷消息的有效性剩盒,特別提出的是數(shù)量不能超過50個谷婆。
消息如何發(fā)送。
答:通過int index = indexOfNotificationLocked(n.getKey());獲取是否已經(jīng)發(fā)送過此notification辽聊,如果是新發(fā)送的notification就走新增流程mNotificationList.add(r);
如果有發(fā)送過纪挎,就獲取old = mNotificationList.get(index);,后面走更新流程?mNotificationList.set(index, r); mUsageStats.registerPostedByApp(r)和mUsageStats.registerUpdatedByApp(r, old);都是更新stats.numPostedByApp++
如:
如果notification的icon不為空,調(diào)用mListeners.notifyPostedLocked(n, oldSbn);如果notification的icon為空跟匆,并且存在舊的NotificationRecord并且沒被canceled异袄,就調(diào)用mListeners.notifyRemovedLocked(n);取消這個notification
有一個新的notification,以異步方式通知所有l(wèi)isteners
消息是如何添加的玛臂。
答:addNotification(sbn, rankingMap);
inflateViewsForHeadsUp 構(gòu)建HeadsUp
mHeadsUpNotificationView.showNotification 顯示HeadsUp
3.createNotificationViews創(chuàng)建NotificationViews
4.tick(notification, true); 顯示tiker
5.該方法主要構(gòu)造Notification Icons以及expanded View
addNotificationViews(shadeEntry, ranking);
重新計算滑動窗口的位置和標(biāo)題隙轻。
setAreThereNotifications();
刷新一下ExpandedView的位置updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
四:遇到的問題:
1.使用Bulder結(jié)合RemoteView的時候埠帕,不起作用。
原因:沒有使用setSamllIcon(5.0以下版本)
解決方案:添加如下代碼
builder.setSmallIcon(R.drawable.aa);
2. Notification.Bulder不可用玖绿。
原因:不兼容API11以下的版本。
解決方案:改變sdk的版本或者在AndroidManifest.xml寫入:
android:minSdkVersion="11"/>
if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.HONEYCOMB) {
builder=newNotification.Builder(this);
}
3. 如何使手機(jī)通知欄的高度適應(yīng)不來自定義的樣式
原因:4.0以上版本才能支持
解決方案:
notification.bigContentView = remoteView;
4. 有的時候發(fā)現(xiàn)添加的pendingintent不起作用叁巨。
原因:
manager.notify(COUNT++,notification);
當(dāng)有只有一個notificaiton實例時斑匪,Count只能唯一,這里不能加加锋勺。
解決方案:改成一個常量值蚀瘸,這是唯一標(biāo)識。
五庶橱,總結(jié):
總之關(guān)于notificaiton還有很多其他的小知識贮勃,比如如何實現(xiàn)信號燈,震動等苏章。這次的源碼分析也是走的主干邏輯寂嘉,有關(guān)更多的知識歡迎補(bǔ)充,改正和吐槽枫绅。