編輯:關於android開發
一直用的android手機,用過這麼多的app,平時也會遇到有趣的通知提醒,在這裡先總結兩種吧,notification和圖標數字,有的以後看到再研究。還有,推廣一下哈,剛剛建立一個Q群544645972,有興趣的加一下,一起成長。
將一個notification的setOngoing屬性設置為true之後,notification就能夠一直停留在系統的通知欄直到cancel或者應用退出。所以有的時候需要實時去根據情景動態改變notification,這裡以一個定時器的功能為例,需要每隔1s去更新一下notification,具體效果:
非常簡單的功能,代碼也很簡單:
<CODE class="hljs java">private Timer timer; private TimerTask task; ... if (timer != null) return; timer = new Timer("time"); task = new TimerTask() { @Override public void run() { showDynamicNotification(); } }; timer.scheduleAtFixedRate(task, 0, 1000); private void showDynamicNotification() { L.i("show dynamic notification"); mBuilder = new NotificationCompat.Builder(NotificationActivity.this); RemoteViews view = new RemoteViews(getPackageName(), R.layout.layout_notification); view.setTextViewText(R.id.tv_number, parseDate()); view.setImageViewResource(R.id.iv_icon, R.mipmap.ic_launcher); Intent intent = new Intent(NOTIFY_ACTION); PendingIntent pendingIntent = PendingIntent.getBroadcast(NotificationActivity.this, 1000, intent, PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setSmallIcon(R.mipmap.ic_launcher) .setContentIntent(pendingIntent) .setTicker("you got a new message") .setOngoing(true) .setContent(view); notification = mBuilder.build(); notificationManager.notify(NOTIFY_ID2, notification); } private String parseDate() { SimpleDateFormat format = new SimpleDateFormat("yyyy hh:mm:ss", Locale.getDefault()); return format.format(System.currentTimeMillis()); }</CODE>
需要注意的是Notification.Builder 是 Android 3.0 (API 11) 引入的,為了兼容低版本,我們一般使用 Support V4 包提供的 NotificationCompat.Builder 來構建 Notification。要想動態更新notification,需要利用 NotificationManager.notify() 的 id 參數,該 id 在應用內需要唯一(如果不唯一,在有些4.x的手機上會出現pendingIntent無法響應的問題,在紅米手機上出現過類似情況),要想更新特定 id 的通知,只需要創建新的 notification,並觸發與之前所用 id 相同的 notification,如果之前的通知仍然可見,則系統會根據新notification 對象的內容更新該通知,相反,如果之前的通知已被清除,系統則會創建一個新通知。
在這個例子中使用的是完全自定義的remoteViews,remoteViews和普通view的更新機制不一樣,網上資料很多,感興趣的可以去仔細了解。還有一個就是PendingIntent,這就不詳細介紹了,這裡簡單列一下PendingIntent的4個flag的作用
Notification有兩種視覺風格,一種是標准視圖(Normal view)、一種是大視圖(Big view)。標准視圖在Android中各版本是通用的,但是對於大視圖而言,僅支持Android4.1+的版本,比如郵件,音樂等軟件就會使用到這種大視圖樣式的擴展通知欄,系統提供了setStyle()函數用來設置大視圖模式,一般情況下有三種模式提供選擇:
NotificationCompat.BigPictureStyle, 在細節部分顯示一個256dp高度的位圖NotificationCompat.BigTextStyle,在細節部分顯示一個大的文本塊。NotificationCompat.InboxStyle,在細節部分顯示一段行文本。 在21版本之後增加了一個Notification.MediaStyle,這個可以達到類似
RemoteViews smallView = new RemoteViews(getPackageName(), R.layout.layout_notification);
smallView.setTextViewText(R.id.tv_number, parseDate());
smallView.setImageViewResource(R.id.iv_icon, R.mipmap.ic_launcher);
mBuilder = new NotificationCompat.Builder(NotificationActivity.this);
mBuilder.setSmallIcon(R.mipmap.ic_launcher)
.setNumber((int) (Math.random() * 1000))
//No longer displayed in the status bar as of API 21.
.setTicker("you got a new message")
.setDefaults(Notification.DEFAULT_SOUND
| Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS)
// .setDeleteIntent()
.setAutoCancel(true)
.setWhen(0)
.setPriority(NotificationCompat.PRIORITY_LOW);
intent = new Intent(NOTIFY_ACTION);
pendingIntent = PendingIntent.getBroadcast(NotificationActivity.this,
1000, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(pendingIntent);
//在5.0版本之後,可以支持在鎖屏界面顯示notification
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
mBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
}
notification = mBuilder.build();
notification.contentView = smallView;
//如果系統版本 >= Android 4.1,設置大視圖 RemoteViews
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
RemoteViews view = new RemoteViews(getPackageName(), R.layout.layout_big_notification);
view.setTextViewText(R.id.tv_name, "我是名字1我是名字2我是名字3我是名字4我是名字5我是名字6我是名字7我是名字");
view.setOnClickPendingIntent(R.id.btn_click_close,
PendingIntent.getBroadcast(NotificationActivity.this, 1001,
new Intent(CLICK_ACTION), PendingIntent.FLAG_UPDATE_CURRENT));
//textview marquee property is useless for bigContentView
notification.bigContentView = view;
}
notificationManager.notify(NOTIFY_ID3, notification);
xml布局:
<framelayout android:layout_height="150dp" android:layout_width="match_parent">
</framelayout>
這裡有幾點需要著重說明一下
setTicker函數在21版本之後已經Deprecated了,沒有效果。監聽notification刪除函數setDeleteIntent是在11版本後新增的,而且setAutoCancel為true後,該函數會失效。setPriority函數用來給notification設置優先級,上面給的google文檔中有很詳細的介紹。21版本之後,可以支持在鎖屏界面顯示notification,這個在google文檔中也有介紹,這個體驗對於我個人來說感觸很深,對於短信等私密性通知可以隱藏,但是對於一般毫無隱私的應用通知,就可以設置其為public,省去用戶解鎖,下拉通知欄的操作。自定義大圖模式也是將自定義的RemoteViews賦值給notification.bigContentView變量,而且這個功能也只是在api16(4.1)之後生效。大圖模式高度的設置有些奇怪,在上面的xml文件中,LinearLayout設置高度是無效的,必須要套一層FrameLayout,設置FrameLayout的高度才行,貌似定義最外層的LinearLayout的layoutParams是無效的。在bigContentView中是無法實現textview的marquee效果,而且事實也很奇怪,單獨使用contentView,傳入的remoteViews中的textview的marquee屬性是好用的,但是一旦設置了bigContentView,contentView中的textview屬性也失效了,這點使用的時候要注意。
這種效果大家應該在微信中看的很多,其實實現也很簡單:
代碼:
RemoteViews headsUpView = new RemoteViews(getPackageName(), R.layout.layout_heads_up_notification);
intent = new Intent(NOTIFY_ACTION);
pendingIntent = PendingIntent.getBroadcast(NotificationActivity.this,
1000, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder = new NotificationCompat.Builder(NotificationActivity.this);
mBuilder.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("this is notification title test")
.setContentText("this is notification text test")
.setNumber((int) (Math.random() * 1000))
.setTicker("you got a new message")
//must set pendingintent for this notification, or will be crash
.setContentIntent(pendingIntent)
.setDefaults(Notification.DEFAULT_SOUND
| Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS)
.setAutoCancel(true)
.setWhen(0);
notification = mBuilder.build();
if (Build.VERSION.SDK_INT >= 21) {
notification.priority = Notification.PRIORITY_MAX;
notification.headsUpContentView = headsUpView;
}
notificationManager.notify(NOTIFY_ID1, notification);
1.通過notification打開activity的時候,就要涉及到保存用戶導航的問題,這個時候就要使用到activity task的相關內容了,我以前寫過一篇博客中有介紹到activity task的內容:android深入解析Activity的launchMode啟動模式,Intent Flag,taskAffinity,感興趣的可以去看看。那麼要實現點擊notification打開指定activity,就需要設置相關的pendingIntent,有兩種特殊的情況需要說明一下:
第一種是需要打開該activity的整個task棧,也就是說父activity也需要同時全部打開,而且按照次序排列在task棧中。但是這裡會有一個問題,它在打開整個activity棧之前會先清空原先的activity task棧,所以最後在task棧中只剩下相關的幾個activity,舉個例子我要打開A->B->C的activity棧,但是我原先的activity棧中有D和C這兩個activity,系統會直接按順序關閉D和C這兩個activity,接著按順序打開A->B->C,這種情況在使用的時候需要注意。第二種是直接打開一個activity在一個單獨的task棧中這種情況會生成兩個task棧,這兩種情況在google官方文檔中已經詳細介紹了:
RemoteViews rvMain = new RemoteViews(context.getPackageName(), R.layout.notification_layout);
//TODO rvMain...
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setContent(rvMain);
// TOOD ...
是無效的,需要換一種方式:
RemoteViews rvMain = new RemoteViews(context.getPackageName(), R.layout.notification_layout);
//TODO rmMain...
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setContent(rvMain);
// TOOD ...
Notification notification = builder.build();
if(Build.VERSION.SDK_INT <= 10){
notification.contentView = rvMain;
}
4.通知欄上的操作事件:
setContentIntent():用戶點擊通知時觸發setFullScreenIntent()://TODO 這個在通知顯示的時候會被調用setDeleteIntent():用戶清除通知時觸發,可以是點擊清除按鈕,也可以是左右滑動刪除(當然了,前提是高版本)2.3及以下是無法處理自定義布局中的操作事件的,這樣我們就不要去考慮增加自定義按鈕了。
分析一下源碼,以NotificationManager.notify為入口進行分析:
INotificationManager service = getService();
......
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
stripped, idOut, UserHandle.myUserId());
getService()函數:
static public INotificationManager getService()
{
if (sService != null) {
return sService;
}
IBinder b = ServiceManager.getService("notification");
sService = INotificationManager.Stub.asInterface(b);
return sService;
}
該函數通過ServiceManager.getService(“notification”)獲取了INotificationManager的Binder對象,用來進行跨進程通信,這裡獲取的Binder對象就是NotificationManagerService,這裡涉及的兩個類要介紹一下,StatusBarManagerService和NotificationManagerService,這兩個service都會在frameworks/base/services/java/com/android/server/SystemServer.java文件裡面進行啟動的:
class ServerThread extends Thread {
public void run() {
......
StatusBarManagerService statusBar = null;
NotificationManagerService notification = null;
......
statusBar = new StatusBarManagerService(context, wm);
ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar);
......
notification = new NotificationManagerService(context, statusBar, lights);
ServiceManager.addService(Context.NOTIFICATION_SERVICE, notification);
......
}
}
我在早期的博客中介紹過SystemServer,system_server子進程是zygote通過forkSystemServer函數創建的,感興趣的可以去看看android啟動過程詳細講解。上面的代碼就調用到了NotificationManagerService的enqueueNotificationWithTag方法,enqueueNotificationWithTag方法會調用到enqueueNotificationInternal方法,這個方法就是核心了,我們抽取其中比較重要的代碼分析一下:
void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int[] idOut, int incomingUserId) {
...
//------這裡會做一個限制,除了系統級別的應用之外,其他應用的notification數量會做限制,
//------用來放置DOS攻擊導致的洩露
// Limit the number of notifications that any given package except the android
// package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks.
...
//------post到工作handler中進行工作
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (mNotificationList) {
// === Scoring ===
//------審查參數priority
// 0. Sanitize inputs
notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,
Notification.PRIORITY_MAX);
.....
//------初始化score
// 1. initial score: buckets of 10, around the app [-20..20]
final int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER;
//------將前面傳遞進來的Notification封裝成一個StatusBarNotification對象,然後
//------和score封裝成一個NotificationRecord對象,接著會調用handleGroupedNotificationLocked
//------方法,看能否跳過下一步操作,額外的會對downloadManager進行單獨處理
// 2. extract ranking signals from the notification data
.....
//------主要是統計notification的各種行為,另外將該上面封裝好的NotificationRecord對象
//------加入到mNotificationList中,然後排序,排序外後,如果notification設置了smallIcon,
//------調用所有NotificationListeners的notifyPostedLocked方法,通知有新的notification,
//------傳入的參數為上面封裝成的StatusBarNotification對象。
// 3. Apply local rules
.....
mRankingHelper.sort(mNotificationList);
if (notification.getSmallIcon() != null) {
StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
mListeners.notifyPostedLocked(n, oldSbn);
} else {
......
}
//通知status bar顯示該notification
buzzBeepBlinkLocked(r);
}
}
});
}
notifyPostedLocked方法中會繼續post到工作handler中,在該工作handler中調用notifyPosted方法,notifyPosted方法很簡單,也是通過Binder調用到了NotificationListenerService中,這個NotificationListenerService類很實用,它可以繼承,用來監聽系統notification的各種動作:Android 4.4 KitKat NotificationManagerService使用詳解與原理分析(一)__使用詳解。通知完成,最後異步操作就是調用buzzBeepBlinkLocked()方法去顯示該notification了,這個函數也很長,但是職責很明確,確認是否需要聲音,震動和閃光,如果需要,那麼就發出聲音,震動和閃光:
private void buzzBeepBlinkLocked(NotificationRecord record) {
.....
// Should this notification make noise, vibe, or use the LED?
......
// If we're not supposed to beep, vibrate, etc. then don't.
.....
if (disableEffects == null
&& (!(record.isUpdate
&& (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
&& (record.getUserId() == UserHandle.USER_ALL ||
record.getUserId() == currentUser ||
mUserProfiles.isCurrentProfile(record.getUserId()))
&& canInterrupt
&& mSystemReady
&& mAudioManager != null) {
if (DBG) Slog.v(TAG, "Interrupting!");
sendAccessibilityEvent(notification, record.sbn.getPackageName());
// sound
// should we use the default notification sound? (indicated either by
// DEFAULT_SOUND or because notification.sound is pointing at
// Settings.System.NOTIFICATION_SOUND)
.....
// vibrate
// Does the notification want to specify its own vibration?
....
// light
....
if (buzz || beep || blink) {
EventLogTags.writeNotificationAlert(record.getKey(),
buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
mHandler.post(mBuzzBeepBlinked);
}
}
最後將mBuzzBeepBlinked post到工作handler,最後會調用到mStatusBar.buzzBeepBlinked(),mStatusBar是StatusBarManagerInternal對象,這個對象是在StatusBarManagerService中初始化,所以最後調用到了StatusBarManagerService中StatusBarManagerInternal的buzzBeepBlinked()方法:
public void buzzBeepBlinked() {
if (mBar != null) {
try {
mBar.buzzBeepBlinked();
} catch (RemoteException ex) {
}
}
}
http://www.tutorialsface.com/2015/08/android-custom-notification-tutorial/
http://developer.android.com/intl/zh-cn/guide/topics/ui/notifiers/notifications.html#Heads-up
http://glgjing.github.io/blog/2015/11/18/android-kai-fa-zhi-notification-xiang-jie/
http://www.codeceo.com/article/android-notification-4-types.html
http://www.itnose.net/detail/6169442.html
http://www.cnblogs.com/over140/p/4249503.html
http://blog.csdn.net/loongggdroid/article/details/17616509/
http://www.Bkjia.com/kf/201408/327782.html
http://blog.csdn.net/xxbs2003/article/details/19167331
http://www.jianshu.com/p/4d76b2bc8784
http://home.bdqn.cn/thread-42153-1-1.html
雖然說這是iOS上的風格,但是在某些手機上還是支持的,比如三星和HTC(m8t,6.0)的有些手機都可以,小米手機是個特例,它是根據notification的數量來自動生成的。
一般情況下,HTC和三星可以使用下面的函數生成
public static void setBadge(Context context, int count) {
String launcherClassName = getLauncherClassName(context);
if (launcherClassName == null) {
return;
}
Intent intent = new Intent("android.intent.action.BADGE_COUNT_UPDATE");
intent.putExtra("badge_count", count);
intent.putExtra("badge_count_package_name", context.getPackageName());
intent.putExtra("badge_count_class_name", launcherClassName);
context.sendBroadcast(intent);
}
public static String getLauncherClassName(Context context) {
PackageManager pm = context.getPackageManager();
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
List resolveInfos = pm.queryIntentActivities(intent, 0);
for (ResolveInfo resolveInfo : resolveInfos) {
String pkgName = resolveInfo.activityInfo.applicationInfo.packageName;
if (pkgName.equalsIgnoreCase(context.getPackageName())) {
String className = resolveInfo.activityInfo.name;
return className;
}
}
return null;
}
Android 圖片的平移和鏡面和倒影效果,在前面的文章中陸續介紹了圖片的旋轉與縮放,本文繼續介紹關於圖片的操作 圖片的平移 使用下面的代碼將圖水平豎直方向平移10個像素
Android工程打包成jar文件,並且將工程中引用的jar一起打入新的jar文件中,androidjar前言: 關於.jar文件: 平時我們Android項目開發中經常
Android APP 兩種用程序撥號的方式,androidapp想在APP中添加一個撥號功能該怎樣做呢?Android提供了兩種方式,一種是ACTION_CALL方式直
【騰訊Bugly干貨分享】微信Tinker的一切都在這裡,包括源碼(一),buglytinker微信Tinker的一切都在這裡,包括源碼(一) 最近半年以來,Androi