android:手搓一个即时消息聊天框(包含消息记录)

Posted Wenlong Yang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android:手搓一个即时消息聊天框(包含消息记录)相关的知识,希望对你有一定的参考价值。

先看一下效果

1.后端

要实现这个,先说一下后端要实现的接口

1.创建会话id

传入“发送id”和“接收id”给服务端,服务端去创建“会话id”

比如

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

返回seesionId,也就是会话id


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

2.获取消息记录

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

返回如下:我让后台按这个来写的,虽然后端表示接口很丑陋~

这个返回的有个要求,就是日期要升序,第一页要显示最新的15条,这个应该能理解吧,聊天框里面最新的日期都是要显示在最下面的,当然,也可以安卓端手动再排序:

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,让后端按下面这个要求让客户端进行连接的,具体怎么搓的我也不清楚,好像是消息之间的转发

ws://"+IP+":8110/websocket/发送id/会话id

 2.APP安卓端

因为安卓端是我写的,所以~代码我可以尽量贴的完整一点

1.聊天的布局:

我这个写法比较简单,就直接将对方的消息和我的消息写在一个item里面了,对比一下数据是不是自己发的,是的话就显示自己的消息ui,不是就隐藏自己的ui

 代码布局如下:

<?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.消息框的总布局

 这个没啥重要的,只是我额外用了一个自定义的listview,这个是用来监听下拉的,毕竟会有历史消息,下拉就直接加载上一页了

布局

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

支持下拉的listview控件


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) 
    

里面有些控件之类的,可以自己画画补充,我是随便放的

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代码

记住要在清单文件里面添加这一行

        <service
            android:name=".websocket.JWebSocketClientService1"
            android:enabled="true"
            android:exported="true" />
下面是我百度来的,改一改可以直接用
JWebSocketClientService1
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.实体类,和适配器

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核心代码

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.常量参数文件

这个值得一说的,跳转之前我把房间号和sendId存在这个文件,比对的时候就直接拿这个文件里面的sendId和fromUserId进行比对,用来区分是自己发送的,还是对方发送的

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.http请求的回调

写的很糙,不看也罢

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




小程序如何集成即构IM实现即时通讯发消息聊天

之前的文章已经介绍了如何实现Web端的即时通讯IM,为了让大家全面的体验通信互动的快乐。 本文介绍如何使用 ZIM SDK 快速实现实现小程序端的基本的消息收发功能,在微信中实现一个mini版微信,也就是常见的聊天功能。
之前的文章已经介绍了如何实现Web端的即时通讯IM,为了让大家全面的体验通信互动的快乐。 本文介绍如何使用 ZIM SDK 快速实现实现小程序端的基本的消息收发功能,在微信中实现一个mini版微信,也就是常见的聊天功能。

以上是关于android:手搓一个即时消息聊天框(包含消息记录)的主要内容,如果未能解决你的问题,请参考以下文章

Android之聊天室设计与开发

即时聊天消息分发,实现婚恋消息服务

小程序如何集成即构IM实现即时通讯发消息聊天

如何在Android系统下开发一个聊天软件?

im即时通讯开发:百万人的直播实时聊天消息分发技术

Android消息推送能推送图片吗?还是只能推送文字信息?