先看下oschina-app裏實現標籤的效果圖:
功能需求比較較簡單,就是服務器有新的消息(文章、公告、評論等)就要通知客戶端,並在相應的模塊tab上顯示標籤,標籤的顯示方法上節已經講過,這裏主要講標籤實現邏輯。
主要流程:
1、初始化BadgeView:在標籤顯示頁面初始化BadgeView控件。
2、獲取提醒數據:起定時器,輪詢請求服務器,獲取需要提醒的消息數據;
3、發送消息廣播:獲取到消息數據後發送消息廣播,通知需要顯示標籤的頁面刷新。
4、標籤頁面刷新:廣播接收者接收到廣播後,刷新標籤頁面,顯示狀態欄通知。
5、更新服務器消息數據:查看某類信息的最新數據後,發請求通知服務器,該信息已經查看過,可標記爲非新消息。
以前弄的一個項目裏,設計有個漏洞,就是缺少第5步,在第2部返回數據的時候就把服務器的數據標記成非新信息。其實是一種概念性錯誤,消息發給給用戶了,不能證明就是舊的了,只有用戶看過了,對用戶來說纔是舊的消息。
1、初始化BadgeView
oschina裏,顯示標籤的頁面是主界面,在主界面創建的時候就要初始化BadgeView控件:
public static BadgeView bv_active;
public static BadgeView bv_message;
public static BadgeView bv_atme;
public static BadgeView bv_review;
/**
* 初始化通知信息標籤控件
*/
private void initBadgeView() {
bv_active = new BadgeView(this, fbactive);
bv_active.setBackgroundResource(R.drawable.widget_count_bg);
bv_active.setIncludeFontPadding(false);
bv_active.setGravity(Gravity.CENTER);
bv_active.setTextSize(8f);
bv_active.setTextColor(Color.WHITE);
bv_atme = new BadgeView(this, framebtn_Active_atme);
bv_atme.setBackgroundResource(R.drawable.widget_count_bg);
bv_atme.setIncludeFontPadding(false);
bv_atme.setGravity(Gravity.CENTER);
bv_atme.setTextSize(8f);
bv_atme.setTextColor(Color.WHITE);
bv_review = new BadgeView(this, framebtn_Active_comment);
bv_review.setBackgroundResource(R.drawable.widget_count_bg);
bv_review.setIncludeFontPadding(false);
bv_review.setGravity(Gravity.CENTER);
bv_review.setTextSize(8f);
bv_review.setTextColor(Color.WHITE);
bv_message = new BadgeView(this, framebtn_Active_message);
bv_message.setBackgroundResource(R.drawable.widget_count_bg);
bv_message.setIncludeFontPadding(false);
bv_message.setGravity(Gravity.CENTER);
bv_message.setTextSize(8f);
bv_message.setTextColor(Color.WHITE);
}
共定義了4個標籤,對應四個提醒模塊;初始化完成後,只要控制他的顯示與否和內容就可以了。
標籤定義成靜態的,目的是在廣播接收者裏更方便的訪問到,4個標籤控件,例如顯示一個標籤:Main.bv_active.show();
2、獲取提醒數據
oschina,是在主界面裏,啓動一個線程定時請求消息數據,通過hander消息機制傳遞給UI主線程,發送消息廣播,看下代碼:
/**
* 輪詢通知信息
*/
private void foreachUserNotice() {
final int uid = appContext.getLoginUid();
final Handler handler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 1) {
UIHelper.sendBroadCast(Main.this, (Notice) msg.obj);
}
foreachUserNotice();// 回調
}
};
new Thread() {
public void run() {
Message msg = new Message();
try {
sleep(60 * 1000);
if (uid > 0) {
Notice notice = appContext.getUserNotice(uid);
msg.what = 1;
msg.obj = notice;
} else {
msg.what = 0;
}
} catch (AppException e) {
e.printStackTrace();
msg.what = -1;
} catch (Exception e) {
e.printStackTrace();
msg.what = -1;
}
handler.sendMessage(msg);
}
}.start();
}
Notice notice = appContext.getUserNotice(uid);是公共http請求獲取網絡消息數據,這個不在贅述。
這個地方我有點疑問,這種後臺運行的操作,爲什麼不放在service裏?這樣在Main裏起線程,等Main退出以後,這個不是很容易被搞死嗎?
除了上面的輪詢請求消息數據意外,oschina裏其他獲得數據的接口、提交數據的接口,也返回來Notice消息併發送消息廣播,目的是提高消息的及時性。
我認爲這種消息提醒功能,最好還是做成主動推的方式,類似apple提供的推送服務,由於google的服務在大陸不穩定,所以有時間可以研究下開源的xmpp。
3、發送廣播
* 發送通知廣播
*
* @param context
* @param notice
*/
public static void sendBroadCast(Context context, Notice notice) {
if (!((AppContext) context.getApplicationContext()).isLogin()
|| notice == null)
return;
Intent intent = new Intent("net.oschina.app.action.APPWIDGET_UPDATE");
intent.putExtra("atmeCount", notice.getAtmeCount());
intent.putExtra("msgCount", notice.getMsgCount());
intent.putExtra("reviewCount", notice.getReviewCount());
intent.putExtra("newFansCount", notice.getNewFansCount());
context.sendBroadcast(intent);
}
4、標籤頁面刷新
在廣播接收這裏實現頁面刷新,主要是通過消息的數量來控制主頁面標籤的顯示和隱藏,還有狀態欄的通知是否顯示:
/**
* 通知信息廣播接收器
* @author liux (http://my.oschina.net/liux)
* @version 1.0
* @created 2012-4-16
*/
public class BroadCast extends BroadcastReceiver {
private final static int NOTIFICATION_ID = R.layout.main;
private static int lastNoticeCount;
@Override
public void onReceive(Context context, Intent intent) {
String ACTION_NAME = intent.getAction();
if("net.oschina.app.action.APPWIDGET_UPDATE".equals(ACTION_NAME))
{
int atmeCount = intent.getIntExtra("atmeCount", 0);//@我
int msgCount = intent.getIntExtra("msgCount", 0);//留言
int reviewCount = intent.getIntExtra("reviewCount", 0);//評論
int newFansCount = intent.getIntExtra("newFansCount", 0);//新粉絲
int activeCount = atmeCount + reviewCount + msgCount + newFansCount;//信息總數
//動態-總數
if(Main.bv_active != null){
if(activeCount > 0){
Main.bv_active.setText(activeCount+"");
Main.bv_active.show();
}else{
Main.bv_active.setText("");
Main.bv_active.hide();
}
}
//@我
if(Main.bv_atme != null){
if(atmeCount > 0){
Main.bv_atme.setText(atmeCount+"");
Main.bv_atme.show();
}else{
Main.bv_atme.setText("");
Main.bv_atme.hide();
}
}
//評論
if(Main.bv_review != null){
if(reviewCount > 0){
Main.bv_review.setText(reviewCount+"");
Main.bv_review.show();
}else{
Main.bv_review.setText("");
Main.bv_review.hide();
}
}
//留言
if(Main.bv_message != null){
if(msgCount > 0){
Main.bv_message.setText(msgCount+"");
Main.bv_message.show();
}else{
Main.bv_message.setText("");
Main.bv_message.hide();
}
}
//通知欄顯示
this.notification(context, activeCount);
}
}
private void notification(Context context, int noticeCount){
//創建 NotificationManager
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
String contentTitle = "開源中國";
String contentText = "您有 " + noticeCount + " 條最新信息";
int _lastNoticeCount;
//判斷是否發出通知信息
if(noticeCount == 0)
{
notificationManager.cancelAll();
lastNoticeCount = 0;
return;
}
else if(noticeCount == lastNoticeCount)
{
return;
}
else
{
_lastNoticeCount = lastNoticeCount;
lastNoticeCount = noticeCount;
}
//創建通知 Notification
Notification notification = null;
if(noticeCount > _lastNoticeCount)
{
String noticeTitle = "您有 " + (noticeCount-_lastNoticeCount) + " 條最新信息";
notification = new Notification(R.drawable.icon, noticeTitle, System.currentTimeMillis());
}
else
{
notification = new Notification();
}
//設置點擊通知跳轉
Intent intent = new Intent(context, Main.class);
intent.putExtra("NOTICE", true);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
//設置最新信息
notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
//設置點擊清除通知
notification.flags = Notification.FLAG_AUTO_CANCEL;
if(noticeCount > _lastNoticeCount)
{
//設置通知方式
notification.defaults |= Notification.DEFAULT_LIGHTS;
//設置通知音-根據app設置是否發出提示音
if(((AppContext)context.getApplicationContext()).isAppSound())
notification.sound = Uri.parse("android.resource://" + context.getPackageName() + "/" + R.raw.notificationsound);
//設置振動 <需要加上用戶權限android.permission.VIBRATE>
//notification.vibrate = new long[]{100, 250, 100, 500};
}
//發出通知
notificationManager.notify(NOTIFICATION_ID, notification);
}
}
每次刷新頁面除了確保標籤顯示,還要確保改隱藏的標籤隱藏掉。
當沒有消息提醒時要取消狀態欄通知:notificationManager.cancelAll();
5、更新服務器消息數據
當用戶已經看過新的消息後,則要通知服務器更新數據。一般是在第一次加載數據和刷新數據的時候,去更新服務器消息數據,因爲這兩個操作是獲得當前最新的該類信息。
/**
* 通知信息處理
*
* @param type
* 1:@我的信息 2:未讀消息 3:評論個數 4:新粉絲個數
*/
private void ClearNotice(final int type) {
final int uid = appContext.getLoginUid();
final Handler handler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 1 && msg.obj != null) {
Result res = (Result) msg.obj;
if (res.OK() && res.getNotice() != null) {
UIHelper.sendBroadCast(Main.this, res.getNotice());
}
} else {
((AppException) msg.obj).makeToast(Main.this);
}
}
};
new Thread() {
public void run() {
Message msg = new Message();
try {
Result res = appContext.noticeClear(uid, type);
msg.what = 1;
msg.obj = res;
} catch (AppException e) {
e.printStackTrace();
msg.what = -1;
msg.obj = e;
}
handler.sendMessage(msg);
}
}.start();
}
更新服務器消息數據,其實也是一個獲取消息數據的過程,因爲要獲得更新後的消息數據,併發出消息廣播。
更新數據時候,只需更新當前類別的數據即可,如果你刷新了新聞頁,你只要告訴服務器你已經看過了最新的新聞。
整個消息標籤顯示流程就講完了,一般自己寫也是這個思路,就是有的地方想的不夠全面,在這裏記錄下容易忽略的地方。