Android Device Blacklist Synchronization Issue

用户: 那请你对我的项目进行大改,要求能实现把两个fragment的item拉黑进入黑名单,请你仔细思考认真写代码

Claude: 来对你的项目进行完整的大改,实现跨Fragment的黑名单数据共享。

ini 复制代码
package com.tplink.deco.homepage.repository;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import com.tplink.deco.homepage.viewmodel.ClientViewModel;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;

/**
 * 设备数据仓库 - 单例模式
 * 负责管理所有设备数据,是唯一的数据源
 * 确保Fragment和Activity之间的数据共享
 */
public class DeviceRepository {
    private static volatile DeviceRepository INSTANCE;
    
    // 设备数据模型
    public static class DeviceInfo {
        private String deviceName;
        private String macAddress;
        private int deviceType; // 0-手机, 1-电脑
        private boolean isOnline;
        private boolean isGuestNetwork;
        
        public DeviceInfo(String deviceName, String macAddress, int deviceType, boolean isOnline, boolean isGuestNetwork) {
            this.deviceName = deviceName;
            this.macAddress = macAddress;
            this.deviceType = deviceType;
            this.isOnline = isOnline;
            this.isGuestNetwork = isGuestNetwork;
        }
        
        // Getters
        public String getDeviceName() { return deviceName; }
        public String getMacAddress() { return macAddress; }
        public int getDeviceType() { return deviceType; }
        public boolean isOnline() { return isOnline; }
        public boolean isGuestNetwork() { return isGuestNetwork; }
        
        // Setters
        public void setOnline(boolean online) { isOnline = online; }
        public void setDeviceType(int deviceType) { this.deviceType = deviceType; }
        
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            DeviceInfo that = (DeviceInfo) obj;
            return deviceName.equals(that.deviceName) && macAddress.equals(that.macAddress);
        }
        
        @Override
        public int hashCode() {
            return deviceName.hashCode() + macAddress.hashCode();
        }
    }

    // 黑名单项目模型
    public static class BlacklistItem {
        private String displayName;
        private String macAddress;
        private boolean isMacOnly;

        public BlacklistItem(String deviceName, String macAddress) {
            this.displayName = deviceName;
            this.macAddress = macAddress;
            this.isMacOnly = false;
        }

        public BlacklistItem(String macAddress) {
            this.displayName = macAddress;
            this.macAddress = macAddress;
            this.isMacOnly = true;
        }

        public String getDisplayName() { return displayName; }
        public String getMacAddress() { return macAddress; }
        public boolean isMacOnly() { return isMacOnly; }
        public String getIdentifier() { return isMacOnly ? macAddress : displayName; }
        
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            BlacklistItem that = (BlacklistItem) obj;
            return macAddress.equals(that.macAddress);
        }
    }

    // 数据存储
    private List<DeviceInfo> allDevices = new ArrayList<>();
    private List<BlacklistItem> blacklistItems = new ArrayList<>();
    
    // LiveData - 供外部观察
    private MutableLiveData<List<DeviceInfo>> allDevicesLiveData = new MutableLiveData<>();
    private MutableLiveData<List<BlacklistItem>> blacklistLiveData = new MutableLiveData<>();
    private MutableLiveData<Boolean> isRefreshing = new MutableLiveData<>(false);

    private DeviceRepository() {
        initializeData();
    }

    // 单例获取方法
    public static DeviceRepository getInstance() {
        if (INSTANCE == null) {
            synchronized (DeviceRepository.class) {
                if (INSTANCE == null) {
                    INSTANCE = new DeviceRepository();
                }
            }
        }
        return INSTANCE;
    }

    // 初始化设备数据
    private void initializeData() {
        allDevices.clear();
        blacklistItems.clear();
        
        // 主网络在线设备
        allDevices.add(new DeviceInfo("Melanie's iPhone", generateMacAddress("iPhone"), 0, true, false));
        allDevices.add(new DeviceInfo("Melanie's iMac", generateMacAddress("iMac"), 1, true, false));
        allDevices.add(new DeviceInfo("Melanie's iPad", generateMacAddress("iPad"), 0, true, false));
        allDevices.add(new DeviceInfo("Office PC", generateMacAddress("PC"), 1, true, false));
        
        // 主网络离线设备
        allDevices.add(new DeviceInfo("John's MacBook", generateMacAddress("MacBook"), 1, false, false));
        allDevices.add(new DeviceInfo("Smart TV", generateMacAddress("TV"), 1, false, false));
        allDevices.add(new DeviceInfo("Gaming Console", generateMacAddress("Console"), 1, false, false));
        
        // 访客网络设备
        allDevices.add(new DeviceInfo("Guest Phone 1", generateMacAddress("GuestPhone1"), 0, true, true));
        allDevices.add(new DeviceInfo("Guest Laptop", generateMacAddress("GuestLaptop"), 1, true, true));
        
        // 更新LiveData
        updateLiveData();
        
        System.out.println("设备数据初始化完成,总设备数:" + allDevices.size());
    }

    // 生成MAC地址
    private String generateMacAddress(String deviceIdentifier) {
        int hash = Math.abs(deviceIdentifier.hashCode());
        return String.format("%02X-%02X-%02X-%02X-%02X-%02X", 
                (hash >> 20) & 0xFF, (hash >> 16) & 0xFF, 
                (hash >> 12) & 0xFF, (hash >> 8) & 0xFF, 
                (hash >> 4) & 0xFF, hash & 0xFF);
    }

    // 更新LiveData
    private void updateLiveData() {
        allDevicesLiveData.setValue(new ArrayList<>(allDevices));
        blacklistLiveData.setValue(new ArrayList<>(blacklistItems));
    }

    // ========== 核心功能方法 ==========
    
    /**
     * 拉黑设备 - 核心方法
     * 从设备列表移除设备并添加到黑名单
     */
    public boolean blockDevice(String deviceName) {
        System.out.println("准备拉黑设备: " + deviceName);
        
        // 查找要拉黑的设备
        DeviceInfo deviceToBlock = null;
        for (DeviceInfo device : allDevices) {
            if (device.getDeviceName().equals(deviceName)) {
                deviceToBlock = device;
                break;
            }
        }
        
        if (deviceToBlock != null) {
            // 检查是否已在黑名单中
            boolean alreadyBlocked = false;
            for (BlacklistItem item : blacklistItems) {
                if (item.getIdentifier().equals(deviceName)) {
                    alreadyBlocked = true;
                    break;
                }
            }
            
            if (!alreadyBlocked) {
                // 从设备列表移除
                allDevices.remove(deviceToBlock);
                
                // 添加到黑名单
                BlacklistItem newBlacklistItem = new BlacklistItem(
                    deviceToBlock.getDeviceName(), 
                    deviceToBlock.getMacAddress()
                );
                blacklistItems.add(newBlacklistItem);
                
                // 更新LiveData,通知所有观察者
                updateLiveData();
                
                System.out.println("设备拉黑成功: " + deviceName + 
                    ", 剩余设备数: " + allDevices.size() + 
                    ", 黑名单数: " + blacklistItems.size());
                
                return true;
            } else {
                System.out.println("设备已在黑名单中: " + deviceName);
            }
        } else {
            System.out.println("未找到设备: " + deviceName);
        }
        
        return false;
    }

    /**
     * 通过MAC地址拉黑
     */
    public boolean blockDeviceByMac(String macAddress) {
        // 检查MAC地址格式
        if (!isValidMacAddress(macAddress)) {
            return false;
        }
        
        // 检查是否已存在
        for (BlacklistItem item : blacklistItems) {
            if (item.getMacAddress().equals(macAddress)) {
                return false; // 已存在
            }
        }
        
        // 添加到黑名单
        blacklistItems.add(new BlacklistItem(macAddress));
        updateLiveData();
        
        System.out.println("MAC地址拉黑成功: " + macAddress);
        return true;
    }

    /**
     * 解除拉黑
     */
    public boolean unblockDevice(String identifier) {
        BlacklistItem itemToRemove = null;
        
        for (BlacklistItem item : blacklistItems) {
            if (item.getIdentifier().equals(identifier)) {
                itemToRemove = item;
                break;
            }
        }
        
        if (itemToRemove != null) {
            blacklistItems.remove(itemToRemove);
            
            // 如果是设备名称(非纯MAC),重新添加到设备列表
            if (!itemToRemove.isMacOnly()) {
                DeviceInfo restoredDevice = new DeviceInfo(
                    itemToRemove.getDisplayName(),
                    itemToRemove.getMacAddress(),
                    0, // 默认手机类型
                    false, // 离线状态
                    false  // 主网络
                );
                allDevices.add(restoredDevice);
            }
            
            updateLiveData();
            
            System.out.println("设备解除拉黑: " + identifier);
            return true;
        }
        
        return false;
    }

    /**
     * 删除设备(永久删除)
     */
    public boolean deleteDevice(String deviceName) {
        Iterator<DeviceInfo> iterator = allDevices.iterator();
        boolean removed = false;
        
        while (iterator.hasNext()) {
            DeviceInfo device = iterator.next();
            if (device.getDeviceName().equals(deviceName)) {
                iterator.remove();
                removed = true;
                break;
            }
        }
        
        if (removed) {
            updateLiveData();
            System.out.println("设备删除成功: " + deviceName);
        }
        
        return removed;
    }

    /**
     * 删除黑名单项目
     */
    public boolean deleteBlacklistItem(String identifier) {
        Iterator<BlacklistItem> iterator = blacklistItems.iterator();
        boolean removed = false;
        
        while (iterator.hasNext()) {
            BlacklistItem item = iterator.next();
            if (item.getIdentifier().equals(identifier)) {
                iterator.remove();
                removed = true;
                break;
            }
        }
        
        if (removed) {
            updateLiveData();
            System.out.println("黑名单项目删除: " + identifier);
        }
        
        return removed;
    }

    // ========== LiveData 获取方法 ==========
    
    public LiveData<List<BlacklistItem>> getBlacklistItems() {
        return blacklistLiveData;
    }

    public LiveData<List<DeviceInfo>> getAllDevices() {
        return allDevicesLiveData;
    }

    public LiveData<List<DeviceInfo>> getMainOnlineDevices() {
        return Transformations.map(allDevicesLiveData, devices -> {
            List<DeviceInfo> result = new ArrayList<>();
            if (devices != null) {
                for (DeviceInfo device : devices) {
                    if (!device.isGuestNetwork() && device.isOnline()) {
                        result.add(device);
                    }
                }
            }
            return result;
        });
    }

    public LiveData<List<DeviceInfo>> getMainOfflineDevices() {
        return Transformations.map(allDevicesLiveData, devices -> {
            List<DeviceInfo> result = new ArrayList<>();
            if (devices != null) {
                for (DeviceInfo device : devices) {
                    if (!device.isGuestNetwork() && !device.isOnline()) {
                        result.add(device);
                    }
                }
            }
            return result;
        });
    }

    public LiveData<List<DeviceInfo>> getGuestDevices() {
        return Transformations.map(allDevicesLiveData, devices -> {
            List<DeviceInfo> result = new ArrayList<>();
            if (devices != null) {
                for (DeviceInfo device : devices) {
                    if (device.isGuestNetwork()) {
                        result.add(device);
                    }
                }
            }
            return result;
        });
    }

    public LiveData<Boolean> getRefreshingState() {
        return isRefreshing;
    }

    // ========== 辅助方法 ==========
    
    /**
     * 刷新数据
     */
    public void refreshData() {
        isRefreshing.setValue(true);
        
        // 模拟网络请求
        new android.os.Handler().postDelayed(() -> {
            initializeData();
            isRefreshing.setValue(false);
        }, 1500);
    }

    /**
     * 验证MAC地址格式
     */
    private boolean isValidMacAddress(String mac) {
        return mac != null && mac.matches("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$");
    }

    /**
     * 获取当前统计数据
     */
    public int getCurrentMainOnlineCount() {
        int count = 0;
        for (DeviceInfo device : allDevices) {
            if (!device.isGuestNetwork() && device.isOnline()) {
                count++;
            }
        }
        return count;
    }

    public int getCurrentMainOfflineCount() {
        int count = 0;
        for (DeviceInfo device : allDevices) {
            if (!device.isGuestNetwork() && !device.isOnline()) {
                count++;
            }
        }
        return count;
    }

    public int getCurrentGuestCount() {
        int count = 0;
        for (DeviceInfo device : allDevices) {
            if (device.isGuestNetwork()) {
                count++;
            }
        }
        return count;
    }

    public int getCurrentBlacklistCount() {
        return blacklistItems.size();
    }
}

