自己写代码 - HelloHi开发流水账 三 别再那么二

我用了一种最简单最二的update机制,不管3721的眉毛胡子一把抓的方法。update,从字面意思看这个函数就是根据当前的数据来修改界面以反映数据。 现在我的数据就是服务端的那一串字符串,我把数据拿过来,根据数据画界面,这,就是最淳朴的update。简单粗暴,但是不太有效率。因为对我来说,我的 数据不是那么的抽象,而是有一些约束的,比如,聊天的内容只会多不会少,说出来的话泼出去的水,也不能收回。这导致了原始的update勤勤恳恳做了很多 工作都是在浪费时间。每次只需要获取update新的消息就可以了,一来可以减少网络负担,二来,update后刷新整个contentPanel我认为 早晚是不会有好下场的。

这里还有一个问题值得考虑,自己发送的消息到底需不需要通过update的方式来显示呢,直接把它写到 contentPanel里面不更好吗?自己发送的信息先直接写到界面上,还真有这么干的。我就经常碰到,在msn或者qq上发出一条消息,过了一会儿又 收到一条消息称刚才的消息没发出去。能别这么玩人么?其实这就是异步调用的问题,在本例子中,如果在调用异步的sendMessage之前或者之后,把消 息写到contentPanel里面,到头来就会是那德行,如果把写contentPanel的代码放到异步方法类的onSuccess里面就没这个问题 了。这也是为什么把inputText清空的代码要写在这里。清空输入框在这里做似乎是没什么问题,那么显示发送成功的消息呢?应该还可能有问题,时间问 题。时间啊,这一切善恶的源头。发送成功一条消息,你可以确定的是这条消息会添加到服务端消息列表的末端,但是前一个消息是什么呢,不先更新一下,你是不 确定的。而即使我们在发送消息之前先更新,你也不能保证你发送之前没有人往里面加了更新的消息。这有点像版本控制,每次提交代码,你最好是先锁住,然后更 新,再提交,否则你更新和提交之间又可能产生新的版本。或者更大众的办法是提交到一个分支,然后merge到主干。我们的聊天系统要做成这样就太至于了。 如果本地没有版本呢?我们每次只是送一个字符串到服务端,然后update从服务端得到新的聊天内容,这样我根本不用在乎这句话是谁说的,谁说的都一样, 都是从服务端来的。至于update返回哪些字符串,我们可以在本地存放一个当前最新消息的编号,按这个编号去update新的消息。

现 在本地需要维持一个lastIndex记录上次读到哪条消息了,由于消息都按顺序存放,因此lastIndex和消息的数量正好是一样的。

在 HelloUi里面添加字段private int lastIndex = 0;, 修改接口,把updateMessage加一个int lastIndex参数,实现改成这样

    @Override
    public String[] updateMessage(int clientId, int lastIndex) {
        String[] messageArray = new String[messageList.size() - lastIndex];
        return messageList.subList(lastIndex, messageList.size()).toArray(messageArray);
    }

客户端那边updateMessage的onSuccess改为

                @Override
                public void onSuccess(String[] result) {
                    for (String message : result) {                        
                        contentPanel.add(new HTML(message));
                        lastIndex++;
                    }
                    scrollPanel.scrollToBottom();
                }

Run一下,嗯,依然可以跑,而且比以前要顺畅一些了。修几个小bug
1.当我把滚动条拉上去看上面的 文字的时候,update就会把滚动条拉下来,这可不行。貌似有些聊天工具是这样做的,除非有新的消息,否则不会强制把滚动条拖到底。其实这也不是我喜欢 的方式,因为我要是把滚动条手动拉上去了,即使来了新消息,也许提醒我有新消息是善意的,但是把我找了半天的滚动条拉下去,我觉得不妥。我想改为,如果原 来滚动条是在底的,添加信息后还置为底,否则不动。大概看了下,以GWT提供的ScrollPanel似乎是做不到,也许通过DOM自己写 JavaScript可以做到,本着尽量简陋的原则,只好日后再说。先改成result没料的情况下直接返回。

                @Override
                public void onSuccess(String[] result) {
                    if (result.length != 0) {
                        for (String message : result) {    
                            contentPanel.add(new HTML(message));
                            lastIndex++;
                        }
                        scrollPanel.scrollToBottom();
                    }

                }

