android: Create an instant messaging chat box (including message records)

Let’s take a look at the effect first

1.Backend

To achieve this, let’s first talk about the interface to be implemented on the backend.

1.Create session id

Pass in the "sending id" and "receiving id" to the server, and the server creates a "session id"

for example

get请求:http://xxxx:8110/picasso/createSession?fromUserId=1&toUserId=2

Returns seesionId, which is the session id

{
  "code": 200,
  "data": {
    "seesionId": 13
  }
}

2. Get message records

 get request: http://xxxx:8110/picasso/msgList?sessionId=13

The return is as follows: I asked the backend to write it according to this, although the backend said that the interface is very ugly~

There is a requirement for this return, that is, the dates must be in ascending order, and the latest 15 items must be displayed on the first page. This should be understandable. The latest dates in the chat box must be displayed at the bottom. Of course, you can also manually do so on the Android side. Reorder:

Collections.reverse(msgChat1);
{
  "code": 200,
  "data": {
    "total": 30,
    "current": 2,
    "pages": 2,
    "size": 15,
    "list": [
      {
        "id": 195,
        "fromUserId": 2,
        "fromUserName": "青山不改",
        "toUserId": 1,
        "toUserName": "绿水长流",
        "content": "123123",
        "createTime": "2023-03-30 20:42:21",
        "unReadFlag": 1
      },
      {
        "id": 196,
        "fromUserId": 1,
        "fromUserName": "青山不改",
        "toUserId": 2,
        "toUserName": "绿水长流",
        "content": "rqwewqeqw",
        "createTime": "2023-03-30 20:42:26",
        "unReadFlag": 0
      },
      {
        "id": 197,
        "fromUserId": 2,
        "fromUserName": "青山不改",
        "toUserId": 1,
        "toUserName": "绿水长流",
        "content": "rwerew",
        "createTime": "2023-03-30 20:43:01",
        "unReadFlag": 1
      },
      {
        "id": 198,
        "fromUserId": 1,
        "fromUserName": "青山不改",
        "toUserId": 2,
        "toUserName": "绿水长流",
        "content": "1---2",
        "createTime": "2023-03-30 20:43:11",
        "unReadFlag": 0
      },
      {
        "id": 199,
        "fromUserId": 2,
        "fromUserName": "青山不改",
        "toUserId": 1,
        "toUserName": "绿水长流",
        "content": "2----1",
        "createTime": "2023-03-30 20:43:21",
        "unReadFlag": 1
      },
      {
        "id": 200,
        "fromUserId": 2,
        "fromUserName": "青山不改",
        "toUserId": 1,
        "toUserName": "绿水长流",
        "content": "23123",
        "createTime": "2023-03-30 20:52:27",
        "unReadFlag": 1
      },
      {
        "id": 201,
        "fromUserId": 1,
        "fromUserName": "青山不改",
        "toUserId": 2,
        "toUserName": "绿水长流",
        "content": "333",
        "createTime": "2023-03-30 20:52:31",
        "unReadFlag": 0
      },
      {
        "id": 202,
        "fromUserId": 2,
        "fromUserName": "青山不改",
        "toUserId": 1,
        "toUserName": "绿水长流",
        "content": "333",
        "createTime": "2023-03-30 21:02:27",
        "unReadFlag": 1
      },
      {
        "id": 203,
        "fromUserId": 1,
        "fromUserName": "青山不改",
        "toUserId": 2,
        "toUserName": "绿水长流",
        "content": "444",
        "createTime": "2023-03-30 21:02:42",
        "unReadFlag": 0
      },
      {
        "id": 204,
        "fromUserId": 2,
        "fromUserName": "青山不改",
        "toUserId": 1,
        "toUserName": "绿水长流",
        "content": "22",
        "createTime": "2023-03-30 21:03:57",
        "unReadFlag": 1
      },
      {
        "id": 205,
        "fromUserId": 1,
        "fromUserName": "青山不改",
        "toUserId": 2,
        "toUserName": "绿水长流",
        "content": "uqwe",
        "createTime": "2023-03-30 21:05:36",
        "unReadFlag": 0
      },
      {
        "id": 206,
        "fromUserId": 1,
        "fromUserName": "青山不改",
        "toUserId": 2,
        "toUserName": "绿水长流",
        "content": "yyy",
        "createTime": "2023-03-30 21:07:46",
        "unReadFlag": 0
      },
      {
        "id": 207,
        "fromUserId": 2,
        "fromUserName": "青山不改",
        "toUserId": 1,
        "toUserName": "绿水长流",
        "content": "2---1",
        "createTime": "2023-03-30 21:12:28",
        "unReadFlag": 1
      },
      {
        "id": 208,
        "fromUserId": 2,
        "fromUserName": "青山不改",
        "toUserId": 1,
        "toUserName": "绿水长流",
        "content": "2----1",
        "createTime": "2023-03-30 21:12:35",
        "unReadFlag": 1
      },
      {
        "id": 209,
        "fromUserId": 1,
        "fromUserName": "青山不改",
        "toUserId": 2,
        "toUserName": "绿水长流",
        "content": "1----2",
        "createTime": "2023-03-30 21:12:59",
        "unReadFlag": 0
      }
    ]
  }
}

 3. Websocket allows the backend to allow the client to connect according to the following requirements. I don’t know exactly how it is done. It seems to be the forwarding of messages.

