使用 androidNodeJs 和 Socket.io 创建一个实时聊天应用程序
Posted 小陈乱敲代码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 androidNodeJs 和 Socket.io 创建一个实时聊天应用程序相关的知识,希望对你有一定的参考价值。
介绍
WebSockets 是非常漂亮的工具,它允许我们在现代 Web 应用程序中建立实时通信。事实上,这个机制非常强大,它被用来构建不同类型的应用程序,如实时聊天或通知系统等。
在本文中,我们将向您展示如何使用 android nodeJs 和 Socket.io 构建一个实时聊天应用程序
入门
我们的聊天应用分为两部分:
1-服务器端:一个节点js服务器,为服务器实现socket.io
2-客户端:创建android应用程序并为客户端实现socket.io
我们的 NodeJs 服务器
好吧,为了清楚起见,我们的项目架构将由 2 个文件组成:
package.json 将处理我们的 node js 应用程序的所有依赖项, index.js 将是我们的主服务器。
创建这两个文件后,我们打开项目
目录下的命令行并执行此命令
npm install --save express socket.io
现在在我们的 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')
);
要确保我们的服务器正在运行,请转到我们项目目录下的命令行并执行此命令
node index.js
注意:使用 node 命令我们可以运行任何使用 node 环境创建的服务器,但问题是我们每次更新 index.js 文件时都必须运行相同的命令,所以为了使事情更简单,我们可以使用 nodemon 命令,它会自动每次我们进行更改时重新启动我们的服务器
所以要安装 nodemon 去你的命令行并运行
npm install -g nodemon
为了确保我们的项目正在运行,我们应该在控制台中看到这个日志
现在是最好的部分!
我们现在将尝试在我们的服务器中实现一些 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 ")
);
);
实际上 socket.io 机制是基于监听和触发事件的,在第一个实现中,我们完成了 (on) 方法,该方法接受两个参数 (‘eventname’,callback) 定义了一个名为 connection 的事件的侦听器,这个事件将是从客户端触发,以便节点 js 可以处理它,之后我们定义了一个方法,该方法将侦听一个名为“加入”的发出事件,并将在控制台中记录加入聊天的用户的名称。
现在,当节点 js 检测到用户时,它会使用方法 emit 向客户端触发一个名为“userjoinedthechat”的事件,注意 socket.broadcast.emit 会将事件发送给连接到服务器的每个用户,除了 sender 。
如果我们想将消息发送给包括发送者在内的所有用户,我们只需要使用 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 )
);
最后,当用户与客户端断开连接时,该事件将由该实现处理。
socket.on('disconnect', function()
console.log( 'user has left ')
socket.broadcast.emit( "userdisconnect" ,' user has left')
);
现在我们的服务器已经准备好了, 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')
)
我们的 Android 应用程序(Socket 客户端)
要开始打开 android studio 并创建一个带有空活动的新项目,然后打开 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'
现在关于这些行:
第一个是回收器视图,我们将使用它来显示我们的消息列表,第二个是库,它将为我们提供客户端的 socket.io 实现,以便我们可以触发或监听事件。
不要忘记在 manifest.xml 中启用 INTERNET 权限
<uses-permission android:name="android.permission.INTERNET" ></uses-permission>
在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>
使预览看起来像这样
现在你的 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);
);
现在创建第二个名为 ChatBoxActivity 的空活动,并在 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>
你的预览应该是这样的
现在在实现套接字客户端之前,我们应该创建一个适配器来处理和显示我们的消息,为此我们需要创建一个名为 item.xml 的文件和一个名为 message 的 java 类,它有两个简单的字符串属性 (nickname,message)。
在我们的项目目录中,与活动一起创建一个名为 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;
现在在布局目录下创建一个名为 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>
创建一个名为 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() );
现在有了所有设置,我们可以在 ChatBoxActivity.java 中实现套接字客户端,这就是我们要继续的方式:
1.从intent extra中获取用户昵称
2.调用并实现所有与回收器视图相关的方法,包括适配器实例化
3.声明和定义socket客户端与服务器建立连接的主机
4.处理从服务器触发的所有事件
5.当用户连接、断开或发送消息时发出事件
但在此之前让我们检查一切是否正常,所以在我们的 ChatBoxActivity 中,我们将声明套接字对象并在方法 onCreate 中添加套接字连接,以便在调用活动时套接字客户端将直接触发事件连接
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();
现在运行您的模拟器并在第一个活动中输入昵称,然后单击 go to chat 您将在服务器控制台中看到一个日志,表明用户已成功与服务器建立连接,我们可以看到触发事件的侦听器加入我们的服务器工作正常,以便记录连接用户的名称
现在一切正常,我们不应该忘记,当我们的服务器处理一个事件时,它也会广播其他 costum 事件,因此这些触发的事件应该在客户端处理,因为我们将为事件“userjoinedthechat”创建第一个侦听器这是服务器处理事件“加入”时触发的自定义事件。
在我们的 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();
);
现在我们同时运行 2 个模拟器,我们从双方输入两个不同的昵称,我们可以看到两个模拟器之一表明用户已成功加入聊天
现在是我们应用程序中最好的部分,即聊天消息:
要显示消息,我们必须以这种方式进行
1.将onclickListener添加到按钮发送并从EditText中获取消息内容,然后使用emit()方法以及发送者的昵称和消息内容发出事件“messagedetection”
2.事件将由服务器处理并广播给所有用户
3.在android中添加一个socket监听器来监听服务器触发的事件“message”
4.从额外数据中提取昵称和消息,并创建对象Message的新实例
5.将实例添加到消息的ArrayList中,并通知适配器更新recycler视图
但在此之前,让我们设置我们的回收器视图、适配器、消息文本字段和按钮发送。
在 ChatBoxActivity 中添加以下声明
public RecyclerView myRecylerView ;
public List<Message> MessageList ;
public ChatBoxAdapter chatBoxAdapter;
public EditText messagetxt ;
public Button send ;
在 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());
现在在您的 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(" ");
);
听者应该是这样的
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();
);
);
正如我们在下面的屏幕截图中所见,一切正常
在结束本教程之前,我们必须创建最后一个功能,即检测用户是否与聊天框断开连接。
在我们的 ChatBoxActivity 中重写 onDestroy() 方法并添加这些行
@Override
protected void onDestroy()
super.onDestroy();
socket.disconnect();
并且对于听众
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();
);
);
最后我们的 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();
结论
在这个例子中,我们很好地了解了 socket.io 以及 node js 和 android 的用法,我们也尝试解释一些基础知识并了解 socket.io 的机制以及如何在一个节点之间建立双向通信客户端和服务器,请注意 socket.io 中还有其他工具,例如房间和命名空间,它们可能对制作精美的 Web 和移动应用程序非常有帮助。
以上是关于使用 androidNodeJs 和 Socket.io 创建一个实时聊天应用程序的主要内容,如果未能解决你的问题,请参考以下文章
使用java实现的socket代理(支持socket4和socket5)