发布于 2026-01-06 0 阅读
0

Creating a realtime chat app with android , NodeJs and Socket.io Introduction Getting started Our NodeJs Server Our Android app (Socket client) Conclusion

使用 Android、Node.js 和 Socket.io 创建一个实时聊天应用

介绍

入门

我们的Node.js服务器

我们的安卓应用(Socket客户端)

结论

介绍

WebSocket 是一种非常强大的工具,它允许我们在现代 Web 应用程序中建立实时通信。事实上,这种机制非常强大,被用于构建各种类型的应用程序,例如实时聊天或通知系统等等。

本文将向您展示如何使用 Android Node.js 和 Socket.io 构建实时聊天应用程序。

入门

我们的聊天应用程序分为两部分:

1. 服务器端:一个使用 Node.js 并实现了 socket.io 的服务器端。

2- 客户端:创建安卓应用并为客户端实现 socket.io。

我们的Node.js服务器

为了更清楚地说明,我们的项目架构将由两个文件组成:
package.json,它将处理我们 node js 应用程序的所有依赖项;index.js,它将是我们的主服务器。

创建完这两个文件后,我们在项目目录下打开命令行
并执行以下命令

npm install --save  express socket.io  
Enter fullscreen mode Exit fullscreen mode

现在,在 index.js 文件中,我们将构建服务器并进行所有配置,使其看起来像这样。

const express = require('express'),
http = require('http'),
app = express(),
server = http.createServer(app),
io = require('socket.io').listen(server);
app.get('/', (req, res) => {

res.send('Chat Server is running on port 3000')
});


server.listen(3000,()=>{

console.log('Node app is running on port 3000')

});


Enter fullscreen mode Exit fullscreen mode

为确保服务器正在运行,请进入项目目录下的命令行并执行以下命令

node index.js
Enter fullscreen mode Exit fullscreen mode

注意:虽然可以使用 node 命令运行任何用 node 环境创建的服务器,但问题在于每次更新 index.js 文件时都必须运行相同的命令。为了简化操作,我们可以使用 nodemon 命令,它会在每次更改后自动重启服务器。

所以要安装 nodemon,请打开命令行并运行

npm install -g nodemon
Enter fullscreen mode Exit fullscreen mode

为了确保项目正在运行,我们应该在控制台中看到以下日志。

图片替代文字

现在到了最精彩的部分!!

现在我们将尝试在服务器中实现一些 socket.io 方法,以处理聊天应用程序的所有事件,包括用户的连接状态和消息。

在我们的 index.js 文件中,我们添加了第一个实现,用于检测是否有用户连接到我们的服务器。

io.on('connection', (socket) => {

console.log('user connected')

socket.on('join', function(userNickname) {

        console.log(userNickname +" : has joined the chat "  )

        socket.broadcast.emit('userjoinedthechat',userNickname +" : has joined the chat ")
    });

});
Enter fullscreen mode Exit fullscreen mode

实际上,socket.io 机制是基于监听和触发事件的。在我们完成的第一个实现中,(on) 方法接受两个参数('eventname',callback),定义了一个名为 connection 的事件的监听器。该事件将从客户端触发,以便 node js 可以处理它。之后,我们定义了一个方法,该方法将监听名为 'join' 的已发出事件,并将加入聊天的用户的姓名记录到控制台中。

现在,当 Node.js 检测到用户时,它会使用 emit 方法向客户端触发一个名为“userjoinedthechat”的事件,请注意 socket.broadcast.emit 会将事件发送给除发送者之外连接到服务器的每个用户。

如果我们想将消息发送给包括发件人在内的所有用户,我们只需要使用 io.emit() 而不是 socket.emit()。

现在为了处理消息,我们添加以下几行代码,可以看到我们向回调函数添加了额外的参数,即用户昵称和消息内容。实际上,这些信息将在触发“messagedetection”事件时从客户端发送。

 socket.on('messagedetection', (senderNickname,messageContent) => {

       //log the message in console 

       console.log(senderNickname+" :" +messageContent)
        //create a message object

      let  message = {"message":messageContent, "senderNickname":senderNickname}

// send the message to the client side  

       socket.emit('message', message )

      });
Enter fullscreen mode Exit fullscreen mode

最后,当用户与客户端断开连接时,该事件将由该实现处理。


 socket.on('disconnect', function() {
    console.log( 'user has left ')
    socket.broadcast.emit( "userdisconnect" ,' user has left')


});
Enter fullscreen mode Exit fullscreen mode