ws://"+IP+":8110/websocket/send id/session id

 2. APP Android version

Because I wrote the Android version, I can post the code as completely as possible.

1. Chat layout:

My writing method is relatively simple. I just write the other party's message and mine directly in one item. I compare it to see if the data is sent by myself. If so, I will display my own message UI. If not, I will hide my own UI.

 The code layout is as follows:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="right"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_time"
        android:gravity="center"
        android:textSize="8dp"
        android:text="2022-10-22 12:00"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <LinearLayout
        android:id="@+id/ll_left"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="left"
        >

        <TextView
            android:visibility="gone"
            android:id="@+id/to_user_id"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <TextView
            android:id="@+id/to_user_head"
            android:layout_margin="10dp"
            android:background="@drawable/ic_head_rec_bg"
            android:text="LL"
            android:gravity="center"
            android:textColor="@color/white"
            android:layout_width="50dp"
            android:layout_height="50dp"/>
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_marginRight="50dp"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="16dp"
            android:gravity="left">
            <TextView
                android:visibility="gone"
                android:id="@+id/to_user_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="#cccccc"
                android:textStyle="bold"
                android:textSize="12sp"
                android:text="demo_9999"
                />
            <TextView
                android:id="@+id/to_user_msg"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="#000000"
                android:textSize="14sp"
                android:gravity="left"
                android:paddingTop="6dp"
                android:paddingBottom="7dp"
                android:paddingRight="10dp"
                android:paddingLeft="10dp"
                android:background="@drawable/bg_popo_left"
                android:text="mmm"
                />
        </LinearLayout>


    </LinearLayout>




    <LinearLayout
        android:id="@+id/ll_right"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="right"
        >
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_marginTop="16dp"
            android:layout_marginRight="5dp"
            android:layout_marginLeft="50dp"
            android:gravity="right">
            <TextView
                android:visibility="gone"
                android:id="@+id/from_user_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="#cccccc"
                android:textStyle="bold"
                android:textSize="12sp"
                android:text="demo_9999"
                />
            <TextView
                android:id="@+id/from_user_msg"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@color/theme"
                android:textSize="14sp"
                android:layout_marginBottom="5dp"
                android:paddingTop="6dp"
                android:paddingBottom="7dp"
                android:paddingRight="10dp"
                android:paddingLeft="10dp"
                android:background="@drawable/bg_popo_right"
                android:text="毕加索"
                />
        </LinearLayout>

        <TextView
            android:id="@+id/from_user_head"
            android:layout_margin="10dp"
            android:background="@drawable/ic_head_bg"
            android:text="LL"
            android:gravity="center"
            android:textColor="@color/white"
            android:layout_width="50dp"
            android:layout_height="50dp"/>

        <TextView
            android:id="@+id/from_user_id"
            android:visibility="gone"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    </LinearLayout>

</LinearLayout>

2. General layout of message box

 This is not important, it’s just that I used an additional custom listview. This is used to monitor the drop-down. After all, there will be historical messages, and the drop-down will directly load the previous page.

layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:layout_marginTop="-25dp"
    android:background="#F6F6F6"
    android:fitsSystemWindows="true"
    android:orientation="vertical">

    <com.picasso.module.ui.CustomRefreshListView
        android:id="@+id/msg_list"
        android:layout_weight="1"
        android:divider="#00000000"
        android:scrollbars="none"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#ffffff"
        android:elevation="5dp"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:paddingBottom="10dp">

        <EditText
            android:id="@+id/id_input"
            android:layout_width="0dp"
            android:layout_height="40dp"
            android:layout_marginLeft="15dp"
            android:layout_marginTop="5dp"
            android:layout_marginBottom="5dp"
            android:layout_weight="1"
            android:background="@drawable/bg_round_corner_grey"
            android:gravity="center_vertical"
            android:hint="来聊吧~"
            android:paddingLeft="15dp"
            android:textSize="12dp" />

        <RelativeLayout
            android:layout_width="75dp"
            android:layout_height="40dp"
            android:layout_marginLeft="15dp"
            android:layout_marginRight="15dp"
            android:background="@drawable/bg_btn_orange_back">

            <TextView
                android:id="@+id/send_btn"
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:gravity="center"
                android:text="发送"
                android:textColor="#ffffff"
                android:textSize="16sp" />
        </RelativeLayout>
    </LinearLayout>
