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




}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/5054.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【SSconv:全色锐化:显式频谱-空间卷积】

SSconv: Explicit Spectral-to-Spatial Convolution for Pansharpening &#xff08;SSconv&#xff1a;用于全色锐化的显式频谱-空间卷积&#xff09; 全色锐化的目的是融合高空间分辨率的全色&#xff08;PAN&#xff09;图像和低分辨率的多光谱&#xff08;LR-MS&#xff…

HTML5 Web 存储

HTML5 Web 存储 在HTML5之前&#xff0c;主要是使用cookies存储&#xff0c;cookies的缺点有&#xff1a;需要在请求头上带着数据&#xff0c;存储大小不过&#xff0c;在4k之内。本节&#xff0c; HTML5 web 存储&#xff0c;一个比cookie更好的本地存储方式。 什么是 HTML5 …

Redis技术详解

Redis技术详解 Redis是一种支持key-value等多种数据结构的存储系统。可用于缓存&#xff0c;事件发布或订阅&#xff0c;高速队列等场景。支持网络&#xff0c;提供字符串&#xff0c;哈希&#xff0c;列表&#xff0c;队列&#xff0c;集合结构直接存取&#xff0c;基于内存&…

Proxmox VE 超融合集群虚拟的NFS服务性能很差的问题解决

作者&#xff1a;田逸&#xff08;formyz&#xff09; 场景描述 五节点Proxmox VE集群&#xff0c;万兆网络,数据网络与存储网络独立&#xff0c;接口两两bond&#xff0c;交换机堆叠。 单机配置两颗AMD 宵龙CPU&#xff0c;核心数48&#xff0c;单台线程数192&#xff0c;单台…

服务器版RstudioServer安装与配置详细教程

Docker部署Rstudio server 背景&#xff1a;如果您想在服务器上运行RstudioServer&#xff0c;可以按照如下方法进行操作&#xff0c;笔者测试时使用腾讯云服务器&#xff08;系统centos7&#xff09;&#xff0c;需要在管理员权限下运行 Rstudio 官方提供了使用不同 R 版本的 …

Baumer工业相机中偏振相机如何使用Baumer堡盟GAPI SDK来进行偏振数据的计算转换输出(C++)

项目场景 Baumer工业相机堡盟相机是一种高性能、高质量的工业相机&#xff0c;可用于各种应用场景&#xff0c;如物体检测、计数和识别、运动分析和图像处理。 Baumer的万兆网相机拥有出色的图像处理性能&#xff0c;可以实时传输高分辨率图像。此外&#xff0c;该相机还具…

【ansible】管理变量与事实详解

目录 管理变量与事实 一&#xff0c;变量 1&#xff0c;变量命名 2&#xff0c;变量优先级&#xff08;高--低&#xff09; 3&#xff0c;命令行引用 4&#xff0c; 引用playbook中的变量 5&#xff0c; 在主机清单中定义变量 6&#xff0c; 在自定义变量文件中定义变量 7&…

Linux基础IO - 文件描述符、重定向

前面的文章中我们讲述了C语言中文件相关的操作与系统文件IO的接口&#xff0c;这篇文章中将会讲述文件描述符与重定向的知识。 运行在前文中的系统文件程序&#xff0c;通过观察可以看到图中的数据3非常的奇怪没头没尾的&#xff0c;下面我们就来从这里开始。 通过查看man手册…

console使用方法介绍

console是在写前端Javascript时经常会使用到&#xff0c;我平时使用最多的是console.log&#xff0c;相比大多数人也是如此吧&#xff01; 下面一起来看一下强大的console吧&#xff01; 01函数&#xff08;属性&#xff09; 包含如下函数 / 属性&#xff1a;memory、assert、c…

Hadoop三大框架之HDFS

一、概述HDFS产生的背景及定义HDFS产生背景随着数据量越来越大&#xff0c;在一个操作系统存不下所有的数据&#xff0c;那么就分配到更多的操作系统管理的磁盘中&#xff0c;但是不方便管理和维护&#xff0c;需要一种系统来管理多台机器上的文件&#xff0c;这就是分布式文件…