服务器准备就绪后,index.js 文件应该如下所示:

const express = require('express'),
http = require('http'),
app = express(),
server = http.createServer(app),
io = require('socket.io').listen(server);
app.get('/', (req, res) => {

res.send('Chat Server is running on port 3000')
});
io.on('connection', (socket) => {

console.log('user connected')

socket.on('join', function(userNickname) {

        console.log(userNickname +" : has joined the chat "  );

        socket.broadcast.emit('userjoinedthechat',userNickname +" : has joined the chat ");
    })


socket.on('messagedetection', (senderNickname,messageContent) => {

       //log the message in console 

       console.log(senderNickname+" : " +messageContent)

      //create a message object 

      let  message = {"message":messageContent, "senderNickname":senderNickname}

       // send the message to all users including the sender  using io.emit() 

      io.emit('message', message )

      })

socket.on('disconnect', function() {

        console.log(userNickname +' has left ')

        socket.broadcast.emit( "userdisconnect" ,' user has left')




    })




})






server.listen(3000,()=>{

console.log('Node app is running on port 3000')

})


Enter fullscreen mode Exit fullscreen mode

我们的安卓应用(Socket客户端)

首先打开 Android Studio 并创建一个包含空 Activity 的新项目,然后打开 app 的 build.gradle 文件并添加这些依赖项,最后同步你的项目。

compile 'com.android.support:recyclerview-v7:25.3.1'
compile('com.github.nkzawa:socket.io-client:0.5.0') {
    exclude group: 'org.json', module: 'json'
}
Enter fullscreen mode Exit fullscreen mode

现在来说说这些句子:

第一个是 RecyclerView,我们将使用它来显示消息列表;第二个是库,它将为我们提供客户端 socket.io 的实现,以便我们可以触发或监听事件。

别忘了在 manifest.xml 文件中启用 INTERNET 权限。

<uses-permission android:name="android.permission.INTERNET" ></uses-permission>
Enter fullscreen mode Exit fullscreen mode

在 activity_main.xml 文件中,我们将添加一个 EditText 控件供用户输入昵称,以及一个按钮,允许用户进入聊天框。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.aymen.androidchat.MainActivity">

   <EditText 
      android:id="@+id/nickname"android:layout_centerInParent="true"android:textSize="30dp"android:hint="Enter your nickname !"android:layout_width="match_parent"android:layout_height="wrap_content" /><Buttonandroid:layout_below="@+id/nickname"android:id="@+id/enterchat"android:text="Go to chat "android:layout_width="match_parent"android:layout_height="wrap_content" />

 </RelativeLayout>
Enter fullscreen mode Exit fullscreen mode

这样预览就会像这样。
图片替代文字

现在你的 MainActivity.java 文件应该看起来像这样

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity {


    private Button btn;
    private EditText nickname;
    public static final String NICKNAME = "usernickname";
    @Overrideprotected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //call UI components  by id
        btn = (Button)findViewById(R.id.enterchat) ;
        nickname = (EditText) findViewById(R.id.nickname);

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //if the nickname is not empty go to chatbox activity and add the nickname to the intent extra


    if(!nickname.getText().toString().isEmpty()){

              Intent i  = new Intent(MainActivity.this,ChatBoxActivity.class);

                     //retreive nickname from EditText and add it to intent extra
                     i.putExtra(NICKNAME,nickname.getText().toString());

                     startActivity(i);
                 }
            }
        });

    }
}
Enter fullscreen mode Exit fullscreen mode

现在创建第二个名为 ChatBoxActivity 的空 Activity,并在 activity_chat_box.xml 文件中添加以下几行代码。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.aymen.androidchat.ChatBoxActivity">
<LinearLayout
android:weightSum="3"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerViewandroid:layout_weight="3"android:id="@+id/messagelist"android:layout_width="match_parent"android:layout_height="wrap_content"android:clipToPadding="false"android:scrollbars="vertical"/><Viewandroid:layout_marginTop="5mm"android:id="@+id/separator"android:layout_width="match_parent"android:layout_height="1dp"android:background="@android:color/darker_gray"/>

<LinearLayoutandroid:weightSum="3"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content">

            <EditText

                android:id="@+id/message"android:layout_weight="3"android:layout_width="wrap_content"android:hint="your message"

                android:layout_height="match_parent" />

            <Button

                android:id="@+id/send"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#00000000"android:text="send"
                /></LinearLayout>

    </LinearLayout>