</LinearLayout>

Support drop-down listview control


public class CustomRefreshListView extends ListView implements AbsListView.OnScrollListener {

    /**
     * 头布局
     */
    private View headerView;

    /**
     * 头部布局的高度
     */
    private int headerViewHeight;

    /**
     * 头部旋转的图片
     */
    private ImageView iv_arrow;

    /**
     * 头部下拉刷新时状态的描述
     */
    private TextView tv_state;

    /**
     * 下拉刷新时间的显示控件
     */
    private TextView tv_time;


    /**
     * 底部布局
     */
    private View footerView;

    /**
     * 底部旋转progressbar
     */
    private ProgressBar pb_rotate;


    /**
     * 底部布局的高度
     */
    private int footerViewHeight;


    /**
     * 按下时的Y坐标
     */
    private int downY;

    private final int PULL_REFRESH = 0;//下拉刷新的状态
    private final int RELEASE_REFRESH = 1;//松开刷新的状态
    private final int REFRESHING = 2;//正在刷新的状态

    /**
     * 当前下拉刷新处于的状态
     */
    private int currentState = PULL_REFRESH;

    /**
     * 头部布局在下拉刷新改变时,图标的动画
     */
    private RotateAnimation upAnimation,downAnimation;

    /**
     * 当前是否在加载数据
     */
    private boolean isLoadingMore = false;

    public CustomRefreshListView(Context context) {
        this(context,null);
    }

    public CustomRefreshListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init(){
        //设置滑动监听
        setOnScrollListener(this);
        //初始化头布局
        initHeaderView();
        //初始化头布局中图标的旋转动画
        initRotateAnimation();
        //初始化为尾布局
        initFooterView();
    }


    /**
     * 初始化headerView
     */
    private void initHeaderView() {
        headerView = View.inflate(getContext(), R.layout.head_custom_listview, null);
        iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow);
        pb_rotate = (ProgressBar) headerView.findViewById(R.id.pb_rotate);
        tv_state = (TextView) headerView.findViewById(R.id.tv_state);
        tv_time = (TextView) headerView.findViewById(R.id.tv_time);

