android: crea un cuadro de chat de mensajería instantánea (incluidos registros de mensajes)

Primero echemos un vistazo al efecto.

1.Servicio de fondo

Para lograr esto, primero hablemos de la interfaz que se implementará en el backend.

1.Crear identificación de sesión

Pase el "ID de envío" y el "ID de recepción" al servidor, y el servidor creará un "ID de sesión".

Por ejemplo

obtener enlace: http://xxxx:8110/picasso/createSession?fromUserId=1&toUserId=2

Devuelve seesionId, que es el ID de la sesión.

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

2. Obtener registros de mensajes

 obtener solicitud: http://xxxx:8110/picasso/msgList?sessionId=13

El retorno es el siguiente: le pedí al backend que lo escribiera de acuerdo con esto, aunque el backend dijo que la interfaz es muy fea ~

Existe un requisito para esta devolución, es decir, las fechas deben estar en orden ascendente y los últimos 15 elementos deben mostrarse en la primera página. Esto debe ser comprensible. Las últimas fechas en el cuadro de chat deben mostrarse en la parte inferior. Por supuesto, también puedes hacerlo manualmente en el lado de Android.

Colecciones.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 permite que el backend permita que el cliente se conecte de acuerdo con los siguientes requisitos. No sé exactamente cómo se hace. Parece ser el reenvío de mensajes.

ws://"+IP+":8110/websocket/ID de envío/ID de sesión

 2. Versión de Android de la aplicación

Como escribí la versión de Android, puedo publicar el código lo más completo posible.

1. Diseño del chat:

Mi método de escritura es relativamente simple. Simplemente escribo el mensaje de la otra parte y el mío directamente en un elemento. Lo comparo para ver si los datos los envié yo mismo. Si es así, mostraré mi propia interfaz de usuario de mensaje. Si no, lo haré. ocultar mi propia interfaz de usuario.

 El diseño del código es el siguiente:

<?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. Diseño general del cuadro de mensaje.

 Esto no es importante, es solo que utilicé una vista de lista personalizada adicional. Esto se usa para monitorear el menú desplegable. Después de todo, habrá mensajes históricos y el menú desplegable cargará directamente la página anterior.

disposición

<?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>

Admite control de vista de lista desplegable


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) {
    }

Hay algunos controles y cosas similares ahí, puedes dibujarlos y agregarlos tú mismo, yo simplemente los puse al azar.

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.código websocket

Recuerde agregar esta línea al archivo de manifiesto.

        <service
            android:name=".websocket.JWebSocketClientService1"
            android:enabled="true"
            android:exported="true" />
Lo siguiente es de Baidu: si lo cambia, puede usar 
JWebSocketClientService1 directamente.
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();
    }
Cliente JWebSocket
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. Clases de entidad y adaptadores.

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;
    }
}

Adaptador de mensajes.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.Código central de ChatActivity

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. Archivo de parámetros constantes

Vale la pena mencionar esto. Antes de saltar, guardé el número de habitación y el sendId en este archivo. Al comparar, comparo directamente el sendId y el fromUserId en este archivo para distinguir si lo envié yo o la otra parte.

constante.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. Devolución de llamada para solicitud http

La escritura es muy tosca, así que no la leas.

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);
            }
        });
    }




}

Supongo que te gusta

Origin blog.csdn.net/title71/article/details/129879409
Recomendado
Clasificación