</RelativeLayout>
Enter fullscreen mode Exit fullscreen mode

您的预览应该如下所示
图片替代文字

在实现套接字客户端之前,我们应该创建一个适配器来处理和显示我们的消息。为此,我们需要创建一个名为 item.xml 的文件和一个名为 message 的 Java 类,它有两个简单的字符串属性(nickname,message)。

在项目目录下的 activities 文件夹中,创建一个名为 Message.java 的文件:

public class Message {

    private String nickname; 
    private String message ;

    public  Message(){

    }
    public Message(String nickname, String message) {
        this.nickname = nickname;
        this.message = message;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
Enter fullscreen mode Exit fullscreen mode

现在在 layout 目录下创建一个名为 item.xml 的文件,并添加以下几行代码

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="horizontal" android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:id="@id/nickname"android:textSize="15dp"android:textStyle="bold"android:text="Nickname : "android:layout_width="wrap_content"android:layout_height="wrap_content" /><TextViewandroid:id="@id/message"android:textSize="15dp"android:text=" message "android:layout_width="wrap_content"android:layout_height="wrap_content" />
</LinearLayout>
Enter fullscreen mode Exit fullscreen mode

创建一个名为 ChatBoxAdapter.java 的文件,并将以下代码放入其中。

package com.example.aymen.androidchat;

import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;


public class ChatBoxAdapter  extends RecyclerView.Adapter<ChatBoxAdapter.MyViewHolder> {
    private List<Message> MessageList;

    public  class MyViewHolder extends RecyclerView.ViewHolder {
        public TextView nickname;
        public TextView message;


        public MyViewHolder(View view) {
            super(view);

            nickname = (TextView) view.findViewById(R.id.nickname);
            message = (TextView) view.findViewById(R.id.message);





        }
    }
// in this adaper constructor we add the list of messages as a parameter so that 
// we will passe  it when making an instance of the adapter object in our activity 



public ChatBoxAdapter(List<Message>MessagesList) {

        this.MessageList = MessagesList;


    }

    @Overridepublic int getItemCount() {
        return MessageList.size();
    }
    @Overridepublic ChatBoxAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item, parent, false);



        return new ChatBoxAdapter.MyViewHolder(itemView);
    }

    @Overridepublic void onBindViewHolder(final ChatBoxAdapter.MyViewHolder holder, final int position) {

 //binding the data from our ArrayList of object to the item.xml using the viewholder 



        Message m = MessageList.get(position);
        holder.nickname.setText(m.getNickname());

        holder.message.setText(m.getMessage() );




    }



}
Enter fullscreen mode Exit fullscreen mode

现在一切准备就绪,我们可以在 ChatBoxActivity.java 中实现套接字客户端,具体步骤如下:

1. 从 intent extra 中获取用户的昵称

2.调用并实现与 RecyclerView 相关的所有方法,包括适配器实例化。

3.声明并定义套接字客户端与服务器建立连接的主机。

4.处理服务器触发的所有事件

5.当用户连接、断开连接或发送消息时发出事件

但在此之前,让我们先检查一下一切是否正常。因此,在我们的 ChatBoxActivity 中,我们将声明 socket 对象,并在 onCreate 方法中添加 socket 连接,这样当 Activity 被调用时,socket 客户端将直接触发连接事件。

public class ChatBoxActivity extends AppCompatActivity {

    //declare socket object

private Socket socket;
private String Nickname ;

@Overrideprotected 
void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat_box);

// get the nickame of the user



  Nickname= (String)getIntent().getExtras().getString(MainActivity.NICKNAME);

//connect you socket client to the server

try {


//if you are using a phone device you should connect to same local network as your laptop and disable your pubic firewall as well 

 socket = IO.socket("http://yourlocalIPaddress:3000");

 //create connection 

socket.connect()

// emit the event join along side with the nickname

socket.emit('join',Nickname); 


        } catch (URISyntaxException e) {
            e.printStackTrace();

        }

    }
}
Enter fullscreen mode Exit fullscreen mode

现在运行你的模拟器,在第一个活动中输入一个昵称,然后点击“开始聊天”。你会在服务器控制台中看到一条日志,表明用户已成功连接到服务器。我们可以看到,服务器中触发的 join 事件监听器工作正常,能够记录已连接用户的姓名。

图片替代文字

现在一切正常,我们不应该忘记,当我们的服务器处理一个事件时,它也会广播其他自定义事件,因此这些触发的事件应该在客户端处理。为此,我们将为事件“userjoinedthechat”创建第一个监听器,这是一个在服务器处理事件“join”时触发的自定义事件。