        //测量headView的高度
        headerView.measure(0, 0);
        //获取高度,并保存
        headerViewHeight = headerView.getMeasuredHeight();
        //设置paddingTop = -headerViewHeight;这样,该控件被隐藏
        headerView.setPadding(0, -headerViewHeight, 0, 0);
        //添加头布局
        addHeaderView(headerView);
    }

    /**
     * 初始化旋转动画
     */
    private void initRotateAnimation() {

        upAnimation = new RotateAnimation(0, -180, 
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        upAnimation.setDuration(300);
        upAnimation.setFillAfter(true);

        downAnimation = new RotateAnimation(-180, -360, 
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        downAnimation.setDuration(300);
        downAnimation.setFillAfter(true);
    }

    //初始化底布局,与头布局同理
    private void initFooterView() {
        footerView = View.inflate(getContext(), R.layout.foot_custom_listview, null);
        footerView.measure(0, 0);
        footerViewHeight = footerView.getMeasuredHeight();
        footerView.setPadding(0, -footerViewHeight, 0, 0);
        addFooterView(footerView);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            //获取按下时y坐标
            downY = (int) ev.getY();
            break;
        case MotionEvent.ACTION_MOVE:

            if(currentState==REFRESHING){
                //如果当前处在滑动状态,则不做处理
                break;
            }
            //手指滑动偏移量
            int deltaY = (int) (ev.getY() - downY);

            //获取新的padding值
            int paddingTop = -headerViewHeight + deltaY;
            if(paddingTop>-headerViewHeight && getFirstVisiblePosition()==0){
                //向下滑,且处于顶部,设置padding值,该方法实现了顶布局慢慢滑动显现
                headerView.setPadding(0, paddingTop, 0, 0);

                if(paddingTop>=0 && currentState==PULL_REFRESH){
                    //从下拉刷新进入松开刷新状态
                    currentState = RELEASE_REFRESH;
                    //刷新头布局
                    refreshHeaderView();
                }else if (paddingTop<0 && currentState==RELEASE_REFRESH) {
                    //进入下拉刷新状态
                    currentState = PULL_REFRESH;
                    refreshHeaderView();
                }


                return true;//拦截TouchMove,不让listview处理该次move事件,会造成listview无法滑动
            }


            break;
        case MotionEvent.ACTION_UP:
            if(currentState==PULL_REFRESH){
                //仍处于下拉刷新状态,未滑动一定距离,不加载数据,隐藏headView
                headerView.setPadding(0, -headerViewHeight, 0, 0);
            }else if (currentState==RELEASE_REFRESH) {
                //滑倒一定距离,显示无padding值得headcView
                headerView.setPadding(0, 0, 0, 0);
                //设置状态为刷新
                currentState = REFRESHING;

                //刷新头部布局
                refreshHeaderView();

                if(listener!=null){
                    //接口回调加载数据
                    listener.onPullRefresh();
                }
            }
            break;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 根据currentState来更新headerView
     */
    private void refreshHeaderView(){
        switch (currentState) {
        case PULL_REFRESH:
            tv_state.setText("下拉刷新");
            iv_arrow.startAnimation(downAnimation);
            break;
        case RELEASE_REFRESH:
           // tv_state.setText("松开刷新");
            iv_arrow.startAnimation(upAnimation);
            break;
        case REFRESHING:
            iv_arrow.clearAnimation();//因为向上的旋转动画有可能没有执行完
            iv_arrow.setVisibility(View.INVISIBLE);
            pb_rotate.setVisibility(View.VISIBLE);
           // tv_state.setText("正在刷新...");
            break;
        }
    }

    /**
     * 完成刷新操作,重置状态,在你获取完数据并更新完adater之后,去在UI线程中调用该方法
     */
    public void completeRefresh(){
        if(isLoadingMore){
            //重置footerView状态
            footerView.setPadding(0, -footerViewHeight, 0, 0);
            isLoadingMore = false;
        }else {
            //重置headerView状态
            headerView.setPadding(0, -headerViewHeight, 0, 0);
            currentState = PULL_REFRESH;
            pb_rotate.setVisibility(View.INVISIBLE);
            iv_arrow.setVisibility(View.VISIBLE);
            tv_state.setText("下拉刷新");
            tv_time.setText("最后刷新:"+getCurrentTime());
        }
    }

    /**
     * 获取当前系统时间,并格式化
     * @return
     */
    private String getCurrentTime(){
        SimpleDateFormat format = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
        return format.format(new Date());
    }

    private OnRefreshListener listener;
    public void setOnRefreshListener(OnRefreshListener listener){
        this.listener = listener;
    }
    public interface OnRefreshListener{
        void onPullRefresh();
        void onLoadingMore();
    }

    /**
     * SCROLL_STATE_IDLE:闲置状态,就是手指松开
     * SCROLL_STATE_TOUCH_SCROLL:手指触摸滑动,就是按着来滑动
     * SCROLL_STATE_FLING:快速滑动后松开
     */
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if(scrollState==OnScrollListener.SCROLL_STATE_IDLE 
                && getLastVisiblePosition()==(getCount()-1) &&!isLoadingMore){
            isLoadingMore = true;

            footerView.setPadding(0, 0, 0, 0);//显示出footerView
            setSelection(getCount());//让listview最后一条显示出来,在页面完全显示出底布局

            if(listener!=null){
                listener.onLoadingMore();
            }
        }
    }


    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
    }

There are some controls and the like in there, you can draw and add them by yourself, I just put them randomly.

head_custom_listview.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ProgressBar
        android:id="@+id/pb_rotate"
        android:layout_width="match_parent"
        android:layout_height="20dp"/>
    <TextView
        android:id="@+id/tv_state"
        android:layout_width="match_parent"
        android:layout_height="20dp"/>
    <TextView
        android:id="@+id/tv_time"
        android:layout_width="match_parent"
        android:layout_height="20dp"/>
    <ImageView
        android:id="@+id/iv_arrow"
        android:layout_width="match_parent"
        android:layout_height="20dp"/>

</LinearLayout>

3.websocket code

Remember to add this line to the manifest file

        <service
            android:name=".websocket.JWebSocketClientService1"
            android:enabled="true"
            android:exported="true" />
The following is from Baidu. If you change it, you can use 
JWebSocketClientService1 directly.
public class JWebSocketClientService1 extends Service {
    private String TAG="JWebSocketClientService";
    private URI uri;
    public JWebSocketClient client;
    private JWebSocketClientBinder mBinder = new JWebSocketClientBinder();

    //用于Activity和service通讯
    public class JWebSocketClientBinder extends Binder {
        public JWebSocketClientService1 getService() {
            return JWebSocketClientService1.this;
        }
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }


    @Override
    public void onCreate() {
        super.onCreate();

        //初始化websocket
        initSocketClient();
        mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);//开启心跳检测
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }


    /**
     * 初始化websocket连接
     */
    private void initSocketClient() {
        URI uri = URI.create(Constant.webSocketHost+"/"+Constant.SendId+"/"+Constant.ChatId);//测试使用
        //Log.e("initSocketClient",Constant.webSocketHost+"/"+Constant.SendId+"/"+Constant.ChatId);
        Log.e("SocketInfo-host",Constant.webSocketHost+"/"+Constant.SendId+"/"+Constant.ChatId);
        client = new JWebSocketClient(uri) {
            @Override
            public void onMessage(String message) {
                Log.e("JWebSocketClientService", "收到的消息:" + message);

                Intent intent = new Intent();//广播接收到的消息,在Activity接收
                intent.setAction("com.picasso.shanghai.activity.ChatActivity$ChatMessageReceiver");
                intent.putExtra("message", message);
                sendBroadcast(intent);
            }

            @Override
            public void onOpen(ServerHandshake handshakedata) {
                super.onOpen(handshakedata);
                Log.e("JWebSocketClientService", "websocket连接成功");
            }
        };
        connect();
    }
    /**
     * 连接websocket
     */
    private void connect() {
        new Thread() {
            @Override
            public void run() {
                try {
                    //connectBlocking多出一个等待操作,会先连接再发送,否则未连接发送会报错
                    client.connectBlocking();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();

    }
    /**
     * 发送消息
     *
     * @param msg
     */
    public void sendMsg(String msg) {
        if (null != client) {
            Log.e("JWebSocketClientService", "发送的消息:" + msg);
            client.send(msg);
        }else {

        }
    }

    //    -------------------------------------websocket心跳检测------------------------------------------------
    private static final long HEART_BEAT_RATE = 30 * 1000;//每隔30秒进行一次对长连接的心跳检测
    private Handler mHandler = new Handler();
    private Runnable heartBeatRunnable = new Runnable() {
        @Override
        public void run() {

            Log.e("JWebSocketClientService", "心跳包检测websocket连接状态");
            if (client != null) {
                if (client.isClosed()) {
                    reconnectWs();
                }else {
                    Log.e("JWebSocketClientService", "!client.isClosed()");
                    //业务逻辑 这里如果服务端需要心跳包为了防止断开 需要不断发送消息给服务端
                    // client.send("");
                }
            } else {
                //如果client已为空,重新初始化连接
                client = null;
                Log.e("JWebSocketClientService", "client = null");
                initSocketClient();
            }
            //每隔一定的时间,对长连接进行一次心跳检测
            mHandler.postDelayed(this, HEART_BEAT_RATE);
        }
    };
    /**
     * 开启重连
     */
    private void reconnectWs() {
        mHandler.removeCallbacks(heartBeatRunnable);
        new Thread() {
            @Override
            public void run() {
                try {
                    Log.e("JWebSocketClientService", "开启重连");
                    client.reconnectBlocking();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy: ....");
        super.onDestroy();
    }
JWebSocketClient
public class JWebSocketClient extends WebSocketClient {
    public JWebSocketClient(URI serverUri) {
        super(serverUri, new Draft_6455());
    }

    @Override
    public void onOpen(ServerHandshake handshakedata) {
        Log.e("JWebSocketClient", "onOpen()");
    }

    @Override
    public void onMessage(String message) {
        Log.e("JWebSocketClient", "onMessage()");
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        Log.e("JWebSocketClient", "onClose()");
    }

    @Override
    public void onError(Exception ex) {
        Log.e("JWebSocketClient", "onError()");
    }
}

4. Entity classes, and adapters

MsgChat.java

public class MsgChat {

    private int fromUserId,toUserId,unReadFlag;
    private String fromUserName,toUserName,createTime,content;


    public int getFromUserId() {
        return fromUserId;
    }

    public void setFromUserId(int fromUserId) {
        this.fromUserId = fromUserId;
    }

    public int getToUserId() {
        return toUserId;
    }

    public void setToUserId(int toUserId) {
        this.toUserId = toUserId;
    }

    public String getCreateTime() {
        return createTime;
    }

    public void setCreateTime(String createTime) {
        this.createTime = createTime;
    }

    public int getUnReadFlag() {
        return unReadFlag;
    }

    public void setUnReadFlag(int unReadFlag) {
        this.unReadFlag = unReadFlag;
    }

    public String getFromUserName() {
        return fromUserName;
    }

    public void setFromUserName(String fromUserName) {
        this.fromUserName = fromUserName;
    }

    public String getToUserName() {
        return toUserName;
    }

    public void setToUserName(String toUserName) {
        this.toUserName = toUserName;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

MessageAdapter.java

public class MessageAdapter extends BaseAdapter implements ListAdapter {

    private ArrayList<MsgChat> msgChatArrayList;
    private int id;
    private Context mcontext;
    private LayoutInflater inflater;


    public MessageAdapter(int sub_item, Context mcontext, ArrayList<MsgChat> msgChatArrayList) {
        this.msgChatArrayList = msgChatArrayList;
        this.mcontext = mcontext;
        this.id = sub_item;
        inflater = LayoutInflater.from(mcontext);
    }

    @Override
    public int getCount() {
        return msgChatArrayList.size();
    }

    @Override
    public Object getItem(int i) {
        return msgChatArrayList.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }


    public void addItem(MsgChat msgChat, ArrayList<MsgChat> msgChats) {

        msgChats.add(msgChat);
        this.msgChatArrayList = msgChats;
        this.notifyDataSetChanged();
    }




    @SuppressLint("WrongConstant")
    @Override
    public View getView(int position, View view, ViewGroup viewGroup) {

        LinearLayout ll_left = null;
        LinearLayout ll_right = null;

        TextView tv_time = null;

        TextView from_user_id = null;
        TextView from_user_name = null;
        TextView from_user_head = null;
        TextView from_user_msg = null;

        TextView to_user_id = null;
        TextView to_user_name = null;
        TextView to_user_head = null;
        TextView to_user_msg = null;

        ViewHolder viewHolder;
        if (view == null) {
            //获取item视图类型
            view = inflater.inflate(id, null);
            ll_left = (LinearLayout) view.findViewById(R.id.ll_left);
            ll_right = (LinearLayout) view.findViewById(R.id.ll_right);

            tv_time = (TextView) view.findViewById(R.id.tv_time);

            from_user_id = (TextView) view.findViewById(R.id.from_user_id);
            from_user_name = (TextView) view.findViewById(R.id.from_user_name);
            from_user_head = (TextView) view.findViewById(R.id.from_user_head);
            from_user_msg = (TextView) view.findViewById(R.id.from_user_msg);

            to_user_id = (TextView) view.findViewById(R.id.to_user_id);
            to_user_name = (TextView) view.findViewById(R.id.to_user_name);
            to_user_head = (TextView) view.findViewById(R.id.to_user_head);
            to_user_msg = (TextView) view.findViewById(R.id.to_user_msg);

            view.setTag(new ViewHolder(ll_left,ll_right,tv_time,from_user_id,from_user_head,from_user_name,from_user_msg,to_user_id,to_user_head,to_user_name,to_user_msg));
        } else {
            ViewHolder viewHolder1 = (ViewHolder) view.getTag(); // 重新获取ViewHolder
            ll_left = viewHolder1.ll_left;
            ll_right = viewHolder1.ll_right;

            tv_time = viewHolder1.tv_time;

            from_user_id = viewHolder1.from_user_id;
            from_user_name = viewHolder1.from_user_name;
            from_user_head = viewHolder1.from_user_head;
            from_user_msg = viewHolder1.from_user_msg;

            to_user_id = viewHolder1.to_user_id;
            to_user_name = viewHolder1.to_user_name;
            to_user_head = viewHolder1.to_user_head;
            to_user_msg = viewHolder1.to_user_msg;
        }

        MsgChat msgChat = (MsgChat) msgChatArrayList.get(position);
        from_user_id.setText(msgChat.getFromUserId()+"");
        if(msgChat.getFromUserId()==Constant.SendId){
            ll_left.setVisibility(View.GONE);
            ll_right.setVisibility(View.VISIBLE);
        }else {
            ll_right.setVisibility(View.GONE);
            ll_left.setVisibility(View.VISIBLE);
        }


        try {
            from_user_name.setText(msgChat.getFromUserName().substring(0, 2));
        }catch(Exception e){
            from_user_name.setText("NULL");
        }

        try {
            from_user_head.setText(msgChat.getFromUserName().substring(0, 2));
        }catch(Exception e){
            from_user_head.setText("NULL");
        }
        from_user_msg.setText(msgChat.getContent()+"");

        to_user_id.setText(msgChat.getToUserId()+"");
        try {
            to_user_name.setText(msgChat.getToUserName().substring(0, 2));
        }catch(Exception e){
            to_user_name.setText("NULL");
        }

        try {
            to_user_head.setText(msgChat.getToUserName().substring(0, 2));
        }catch(Exception e){
            to_user_head.setText("NULL");
        }
        to_user_msg.setText(msgChat.getContent()+"");


        tv_time.setText(msgChat.getCreateTime());



        return view;
    }
    private final class ViewHolder {

        LinearLayout ll_left = null;
        LinearLayout ll_right = null;

        TextView tv_time = null;

        TextView from_user_id = null;
        TextView from_user_name = null;
        TextView from_user_head = null;
        TextView from_user_msg = null;

        TextView to_user_id = null;
        TextView to_user_name = null;
        TextView to_user_head = null;
        TextView to_user_msg = null;

        public ViewHolder(LinearLayout ll_left,LinearLayout ll_right,TextView tv_time,TextView from_user_id,TextView from_user_head,TextView from_user_name,TextView from_user_msg,TextView to_user_id,TextView to_user_head,TextView to_user_name,TextView to_user_msg) {
            this.ll_left = ll_left;
            this.ll_right = ll_right;

            this.tv_time = tv_time;

            this.from_user_id = from_user_id;
            this.from_user_name = from_user_name;
            this.from_user_head = from_user_head;
            this.from_user_msg = from_user_msg;

            this.to_user_id = to_user_id;
            this.to_user_name = to_user_name;
            this.to_user_head = to_user_head;
            this.to_user_msg = to_user_msg;
        }


    }

5.ChatActivity core code

public class ChatActivity extends BaseActivity {
    private JWebSocketClientService1.JWebSocketClientBinder binder;
    private JWebSocketClientService1 jWebSClientService;
    private JWebSocketClient client;
    private ChatMessageReceiver chatMessageReceiver;
    private Intent intent;
    private TextView send_btn;
    private EditText id_input;
    private CustomRefreshListView msg_list;
    private ArrayList<MsgChat> msgChats = new ArrayList<>();
    private MsgChat msgChat;
    private MessageAdapter messageAdapter;
    private int PAGES = 0;
    private int CURRENT = 0;
    private boolean StateY = false;//true 下拉,false 上拉


    private class ChatMessageReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String message=intent.getStringExtra("message");
            Log.e("SocketInfo-Receiver",message);
            MsgChat msgChat = new MsgChat();
            JSONObject msg = JSONObject.parseObject(message);
            msgChat.setFromUserId(msg.getInteger("fromUserId"));
            msgChat.setFromUserName(msg.getString("fromUserName"));
            msgChat.setToUserId(msg.getInteger("toUserId"));
            msgChat.setToUserName(msg.getString("toUserName"));
            msgChat.setContent(msg.getString("content"));
            msgChat.setCreateTime(msg.getString("createTime"));
            msgChat.setUnReadFlag(msg.getInteger("unReadFlag"));
            Log.e("====>>", Constant.SendId+"----"+msg.getInteger("fromUserId"));
            Log.e("====>>", msg.getString("content"));
            messageAdapter.addItem(msgChat,msgChats);
            msg_list.setSelection(messageAdapter.getCount()-1);
        }
    }

    private void doRegisterReceiver() {
        Log.e("ChatMessageReceiver", "广播的权限1");
        chatMessageReceiver = new ChatMessageReceiver();
        IntentFilter filter = new IntentFilter("com.picasso.shanghai.activity.ChatActivity$ChatMessageReceiver");
        registerReceiver(chatMessageReceiver, filter);
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.e("this", "服务与活动成功绑定");
            binder = (JWebSocketClientService1.JWebSocketClientBinder) iBinder;
            jWebSClientService = binder.getService();
            client = jWebSClientService.client;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.e("this", "服务与活动成功断开");
        }
    };

    @Override
    public int getLayout() {
        return R.layout.activity_chat;
    }

    @Override
    public void initView() {

        setStatusBar();

        //启动服务
        startJWebSClientService();

        //绑定服务
        bindService();

        //注册广播
        doRegisterReceiver();

        //初始化历史消息
        initHistoryMsg(Constant.ChatId,"");

        msg_list = findViewById(R.id.msg_list);
        msg_list.setOnRefreshListener(new CustomRefreshListView.OnRefreshListener() {
            @Override
            public void onPullRefresh() {
                if(CURRENT!=PAGES){
                    CURRENT++;
                    Log.e("list","xia"+CURRENT);
                    StateY = true;
                    initHistoryMsg(Constant.ChatId,String.valueOf(CURRENT));
                }
                msg_list.completeRefresh();
            }

            @Override
            public void onLoadingMore() {
                msg_list.completeRefresh();
                StateY = false;
                Log.e("list","shang");
            }
        });
       // msg_list.setLayoutManager(new LinearLayoutManager(this));
        send_btn = findViewById(R.id.send_btn);
        id_input = findViewById(R.id.id_input);
        send_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                jWebSClientService.sendMsg(id_input.getText().toString());
                id_input.setText("");
            }
        });
    }

    private Handler Thandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    messageAdapter = new MessageAdapter(R.layout.item_chat_msg_list,ChatActivity.this,msgChats);
                    msg_list.setAdapter(messageAdapter);
                    if(StateY){
                        //msg_list.setSelection(0);
                    }else {
                        msg_list.setSelection(messageAdapter.getCount()-1);
                    }
                    break;
            }
        }
    };



    //初始化历史消息
    private void initHistoryMsg(int chatId,String pageNum) {
        ChatListener chatListener = new ChatListener();
        chatListener.historyMessage(chatId,pageNum, new RequestCallback() {
            @Override
            public void success(String str) {

                JSONObject data_key5 = JSONObject.parseObject(str);
                String _data = data_key5.getString("data");
                JSONObject data_key6 = JSONObject.parseObject(_data);
                PAGES = data_key6.getInteger("pages");
                CURRENT = data_key6.getInteger("current");
                Log.e("list",PAGES+"");

                try {
                    String list = data_key6.getString("list");
                    Log.e("list",list);
                    List<MsgChat> msgChat1 = JSON.parseArray(list, MsgChat.class);

                    //数据翻转一下
                    if(StateY){
                        Collections.reverse(msgChat1);
                    }

                    for (MsgChat pp : msgChat1) {
                        msgChat = new MsgChat();
                        int fromUserId = pp.getFromUserId();
                        String fromUserName = pp.getFromUserName();
                        int toUserId = pp.getToUserId();
                        String toUserName = pp.getToUserName();
                        String createTime = pp.getCreateTime();
                        String content = pp.getContent();
                        int unReadFlag = pp.getUnReadFlag();
                        msgChat.setFromUserId(fromUserId);
                        msgChat.setFromUserName(fromUserName);
                        msgChat.setToUserId(toUserId);
                        msgChat.setToUserName(toUserName);
                        msgChat.setContent(content);
                        msgChat.setCreateTime(createTime);
                        msgChat.setUnReadFlag(unReadFlag);
                        if(StateY){
                            msgChats.add(0,msgChat);
                        }else {
                            msgChats.add(msgChat);
                        }


                    }
                }catch(Exception e){
                    Log.e("list","数据为空");
                }
                Message msg = new Message();
                msg.what = 1;
                Thandler.sendMessage(msg);
            }
            @Override
            public void error(String error) {
                Message msg = new Message();
                msg.what = 1;
                Thandler.sendMessage(msg);
            }
        });
    }





    private void startJWebSClientService() {
        Log.e("this", "开启服务");
        intent = new Intent(ChatActivity.this, JWebSocketClientService1.class);
        startService(intent);
    }

    private void restartJWebSClientService() {
        //销毁服务
        Log.e("this", "销毁服务");
        stopService(intent);
    }


    /**
     * 绑定服务
     */
    private void bindService() {
        Intent bindIntent = new Intent(ChatActivity.this, JWebSocketClientService1.class);
        bindService(bindIntent, serviceConnection, 0000);
    }


    /**
     * 注销广播
     */
    private void unRegisterReceiver() {
        unregisterReceiver(chatMessageReceiver);
    }

    //设置状态栏为透明
    protected void setStatusBar() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unRegisterReceiver();
        unbindService(serviceConnection);
        restartJWebSClientService();
    }

    @Override
    public void initData() {

    }



}