凑合着先。

差不多也该实现一对一的聊天了,我大概的想法是,对于每一个client,在服务端都需要对 应一个对象,对象至少包含一个id,和一个room,room呢,就是聊天的环境,room里面至少要包含聊天内容,姑且保持一个String的 ArrayList。其实对于发消息,只需要知道room即可,但两个聊天的client最终肯定还是需要区别开来的不是吗,所以clientId还是必 要地,光是在客户端维持一个roomId还是不够。
添加HiClient类和HiRoom类,拖到现在至少getClientId必须要实现了。 后面的各种操作都要提供clientId,我需要根据clientId快速的找到相应的HiClient对象,还有什么比数组的随机寻址更简陋淳朴呢,先 这样做吧。创建一个类专门负责管理HiClient对象,定义好接口,这样即使后面改用其他数据结构,接口也不用改。接口至少应该提供 createClient,getClient,removeClient的功能,嗯,这就是一个manager类,取名 HiClientManager。

创建出一个新的HiClient对象还需要把它连接到一个有效地HiRoom对象。虽然将来还有很多问 题需要解决,比如room其实不仅仅是一个存放消息的容器,它还有状态,当room里面只有一个client,这还分几种情况,是刚创建的时候只有一个 client,还是经过一段对话后有一个client离开了。先不管那么多,不管room什么状态,连上了就往里写消息。

public class HiClient {
    private int id;
    private HiRoom room;
    
    public HiClient(int id) {
        this.setId(id);
    }

    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }

    public void setRoom(HiRoom room) {
        this.room = room;
    }
    public HiRoom getRoom() {
        return room;
    }
}

 

public class HiRoom {
    private HiClient host;
    private HiClient guest;
    
    private ArrayList<String> messageList = new ArrayList<String>();
    
    public HiRoom(HiClient host) {
        this.host = host;
        host.setRoom(this);
    }
    
    public void invite(HiClient guest) {
        this.guest = guest;
        guest.setRoom(this);
    }

    public void setMessageList(ArrayList<String> messageList) {
        this.messageList = messageList;
    }
    public ArrayList<String> getMessageList() {
        return messageList;
    }
}

 

public class HiClientManager {
    private static final int CAPACITY = 222;
    private HiClient[] clientArray = new HiClient[CAPACITY];
    
    private HiRoom waitingRoom = null;
    
    public HiClient createClient() {
        for (int i = 0; i < CAPACITY; i++) {
            if (clientArray[i] == null) {
                HiClient client = new HiClient(i);
                clientArray[i] = client;
                if (waitingRoom != null) {
                    waitingRoom.invite(client);
                    waitingRoom = null;
                } else {
                    waitingRoom = new HiRoom(client);
                }
                return client;
            }
        }
        return null;
    }
    
    public HiClient getClient(int id) {
        return clientArray[id];
    }
    
    public void removeClient(int id) {
        clientArray[id] = null;
    }
    
}

 

@SuppressWarnings("serial")
public class HelloServiceImpl    extends RemoteServiceServlet
                                    implements HelloService {
    
    private HiClientManager clientMgr = new HiClientManager();

    @Override
    public int getClientId() {
        return clientMgr.createClient().getId();
    }

    @Override
    public void sendMessage(int clientId, String message) {
        ArrayList<String> messageList = clientMgr.getClient(clientId).getRoom().getMessageList();
        messageList.add(message);
    }

    @Override
    public String[] updateMessage(int clientId, int lastIndex) {
        ArrayList<String> messageList = clientMgr.getClient(clientId).getRoom().getMessageList();
        String[] messageArray = new String[messageList.size() - lastIndex];
        return messageList.subList(lastIndex, messageList.size()).toArray(messageArray);
    }
    
}


异 步接口的实现几乎没怎么改,只不过把原来全局的消息列表换成了根据clientId找到的room的消息列表。好了,现在聊天成了一对一的了。对应每个客 户端,服务端有一个HiClient对象,按理说任何接口只需要提供clientId,其他信息都可以在服务端得到,为何又需要提供lastIndex 呢。嗯,lastIndex可以存放在服务端,也可以保证每条信息只取一次,不会取重,但是万一网络发生异常,一次没取到的信息就再也取不到了,所以我还 是坚持把lastIndex放在客户端。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值