在我们的 ChatBoxActivity 中,我们将添加以下代码行

socket.on("userjoinedthechat", new Emitter.Listener() {
    @Overridepublic void call(final Object... args) {
        runOnUiThread(new Runnable() {
            @Overridepublic void run() {
                String data = (String) args[0];
                // get the extra data from the fired event and display a toast 
                Toast.makeText(ChatBoxActivity.this,data,Toast.LENGTH_SHORT).show();

            }
        });
    }

Enter fullscreen mode Exit fullscreen mode

现在我们同时运行两个模拟器,分别从两端输入不同的昵称,可以看到其中一个模拟器显示有用户已成功加入聊天。

图片替代文字

现在到了我们应用最精彩的部分——聊天信息:

要显示这些消息,我们必须按以下步骤操作。

1. 为发送按钮添加 onclickListener,并在获取 EditText 中的消息内容后,使用 emit() 方法发出“messagedetection”事件,同时传递发送者的昵称和消息内容。

2.该事件将由服务器处理并广播给所有用户。

3. 在 Android 中添加一个套接字监听器,用于监听服务器触发的“message”事件。

4. 从额外数据中提取昵称和消息,并创建一个新的 Message 对象实例。

5. 将实例添加到消息 ArrayList 中,并通知适配器更新 RecyclerView。

但在此之前,让我们先设置一下回收站视图、适配器、消息文本字段和发送按钮。

在 ChatBoxActivity 中添加以下声明

public RecyclerView myRecylerView ;
public List<Message> MessageList ;
public ChatBoxAdapter chatBoxAdapter;
public  EditText messagetxt ;
public  Button send ;
Enter fullscreen mode Exit fullscreen mode

在 onCreate 方法中添加以下几行

messagetxt = (EditText) findViewById(R.id.message) ;
send = (Button)findViewById(R.id.send);
MessageList = new ArrayList<>();
myRecylerView = (RecyclerView) findViewById(R.id.messagelist);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
myRecylerView.setLayoutManager(mLayoutManager);
myRecylerView.setItemAnimator(new DefaultItemAnimator());
Enter fullscreen mode Exit fullscreen mode

现在,在你的 ChatBoxActivity 中,按钮操作应该如下所示:

send.setOnClickListener(new View.OnClickListener() {
    @Overridepublic void onClick(View v) {
        //retrieve the nickname and the message content and fire the event messagedetection


  if(!messagetxt.getText().toString().isEmpty()){

            socket.emit("messagedetection",Nickname,messagetxt.getText().toString());

            messagetxt.setText(" ");         
    }




    }
});
Enter fullscreen mode Exit fullscreen mode

监听器应该看起来像这样

socket.on("message", new Emitter.Listener() {
    @Overridepublic void call(final Object... args) {
        runOnUiThread(new Runnable() {
            @Overridepublic void run() {
                JSONObject data = (JSONObject) args[0];
                try {
                    //extract data from fired event

              String nickname = data.getString("senderNickname");
              String message = data.getString("message");

           // make instance of message

          Message m = new Message(nickname,message);


          //add the message to the messageList

          MessageList.add(m);

          // add the new updated list to the adapter 
          chatBoxAdapter = new ChatBoxAdapter(MessageList);

           // notify the adapter to update the recycler view

          chatBoxAdapter.notifyDataSetChanged();

           //set the adapter for the recycler view 

          myRecylerView.setAdapter(chatBoxAdapter);


                } catch (JSONException e) {
                    e.printStackTrace();
                }


            }
        });
    }
});
Enter fullscreen mode Exit fullscreen mode

如下面的截图所示,一切运行正常:))双方都能显示消息。请注意,我们可以与其他许多用户连接,但只需运行其他模拟器并输入昵称即可加入聊天室。

图片替代文字

在本教程结束之前,我们还需要实现最后一个功能,即检测用户是否已与聊天框断开连接。

在我们的 ChatBoxActivity 中重写 onDestroy() 方法并添加以下代码行

@Override
protected void onDestroy() {
    super.onDestroy();
    socket.disconnect(); 
}
Enter fullscreen mode Exit fullscreen mode

对于听众而言

socket.on("userdisconnect", new Emitter.Listener() {
    @Overridepublic void call(final Object... args) {
        runOnUiThread(new Runnable() {
            @Overridepublic void run() {
                String data = (String) args[0];

                Toast.makeText(ChatBoxActivity.this,data,Toast.LENGTH_SHORT).show();

            }
        });
    }
});
Enter fullscreen mode Exit fullscreen mode

