大家用過Google Talk嗎?它是Google推出的一個IM,通訊協議是我們熟悉的Jabber協議。我通過這篇文章給大家簡單介紹一下如何利用ECF實現一個Google Talk用戶端。原始碼下載:http://www.blogjava.net/Files/reloadcn/Chat.rar
1.準備工作
先下載ECF:
www.eclipse.org/ecf
為了能夠測試我們這個用戶端是否能正常運行,我們還需要下載一個Goolge Talk用戶端:www.google.com/talk
當然,我們想要登陸Google的伺服器必須擁有一個GoogleMail帳號,由於現在GoogleMail帳號不是隨便申請的,需要GoogleMail使用者推薦才能申請,但也能通過一些網站進入GoogleMail申請頁面,大家可以留言,我可以邀請。
我們要建立一個Google Talk的用戶端,需要瞭解一些ECF的知識。大家可以去Eclipse主站獲得更多的資訊。
2.建立一個RCP Mail Example
我們先選擇建立Plugin Project,取名為“Chat”,當到嚮導頁的第二頁的時候,注意在“Would you to create a rich client platform”選項選擇“yes”,這樣確保你建立的是一個RCP工程,見:
當到最後一頁的時候,選擇Mail Template:
完成嚮導後我們將會得到一個簡單的RCP工程。
3.登陸的代碼
1)串連前工作
ECF是一個基於Eclipse的通訊平台,它其中一部分實現了Jabber協議。ECF有一個ClientContainer概念,其實就相當於一個維護用戶端的對象,它具有串連、中斷連線服務的方法,並且能夠添加一些通訊中的事件監聽器。所以,我們建立Google Talk用戶端首先就要擁有這麼一個對象,而且它在整個程式生命週期中是唯一的。
讓我們修改一下ChatPlugin中的代碼:
首先,我們在這個類裡增加一個私人變數clientContainer,並且給他加上Getter、Setter方法:
XMPPClientSOContainer clientContainer;
? public XMPPClientSOContainer getClientContainer() {
? ? return clientContainer;
? }
? public void setClientContainer(XMPPClientSOContainer clientContainer) {
? ? this.clientContainer = clientContainer;
? }
OK,試想一下,當我們在登陸Google伺服器的時候才會去使用這個clientContainer去串連伺服器,而且我們登陸的使用者資訊是需要儲存下來的,以供後面的代碼訪問,所以這個clientContainer的產生方式應該是Lazy的,並且我們還需要建立一個我們登陸帳戶的變數:
? private ID userID;
? public ID getUserID() {
? ? return userID;
? }
? public void setUserID(ID userID) {
? ? this.userID = userID;
? }
ECF中針對使用者的資訊是用ID來表示的,它是一個介面,ECF已經實現了一個XMPPID,正好是我們Jabber帳戶需要的。
clientContainer有一個connect方法去登陸伺服器,而且在串連後不再具有其他什麼動作。讀者會問:那什麼時候通知我們串連成功呢?並且使用者在伺服器端的好友怎麼獲得呢?
clientContainer只負責串連,上述的那些事情都屬於在串連伺服器過程中或者串連後,伺服器反饋給用戶端的資訊,這些資訊需要我們給clientContainer設定監聽器去捕獲。
其中有一個監聽器名為ISharedObjectContainerListener,這個監聽器能夠捕獲一些在串連過程和中斷連線過程中的事件,比如SharedObjectConnectedEvent (串連成功事件)、SharedObjectDisconnectedEvent (中斷連線成功事件),如果我們需要在用戶端串連上伺服器後做點什麼,那這個監聽器是必須的。
clientContainer.addListener(
? ? ? ? ? ? new ISharedObjectContainerListener() {
? ? ? ? ? ? public void handleEvent(IContainerEvent evt)
? ? ? ? ? ? ? if (evt instanceof ISharedObjectContainerConnectedEvent) {
? ? ? ? ? ? ? ? ? ? ? // 串連伺服器成功後做點什麼呢?
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? if (evt instanceof ISharedObjectContainerDisconnectedEvent) {
? ? ? ? ? ? ? ? ? ? ? // 斷開伺服器成功後做點什麼呢?
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? }, null);
2)開始串連伺服器
我們看看clientContainer有一個connect方法。
這個方法需要有兩個參數:使用者的ID、串連上下文
使用者ID我們剛才已經說過了,它是ECF提出的一個概念,我們可以通過IDFactory產生它:
userID = IDFactory.getDefault().makeID(
? ? ? ? ? ? ? ? ? ? ? ? ? clientContainer.getConnectNamespace(),
? ? ? ? ? ? ? ? ? ? ? ? ? getUserName());
大家發現了嗎,上面代碼中的makeID方法需要兩個參數,一個參數我們可以從clientContainer獲得,它是串連名字空間,我的理解是某種協議。第二個是使用者名稱,這個參數在我們這裡是Google Talk的帳號,也就是GMail帳號,但是目前我們還沒有辦法從外部獲得,這我會在下面的內容中提到,到時候就可以將這個程式串起來,大家現在可以把它看作已經具備某些值。
好,我們已經有了ID,現在看看什麼如何建立上下文。串連上下文其實很簡單,我們可以這樣理解:就是在我們串連的時候,clientContainer會向用戶端所取一些相關的資訊,比如nikename,password,這樣理解起來就不麻煩了,而且在我們的這個Google Talk用戶端中,它也只會向我們索取password和username,來看看我們代碼就更清楚了:
clientContainer.connect(userID, new IConnectContext() {
? ? ? public CallbackHandler getCallbackHandler() {
? ? ? ? ? return new CallbackHandler() { ?
? ? ? ? ? ? ? public void handle( Callback[] callbacks)throws IOException,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? UnsupportedCallbackException {
? ? ? ? ? ? ? ? ? if (callbacks == null) return;
? ? ? ? ? ? ? ? ? ? for (int i = 0; i < callbacks.length; i++) {
? ? ? ? ? ? ? ? ? ? ? ? if (callbacks【i】 instanceof NameCallback) {
? ? ? ? ? ? ? ? ? ? ? ? NameCallback ncb = (NameCallback) callbacks【i】
? ? ? ? ? ? ? ? ? ? ? ? ncb.setName(getUserName());
? ? ? ? ? ? ? ? ? ? ? ? } else
? ? ? ? ? ? ? ? ? ? if (callbacks【i】 instanceof ObjectCallback) {
? ? ? ? ? ? ? ? ? ? ? ObjectCallback ocb = (ObjectCallback) callbacks【i】
? ? ? ? ? ? ? ? ? ? ? ocb.setObject(password);
? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? };
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? });
到目前為止,我們已經完成了串連這個環節,我們將這些代碼都封裝到ChatPlugin的login方法中,到時候通過外部的操作好調用。
4.開始登陸
我們利用SWT Dialog建立一個簡單的登陸對話方塊:
這個類需要有幾個屬性:使用者帳號、使用者密碼、對話方塊傳回值。
當我們點擊了Login後,對話方塊關閉,並將文本中的值賦給帳號和密碼這兩個屬性,傳回值設為SWT.OK;如果是Cancel的話那我們就直接關閉對話方塊,傳回值設定為SWT.CANCEL。
我們再到Mail RCP中提供的MessagePopupAction類中修改它的run方法:
public void run() {
? ? if(ChatPlugin.getDefault().getClientContainer() != null) {
? ? ? ? MessageDialog.openInformation(window.getShell(),"Info","已經登陸了,請先登出再重新登陸");
? ? ? ? return;
? ? }
? ? LoginDialog dialog = new LoginDialog(window.getShell(),SWT.NONE);
? ? dialog.open();
? ? if(dialog.getDialogResult() == SWT.OK){
? ? ? ? ChatPlugin.getDefault().setPassword(dialog.getPassword());
? ? ? ? ChatPlugin.getDefault().setUserName(dialog.getUser());
? ? ? ? ChatPlugin.getDefault().login();
? ? }
? }
代碼邏輯很清楚。當我們點擊這個按鈕的時候,就會彈出登陸的對話方塊,然後我們輸入資訊後就可以正常登陸了。
注意後面的代碼,我們將ChatPlugin中的使用者名稱和密碼先設定好後再調用登陸方法。如果登陸失敗的話會在ChatPlugin的login方法中捕獲到串連失敗的異常。
5.獲得我的好友們
怎麼去獲得我的好友呢?
剛才已經在前面提到了一點:clientContainer只負責去串連,而那些網路的事件需要我們去增加監聽器捕獲。獲得好友也是一樣的,我簡單說一下。
clientContainer可以通過getAdapter去獲得一個IPresenceContainer類型對象,這個對象可以增加監聽獲得好友資訊的監聽器,不僅如此,它還可以獲得訊息發送對象和訊息的監聽對象,這我會在後面介紹。
我們要想獲得好友資訊,就應該通過clientContainer獲得IPresenceContainer對象,然後給它增加一個能夠獲得好友事件的監聽器。
問題在這裡,我們應該在什麼時候去獲得這個對象呢?那這個監聽器介面是不是需要一些現有類去實現呢?
先說第一個問題:我們什麼時候去獲得這個對象,並為它增加監聽器
一般情況下,我們在登陸成功以前的時候是不會去捕獲我們的好友名單的訊息的,而且也捕獲不到,伺服器在沒有驗證我們的用戶端時,是不會發過來的,所以我們需要在登陸成功後去獲得這個對象,並為它增加一個監聽去。而這個對象也是需要作為一個私人變數存放起來,供其他類去訪問。所以我們需要在第3節中提到了監聽登陸成功的方法中寫這段代碼,由於篇幅問題,我不在這裡給出程式碼片段,讀者可以去看原始碼。
看看第二個問題:誰需要實現這個監聽器?
我們常見的IM中,都是有一個清單控制項儲存我們當前的使用者資訊的,所以我們在獲得好友名單後就需要往某些Viewer中增加一些內容,來表示這是我們的好友名單。
我在這個用戶端中,採用了一個View作為顯示好友名單的控制項,該View名為SimpleView,這個View具有一個TableViewer。該類的具體產生方法我不在多說,大家可以看看原始碼,我只說一下這個View如何去實現監聽獲得好友資訊的事件的。
我們讓它實現IPresenceListener介面,並修改handleSetRosterEntry方法:
public void handleSetRosterEntry(IRosterEntry entry) {
? ? final IRosterEntry e1 = entry;
? ? Display.getDefault().asyncExec(new Runnable() {
? ? ? ? public void run() {
? ? ? ? ? if(e1.getInterestType() ==InterestType.BOTH){
? ? ? ? ? roseters.add(e1);
? ? ? ? ? if(viewer.getInput() != roseters) viewer.setInput(roseters);
? ? ? ? ? viewer.refresh();
? ? ? ? ? }
? ? ? ? }
? ? });
? }
這個方法就是截獲獲得好友資訊的介面函數,entry表示的是從伺服器獲得的一些和用戶端好友有關的資訊,每當獲得一個,判斷一下這個好友是否都在雙方的好友名單中,如果不是那就不要增加它;反之,我們就會把這個entry放到一個名位roseters的List對象中,然後重新整理viewer。這裡的viewer是剛才我們提到的TableViewer,做過SWT/JFace的讀者一定知道,這個類需要我們去為它添加兩個介面實現,一個是ContentProvider介面,一個是LabelProvier介面,這兩個介面代碼讀者可以看看我的源碼,這裡就不寫了。如果您對SWT/JFace不熟悉的話也沒關係,這方面的資料很多。
看看我們登陸後獲得好友名單是什麼樣的:
6.監聽訊息
有了剛才增加好友的經驗,我們現在就很容易解決這個問題。
同樣,監聽訊息還是由IPresenceContainer對象增加的監聽器來截獲的。
而我讓我們工程中一個名為View的類實現了這個監聽器,並且實現這個介面的方法如下:
public void handleMessage(ID fromID, ID toID, Type type, String subject, String messageBody) {
? ? final ID id = fromID;
? ? if(type == Type.CHAT){
? ? final String message = messageBody;
? ? Display.getDefault().asyncExec(new Runnable(){
? ? ? ? public void run(){
? ? ? ? ? try {
? ? ? ? ? ? if(id.toURI().compareTo(chaterID.toURI()) ==0){
? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? String s = chaterID.toURI().getUserInfo().toString();
? ? ? ? ? ? ? ? s += " say: " + message +"/n";
? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? showText.append(s);
? ? ? ? ? ? ? ? View.this.getSite().getWorkbenchWindow()
? ? ? ? ? ? ? ? .getWorkbench().getActiveWorkbenchWindow()
? ? ? ? ? ? ? ? .getActivePage().activate(
? ? ? ? ? ? (IViewPart)ChatPlugin.getDefault().getMessageDialogForID(chaterID));
? ? ? ? ? ? }
? ? ? ? ? } catch (URISyntaxException e) {
? ? ? ? ? ? // TODO Auto-generated catch block
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? }
? ? ? ? }
? ? });}
? ?
? }
可能讀者這會看上面的代碼會一頭霧水。我解釋一下:
變數chaterID是一個ID類型的,它其實是從剛才我們好友名單中,雙擊某一項時產生這個View對象的時候傳進來的,讓我們看看SimpleView 中的雙擊action的代碼:
doubleClickAction = new Action() {
? ? ? ? public void run() {
? ? ? ? ? ISelection selection = viewer.getSelection();
? ? ? ? ? IRosterEntry entry = (IRosterEntry) ((StructuredSelection) selection)
? ? ? ? ? ? ? ? .getFirstElement();
? ? ? ? ? View chatView = (View) ChatPlugin.getDefault()
? ? ? ? ? ? ? ? .getMessageDialogForID(entry.getUserID());
? ? ? ? ? if (chatView != null) {
? ? ? ? ? ? SampleView.this.getSite().getWorkbenchWindow()
? ? ? ? ? ? ? ? ? .getWorkbench().getActiveWorkbenchWindow()
? ? ? ? ? ? ? ? ? .getActivePage().activate(chatView);
? ? ? ? ? }
? ? ? ? }
? ? };
可以看出來,當我們雙擊某個好友的時候,就會從entry中得到他的ID,然後產生一個View,並將ID給View,所以View的chaterID就時這麼來的。
接著上面的解釋:
showText變數其實是一個StyleText對象,他專門負責顯示聊天資訊,而下面那一長段代碼讀者大可不必理會,那是為了使一個好友對應一個View而做的一些工作,大概瞭解即可,也可以去看原始碼獲得更多的資訊。
7.發送訊息
讓我們看看View類中的一段代碼:
messageText.addKeyListener(new KeyListener(){
? ? ? ? public void keyPressed(KeyEvent e) {
? ? ? ? ?
? ? ? ? }
? ? ? ? public void keyReleased(KeyEvent e) {
? ? ? ? ? if(e.character == '/r'){
? ? ? ? ? ? sendMessage(messageText.getText());
? ? ? ? ? ? messageText.setText("");
? ? ? ? ? }
? ? ? ? }
? ? ? ?
? ? });
不難看出這段代碼的意思:當遇到輸入字元為斷行符號的時候,就調用sendMessage方法:
public void sendMessage(String message) {
? ? if(this.getChaterID() == null) return;
? ? String s = "你說:";
? ? s+= message;
? ?
? ? ChatPlugin.getDefault().getPresenceContainer().getMessageSender()
? ? ? ? ? .sendMessage(ChatPlugin.getDefault().getUserID(),chaterID, null, null, message);
? ?
? ? showText.append(s + "/n");
? }
sendMessage方法是從ChatPlugin中獲得IPresenceContainer的messagesender去發送訊息的,發送訊息的函數第一個參數是寄件者的ID,第二個是接收者的ID(chaterID已經在上面講過了擷取的來源),最後一個是發送的訊息,中間兩個參數一個訊息類型和標題,他們可以為空白。
8.結束語
通過我們上面所說的如何去登陸、獲得好友名單、接收訊息和發送訊息,我們已經能夠簡單地建立一個Google Talk的用戶端了,但是還有很多功能沒有實現,比如添加好友、監聽好友狀態改變等等,這些都需要大家去增加。就講到這裡,我們下次再見。