在继续重构其他核心文件:

typescript 复制代码
package com.tplink.deco.homepage.viewmodel;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import com.tplink.deco.homepage.repository.DeviceRepository;
import java.util.List;

/**
 * 客户端设备ViewModel
 * 职责:作为UI和Repository之间的桥梁,提供UI需要的数据和操作方法
 * 所有ViewModel实例都使用同一个Repository,确保数据共享
 */
public class ClientViewModel extends ViewModel {
    
    private DeviceRepository repository;
    
    // 兼容原有代码的数据模型类
    public static class DeviceInfo {
        private String deviceName;
        private String macAddress;
        private int deviceType;
        private boolean isOnline;
        private boolean isGuestNetwork;
        
        public DeviceInfo(String deviceName, String macAddress, int deviceType, boolean isOnline, boolean isGuestNetwork) {
            this.deviceName = deviceName;
            this.macAddress = macAddress;
            this.deviceType = deviceType;
            this.isOnline = isOnline;
            this.isGuestNetwork = isGuestNetwork;
        }
        
        // 从Repository的DeviceInfo转换
        public DeviceInfo(DeviceRepository.DeviceInfo repoDevice) {
            this.deviceName = repoDevice.getDeviceName();
            this.macAddress = repoDevice.getMacAddress();
            this.deviceType = repoDevice.getDeviceType();
            this.isOnline = repoDevice.isOnline();
            this.isGuestNetwork = repoDevice.isGuestNetwork();
        }
        
        public String getDeviceName() { return deviceName; }
        public String getMacAddress() { return macAddress; }
        public int getDeviceType() { return deviceType; }
        public boolean isOnline() { return isOnline; }
        public boolean isGuestNetwork() { return isGuestNetwork; }
    }
    
    public static class BlacklistItem {
        private String displayName;
        private String macAddress;
        private boolean isMacOnly;
        
        // 从Repository的BlacklistItem转换
        public BlacklistItem(DeviceRepository.BlacklistItem repoItem) {
            this.displayName = repoItem.getDisplayName();
            this.macAddress = repoItem.getMacAddress();
            this.isMacOnly = repoItem.isMacOnly();
        }
        
        public String getDisplayName() { return displayName; }
        public String getMacAddress() { return macAddress; }
        public boolean isMacOnly() { return isMacOnly; }
        public String getIdentifier() { return isMacOnly ? macAddress : displayName; }
    }

    public ClientViewModel() {
        // 获取单例Repository实例
        repository = DeviceRepository.getInstance();
        System.out.println("ClientViewModel创建,Repository实例: " + repository.hashCode());
    }

    // ========== 设备操作方法 ==========
    
    /**
     * 拉黑设备 - 核心方法
     * 这个方法会被Fragment调用,然后数据会自动同步到BlacklistActivity
     */
    public boolean blockDevice(String deviceName) {
        System.out.println("ClientViewModel.blockDevice调用: " + deviceName);
        return repository.blockDevice(deviceName);
    }
    
    /**
     * 通过MAC地址拉黑
     */
    public boolean blockDeviceByMac(String macAddress) {
        return repository.blockDeviceByMac(macAddress);
    }
    
    /**
     * 解除拉黑
     */
    public boolean unblockDevice(String identifier) {
        return repository.unblockDevice(identifier);
    }
    
    /**
     * 删除设备
     */
    public boolean deleteDevice(String deviceName) {
        return repository.deleteDevice(deviceName);
    }
    
    /**
     * 删除黑名单项目
     */
    public boolean deleteBlacklistDevice(String identifier) {
        return repository.deleteBlacklistItem(identifier);
    }

    // ========== LiveData 获取方法 ==========
    // 这些方法返回的LiveData都来自同一个Repository,确保数据共享

    /**
     * 获取黑名单LiveData
     * BlacklistActivity会观察这个LiveData
     */
    public LiveData<List<BlacklistItem>> getBlacklistItems() {
        return Transformations.map(repository.getBlacklistItems(), repoItems -> {
            List<BlacklistItem> viewModelItems = new java.util.ArrayList<>();
            if (repoItems != null) {
                for (DeviceRepository.BlacklistItem repoItem : repoItems) {
                    viewModelItems.add(new BlacklistItem(repoItem));
                }
            }
            System.out.println("黑名单LiveData转换,数量: " + viewModelItems.size());
            return viewModelItems;
        });
    }

    /**
     * 获取主网络在线设备
     */
    public LiveData<List<DeviceInfo>> getMainOnlineDevices() {
        return Transformations.map(repository.getMainOnlineDevices(), repoDevices -> {
            List<DeviceInfo> viewModelDevices = new java.util.ArrayList<>();
            if (repoDevices != null) {
                for (DeviceRepository.DeviceInfo repoDevice : repoDevices) {
                    viewModelDevices.add(new DeviceInfo(repoDevice));
                }
            }
            return viewModelDevices;
        });
    }

    /**
     * 获取主网络离线设备
     */
    public LiveData<List<DeviceInfo>> getMainOfflineDevices() {
        return Transformations.map(repository.getMainOfflineDevices(), repoDevices -> {
            List<DeviceInfo> viewModelDevices = new java.util.ArrayList<>();
            if (repoDevices != null) {
                for (DeviceRepository.DeviceInfo repoDevice : repoDevices) {
                    viewModelDevices.add(new DeviceInfo(repoDevice));
                }
            }
            return viewModelDevices;
        });
    }