最终,我们的 ChatBoxActivity 将如下所示。

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.github.nkzawa.emitter.Emitter;
import com.github.nkzawa.socketio.client.IO;
import com.github.nkzawa.socketio.client.Socket;

import org.json.JSONException;
import org.json.JSONObject;

import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

public class ChatBoxActivity extends AppCompatActivity {
    public RecyclerView myRecylerView ;
    public List<Message> MessageList ;
    public ChatBoxAdapter chatBoxAdapter;
    public  EditText messagetxt ;
    public  Button send ;
    //declare socket objectprivate Socket socket;

    public String Nickname ;
    @Overrideprotected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat_box);

        messagetxt = (EditText) findViewById(R.id.message) ;
        send = (Button)findViewById(R.id.send);
        // get the nickame of the user
        Nickname= (String)getIntent().getExtras().getString(MainActivity.NICKNAME);
        //connect you socket client to the servertry {
            socket = IO.socket("http://yourlocalIPaddress:3000");
            socket.connect();
            socket.emit("join", Nickname);
        } catch (URISyntaxException e) {
            e.printStackTrace();

        }
       //setting up recyler
        MessageList = new ArrayList<>();
        myRecylerView = (RecyclerView) findViewById(R.id.messagelist);
        RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
        myRecylerView.setLayoutManager(mLayoutManager);
        myRecylerView.setItemAnimator(new DefaultItemAnimator());



        // message send action
        send.setOnClickListener(new View.OnClickListener() {
            @Overridepublic void onClick(View v) {
                //retrieve the nickname and the message content and fire the event messagedetectionif(!messagetxt.getText().toString().isEmpty()){
                    socket.emit("messagedetection",Nickname,messagetxt.getText().toString());

                    messagetxt.setText(" ");
                }


            }
        });

        //implementing socket listeners
        socket.on("userjoinedthechat", new Emitter.Listener() {
            @Overridepublic void call(final Object... args) {
                runOnUiThread(new Runnable() {
                    @Overridepublic void run() {
                        String data = (String) args[0];

                        Toast.makeText(ChatBoxActivity.this,data,Toast.LENGTH_SHORT).show();

                    }
                });
            }
        });
        socket.on("userdisconnect", new Emitter.Listener() {
            @Overridepublic void call(final Object... args) {
                runOnUiThread(new Runnable() {
                    @Overridepublic void run() {
                        String data = (String) args[0];

                        Toast.makeText(ChatBoxActivity.this,data,Toast.LENGTH_SHORT).show();

                    }
                });
            }
        });
        socket.on("message", new Emitter.Listener() {
            @Overridepublic void call(final Object... args) {
                runOnUiThread(new Runnable() {
                    @Overridepublic void run() {
                        JSONObject data = (JSONObject) args[0];
                        try {
                            //extract data from fired event

                            String nickname = data.getString("senderNickname");
                            String message = data.getString("message");

                            // make instance of message

                            Message m = new Message(nickname,message);


                            //add the message to the messageList

                            MessageList.add(m);

                            // add the new updated list to the dapter
                            chatBoxAdapter = new ChatBoxAdapter(MessageList);

                            // notify the adapter to update the recycler view

                            chatBoxAdapter.notifyDataSetChanged();

                            //set the adapter for the recycler view

                            myRecylerView.setAdapter(chatBoxAdapter);

                        } catch (JSONException e) {
                            e.printStackTrace();
                        }


                    }
                });
            }
        });
    }

    @Override

protected void onDestroy() {
        super.onDestroy();

        socket.disconnect(); 
    }
}
Enter fullscreen mode Exit fullscreen mode

结论

在这个例子中,我们深入了解了 socket.io 与 node js 和 android 的结合使用,我们也尝试解释一些基础知识,理解 socket.io 的机制以及如何在客户端和服务器之间建立双向通信。请注意,socket.io 中还有其他工具,例如 rooms 和 namespaces,它们对于创建漂亮的 Web 和移动应用程序非常有帮助。

在以下相关链接中查找这两个项目:

客户端:https://github.com/medaymenTN/AndroidChat

服务器端:https://github.com/medaymenTN/NodeJSChatServer

文章来源:https://dev.to/medaymentn/creating-a-realtime-chat-app-with-android--nodejs-and-socketio-4o55