日入500+的程序员都在用的“接私活”平台

网上总说程序员的薪资很高&#xff0c;这我可就不同意了&#xff1a; 程序员的薪资哪里是很高&#xff0c;而是非常高&#xff01;而会接私活的程序员更是能拿到更高的收入&#xff01;作为一个程序员&#xff0c;这些接私活的网站一定要收藏起来&#xff0c;让你在“八小时外…

ChatGPT transformer 5篇经典论文以及代码和解读

一次性读懂ChatGPT的技术演进路线&#xff0c;根据李沐老师推荐的5篇经典论文&#xff0c;整理了论文原文、论文解读、Github代码实现。 2017Transformer继MLP、CNN、RNN后的第四大类架构2018GPT使用 Transformer 解码器来做预训练2018BERTTransformer一统NLP的开始2019GPT-2更…

区块链概论

目录 1.概述 2.密码学原理 2.1.hash函数 2.2.签名 3.数据结构 3.1.区块结构 3.2.hash pointer 3.3.merkle tree 3.3.1.概述 3.3.2.证明数据存在 3.3.3.证明数据不存在 4.比特币的共识协议 4.1.概述 4.2.验证有效性 4.2.1.验证交易有效性 4.2.2.验证节点有效性 …

YOLOv5源码逐行超详细注释与解读(6)——网络结构(1)yolo.py

前言 在上一篇中&#xff0c;我们简单介绍了YOLOv5的配置文件之一 yolov5s.yaml&#xff0c;这个文件中涉及很多参数&#xff0c;它们的调用会在这篇 yolo.py 和下一篇 common.py 中具体实现。 本篇我们会介绍 yolo.py&#xff0c;这是YOLO的特定模块&#xff0c;和网络构建有…

python【selenium的环境配置】

selenium 1.环境配置 1&#xff09;在环境设置里面安装selenium第三方库 pip install --user selenium2&#xff09; from selenium.webdriver import Chrome# 创建谷歌 b Chrome() # 获取网页 b.get(http://www.baidu.com) # 防止自动关闭 input()3&#xff09;在此之前&…

Rancher系列文章-Rancher v2.6使用脚本实现导入集群

概述 最近在玩 Rancher, 先从最基本的功能玩起, 目前有几个已经搭建好的 K8S 集群, 需要批量导入, 发现官网已经有批量导入的文档了. 根据 Rancher v2.6 进行验证微调后总结经验. 1. Rancher UI 获取创建集群参数 访问Rancher_URL/v3/clusters/&#xff0c;单击右上角“Crea…

第07章_单行函数

第07章_单行函数 &#x1f3e0;个人主页&#xff1a;shark-Gao &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是shark-Gao&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f389;目前状况&#xff1a;23届毕业生&#xff0c;目前在某…

进程地址空间+环境变量

目录 环境变量 进程地址空间 理解虚拟地址空间 进程地址空间区域划分 虚拟内存和物理内存建立联系 深刻理解虚拟地址空间 环境变量 当我们需要使用一个物品的时候&#xff0c;首先要先找到这个物品。同样的&#xff0c;当要运行一个程序&#xff08;指令&#xff09;时&a…

大公司为什么禁止SpringBoot项目用Tomcat?

前言 在SpringBoot框架中&#xff0c;我们使用最多的是Tomcat&#xff0c;这是SpringBoot默认的容器技术&#xff0c;而且是内嵌式的Tomcat。同时&#xff0c;SpringBoot也支持Undertow容器&#xff0c;我们可以很方便的用Undertow替换Tomcat&#xff0c;而Undertow的性能和内…

低功耗技术——流水线设计(加法器和乘法器)

文章目录前言一、流水线1、16bit加法器2、无符号4bit乘法器3、编写一个4bit乘法器模块&#xff0c;并例化该乘法器求解c12*a5*b二、降低FPGA功耗1、静态功耗2、动态功耗前言 2023.3.31 今天学习降低功耗的一些方法 一、流水线 电路最高工作频率&#xff1a;取决于最长的组合逻…