即时通讯:XMPP项目实践-微聊

即时通讯系列阅读

  1. 即时通讯基础
  2. 即时通讯:XMPP基础
  3. 即时通讯:XMPP项目实践-微聊
  4. Smack类库最好的学习资料

1. 项目简介

做一个类似QQ 的通讯工具,要求有注册、登录、添加好友、添加分组、聊天、退出登录等功能。我用8 张运行效果图来展示我们将要实现的功能。

注意,服务器用的是Openfire,我们可以用Spark 作为另外一个客户端进行测试。
闪屏页进来以后是登录界面,要求记住上次登陆的账号和密码,在界面的右下角有新用户按钮,点击后进入注册界面。注册只需要输入账号和密码即可。账号不能和其他人重复,否则注册不成功。

主界面是一个Activity 绑定3 个Fragment 实现,分别是消息、联系人、动态。

其中消息界面是一个ListView 展出了最近的联系人。点击其中的一个条目可以进入聊天界面。

联系人界面主要是一个ExpandableListView。ExpandableListView 列出了用户组,以及各个组中的好友。点击任意好友可以进入聊天界面。在ExpandableListView 上面有两个图标,分别是新朋友、新群组。点击新朋友弹出一个自定义对话框,在该对话框中输入对方好友的名称,等待对方同意了即可添加为好友。

点击新群组也弹出一个自定义对话框,在该对话框中输入分组名称,则可以创建一个分组。

如果好友不存则添加好友失败,如果分组不存在则创建分组失败。

Paste_Image.png Paste_Image.png

Paste_Image.png Paste_Image.png

动态界面主要展示了当前用户的信息,最下面有个退出按钮,点击后退出当前登录,并跳转到登录界面。

聊天界面是一个ListView,该ListView 的条目有两类布局,分别用来表示好友的消息和自己的消息。在最下方的输入框输入文本内容,然后点击发送可以将该消息发送给好友。好友有消息过来也可以直接显示在该界面。

2. 项目搭建

1、下载asmak.jar,是个德国网站

Paste_Image.png

asmack 的版本号从0.x 到现在的4.x 变化比较大。不通过版本差异也比较大。本次我写项目用的是0.8.x 的。

用的是13 年的版本。因为该api 在网上能查到的资料比较多。如果是下一次我再写这个项目我就决定用15 年发布的最新版本的了。

Paste_Image.png

我想看看asmack 公司官网,吧asmack 去掉,想着就是贵公司的网址呢,却得到这样的界面。

Paste_Image.png

2、给工程添加jar
简单极了,只需把下载好的jar 包添加到Android 工程的libs 目录下即可。注意如果eclipse 没有自动将该包添加到环境变量中,我们需要手动添加一下。

Paste_Image.png

3、项目目录结构图

Paste_Image.png
Paste_Image.png

3. 项目实现

3.1 Spark 客户端的下载和安装闪屏界面

新颖的闪屏界面,马老师,学习的目的,我借来用一用哈希望您不用太在意。

Paste_Image.png

我要做的效果是闪屏等待3 秒,然后进入主界面。但是每次都让用户等待3 秒,对于急性子来讲估计会很抓狂。那怎么办呢,只要是这个界面用户触摸屏幕则立即进入主界面。布局太简单了,不给了。直接给代码。

/**
   * 闪屏页面默认等待3s 触摸时直接进入主界面
   *
   * @author wzy 2015-8-14
   *
   */
  public class SplashActivity extends Activity {
   

      private static final long DURATION = 3000;
      /**
       * 保证变量的修改是可见的,但是无法保证变量的原子性
       */
      private volatile boolean isEntered = false;
      private Thread splashThread = new Thread(new Runnable() {

          @Override
          public void run() {
              SystemClock.sleep(DURATION);
              enter();
          }
      });

      @Override


      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_splash);
          splashThread.start();
      }

      private synchronized void enter() {
          if (!isEntered) {
              isEntered = true;
              startActivity(new Intent(SplashActivity.this, LoginActivity.class));
              finish();
          }
      }

      @Override
      public boolean onTouchEvent(MotionEvent event) {
          enter();
          return true;
      }
  }

3.2 登录

登录界面布局很简单。如果写不出来就没必要往下看了。登录的核心代码:
mXmppConnection.login(name, pwd);

