oschina-app源碼分析-提醒標籤BadgeView使用邏輯流程

      先看下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();
	}

      更新服務器消息數據,其實也是一個獲取消息數據的過程,因爲要獲得更新後的消息數據,併發出消息廣播。

     更新數據時候,只需更新當前類別的數據即可,如果你刷新了新聞頁,你只要告訴服務器你已經看過了最新的新聞。

    整個消息標籤顯示流程就講完了,一般自己寫也是這個思路,就是有的地方想的不夠全面,在這裏記錄下容易忽略的地方。

 

完整源碼下載:http://download.csdn.net/detail/xiangxue336/7023661

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章