月蝕傳說

          浮躁讓人失去理智
          posts - 25, comments - 101, trackbacks - 0, articles - 0
            BlogJava :: 首頁 ::  :: 聯系 :: 聚合  :: 管理

          用Eclipse RCP & ECF 實現 Google Talk客戶端

          Posted on 2006-10-09 19:07 Dart 閱讀(2004) 評論(2)  編輯  收藏 所屬分類: ECF
          大家用過Google Talk嗎?它是Google推出的一個IM,通訊協議是我們熟悉的Jabber協議。我通過這篇文章給大家簡單介紹一下如何利用ECF實現一個Google Talk客戶端。源代碼下載:http://www.aygfsteel.com/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的客戶端了,但是還有很多功能沒有實現,比如添加好友、監聽好友狀態改變等等,這些都需要大家去增加。就講到這里,我們下次再見。




          評論

          # re: 用Eclipse RCP & ECF 實現 Google Talk客戶端   回復  更多評論   

          2007-01-24 10:00 by alan
          你好,我現在正在用ECF來開發一個項目,其中有一些地方要向你請教,如果方便,請加我的QQ:43833911,謝謝!

          # re: 用Eclipse RCP & ECF 實現 Google Talk客戶端   回復  更多評論   

          2007-01-25 17:24 by alon xiong
          你寫的這篇文章中ECF是什么版本的,怎么0.9.6通不過?

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 石门县| 兴仁县| 汕尾市| 昔阳县| 外汇| 南汇区| 乌鲁木齐市| 望谟县| 林口县| 建始县| 潼南县| 毕节市| 渝北区| 德江县| 岚皋县| 北川| 斗六市| 延川县| 绥阳县| 麟游县| 大荔县| 郧西县| 通化市| 巴马| 武宣县| 肇州县| 凤山市| 宜黄县| 吴忠市| 阳山县| 行唐县| 金门县| 疏附县| 灵石县| 沛县| 瑞昌市| 兴宁市| 梓潼县| 尖扎县| 环江| 宁夏|