springboot 通过websocket与 Android 通信

需求:Android做个轮播图功能,当后台上传了最新的轮播图后,Android立刻更新最新的图像信息

后台用的springboot,和Android通信用的websocket,但之前做类似的功能,比如MQ、MQTT、firebase,基本都是人家后台搭好了我直接调用就行,这次需要自己搭,做个最简单的案例


SpringBoot


import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

@Component
public class MyWebSocketHandler extends TextWebSocketHandler {

    private final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        sessions.add(session);
        System.out.println("新连接: " + session.getId());
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        sessions.remove(session);
        System.out.println("连接关闭: " + session.getId() + ", 原因: " + status.getReason());
    }

    // 向所有客户端广播消息
    public void broadcast(String message) {
        TextMessage textMessage = new TextMessage(message);
        for (WebSocketSession session : sessions) {
            try {
                if (session.isOpen()) {
                    session.sendMessage(textMessage);
                }
            } catch (Exception e) {
                System.err.println("发送消息失败: " + e.getMessage());
            }
        }
    }
}


import com.ruoyi.web.webSocket.MyWebSocketHandler;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@EnableScheduling
@RequestMapping("/system/message/websocket")
public class MessageController {

    private final MyWebSocketHandler webSocketHandler;

    public MessageController(MyWebSocketHandler webSocketHandler) {
        this.webSocketHandler = webSocketHandler;
    }

    // 定时发送消息(每25秒)
    /*@Scheduled(fixedRate = 25000)
    public void sendPeriodicMessage() {
        String message = "服务器消息 #" + (++messageCount) + " | " + System.currentTimeMillis();
        webSocketHandler.broadcast(message);
        System.out.println("已发送: " + message);
    }*/

    // 接收客户端消息
    @MessageMapping("/send")
    public void receiveMessage(String message) {
        System.out.println("收到客户端消息: " + message);
    }

    @GetMapping("/sendMessageToAndroid")
    public void sendMessageToAndroid(String message) {
        System.out.println("发送主题消息给Android: " + message);
        webSocketHandler.broadcast(message);
    }
}

轮播图上传后,调用sendMessageToAndroid方法发消息给Android,这里没做主题消息处理,只有最简单的纯文本内容

因为用了security框架,项目中集成了Spring Security,需要在安全配置中(SecurityConfig)给WebSocket放行(通常是`/ws`)

Android(只包含接收部分)

package com.simulateclick.screen;

import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;
import com.simulateclick.utils.ApiCaller;
import com.simulateclick.utils.RouterUrlManager;
import com.simulateclick.utils.banner.CarouselAdapter;
import com.simulateclick.utils.banner.SimpleWebSocketClient;
import org.json.JSONObject;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.stream.Collectors;

public class MainActivity extends AppCompatActivity{

    // 轮播图控件
    private ViewPager2 viewPager;
    private LinearLayout indicatorContainer;
    private CarouselAdapter adapter;
    // 轮播图集合
    private List<String> carouselList = new ArrayList<>();
    private Timer timer;
    private int currentPosition = 0;
    private SimpleWebSocketClient webSocketClient;