6. Constant parameter file

This is worth mentioning. Before jumping, I saved the room number and sendId in this file. When comparing, I directly compare the sendId and fromUserId in this file to distinguish whether it is sent by myself or the other party.

Constant.java
public class Constant {

    public final static String IP = "192.168.1.104";
    public final static String webSocketHost = "ws://"+IP+":8110/websocket";
    public final static String Host = "http://"+IP+":8110/";
    public final static String get_picasso_createSession = Host + "picasso/createSession";
    public final static String get_picasso_msgList= Host + "picasso/msgList?sessionId=";

    public static int ChatId = 0;
    public static int SendId = 0;

}

7. Callback for http request

The writing is very rough, so don’t read it.

public class ChatListener {


    private String TAG = "RequestListener";
    private RequestCallback requestListener;


    //获取历史消息
    public void historyMessage(int chatId,String pageNum, RequestCallback requestListener) {
        this.requestListener = requestListener;
        HistoryMessageRequest(Constant.get_picasso_msgList,chatId,pageNum);
    }


    //创建会话
    public void createSession(int from_id,int to_id, RequestCallback requestListener) {
        this.requestListener = requestListener;
        CreateSessionRequest(Constant.get_picasso_createSession,from_id,to_id);
    }

    public void CreateSessionRequest(String str,int from_id,int to_id){
        OkHttpClient client = new OkHttpClient();
        Request request1 = new Request.Builder()
                .url(str+"?fromUserId="+from_id+"&toUserId="+to_id)
                .build();

        client.newCall(request1).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e("CreateSessionRequest==>",e.getMessage());
                requestListener.success(e.getMessage());
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                    String result = response.body().string();
                    requestListener.success(result);
            }
        });
    }



    public void HistoryMessageRequest(String str,int chatId,String pageNum){
        OkHttpClient client = new OkHttpClient();
        Request request1 = new Request.Builder()
                .url(str+chatId+"&page="+pageNum)
                .build();

        client.newCall(request1).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e("CreateSessionRequest==>",e.getMessage());
                requestListener.success(e.getMessage());
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String result = response.body().string();
                requestListener.success(result);
            }
        });
    }




}

Guess you like

Origin blog.csdn.net/title71/article/details/129879409