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(); }
更新服务器消息数据,其实也是一个获取消息数据的过程,因为要获得更新后的消息数据,并发出消息广播。
更新数据时候,只需更新当前类别的数据即可,如果你刷新了新闻页,你只要告诉服务器你已经看过了最新的新闻。
整个消息标签显示流程就讲完了,一般自己写也是这个思路,就是有的地方想的不够全面,在这里记录下容易忽略的地方。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。