    /**
     * 快餐店轮播图功能
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.restaurant_banner);

        // 初始化WebSocket
        connectWebSocket();

        // 初始化控件
        viewPager = findViewById(R.id.viewPager);
        indicatorContainer = findViewById(R.id.indicatorContainer);

        // 初始化轮播图适配器
        adapter = new CarouselAdapter(carouselList);
        viewPager.setAdapter(adapter);

        // 设置页面切换监听
        viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                currentPosition = position;
                updateIndicators(position);
            }
        });

        // 获取轮播图数据
        loadCarouselData();

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (timer != null) {
            timer.cancel();
        }

        if (webSocketClient != null) {
            webSocketClient.close();
        }
    }

    private void loadCarouselData() {
        // 调用 GET 接口
        new ApiCaller()
                .setApiPath("/system/shop/weChat/list")
                .setRequestMethod("GET")
                .addParam("deptId", 207)
                .setOnApiResponseListener(new ApiCaller.OnApiResponseListener() {
                    @Override
                    public void onSuccess(String response) {
                        // 处理成功响应
                        System.out.println("response===>"+response);
                        runOnUiThread(() -> {
                            try {
                                JSONObject jsonObject =  new JSONObject(response);
                                String carousel = jsonObject.getString("carousel");
                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                                    List<String> carouselList = Arrays.stream(carousel.split(","))
                                            .map(String::trim)
                                            .filter(item -> !item.isEmpty())
                                            .map(item -> RouterUrlManager.base_url + item)
                                            .collect(Collectors.toList());


                                    carouselList.forEach(item->{
                                        System.out.println("轮播图:"+item);
                                    });

                                    // 更新轮播图数据
                                    updateCarousel(carouselList);
                                }else {
                                    System.out.println("安卓版本过低");
                                }
                            }catch (Exception e){
                                e.printStackTrace();
                            }
                        });
                    }

                    @Override
                    public void onError(String message) {
                        // 处理错误
                        System.out.println("onError===>"+message);
                        // 在子线程中调用Toast,需切回主线程
                        runOnUiThread(() -> {
                            Toast.makeText(MainActivity.this, "轮播数据获取异常:" + message, Toast.LENGTH_SHORT).show();
                        });
                    }
                })
                .execute();
    }

    private void updateCarousel(List<String> imageUrls) {
        carouselList.clear();
        carouselList.addAll(imageUrls);
        adapter.notifyDataSetChanged();
        createIndicators();
        startAutoScroll();
    }

    private void createIndicators() {
        indicatorContainer.removeAllViews();
        for (int i = 0; i < carouselList.size(); i++) {
            ImageView indicator = new ImageView(this);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                    12, 12);
            params.setMargins(4, 0, 4, 0);
            indicator.setLayoutParams(params);
            indicator.setImageResource(i == 0 ?
                    R.drawable.indicator_active : R.drawable.indicator_inactive);
            indicatorContainer.addView(indicator);
        }
    }

    private void updateIndicators(int position) {
        for (int i = 0; i < indicatorContainer.getChildCount(); i++) {
            ImageView indicator = (ImageView) indicatorContainer.getChildAt(i);
            indicator.setImageResource(i == position ?
                    R.drawable.indicator_active : R.drawable.indicator_inactive);
        }
    }

    private void startAutoScroll() {
        if (timer != null) {
            timer.cancel();
        }

        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                runOnUiThread(() -> {
                    if (carouselList.size() > 1) {
                        currentPosition = (currentPosition + 1) % carouselList.size();
                        viewPager.setCurrentItem(currentPosition, true);
                    }
                });
            }
        }, 3000, 3000); // 3秒切换一次
    }

    private void connectWebSocket() {
        try {
            // IP 地址
            URI uri = new URI(RouterUrlManager.websocket_base_url);

            webSocketClient = new SimpleWebSocketClient(uri, new SimpleWebSocketClient.WebSocketListener() {
                @Override
                public void onMessageReceived(String message) {
                    Log.d("MainActivity", "收到消息: " + message);
                    runOnUiThread(() -> {
                        // 重新加载UI界面
                        recreate();
                    });
                }

                @Override
                public void onConnectionOpened() {
                    Log.d("MainActivity", "WebSocket 连接已打开");
                }

                @Override
                public void onConnectionClosed(int code, String reason) {
                    Log.d("MainActivity", "连接关闭: " + reason);
                }

                @Override
                public void onConnectionFailed(Exception ex) {
                    Log.e("MainActivity", "连接失败", ex);
                }
            });

            webSocketClient.connect();

        } catch (URISyntaxException e) {
            Log.e("MainActivity", "URI 语法错误", e);
        }
    }
}

import android.util.Log;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;

public class SimpleWebSocketClient extends WebSocketClient {

    private final WebSocketListener listener;

    public interface WebSocketListener {
        void onMessageReceived(String message);
        void onConnectionOpened();
        void onConnectionClosed(int code, String reason);
        void onConnectionFailed(Exception ex);
    }

    public SimpleWebSocketClient(URI serverUri, WebSocketListener listener) {
        super(serverUri);
        this.listener = listener;
    }

    @Override
    public void onOpen(ServerHandshake handshakedata) {
        Log.d("WebSocket", "连接已打开");
        if (listener != null) {
            listener.onConnectionOpened();
        }
    }

    @Override
    public void onMessage(String message) {
        Log.d("WebSocket", "收到消息: " + message);
        if (listener != null) {
            listener.onMessageReceived(message);
        }
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        Log.d("WebSocket", "连接关闭: " + reason);
        if (listener != null) {
            listener.onConnectionClosed(code, reason);
        }
    }

    @Override
    public void onError(Exception ex) {
        Log.e("WebSocket", "错误: ", ex);
        if (listener != null) {
            listener.onConnectionFailed(ex);
        }
    }
}

本地运行测试,IP地址通过ipconfig命令获取 IPv4

String websocket_base_url = "ws://192.168.xx.xx:8083/ws";

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值