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

       更新服务器消息数据,其实也是一个获取消息数据的过程,因为要获得更新后的消息数据,并发出消息广播。

     更新数据时候,只需更新当前类别的数据即可,如果你刷新了新闻页,你只要告诉服务器你已经看过了最新的新闻。

     整个消息标签显示流程就讲完了,一般自己写也是这个思路,就是有的地方想的不够全面,在这里记录下容易忽略的地方。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值