客户端部分
接着上篇来讲解客户端部分,下面给出代码。
Message和User类的创建很简单,就不贴出代码。
其中用到了EventBus,很好的解耦合工具,github上开源哦!
public class ChatActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private EditText input;
private Button btn_send;
private List<Session> msgList = new ArrayList<>();
private MsgAdapter msgAdapter;
private Intent intent;
private TextView nick;
private TextView sign;
MyApplication继承Application类
private MyApplication application=MyApplication.getApplication();
private Long receiveId;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
initView();
initNet();
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String content = input.getText().toString();
// 如果内容不为空,则发送消息
updateMessage(content);
sendMessage(content);
}
});
}
public void initView(){
recyclerView =findViewById(R.id.recyclerView);
input =findViewById(R.id.et_content);
btn_send =findViewById(R.id.btn_send);
recyclerView.setLayoutManager(new ChatLayoutManager(this));
msgAdapter=new MsgAdapter(msgList);
recyclerView.setAdapter(msgAdapter);
nick=findViewById(R.id.friend_nick);
sign=findViewById(R.id.friend_sign);
}
@SuppressLint("StaticFieldLeak")
public void sendMessage(final String content){
new AsyncTask<String,Void,Void>(){
@Override
protected Void doInBackground(String... strings) {
// 发送信息
DataOutputStream out=application.getOut();
if (content!=null){
Message message=new Message();
message.setReceiveId(receiveId);
message.setNick(User.getInstance().getNick());
message.setType(MessageType.MSG_TYPE_CHAT_P2P);
message.setSenderId(User.getInstance().getId());
message.setContent(content);
message.setSign(User.getInstance().getSign());
try {
out.writeUTF(message.toXML());
out.flush();
Log.i("chat--->",message.toXML());
} catch (IOException e) {
e.printStackTrace();
}catch (NullPointerException e){
}
}
return null;
}
}.execute();
}
更新消息
public void updateMessage(String content){
if (!"".equals(content)){
Session msg = new Session(MessageType.MSG_TYPE_SEND,User.getInstance().getId(),User.getInstance().getNick(),content);
msgList.add(msg);
msgAdapter.notifyItemRangeInserted(msgList.size() - 1,1);// 当有新消息时,刷新适配器
recyclerView.scrollToPosition(msgList.size() - 1);// 将circleRecycle 定位到最后一行
Log.i("chat.send",String.valueOf(msgAdapter.getItemCount()));
input.setText("");// 清空输入框
}
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
Log.i("chat","destroy");
}
@Override
protected void onStart() {
super.onStart();
Log.i("chat","start");
}
public void initNet(){
new Thread(new Runnable() {
@Override
public void run() {
try {
application.init();
Socket socket=application.getSocket();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
@Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
public void receiveMessage(Message message) {
if (message.getSenderId().equals(receiveId) &&message.getType()== MessageType.MSG_TYPE_CHAT_P2P){
Session session=new Session(MessageType.MSG_TYPE_RECEIVE,message.getSenderId(),message.getNick(),message.getContent(),message.getSign());
msgList.add(session);
msgAdapter.notifyItemRangeInserted(msgList.size() - 1,1);// 当有新消息时,刷新适配器
recyclerView.scrollToPosition(msgList.size() - 1);// 将circleRecycle 定位到最后一行
Log.i("chat.receive",String.valueOf(msgAdapter.getItemCount()));
}
}
// 获取bundle信息
public void getBundle(){
EventBus.getDefault().register(this);
Session message=(Session)intent.getSerializableExtra("session");
if (message!=null){
receiveId=message.getSenderId();
nick.setText(message.getNick());
sign.setText(message.getSign());
}
}
@Override
protected void onStop() {
super.onStop();
Log.i("chat","stop");
}
}
这里是专门接收信息的服务。
public class MyService extends Service {
// 后台服务类,不断接受服务器数据,并转发给activity
private Socket socket;
private DataOutputStream out;
private DataInputStream in;
private MyApplication application;
private static int Foreground=11101;
private MyBinder myBinder=new MyBinder();
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
return myBinder;
}
@Override
public void onCreate() {
super.onCreate();
application=MyApplication.getApplication();
socket=application.getSocket();
Log.i("myService","create");
}
//运行时调用
@Override
public int onStartCommand(final Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
in = application.getIn();
String xml = null;
Message message=null;
try{
阻塞读取接收消息
while (null!=(xml=in.readUTF())) {
message=new Message();
message = (Message) message.fromXML(xml);
Log.i("mySerivce", xml);
EventBus.getDefault().postSticky(message);
}
}catch (IOException | NullPointerException E){}
}
}).start();
Log.i("myserivce","服务运行!");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i("myService.destroy","destroy");
}
public class MyBinder extends Binder{
}
}
接下来是适配器,用到了Session实体类,很简单,不贴了。
public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.ViewHolder>{
// 会话界面适配器,加载数据
private List<Session> msgList;
public MsgAdapter(List<Session> msgList) {
this.msgList = msgList;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.items,parent,false);
ViewHolder holder = new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Session msg = msgList.get(position);
if (msg.getType()== MessageType.MSG_TYPE_RECEIVE){
// 如果收到消息,则显示在左边的消息布局,将右边的隐藏
holder.leftLayout.setVisibility(View.VISIBLE);
holder.rightLayout.setVisibility(View.GONE);
holder.leftMsg.setText(msg.getContent());
Log.i("msgAdapter",msg.getContent());
}else if (msg.getType()==MessageType.MSG_TYPE_SEND){
// 如果发送消息,则显示在右边的消息布局,将左边的隐藏
holder.rightLayout.setVisibility(View.VISIBLE);
holder.leftLayout.setVisibility(View.GONE);
holder.rightMsg.setText(msg.getContent());
}
}
@Override
public int getItemCount() {
return msgList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
LinearLayout leftLayout,rightLayout;
TextView leftMsg,rightMsg;
public ViewHolder(View itemView) {
super(itemView);
leftLayout = itemView.findViewById(R.id.left_layout);
rightLayout = itemView.findViewById(R.id.right_layout);
leftMsg = itemView.findViewById(R.id.left_msg);
rightMsg = itemView.findViewById(R.id.right_msg);
}
}
}
最后是布局文件。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#f0fcff"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--android.support.v7.widget.RecyclerView-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:background="#ffffff"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:textColor="#000000"
android:textSize="20sp"
android:id="@+id/friend_nick"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="" />
<TextView
android:textColor="#000000"
android:id="@+id/friend_sign"
android:layout_below="@id/friend_nick"
android:gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:textColor="#000000"
android:id="@+id/et_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="请输入内容"/>
<Button
android:background="@null"
android:id="@+id/btn_send"
android:text="发送"
android:textColor="#000000"
android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
这个是信息条目的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#f0fcff"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:background="#f0fcff"
android:id="@+id/left_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start">
<TextView
android:background="@drawable/chat_left"
android:id="@+id/left_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:textColor="#000000" />
</LinearLayout>
<LinearLayout
android:id="@+id/right_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:background="#f0fcff">
<TextView
android:background="@drawable/chat_right"
android:id="@+id/right_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:textColor="#000000"/>
</LinearLayout>
</LinearLayout>
这是Message类和ProtocalObj类,MessageType类可以自己设计哦。
public class Message extends ProtocalObj implements Serializable {
private int sex;
private String sign;
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
private Long senderId;
private Long receiveId;
private String name;
public Message(Long senderId, String name, String content, int type) {
this.senderId = senderId;
this.name = name;
this.content = content;
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Message() {
}
public Message(Long senderId) {
this.senderId = senderId;
}
public Message(Long senderId, int sex,String nick, double lat, double lng, int type,String content) {
this.senderId = senderId;
this.lat = lat;
this.lng = lng;
this.type = type;
this.nick=nick;
this.sex=sex;
this.sign=content;
}
public Long getSenderId() {
return senderId;
}
public void setSenderId(Long senderTd) {
this.senderId = senderTd;
}
public Long getReceiveId() {
return receiveId;
}
public void setReceiveId(Long receiveId) {
this.receiveId = receiveId;
}
private String content;
public String getNick() {
return nick;
}
public void setNick(String nick) {
this.nick = nick;
}
private String nick;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public double getLat() {
return lat;
}
public void setLat(double lat) {
this.lat = lat;
}
public double getLng() {
return lng;
}
public void setLng(double lng) {
this.lng = lng;
}
private double lat;
private double lng;
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
private int type;
}
---------------------------------------
public class ProtocalObj {
/**
* 生成xml
*
* @return
*/
public String toXML() {
XStream x = new XStream();
// 设置别名,默认是类的全路径名
x.alias(getClass().getSimpleName(), getClass());
String xml = x.toXML(this);// 将本类转换成xml返回
return xml;
}
/**
* xml-->实体类
*
* @param xml
* @return
*/
public Object fromXML(String xml) {
XStream x = new XStream();
x.alias(getClass().getSimpleName(), getClass());
return x.fromXML(xml);
}
}
最后,我来讲解一下思路,首先呢。接收服务端信息部分,需要用到多线程,并且阻塞读取。我这里是个大点的项目,所以采用Service读取信息,其实完全可以Activity中写。然后,我这里继承了Application类实现自定义的应用程序类,主要是比较方便,因为我将Socket保存到应用程序类。最后,就是Eventbus解耦合通信了,在服务中实例化,需要接受的就注册事件就行了,更多的大家可以搜索一下哈。
还有就是布局文件,我相信大家都能看懂,信息左右分发,主要是通过设置了信息的属性。左就是left,右就是right,在适配器的代码中可以看到。
最后是XstreamJAR包
链接:https://pan.baidu.com/s/1PNF5nyBvvXv9zCQfxXJDNw
提取码:rabh
复制这段内容后打开百度网盘手机App,操作更方便哦