    /**
     * 获取访客网络设备
     */
    public LiveData<List<DeviceInfo>> getGuestDevices() {
        return Transformations.map(repository.getGuestDevices(), repoDevices -> {
            List<DeviceInfo> viewModelDevices = new java.util.ArrayList<>();
            if (repoDevices != null) {
                for (DeviceRepository.DeviceInfo repoDevice : repoDevices) {
                    viewModelDevices.add(new DeviceInfo(repoDevice));
                }
            }
            return viewModelDevices;
        });
    }

    /**
     * 获取刷新状态
     */
    public LiveData<Boolean> getRefreshingState() {
        return repository.getRefreshingState();
    }

    // ========== 兼容性方法 - 返回数量 ==========
    // 这些方法是为了兼容原有代码结构

    public LiveData<Integer> getMainOnlineCount() {
        return Transformations.map(getMainOnlineDevices(), devices -> 
            devices != null ? devices.size() : 0);
    }

    public LiveData<Integer> getMainOfflineCount() {
        return Transformations.map(getMainOfflineDevices(), devices -> 
            devices != null ? devices.size() : 0);
    }

    public LiveData<Integer> getGuestCount() {
        return Transformations.map(getGuestDevices(), devices -> 
            devices != null ? devices.size() : 0);
    }

    // ========== 当前数量获取方法 ==========

    public int getCurrentMainOnlineCount() {
        return repository.getCurrentMainOnlineCount();
    }

    public int getCurrentMainOfflineCount() {
        return repository.getCurrentMainOfflineCount();
    }

    public int getCurrentGuestCount() {
        return repository.getCurrentGuestCount();
    }

    public int getCurrentBlacklistCount() {
        return repository.getCurrentBlacklistCount();
    }

    // ========== 其他方法 ==========

    /**
     * 刷新所有数据
     */
    public void refreshData() {
        repository.refreshData();
    }

    // ========== 兼容性方法(废弃但保留) ==========
    // 这些方法保留是为了不破坏现有代码,但实际上不再需要手动更新计数

    @Deprecated
    public void updateMainOnlineCount(int count) {
        // 不再需要手动更新,Repository会自动处理
        System.out.println("updateMainOnlineCount调用被忽略,由Repository自动管理");
    }

    @Deprecated
    public void updateMainOfflineCount(int count) {
        // 不再需要手动更新,Repository会自动处理
        System.out.println("updateMainOfflineCount调用被忽略,由Repository自动管理");
    }

    @Deprecated
    public void updateGuestCount(int count) {
        // 不再需要手动更新,Repository会自动处理
        System.out.println("updateGuestCount调用被忽略,由Repository自动管理");
    }

    // 兼容方法:blockMainDevice 和 blockGuestDevice
    public boolean blockMainDevice(String deviceName) {
        return blockDevice(deviceName);
    }

    public boolean blockGuestDevice(String deviceName) {
        return blockDevice(deviceName);
    }

    public boolean deleteGuestDevice(String deviceName) {
        return deleteDevice(deviceName);
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        System.out.println("ClientViewModel被清理");
    }
}

重构适配器和Fragment文件:

ini 复制代码
package com.tplink.deco.homepage.adapter;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ImageSpan;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.tplink.deco.R;
import com.tplink.deco.homepage.viewmodel.ClientViewModel;
import com.tplink.design.list.TPTwoLineItemView;
import com.tplink.design.list.TPTwoLineItemViewHolder;
import com.tplink.design.menu.TPPopupMenu;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;

/**
 * 设备列表适配器
 * 职责:纯UI组件,负责显示设备列表和处理用户交互
 * 不再直接操作数据,所有数据操作都委托给Fragment
 */