activity_login.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@drawable/login_background" >
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/login_background"
        android:orientation="vertical" >
        <ImageView
            android:id="@+id/iv_touxiang"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"


            android:layout_marginTop="40dp"
            android:contentDescription="@null"
            android:src="@drawable/login_image" />
        <EditText
            android:id="@+id/et_name"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            android:layout_marginTop="30dp"
            android:background="#FFFFFF"
            android:hint="请输入账号"
            android:paddingLeft="5dp"
            android:textSize="20sp" />
        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#55AABBCC" />
        <EditText
            android:id="@+id/et_pwd"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            android:background="#FFFFFF"
            android:hint="请输入密码"
            android:inputType="textPassword"
            android:paddingLeft="5dp"
            android:textSize="20sp" />
        <Button
            android:id="@+id/btn_login"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="15dp"
            android:background="@color/title_layout"
            android:onClick="login"
            android:text="登录"
            android:textColor="#FFFFFF"
            android:textSize="20sp" />
    </LinearLayout>
    <Button
        android:id="@+id/register"
        android:layout_width="100dp"
        android:layout_height="40dp"
        android:layout_alignParentBottom="true"


        android:layout_alignParentRight="true"
        android:layout_marginRight="20dp"
        android:background="@drawable/register_user_btn"
        android:text="新用户"
        android:onClick="gotoRegist"
        android:textColor="@color/blue"
        android:textSize="16sp" />
</RelativeLayout>

LoginActivity 和RegistActivity 都继承自BaseActivity。因此这里先把BaseActivity 的代码列出。

BaseActivity.java

public class XmppConnectionManager {
   