public class ClientListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    
    private List<ClientViewModel.DeviceInfo> deviceList = new ArrayList<>();
    private Context context;
    private boolean isOnlineAdapter;
    
    // 模拟数据
    private int[] signalStrength = {3, 2, 1, 4, 3};
    private int[] downloadRates = {50, 30, 75, 60, 45};
    private int[] uploadRates = {25, 15, 35, 30, 20};

    // 设备操作监听接口
    public interface OnDeviceActionListener {
        void onBlockDevice(ClientViewModel.DeviceInfo device, int position);
        void onDeleteDevice(ClientViewModel.DeviceInfo device, int position);
    }

    private OnDeviceActionListener listener;

    public ClientListAdapter(Context context, boolean isOnlineAdapter) {
        this.context = context;
        this.isOnlineAdapter = isOnlineAdapter;
        System.out.println("ClientListAdapter创建,isOnline: " + isOnlineAdapter);
    }

    public void setOnDeviceActionListener(OnDeviceActionListener listener) {
        this.listener = listener;
    }

    /**
     * 更新设备列表
     * 这个方法会被Fragment调用,传入来自ViewModel的LiveData数据
     */
    public void updateDeviceList(List<ClientViewModel.DeviceInfo> devices) {
        System.out.println("ClientListAdapter.updateDeviceList调用,新设备数量: " + 
            (devices != null ? devices.size() : 0) + ", isOnline: " + isOnlineAdapter);
            
        this.deviceList.clear();
        if (devices != null) {
            this.deviceList.addAll(devices);
        }
        notifyDataSetChanged();
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return TPTwoLineItemViewHolder.create(parent);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (position >= deviceList.size()) return;
        
        TPTwoLineItemView lineItem = (TPTwoLineItemView) holder.itemView;
        ClientViewModel.DeviceInfo device = deviceList.get(position);
        
        // 设置设备类型图标
        if (device.getDeviceType() == 1) { // 电脑
            lineItem.setStartIcon(R.mipmap.laptop3);
        } else { // 手机
            lineItem.setStartIcon(R.mipmap.game3);
        }

        // 设置图标尺寸
        ViewGroup.LayoutParams layoutParams = lineItem.getStartIcon().getLayoutParams();
        int iconSize = (int) context.getResources().getDimension(com.tplink.design.R.dimen.tpds_all_dp_40);
        layoutParams.width = iconSize;
        layoutParams.height = iconSize;
        lineItem.getStartIcon().setLayoutParams(layoutParams);

        lineItem.setTitleText(device.getDeviceName());
        lineItem.showDivider(true);

        // 根据设备状态设置内容和交互
        if (device.isOnline()) {
            setupOnlineDevice(lineItem, device, position);
        } else {
            setupOfflineDevice(lineItem, device, position);
        }
    }

    /**
     * 设置在线设备的显示和交互
     */
    private void setupOnlineDevice(TPTwoLineItemView lineItem, ClientViewModel.DeviceInfo device, int position) {
        // 设置信号和速度显示
        int signal = signalStrength[position % signalStrength.length];
        int downloadRate = downloadRates[position % downloadRates.length];
        int uploadRate = uploadRates[position % uploadRates.length];
        
        lineItem.setEndIcon(createSignalIcon(signal));
        setOnlineContent(uploadRate, downloadRate, lineItem);
        
        // 设置长按菜单 - 在线设备只能拉黑
        lineItem.setOnLongClickListener(v -> {
            TPPopupMenu popupMenu = new TPPopupMenu(context, v, R.menu.menu_block)
                    .setIconEnable(true);
            popupMenu.setOnMenuItemClickListener(item -> {
                if (item.getItemId() == R.id.block) {
                    showBlockConfirmDialog(device, position);
                    return true;
                }
                return false;
            });
            popupMenu.show();
            return true;
        });
    }

    /**
     * 设置离线设备的显示和交互
     */
    private void setupOfflineDevice(TPTwoLineItemView lineItem, ClientViewModel.DeviceInfo device, int position) {
        lineItem.setEndIcon(R.drawable.next);
        setOfflineContent(System.currentTimeMillis() - 24 * 60 * 60 * 1000, lineItem);
        
        // 设置长按菜单 - 离线设备可以拉黑或删除
        lineItem.setOnLongClickListener(v -> {
            TPPopupMenu popupMenu = new TPPopupMenu(context, v, R.menu.menu_delete)
                    .setIconEnable(true);
            popupMenu.setOnMenuItemClickListener(item -> {
                if (item.getItemId() == R.id.block) {
                    showBlockConfirmDialog(device, position);
                    return true;
                } else if (item.getItemId() == R.id.delete) {
                    showDeleteConfirmDialog(device, position);
                    return true;
                }
                return false;
            });
            popupMenu.show();
            return true;
        });
    }

    /**
     * 显示拉黑确认对话框
     */
    private void showBlockConfirmDialog(ClientViewModel.DeviceInfo device, int position) {
        new MaterialAlertDialogBuilder(context)
                .setTitle("拉黑设备")
                .setMessage("确定要将 \"" + device.getDeviceName() + "\" 加入黑名单吗?\n" +
                    "设备MAC地址: " + device.getMacAddress())
                .setPositiveButton("确定", (dialog, which) -> {
                    if (listener != null) {
                        System.out.println("适配器:准备拉黑设备 " + device.getDeviceName());
                        listener.onBlockDevice(device, position);
                    }
                })
                .setNegativeButton("取消", null)
                .show();
    }

    /**
     * 显示删除确认对话框
     */
    private void showDeleteConfirmDialog(ClientViewModel.DeviceInfo device, int position) {
        new MaterialAlertDialogBuilder(context)
                .setTitle("删除设备")
                .setMessage("确定要永久删除 \"" + device.getDeviceName() + "\" 吗?\n" +
                    "删除后设备将从所有列表中移除。")
                .setPositiveButton("确定", (dialog, which) -> {
                    if (listener != null) {
                        listener.onDeleteDevice(device, position);
                    }
                })
                .setNegativeButton("取消", null)
                .show();
    }

    @Override
    public int getItemCount() {
        return deviceList.size();
    }

    /**
     * 设置在线设备的内容显示(上传下载速度)
     */
    private void setOnlineContent(int uploadRate, int downloadRate, TPTwoLineItemView lineItem) {
        TextView textView = lineItem.getContent();
        android.graphics.Paint.FontMetrics fm = textView.getPaint().getFontMetrics();
        float textHeight = fm.descent - fm.ascent;
        
        String downloadText = " " + downloadRate + "KB/s";
        String spaceText = "  ";
        String uploadText = " " + uploadRate + "KB/s";
        SpannableString spannable = new SpannableString(downloadText + spaceText + uploadText);

        Drawable uploadIconDrawable = ContextCompat.getDrawable(context, R.drawable.upload);
        Drawable downloadIconDrawable = ContextCompat.getDrawable(context, R.drawable.download);

        if (uploadIconDrawable != null && downloadIconDrawable != null) {
            float iconAspectRatio = (float) uploadIconDrawable.getIntrinsicWidth() / uploadIconDrawable.getIntrinsicHeight();
            int scaleWidth = (int) (textHeight * iconAspectRatio);
            
            downloadIconDrawable.setBounds(0, 0, scaleWidth, (int) textHeight);
            uploadIconDrawable.setBounds(0, 0, scaleWidth, (int) textHeight);

            ImageSpan downloadIconSpan = new ImageSpan(downloadIconDrawable, ImageSpan.ALIGN_BOTTOM);
            ImageSpan uploadIconSpan = new ImageSpan(uploadIconDrawable, ImageSpan.ALIGN_BOTTOM);

            spannable.setSpan(downloadIconSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            spannable.setSpan(uploadIconSpan,
                    downloadText.length() + spaceText.length(),
                    downloadText.length() + spaceText.length() + 1,
                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }

        textView.setText(spannable);
        lineItem.getContent().setVisibility(View.VISIBLE);
    }

    /**
     * 设置离线设备的内容显示(最后在线时间)
     */
    @SuppressLint("SimpleDateFormat")
    private void setOfflineContent(long lastOnlineTime, TPTwoLineItemView lineItem) {
        try {
            SimpleDateFormat timeFormat = new SimpleDateFormat(context.getString(R.string.clients_time_format_pattern));
            String timeString = timeFormat.format(new Date(lastOnlineTime));
            String contentText = context.getString(R.string.clients_hint_current_online_time, timeString);
            lineItem.setContentText(contentText);
        } catch (Exception e) {
            // 如果格式化失败,显示默认文本
            lineItem.setContentText("上次在线: 昨天");
        }
    }

    /**
     * 创建信号强度图标
     */
    private LayerDrawable createSignalIcon(int strength) {
        Drawable signalIcon;
        switch (strength) {
            case 1:
                signalIcon = ContextCompat.getDrawable(context, R.drawable.wifi1);
                break;
            case 2:
                signalIcon = ContextCompat.getDrawable(context, R.drawable.wifi2);
                break;
            case 3:
                signalIcon = ContextCompat.getDrawable(context, R.drawable.wifi3);
                break;
            default:
                signalIcon = ContextCompat.getDrawable(context, R.drawable.wifi4);
                break;
        }

        Drawable nextIcon = ContextCompat.getDrawable(context, R.drawable.next);
        if (signalIcon == null || nextIcon == null) {
            return null;
        }

        Drawable[] layers = {signalIcon, nextIcon};
        LayerDrawable layerDrawable = new LayerDrawable(layers);

        int iconSize = (int) context.getResources().getDimension(com.tplink.design.R.dimen.tpds_all_dp_20);
        int spacing = (int) context.getResources().getDimension(com.tplink.design.R.dimen.tpds_all_dp_20);

        layerDrawable.setLayerSize(0, iconSize, iconSize);
        layerDrawable.setLayerGravity(0, Gravity.CENTER_HORIZONTAL);
        layerDrawable.setLayerGravity(1, Gravity.CENTER_HORIZONTAL);
        layerDrawable.setLayerInset(0, -spacing, 0, 0, 0);
        layerDrawable.setLayerInset(1, 2 * spacing, 0, 0, 0);

        return layerDrawable;
    }
}

重构Fragment文件:

scss 复制代码
package com.tplink.deco.homepage.view;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import com.tplink.apps.architecture.BaseMvvmFragment;
import com.tplink.deco.R;
import com.tplink.deco.databinding.FragmentClientMainBinding;
import com.tplink.deco.homepage.adapter.ClientListAdapter;
import com.tplink.deco.homepage.viewmodel.ClientViewModel;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;

/**
 * 主网络Fragment
 * 职责:纯UI组件,观察ViewModel数据变化并更新UI
 * 将所有业务逻辑委托给ViewModel和Repository
 */
public class ClientMainFragment extends BaseMvvmFragment<FragmentClientMainBinding> 
        implements ClientListAdapter.OnDeviceActionListener {

    private ClientViewModel clientViewModel;
    private ClientListAdapter onlineAdapter;
    private ClientListAdapter offlineAdapter;

    @Nullable
    @Override
    protected FragmentClientMainBinding bindView(@NonNull LayoutInflater layoutInflater, 
            @Nullable ViewGroup viewGroup, @Nullable Bundle bundle) {
        return FragmentClientMainBinding.inflate(layoutInflater, viewGroup, false);
    }

    @Override
    protected void subscribeViewModel(@Nullable Bundle bundle) {
        // 获取ViewModel实例 - 使用Fragment级别的作用域
        clientViewModel = new ViewModelProvider(this).get(ClientViewModel.class);
        System.out.println("ClientMainFragment: ViewModel实例创建,hash: " + clientViewModel.hashCode());
        
        initView();
        observeViewModel();
    }

    /**
     * 初始化视图
     */
    private void initView() {
        setupRecyclerViews();
        setupClearOfflineButton();
    }

    /**
     * 设置RecyclerView
     */
    private void setupRecyclerViews() {
        // 初始化在线设备适配器
        onlineAdapter = new ClientListAdapter(requireContext(), true);
        onlineAdapter.setOnDeviceActionListener(this);
        viewBinding.rvOnline.setLayoutManager(new LinearLayoutManager(requireContext()));
        viewBinding.rvOnline.setAdapter(onlineAdapter);

        // 初始化离线设备适配器
        offlineAdapter = new ClientListAdapter(requireContext(), false);
        offlineAdapter.setOnDeviceActionListener(this);
        viewBinding.rvOffline.setLayoutManager(new LinearLayoutManager(requireContext()));
        viewBinding.rvOffline.setAdapter(offlineAdapter);
        
        System.out.println("ClientMainFragment: RecyclerView设置完成");
    }

    /**
     * 设置清空离线设备按钮
     */
    private void setupClearOfflineButton() {
        viewBinding.btnOffline.setOnClickListener(v -> showClearOfflineConfirmDialog());
    }

    /**
     * 观察ViewModel数据变化
     */
    private void observeViewModel() {
        // 观察主网络在线设备
        clientViewModel.getMainOnlineDevices().observe(this, onlineDevices -> {
            System.out.println("ClientMainFragment: 在线设备数据更新,数量: " + 
                (onlineDevices != null ? onlineDevices.size() : 0));
            
            if (onlineDevices != null) {
                onlineAdapter.updateDeviceList(onlineDevices);
            }
            updateEmptyState();
        });

        // 观察主网络离线设备
        clientViewModel.getMainOfflineDevices().observe(this, offlineDevices -> {
            System.out.println("ClientMainFragment: 离线设备数据更新,数量: " + 
                (offlineDevices != null ? offlineDevices.size() : 0));
            
            if (offlineDevices != null) {
                offlineAdapter.updateDeviceList(offlineDevices);
            }
            updateEmptyState();
        });

        // 观察刷新状态(如果需要下拉刷新)
        clientViewModel.getRefreshingState().observe(this, isRefreshing -> {
            // TODO: 如果有下拉刷新,可以在这里处理
        });
    }

    /**
     * 更新空状态显示
     */
    private void updateEmptyState() {
        int onlineCount = clientViewModel.getCurrentMainOnlineCount();
        int offlineCount = clientViewModel.getCurrentMainOfflineCount();
        
        System.out.println("ClientMainFragment: 更新空状态,在线: " + onlineCount + ", 离线: " + offlineCount);

        boolean isEmpty = (onlineCount == 0) && (offlineCount == 0);

        if (isEmpty) {
            showEmptyView();
        } else {
            showDeviceList();
        }
    }

    /**
     * 显示空状态视图
     */
    private void showEmptyView() {
        viewBinding.rvOnline.setVisibility(View.GONE);
        viewBinding.rvOffline.setVisibility(View.GONE);
        viewBinding.emptyLayout.setVisibility(View.VISIBLE);
        viewBinding.emptyImage.setImageResource(R.mipmap.empty_device);
        viewBinding.emptyText.setText("没有设备记录");
        
        System.out.println("ClientMainFragment: 显示空状态");
    }

    /**
     * 显示设备列表
     */
    private void showDeviceList() {
        viewBinding.rvOnline.setVisibility(View.VISIBLE);
        viewBinding.rvOffline.setVisibility(View.VISIBLE);
        viewBinding.emptyLayout.setVisibility(View.GONE);
        
        System.out.println("ClientMainFragment: 显示设备列表");
    }

    /**
     * 显示清空离线设备确认对话框
     */
    private void showClearOfflineConfirmDialog() {
        new androidx.appcompat.app.AlertDialog.Builder(requireContext())
                .setTitle("清空离线设备")
                .setMessage("确定要清空所有离线设备吗?这个操作不能撤销。")
                .setPositiveButton("确定", (dialog, which) -> clearAllOfflineDevices())
                .setNegativeButton("取消", null)
                .show();
    }

    /**
     * 清空所有离线设备
     */
    private void clearAllOfflineDevices() {
        // 获取当前离线设备并逐个删除
        clientViewModel.getMainOfflineDevices().observe(this, devices -> {
            if (devices != null && !devices.isEmpty()) {
                int deletedCount = 0;
                for (ClientViewModel.DeviceInfo device : devices) {
                    if (clientViewModel.deleteDevice(device.getDeviceName())) {
                        deletedCount++;
                    }
                }
                
                if (deletedCount > 0) {
                    Toast.makeText(getContext(), "已清空 " + deletedCount + " 个离线设备", 
                        Toast.LENGTH_SHORT).show();
                    System.out.println("ClientMainFragment: 清空了 " + deletedCount + " 个离线设备");
                }
            }
        });
    }

    // ========== 设备操作回调 ==========

    /**
     * 拉黑设备回调
     * 这是关键方法:将设备拉黑操作委托给ViewModel
     */
    @Override
    public void onBlockDevice(ClientViewModel.DeviceInfo device, int position) {
        System.out.println("ClientMainFragment: 准备拉黑设备 " + device.getDeviceName());
        
        // 委托给ViewModel处理 - 这里是关键!
        boolean success = clientViewModel.blockDevice(device.getDeviceName());
        
        if (success) {
            Toast.makeText(getContext(), device.getDeviceName() + " 已加入黑名单", 
                Toast.LENGTH_SHORT).show();
            System.out.println("ClientMainFragment: 设备拉黑成功 " + device.getDeviceName());
        } else {
            Toast.makeText(getContext(), "拉黑设备失败", Toast.LENGTH_SHORT).show();
            System.out.println("ClientMainFragment: 设备拉黑失败 " + device.getDeviceName());
        }
        
        // 注意:不需要手动更新UI!
        // Repository会自动更新LiveData,所有观察者(包括BlacklistActivity)都会收到通知
    }

    /**
     * 删除设备回调
     */
    @Override
    public void onDeleteDevice(ClientViewModel.DeviceInfo device, int position) {
        System.out.println("ClientMainFragment: 准备删除设备 " + device.getDeviceName());
        
        // 委托给ViewModel处理
        boolean success = clientViewModel.deleteDevice(device.getDeviceName());
        
        if (success) {
            Toast.makeText(getContext(), device.getDeviceName() + " 已删除", 
                Toast.LENGTH_SHORT).show();
            System.out.println("ClientMainFragment: 设备删除成功 " + device.getDeviceName());
        } else {
            Toast.makeText(getContext(), "删除设备失败", Toast.LENGTH_SHORT).show();
        }
        
        // 同样不需要手动更新UI,Repository会自动处理
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        System.out.println("ClientMainFragment: onDestroyView");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        System.out.println("ClientMainFragment: onDestroy");
    }
}

重构访客网络Fragment:

typescript 复制代码
package com.tplink.deco.homepage.view;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import com.tplink.apps.architecture.BaseMvvmFragment;
import com.tplink.deco.R;
import com.tplink.deco.databinding.FragmentClientGuestBinding;
import com.tplink.deco.homepage.adapter.ClientListAdapter;
import com.tplink.deco.homepage.viewmodel.ClientViewModel;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;

/**
 * 访客网络Fragment
 * 职责:纯UI组件,观察ViewModel数据变化并更新UI
 * 将所有业务逻辑委托给ViewModel和Repository
 */
public class ClientGuestFragment extends BaseMvvmFragment<FragmentClientGuestBinding> 
        implements ClientListAdapter.OnDeviceActionListener {

    private ClientViewModel clientViewModel;
    private ClientListAdapter guestAdapter;

    @Nullable
    @Override
    protected FragmentClientGuestBinding bindView(@NonNull LayoutInflater layoutInflater, 
            @Nullable ViewGroup viewGroup, @Nullable Bundle bundle) {
        return FragmentClientGuestBinding.inflate(getLayoutInflater());
    }

    @Override
    protected void subscribeViewModel(@Nullable Bundle bundle) {
        // 获取ViewModel实例 - 使用Fragment级别的作用域
        clientViewModel = new ViewModelProvider(this).get(ClientViewModel.class);
        System.out.println("ClientGuestFragment: ViewModel实例创建,hash: " + clientViewModel.hashCode());
        
        initView();
        observeViewModel();
    }

    /**
     * 初始化视图
     */
    private void initView() {
        setupRecyclerView();
    }

    /**
     * 设置RecyclerView
     */
    private void setupRecyclerView() {
        // 访客网络设备都是在线的,所以使用在线适配器
        guestAdapter = new ClientListAdapter(requireContext(), true);
        guestAdapter.setOnDeviceActionListener(this);
        viewBinding.rvOnline.setLayoutManager(new LinearLayoutManager(requireContext()));
        viewBinding.rvOnline.setAdapter(guestAdapter);
        
        System.out.println("ClientGuestFragment: RecyclerView设置完成");
    }

    /**
     * 观察ViewModel数据变化
     */
    private void observeViewModel() {
        // 观察访客网络设备
        clientViewModel.getGuestDevices().observe(this, guestDevices -> {
            System.out.println("ClientGuestFragment: 访客设备数据更新,数量: " + 
                (guestDevices != null ? guestDevices.size() : 0));
            
            if (guestDevices != null) {
                guestAdapter.updateDeviceList(guestDevices);
            }
            
            updateEmptyState(guestDevices);
        });

        // 观察刷新状态(如果需要下拉刷新)
        clientViewModel.getRefreshingState().observe(this, isRefreshing -> {
            // TODO: 如果有下拉刷新,可以在这里处理
        });
    }

    /**
     * 更新空状态显示
     */
    private void updateEmptyState(java.util.List<ClientViewModel.DeviceInfo> guestDevices) {
        boolean isEmpty = (guestDevices == null || guestDevices.isEmpty());
        
        System.out.println("ClientGuestFragment: 更新空状态,isEmpty: " + isEmpty);

        if (isEmpty) {
            showEmptyView();
        } else {
            showDeviceList();
        }
    }

    /**
     * 显示空状态视图
     */
    private void showEmptyView() {
        viewBinding.rvOnline.setVisibility(View.GONE);
        viewBinding.emptyLayout.setVisibility(View.VISIBLE);
        viewBinding.emptyImage.setImageResource(R.mipmap.empty_device);
        viewBinding.emptyText.setText("没有设备记录");
        
        System.out.println("ClientGuestFragment: 显示空状态");
    }

    /**
     * 显示设备列表
     */
    private void showDeviceList() {
        viewBinding.rvOnline.setVisibility(View.VISIBLE);
        viewBinding.emptyLayout.setVisibility(View.GONE);
        
        System.out.println("ClientGuestFragment: 显示设备列表");
    }

    // ========== 设备操作回调 ==========

    /**
     * 拉黑设备回调
     * 这是关键方法:将设备拉黑操作委托给ViewModel
     */
    @Override
    public void onBlockDevice(ClientViewModel.DeviceInfo device, int position) {
        System.out.println("ClientGuestFragment: 准备拉黑访客设备 " + device.getDeviceName());
        
        // 委托给ViewModel处理 - 这里是关键!
        boolean success = clientViewModel.blockDevice(device.getDeviceName());
        
        if (success) {
            Toast.makeText(getContext(), device.getDeviceName() + " 已加入黑名单", 
                Toast.LENGTH_SHORT).show();
            System.out.println("ClientGuestFragment: 访客设备拉黑成功 " + device.getDeviceName());
        } else {
            Toast.makeText(getContext(), "拉黑设备失败", Toast.LENGTH_SHORT).show();
            System.out.println("ClientGuestFragment: 访客设备拉黑失败 " + device.getDeviceName());
        }
        
        // 注意:不需要手动更新UI!
        // Repository会自动更新LiveData,所有观察者(包括BlacklistActivity)都会收到通知
    }

    /**
     * 删除设备回调
     */
    @Override
    public void onDeleteDevice(ClientViewModel.DeviceInfo device, int position) {
        System.out.println("ClientGuestFragment: 准备删除访客设备 " + device.getDeviceName());
        
        // 委托给ViewModel处理
        boolean success = clientViewModel.deleteDevice(device.getDeviceName());
        
        if (success) {
            Toast.makeText(getContext(), device.getDeviceName() + " 已删除", 
                Toast.LENGTH_SHORT).show();
            System.out.println("ClientGuestFragment: 访客设备删除成功 " + device.getDeviceName());
        } else {
            Toast.makeText(getContext(), "删除设备失败", Toast.LENGTH_SHORT).show();
        }
        
        // 同样不需要手动更新UI,Repository会自动处理
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        System.out.println("ClientGuestFragment: onDestroyView");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        System.out.println("ClientGuestFragment: onDestroy");
    }
}

重构黑名单Activity:

typescript 复制代码
package com.tplink.deco.homepage.view;

import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import com.tplink.apps.architecture.BaseMvvmActivity;
import com.tplink.deco.R;
import com.tplink.deco.databinding.ActivityBlacklistBinding;
import com.tplink.deco.homepage.adapter.BlacklistAdapter;
import com.tplink.deco.homepage.viewmodel.ClientViewModel;
import com.tplink.design.bottomsheet.TPModalBottomSheet;
import com.tplink.design.menu.TPPopupMenu;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;

/**
 * 黑名单Activity
 * 职责:显示黑名单列表,观察来自Repository的数据变化
 * 关键:能够自动显示从Fragment拉黑的设备
 */
public class BlacklistActivity extends BaseMvvmActivity<ActivityBlacklistBinding>
        implements BlacklistAdapter.OnDeviceActionListener {

    private ClientViewModel viewModel;
    private BlacklistAdapter adapter;

    @Nullable
    @Override
    protected ActivityBlacklistBinding bindContentView(@Nullable Bundle bundle) {
        return ActivityBlacklistBinding.inflate(getLayoutInflater());
    }

    @Override
    protected void subscribeViewModel(@Nullable Bundle bundle) {
        // 创建ViewModel实例 - 使用同一个Repository
        viewModel = new ViewModelProvider(this).get(ClientViewModel.class);
        System.out.println("BlacklistActivity: ViewModel实例创建,hash: " + viewModel.hashCode());
        
        setupToolbar();
        setupRecyclerView();
        observeViewModel(); // 关键:观察数据变化
    }

    /**
     * 设置工具栏
     */
    private void setupToolbar() {
        viewBinding.toolbar.setTitle("黑名单");
        viewBinding.toolbar.setNavigationOnClickListener(view -> onBackPressed());
        
        // 设置支持菜单
        setSupportActionBar(viewBinding.toolbar);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_home, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        if (item.getItemId() == R.id.add) {
            showAddDeviceMenu();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    /**
     * 显示添加设备菜单
     */
    private void showAddDeviceMenu() {
        View anchorView = findViewById(R.id.add);
        if (anchorView == null) {
            anchorView = viewBinding.toolbar;
        }

        TPPopupMenu popupMenu = new TPPopupMenu(this, anchorView, R.menu.menu_mac)
                .setIconEnable(true);
        popupMenu.setOnMenuItemClickListener(item -> {
            if (item.getItemId() == R.id.add_by_user) {
                // TODO: 实现选择用户端功能
                Toast.makeText(this, "选择用户端功能待实现", Toast.LENGTH_SHORT).show();
                return true;
            } else if (item.getItemId() == R.id.add_by_mac) {
                showAddByMacModal();
                return true;
            }
            return false;
        });
        popupMenu.show();
    }

    /**
     * 显示通过MAC地址添加的对话框
     */
    private void showAddByMacModal() {
        new TPModalBottomSheet.Builder()
                .setScreenType(TPModalBottomSheet.ScreenType.FULL_SCREEN)
                .setDividerEnable(false)
                .setTitle("Add by MAC Address")
                .setEndOptionText("Save")
                .setContentLayoutId(R.layout.fragment_blacklist)
                .setContentViewListener(new TPModalBottomSheet.OnModalContentViewListener() {
                    @Override
                    public void onContentViewCreated(TPModalBottomSheet tpModalBottomSheet, View view) {
                        // 设置保存按钮点击事件
                        tpModalBottomSheet.setEndOptionClickListener(() -> {
                            EditText macEditText = view.findViewById(R.id.etMacAddress);
                            if (macEditText != null) {
                                String macAddress = macEditText.getText().toString().trim();
                                if (!macAddress.isEmpty()) {
                                    if (isValidMacAddress(macAddress)) {
                                        boolean success = viewModel.blockDeviceByMac(macAddress);
                                        if (success) {
                                            Toast.makeText(BlacklistActivity.this, 
                                                "MAC地址已添加到黑名单", Toast.LENGTH_SHORT).show();
                                            tpModalBottomSheet.dismiss();
                                        } else {
                                            Toast.makeText(BlacklistActivity.this, 
                                                "添加失败,可能已存在", Toast.LENGTH_SHORT).show();
                                        }
                                    } else {
                                        macEditText.setError("请输入有效的MAC地址 (格式: XX-XX-XX-XX-XX-XX)");
                                    }
                                } else {
                                    macEditText.setError("MAC地址不能为空");
                                }
                            }
                        });
                    }
                })
                .show(getSupportFragmentManager(), "add_by_mac");
    }

    /**
     * 验证MAC地址格式
     */
    private boolean isValidMacAddress(String mac) {
        return mac != null && mac.matches("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$");
    }

    /**
     * 设置RecyclerView
     */
    private void setupRecyclerView() {
        adapter = new BlacklistAdapter(this);
        adapter.setOnDeviceActionListener(this);

        viewBinding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
        viewBinding.recyclerView.setAdapter(adapter);
    }

    /**
     * 观察ViewModel数据变化 - 这是关键方法!
     * 当Fragment拉黑设备时,这里会自动收到通知并更新UI
     */
    private void observeViewModel() {
        System.out.println("BlacklistActivity: 开始观察黑名单数据");
        
        // 观察黑名单数据变化
        viewModel.getBlacklistItems().observe(this, items -> {
            System.out.println("BlacklistActivity: 收到黑名单数据更新,数量: " + 
                (items != null ? items.size() : 0));
            
            // 打印详细信息用于调试
            if (items != null) {
                for (int i = 0; i < items.size(); i++) {
                    ClientViewModel.BlacklistItem item = items.get(i);
                    System.out.println("  - [" + i + "] " + item.getDisplayName() + 
                        " (MAC: " + item.getMacAddress() + ")");
                }
            }
            
            if (items == null || items.isEmpty()) {
                showEmptyView();
            } else {
                showDeviceList();
                adapter.updateItems(items);
            }
        });
    }

    /**
     * 显示空状态视图
     */
    private void showEmptyView() {
        viewBinding.recyclerView.setVisibility(View.GONE);
        viewBinding.emptyLayout.setVisibility(View.VISIBLE);
        viewBinding.emptyImage.setImageResource(R.mipmap.empty_blacklist);
        viewBinding.emptyText.setText("没有设备记录");
        
        System.out.println("BlacklistActivity: 显示空状态");
    }

    /**
     * 显示设备列表
     */
    private void showDeviceList() {
        viewBinding.recyclerView.setVisibility(View.VISIBLE);
        viewBinding.emptyLayout.setVisibility(View.GONE);
        
        System.out.println("BlacklistActivity: 显示设备列表");
    }

    // ========== 黑名单操作回调 ==========

    /**
     * 解除拉黑回调
     */
    @Override
    public void onUnblockDevice(String identifier, int position) {
        System.out.println("BlacklistActivity: 准备解除拉黑 " + identifier);
        
        boolean success = viewModel.unblockDevice(identifier);
        if (success) {
            Toast.makeText(this, identifier + " 已移出黑名单", Toast.LENGTH_SHORT).show();
            System.out.println("BlacklistActivity: 解除拉黑成功 " + identifier);
        } else {
            Toast.makeText(this, "解除拉黑失败", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 删除黑名单项目回调
     */
    @Override
    public void onDeleteDevice(String identifier, int position) {
        System.out.println("BlacklistActivity: 准备删除黑名单项目 " + identifier);
        
        boolean success = viewModel.deleteBlacklistDevice(identifier);
        if (success) {
            Toast.makeText(this, identifier + " 已删除", Toast.LENGTH_SHORT).show();
            System.out.println("BlacklistActivity: 删除黑名单项目成功 " + identifier);
        } else {
            Toast.makeText(this, "删除失败", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        System.out.println("BlacklistActivity: onStart,当前黑名单数量: " + 
            viewModel.getCurrentBlacklistCount());
    }

    @Override
    protected void onResume() {
        super.onResume();
        System.out.println("BlacklistActivity: onResume");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        System.out.println("BlacklistActivity: onDestroy");
    }
}

更新BlacklistAdapter以兼容新的数据结构:

java 复制代码
package com.tplink.deco.homepage.adapter;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import com.tplink.deco.R;
import com.tplink.deco.homepage.viewmodel.ClientViewModel;
import com.tplink.design.list.TPSingleLineItemView;
import com.tplink.design.list.TPSingleLineItemViewHolder;
import com.tplink.design.list.TPTwoLineItemView;
import com.tplink.design.list.TPTwoLineItemViewHolder;
import com.tplink.design.menu.TPPopupMenu;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

/**
 * 黑名单适配器
 * 职责:显示黑名单项目,处理用户交互
 * 兼容新的数据结构,使用ClientViewModel.BlacklistItem
 */
public class BlacklistAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    
    private static final int TYPE_DEVICE = 0;  // 设备名称+MAC类型
    private static final int TYPE_MAC_ONLY = 1; // 纯MAC地址类型
    
    private List<ClientViewModel.BlacklistItem> blacklistItems = new ArrayList<>();
    private Context context;
    private OnDeviceActionListener listener;

    // 操作监听接口
    public interface OnDeviceActionListener {
        void onUnblockDevice(String identifier, int position);
        void onDeleteDevice(String identifier, int position);
    }

    public BlacklistAdapter(Context context) {
        this.context = context;
        System.out.println("BlacklistAdapter创建");
    }

    public void setOnDeviceActionListener(OnDeviceActionListener listener) {
        this.listener = listener;
    }

    /**
     * 更新黑名单项目
     * 这个方法会被BlacklistActivity调用,传入来自ViewModel的LiveData数据
     */
    public void updateItems(List<ClientViewModel.BlacklistItem> items) {
        System.out.println("BlacklistAdapter.updateItems调用,新项目数量: " + 
            (items != null ? items.size() : 0));
            
        this.blacklistItems.clear();
        if (items != null) {
            this.blacklistItems.addAll(items);
            
            // 打印详细信息用于调试
            for (int i = 0; i < items.size(); i++) {
                ClientViewModel.BlacklistItem item = items.get(i);
                System.out.println("  - [" + i + "] " + item.getDisplayName() + 
                    " (MAC: " + item.getMacAddress() + ", MacOnly: " + item.isMacOnly() + ")");
            }
        }
        notifyDataSetChanged();
    }

    @Override
    public int getItemViewType(int position) {
        if (position >= blacklistItems.size()) return TYPE_DEVICE;
        
        ClientViewModel.BlacklistItem item = blacklistItems.get(position);
        return item.isMacOnly() ? TYPE_MAC_ONLY : TYPE_DEVICE;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == TYPE_MAC_ONLY) {
            return TPSingleLineItemViewHolder.create(parent);
        } else {
            return TPTwoLineItemViewHolder.create(parent);
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (position >= blacklistItems.size()) return;
        
        ClientViewModel.BlacklistItem item = blacklistItems.get(position);
        
        if (getItemViewType(position) == TYPE_MAC_ONLY) {
            bindMacOnlyItem((TPSingleLineItemViewHolder) holder, item, position);
        } else {
            bindDeviceItem((TPTwoLineItemViewHolder) holder, item, position);
        }
    }

    /**
     * 绑定纯MAC地址项目
     */
    private void bindMacOnlyItem(TPSingleLineItemViewHolder holder, 
            ClientViewModel.BlacklistItem item, int position) {
        TPSingleLineItemView lineItem = (TPSingleLineItemView) holder.itemView;
        
        // 设置通用设备图标
        lineItem.setStartIcon(R.mipmap.laptop3);
        
        // 设置图标尺寸
        setIconSize(lineItem.getStartIcon());
        
        lineItem.setTitleText(item.getMacAddress());
        lineItem.setEndIcon(R.drawable.remove_24);
        lineItem.showDivider(true);
        
        setupLongClickListener(lineItem, item.getIdentifier(), position);
    }

    /**
     * 绑定设备项目(设备名称+MAC)
     */
    private void bindDeviceItem(TPTwoLineItemViewHolder holder, 
            ClientViewModel.BlacklistItem item, int position) {
        TPTwoLineItemView lineItem = (TPTwoLineItemView) holder.itemView;
        
        // 根据设备名称判断设备类型并设置图标
        String deviceName = item.getDisplayName().toLowerCase();
        if (deviceName.contains("iphone") || deviceName.contains("phone") || 
            deviceName.contains("mobile") || deviceName.contains("ipod")) {
            lineItem.setStartIcon(R.mipmap.game3); // 手机图标
        } else {
            lineItem.setStartIcon(R.mipmap.laptop3); // 电脑图标
        }
        
        // 设置图标尺寸
        setIconSize(lineItem.getStartIcon());
        
        lineItem.setTitleText(item.getDisplayName());
        
        // 显示MAC地址
        String macText = "MAC: " + item.getMacAddress();
        lineItem.setContentText(macText);
        
        // 设置禁用图标
        lineItem.setEndIcon(R.drawable.remove_24);
        lineItem.showDivider(true);
        
        setupLongClickListener(lineItem, item.getIdentifier(), position);
    }

    /**
     * 设置图标尺寸
     */
    private void setIconSize(android.widget.ImageView iconView) {
        ViewGroup.LayoutParams layoutParams = iconView.getLayoutParams();
        int iconSize = (int) context.getResources().getDimension(com.tplink.design.R.dimen.tpds_all_dp_40);
        layoutParams.width = iconSize;
        layoutParams.height = iconSize;
        iconView.setLayoutParams(layoutParams);
    }

    /**
     * 设置长按监听器
     */
    private void setupLongClickListener(View itemView, String identifier, int position) {
        itemView.setOnLongClickListener(v -> {
            TPPopupMenu popupMenu = new TPPopupMenu(context, v, R.menu.menu_delete)
                    .setIconEnable(true);
            popupMenu.setOnMenuItemClickListener(item -> {
                if (item.getItemId() == R.id.block) {
                    // 在黑名单中,block操作表示解除拉黑
                    showUnblockConfirmDialog(identifier, position);
                    return true;
                } else if (item.getItemId() == R.id.delete) {
                    showDeleteConfirmDialog(identifier, position);
                    return true;
                }
                return false;
            });
            popupMenu.show();
            return true;
        });
    }

    @Override
    public int getItemCount() {
        return blacklistItems.size();
    }

    /**
     * 显示解除拉黑确认对话框
     */
    private void showUnblockConfirmDialog(String identifier, int position) {
        new androidx.appcompat.app.AlertDialog.Builder(context)
                .setTitle("解除拉黑")
                .setMessage("确定要将 \"" + identifier + "\" 从黑名单中移除吗?\n" +
                    "移除后设备将能够重新连接网络。")
                .setPositiveButton("确定", (dialog, which) -> {
                    if (listener != null) {
                        System.out.println("BlacklistAdapter: 准备解除拉黑 " + identifier);
                        listener.onUnblockDevice(identifier, position);
                    }
                })
                .setNegativeButton("取消", null)
                .show();
    }

    /**
     * 显示删除确认对话框
     */
    private void showDeleteConfirmDialog(String identifier, int position) {
        new androidx.appcompat.app.AlertDialog.Builder(context)
                .setTitle("删除设备")
                .setMessage("确定要永久删除 \"" + identifier + "\" 吗?\n" +
                    "删除后将不会保留任何记录。")
                .setPositiveButton("确定", (dialog, which) -> {
                    if (listener != null) {
                        System.out.println("BlacklistAdapter: 准备删除黑名单项目 " + identifier);
                        listener.onDeleteDevice(identifier, position);
                    }
                })
                .setNegativeButton("取消", null)
                .show();
    }
}

完整大改总结

我已经完成了你项目的完整重构。现在来解释核心的数据共享机制和使用方法。

🔧 核心架构变化

之前的架构问题:

复制代码
Fragment拉黑 → 手动更新适配器 → 手动更新ViewModel → BlacklistActivity看不到数据

重构后的架构:

复制代码
Fragment拉黑 → Repository更新数据 → LiveData通知所有观察者 → BlacklistActivity自动更新

📋 数据共享机制详解

1. DeviceRepository(单例)- 数据中枢

java 复制代码
// 单例确保全局唯一数据源
DeviceRepository.getInstance() // 所有地方都用同一个实例

// 拉黑方法(核心)
public boolean blockDevice(String deviceName) {
    // 1. 从设备列表移除
    allDevices.remove(deviceToBlock);
    // 2. 添加到黑名单
    blacklistItems.add(new BlacklistItem(...));
    // 3. 关键:通知所有观察者
    blacklistLiveData.setValue(new ArrayList<>(blacklistItems));
}

2. 数据流示意图

css 复制代码
ClientMainFragment                BlacklistActivity
       ↓                                 ↓
   ViewModel实例A                    ViewModel实例B
       ↓                                 ↓
       └─────── DeviceRepository ────────┘
                (单例,共享数据源)

3. 关键调用链

java 复制代码
// Fragment中拉黑
onBlockDevice() → viewModel.blockDevice() → repository.blockDevice()
                                                      ↓
// Repository更新数据并通知
repository.blacklistLiveData.setValue(newData)
                                                      ↓
// BlacklistActivity自动收到通知
viewModel.getBlacklistItems().observe(this, items -> {
    adapter.updateItems(items); // 自动更新UI!
});

🎯 使用方法

1. 替换文件

将以下7个新文件替换你的原文件:

  • DeviceRepository.java(新建)
  • ClientViewModel.java(重构)
  • ClientListAdapter.java(重构)
  • ClientMainFragment.java(重构)
  • ClientGuestFragment.java(重构)
  • BlacklistActivity.java(重构)
  • BlacklistAdapter.java(更新)

2. 包结构调整

arduino 复制代码
com.tplink.deco.homepage.repository.DeviceRepository  // 新建package

3. 测试数据共享

  1. 启动应用,打开ClientPageActivity
  2. 在主网络或访客网络tab中长按任意设备
  3. 选择"拉黑设备"
  4. 点击右上角进入黑名单页面
  5. 应该能看到刚才拉黑的设备

关键改进点

1. 数据一致性

  • 单一数据源:所有数据都在Repository中
  • 自动同步:任何地方的操作都会自动同步到所有界面

2. 代码简化

  • Fragment不再手动操作数据,只委托给ViewModel
  • 不再需要手动更新计数
  • 移除了所有重复的数据操作代码

3. 调试友好

  • 添加了大量日志输出,方便追踪数据流
  • 每个操作都有成功/失败反馈

4. 功能增强

  • 支持通过MAC地址直接拉黑
  • 改进的确认对话框
  • 更好的用户体验

🚀 验证步骤

运行这个测试来验证数据共享是否工作:

java 复制代码
// 在ClientMainFragment中拉黑设备后,检查日志:
// ClientMainFragment: 设备拉黑成功 Melanie's iPhone
// Repository: 设备拉黑成功: Melanie's iPhone, 黑名单数: 1
// BlacklistActivity: 收到黑名单数据更新,数量: 1

如果看到这些日志,说明数据共享机制工作正常。

💡 核心突破

最关键的突破是Repository单例模式

  • 确保所有ViewModel实例使用同一个数据源
  • LiveData自动在Activity间传播数据变化
  • 解决了原来"两个Activity两个数据孤岛"的问题

现在当你在Fragment中拉黑设备时,BlacklistActivity会立即自动显示新增的黑名单项目。这就是你要的效果。

用户: 不能只有ClientMainFragment噢,ClientGuestFragment有做到吗

Claude: ��说得对,让我检查一下ClientGuestFragment的实现。我发现了一个小错误,并且需要确认访客网络的拉黑功能是否完整实现。 ��的,ClientGuestFragment完全实现了和ClientMainFragment相同的拉黑功能。让我确认给你看:

ClientGuestFragment的拉黑实现检查

1. 接口实现

java 复制代码
public class ClientGuestFragment extends BaseMvvmFragment<FragmentClientGuestBinding> 
        implements ClientListAdapter.OnDeviceActionListener

2. 拉黑方法实现

java 复制代码
@Override
public void onBlockDevice(ClientViewModel.DeviceInfo device, int position) {
    System.out.println("ClientGuestFragment: 准备拉黑访客设备 " + device.getDeviceName());
    
    // 委托给ViewModel处理 - 关键!
    boolean success = clientViewModel.blockDevice(device.getDeviceName());
    
    if (success) {
        Toast.makeText(getContext(), device.getDeviceName() + " 已加入黑名单", 
            Toast.LENGTH_SHORT).show();
        System.out.println("ClientGuestFragment: 访客设备拉黑成功 " + device.getDeviceName());
    } else {
        Toast.makeText(getContext(), "拉黑设备失败", Toast.LENGTH_SHORT).show();
        System.out.println("ClientGuestFragment: 访客设备拉黑失败 " + device.getDeviceName());
    }
}

3. 数据观察

java 复制代码
clientViewModel.getGuestDevices().observe(this, guestDevices -> {
    System.out.println("ClientGuestFragment: 访客设备数据更新,数量: " + 
        (guestDevices != null ? guestDevices.size() : 0));
    
    if (guestDevices != null) {
        guestAdapter.updateDeviceList(guestDevices);
    }
});

数据共享验证

完整的数据流:

css 复制代码
ClientMainFragment拉黑    ClientGuestFragment拉黑
       ↓                        ↓
   ViewModel实例A            ViewModel实例B  
       ↓                        ↓
       └──── DeviceRepository ───┘ (单例,共享数据源)
                    ↓
            blacklistLiveData.setValue()
                    ↓
           通知所有观察者
                    ↓
          BlacklistActivity自动更新

测试步骤

测试主网络拉黑:

  1. 打开ClientPageActivity
  2. 在"主网络"tab中长按任意设备
  3. 选择"拉黑设备"
  4. 进入黑名单页面应该看到该设备

测试访客网络拉黑:

  1. 切换到"访客网络"tab
  2. 长按任意访客设备
  3. 选择"拉黑设备"
  4. 进入黑名单页面应该看到该设备

预期日志输出:

makefile 复制代码
// 访客网络拉黑时的日志
ClientGuestFragment: 准备拉黑访客设备 Guest Phone 1
ClientViewModel.blockDevice调用: Guest Phone 1
设备拉黑成功: Guest Phone 1, 剩余设备数: 8, 黑名单数: 1
ClientGuestFragment: 访客设备拉黑成功 Guest Phone 1
BlacklistActivity: 收到黑名单数据更新,数量: 1

关键确认

两个Fragment使用的是完全相同的机制

  • 相同的Repository单例
  • 相同的blockDevice()方法
  • 相同的LiveData观察机制
  • 相同的数据共享流程

所以无论是在主网络还是访客网络中拉黑设备,BlacklistActivity都会立即看到更新。

我已经修复了ClientGuestFragment中的一个小语法错误(多余的括号),现在两个Fragment的拉黑功能完全一致。

用户: 你觉得这份代码没问题了吗,我明天会再来问你的,具体的问题具体讨论

Claude: ��份代码在核心功能上应该能工作,但还有几个潜在问题需要注意:

可能的问题点

1. 数据持久化缺失

当前所有数据都在内存中,App被杀死后数据会丢失。生产环境中黑名单数据应该持久化到数据库或SharedPreferences。

2. 内存管理

Repository是单例且持有大量数据,可能造成内存泄漏。特别是LiveData的观察者如果没有正确解除可能有问题。

3. 线程安全

虽然用了volatile和synchronized,但Repository中的List操作在高并发下仍可能有线程安全问题。

4. 性能优化空间

java 复制代码
// 每次都创建新List,数据量大时可能影响性能
return Transformations.map(allDevicesLiveData, devices -> {
    List<DeviceInfo> result = new ArrayList<>(); // 频繁创建
    // ...
});

5. 错误处理不完整

很多地方缺少异常处理,比如MAC地址验证、设备操作失败等场景。

6. 测试难度

单例模式让单元测试变得困难,Repository的状态会在测试间相互影响。

建议改进

短期内可以工作,但建议后续:

  1. 添加数据持久化层
  2. 改进错误处理和边界情况
  3. 添加网络请求模拟
  4. 考虑使用依赖注入替代单例
  5. 添加单元测试

核心功能评估

数据共享机制应该能正常工作,两个Fragment拉黑的设备都会出现在BlacklistActivity中。但在生产环境使用前还需要进一步完善。

明天有具体问题时我们再详细讨论。

相关推荐
简单点了1 小时前
SM4加密算法
java·开发语言
用户3721574261351 小时前
Java 实现HTML转Word:从HTML文件与字符串到可编辑Word文档
java
yvya_2 小时前
Mybatis总结
java·spring·mybatis
姜太小白2 小时前
【VSCode】VSCode为Java C/S项目添加图形用户界面
java·c语言·vscode
一路向北North2 小时前
java将doc文件转pdf
java·pdf
咕白m6252 小时前
Java 开发:用 Spire.PDF 高效压缩 PDF 文件
java·编程语言
万行2 小时前
点评项目(Redis中间件)&第一部分Redis基础
java·数据库·redis·缓存·中间件
用户2707912938183 小时前
异常处理!Exception 和 Error 有什么区别?
java
藤椒鱼不爱编程3 小时前
核心类库_常用类
java·编程语言
杨杨杨大侠3 小时前
第5篇:日志处理器的核心逻辑 - 让日志更智能
java·spring·apache log4j