     private static XmppConnectionManager instance = null;
     //私有化构造函数
     private XmppConnectionManager() {
     }
     /**
      * 获取该对象
      * @return
      */
     public static XmppConnectionManager getInstance() {
         if (instance == null) {
             instance = new XmppConnectionManager();
         }
         return instance;
     }
     /**
      * 执行初始化脚本
      * @return
      */
     public XMPPConnection init() {
         /**
          * 创建连接配置对象<br>
          * 第一个参数是Openfire 服务器地址<br>
          * 第二个参数是Openfire 服务器断开号,默认是5222<br>
          * 我们可以把这两个参数配置的清单文件中,也可以写死在代码中
          */
         ConnectionConfiguration connectionConfig = new
                 ConnectionConfiguration(Const.XMPP_HOST,Const.XMPP_PORT);
         /**


          * 不使用SAL 安全验证
          */
         connectionConfig.setSASLAuthenticationEnabled(false);
         /**
          * 设置TLS 安全模式
          */
         connectionConfig.setSecurityMode(ConnectionConfiguration.SecurityMode.enabled);
         // 允许自动连接
         connectionConfig.setReconnectionAllowed(true);
         // 允许登陆成功后更新在线状态
         connectionConfig.setSendPresence(true);
         //设置为debug 模式,该模式可以在控制台看到接收和发送的xmpp 协议
         connectionConfig.setDebuggerEnabled(true);
         // 收到好友邀请后同意添加为好友的模式,有三种,manual 表示需要经过同意,accept_all 表示不经同意自动
         为好友,reject_all 拒绝加为好友邀请
         Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.accept_all);
         /**
          * 该配置时为了解决asmack 的一个bug 或者说弥补一个不足之处。不用细细追究。
          */
         configure(ProviderManager.getInstance());
         //创建一个连接对象,参数为配置对象
         XMPPConnection connection = new XMPPConnection(connectionConfig);
         return connection;
     }

     public void configure(ProviderManager pm) {

         // Private Data Storage
         pm.addIQProvider("query", "jabber:iq:private", new
                 PrivateDataManager.PrivateDataIQProvider());

         // Time
         try {
             pm.addIQProvider("query", "jabber:iq:time",
                     Class.forName("org.jivesoftware.smackx.packet.Time"));
         } catch (ClassNotFoundException e) {
             Log.w("TestClient", "Can't load class for org.jivesoftware.smackx.packet.Time");
         }

         // Roster Exchange
         pm.addExtensionProvider("x", "jabber:x:roster", new RosterExchangeProvider());
         // Message Events
         pm.addExtensionProvider("x", "jabber:x:event", new MessageEventProvider());
         // Chat State


         pm.addExtensionProvider("active", "http://jabber.org/protocol/chatstates", new
                 ChatStateExtension.Provider());
         pm.addExtensionProvider("composing", "http://jabber.org/protocol/chatstates", new
                 ChatStateExtension.Provider());
         pm.addExtensionProvider("paused", "http://jabber.org/protocol/chatstates", new
                 ChatStateExtension.Provider());
         pm.addExtensionProvider("inactive", "http://jabber.org/protocol/chatstates", new
                 ChatStateExtension.Provider());
         pm.addExtensionProvider("gone", "http://jabber.org/protocol/chatstates", new
                 ChatStateExtension.Provider());
         // XHTML
         pm.addExtensionProvider("html", "http://jabber.org/protocol/xhtml-im", new
                 XHTMLExtensionProvider());
         // Group Chat Invitations
         pm.addExtensionProvider("x", "jabber:x:conference", new
                 GroupChatInvitation.Provider());
         // Service Discovery # Items
         pm.addIQProvider("query", "http://jabber.org/protocol/disco#items", new
                 DiscoverItemsProvider());
         // Service Discovery # Info
         pm.addIQProvider("query", "http://jabber.org/protocol/disco#info", new
                 DiscoverInfoProvider());
         // Data Forms
         pm.addExtensionProvider("x", "jabber:x:data", new DataFormProvider());
         // MUC User
         pm.addExtensionProvider("x", "http://jabber.org/protocol/muc#user", new
                 MUCUserProvider());
         // MUC Admin
         pm.addIQProvider("query", "http://jabber.org/protocol/muc#admin", new
                 MUCAdminProvider());
         // MUC Owner
         pm.addIQProvider("query", "http://jabber.org/protocol/muc#owner", new
                 MUCOwnerProvider());
         // Delayed Delivery
         pm.addExtensionProvider("x", "jabber:x:delay", new DelayInformationProvider());
         // Version
         try {
             pm.addIQProvider("query", "jabber:iq:version",
                     Class.forName("org.jivesoftware.smackx.packet.Version"));
         } catch (ClassNotFoundException e) {
             // Not sure what's happening here.
         }
         // VCard
         pm.addIQProvider("vCard", "vcard-temp", new VCardProvider());
         // Offline Message Requests


         pm.addIQProvider("offline", "http://jabber.org/protocol/offline", new
                 OfflineMessageRequest.Provider());
         // Offline Message Indicator
         pm.addExtensionProvider("offline", "http://jabber.org/protocol/offline", new
                 OfflineMessageInfo.Provider());
         // Last Activity
         pm.addIQProvider("query", "jabber:iq:last", new LastActivity.Provider());
         // User Search
         pm.addIQProvider("query", "jabber:iq:search", new UserSearch.Provider());
         // SharedGroupsInfo
         pm.addIQProvider("sharedgroup",
                 "http://www.jivesoftware.org/protocol/sharedgroup", new SharedGroupsInfo.Provider());
         // JEP-33: Extended Stanza Addressing
         pm.addExtensionProvider("addresses", "http://jabber.org/protocol/address", new
                 MultipleAddressesProvider());
         // FileTransfer
         pm.addIQProvider("si", "http://jabber.org/protocol/si", new
                 StreamInitiationProvider());
         pm.addIQProvider("query", "http://jabber.org/protocol/bytestreams", new
                 BytestreamsProvider());
         // Privacy
         pm.addIQProvider("query", "jabber:iq:privacy", new PrivacyProvider());
         pm.addIQProvider("command", "http://jabber.org/protocol/commands", new
                 AdHocCommandDataProvider());
         pm.addExtensionProvider("malformed-action",
                 "http://jabber.org/protocol/commands", new
                         AdHocCommandDataProvider.MalformedActionError());
         pm.addExtensionProvider("bad-locale", "http://jabber.org/protocol/commands", new
                 AdHocCommandDataProvider.BadLocaleError());
         pm.addExtensionProvider("bad-payload", "http://jabber.org/protocol/commands", new
                 AdHocCommandDataProvider.BadPayloadError());
         pm.addExtensionProvider("bad-sessionid", "http://jabber.org/protocol/commands",
                 new AdHocCommandDataProvider.BadSessionIDError());
         pm.addExtensionProvider("session-expired","http://jabber.org/protocol/commands",
                 new AdHocCommandDataProvider.SessionExpiredError());
     }
 }

LoginActivity.java

import org.jivesoftware.smack.XMPPException;
import com.itheima.qq.MainActivity;
import com.itheima.qq.QQApplication;
import com.itheima.qq.R;


import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK;
import static android.content.Context.MODE_PRIVATE;

public class LoginActivity extends BaseActivity {
   
    private EditText et_name;
    private EditText et_pwd;
    private String name;
    private String pwd;
    private SharedPreferences sp;
    private Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
                case RESULT_OK:
                    Toast.makeText(LoginActivity.this, msg.obj + " 登录成功", 0).show();
                    //获取自定义的Application,并将连接对象保存在Application
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值