RxJava——FlowableProcessor详解

FlowableProcessor详解

一、概述

核心概念:

FlowableProcessor​ 是支持背压的 Subject,它同时实现了:

  • Processor<T, R> 接口(Reactive Streams 规范)
  • Flowable 接口(可以被订阅)
  • FlowableSubscriber 接口(可以订阅其他Flowable)

继承关系:

java 复制代码
// 核心继承关系
public abstract class FlowableProcessor<T> 
    extends Flowable<T> 
    implements Processor<T, T>, FlowableSubscriber<T> {
    
    // 核心方法
    public abstract boolean hasSubscribers();
    public abstract Throwable getThrowable();
    public abstract boolean hasThrowable();
    public abstract boolean hasComplete();
    public abstract boolean hasSubscribers();
}

与Subject对比:

特性 FlowableProcessor Subject
背压支持 ✅ 完全支持 ❌ 不支持(Observable版本)
Reactive Streams兼容 ✅ Processor接口 ❌ 不兼容
订阅者数量限制 可配置 无限制
缓冲区策略 多种策略 无缓冲区
性能开销 较高 较低

二、PublishProcessor(实时发布处理器)

2.1、核心特性

  • 类似 PublishSubject,但支持背压
  • 只向当前订阅者发射数据
  • 不缓存任何数据
  • 支持背压策略
java 复制代码
@Test
public void testPublishProcessor() {
    //基本用法
    PublishProcessor<Integer> processor = PublishProcessor.create();

    //生产者
    Flowable.range(1, 1000)
            .subscribe(processor);
    //消费者1
    processor.onBackpressureBuffer(100)//缓冲区大小
            .subscribe(
                    data -> System.out.println("Consumer 1:" + data),
                    error -> System.out.println("Error 1:" + error),
                    () -> System.out.println("Completed 1")
            );

    //消费者2(稍后订阅,收不到之前的数据)
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    processor.subscribe(
            data -> System.out.println("Consumer 2:" + data),
            error -> System.out.println("Error 2:" + error),
            () -> System.out.println("Completed 2")
    );
}

背压处理示例:

java 复制代码
public class PublishProcessorBackpressure {
    
    public static void demoBackpressure() {
        PublishProcessor<Integer> processor = PublishProcessor.create();
        
        // 快速生产者
        Flowable.interval(10, TimeUnit.MILLISECONDS)
            .take(1000)
            .map(Long::intValue)
            .subscribe(processor);
        
        // 慢速消费者
        processor
            .onBackpressureBuffer(
                50,                     // 缓冲区大小
                () -> System.out.println("Buffer overflow"),
                BackpressureOverflowStrategy.DROP_OLDEST
            )
            .observeOn(Schedulers.io(), false, 16)  // 缓冲区16
            .subscribe(
                data -> {
                    // 慢速处理
                    Thread.sleep(100);
                    System.out.println("Processed: " + data);
                },
                error -> System.err.println("Error: " + error)
            );
    }
    
    // 使用不同的背压策略
    public static void differentStrategies() {
        PublishProcessor<Integer> processor = PublishProcessor.create();
        
        // 策略1: BUFFER - 无限缓冲(可能OOM)
        processor.onBackpressureBuffer();
        
        // 策略2: DROP - 丢弃溢出数据
        processor.onBackpressureDrop(dropped -> 
            System.out.println("Dropped: " + dropped)
        );
        
        // 策略3: LATEST - 保留最新数据
        processor.onBackpressureLatest();
        
        // 策略4: ERROR - 缓冲区满时抛异常
        processor.onBackpressureBuffer(100, 
            () -> {}, 
            BackpressureOverflowStrategy.ERROR
        );
    }
}

2.2、使用场景

java 复制代码
// 1. 实时数据流处理
public class RealTimeDataProcessor {
    private final PublishProcessor<SensorData> dataProcessor = PublishProcessor.create();
    private final CompositeDisposable disposables = new CompositeDisposable();
    
    public RealTimeDataProcessor() {
        // 设置数据处理管道
        disposables.add(
            dataProcessor
                .onBackpressureBuffer(1000)
                .observeOn(Schedulers.computation())
                .subscribe(this::processData)
        );
    }
    
    public void feedData(SensorData data) {
        dataProcessor.onNext(data);
    }
    
    public void addConsumer(Consumer<SensorData> consumer) {
        disposables.add(
            dataProcessor
                .onBackpressureBuffer(100)
                .subscribe(consumer::accept)
        );
    }
    
    private void processData(SensorData data) {
        // 复杂的数据处理逻辑
    }
}

// 2. 事件总线(支持背压)
public class BackpressureEventBus {
    private final PublishProcessor<Event> eventProcessor = PublishProcessor.create();
    
    public void postEvent(Event event) {
        eventProcessor.onNext(event);
    }
    
    public Flowable<Event> getEventStream() {
        return eventProcessor
            .onBackpressureBuffer(1000)
            .filter(event -> event != null)
            .share();  // 多播
    }
    
    public Flowable<Event> getEventStream(String eventType) {
        return getEventStream()
            .filter(event -> event.getType().equals(eventType));
    }
}

// 3. 消息队列桥接
public class MessageQueueBridge {
    private final PublishProcessor<Message> messageProcessor = PublishProcessor.create();
    private final MessageQueue queue;
    
    public MessageQueueBridge(MessageQueue queue) {
        this.queue = queue;
        
        // 从队列消费
        Flowable.generate(
            () -> queue.createConsumer(),
            (consumer, emitter) -> {
                Message msg = consumer.receive(1000, TimeUnit.MILLISECONDS);
                if (msg != null) {
                    emitter.onNext(msg);
                } else {
                    emitter.onComplete();
                }
            },
            Consumer::close
        )
        .subscribe(messageProcessor);
    }
    
    public void sendMessage(Message message) {
        messageProcessor.onNext(message);
    }
    
    public Flowable<Message> getMessageStream() {
        return messageProcessor
            .onBackpressureBuffer(100)
            .doOnNext(msg -> queue.acknowledge(msg.getId()));
    }
}

三、BehaviorProcessor(行为处理器)

3.1、核心特性

  • 类似 BehaviorSubject,但支持背压
  • 发射最近的一个值和后续值
  • 需要初始值(或默认值)
  • 支持背压策略
java 复制代码
@Test
public void testBehaviorProcessor() {
    //基本用法
    BehaviorProcessor<String> processor = BehaviorProcessor.createDefault("Initial");

    //订阅者1
    processor.subscribe(data -> System.out.println("Subscriber 1:" + data));

    processor.onNext("Update 1");
    processor.onNext("Update 2");

    //订阅者2(立即受到最近的值update 2)
    processor.subscribe(data -> System.out.println("Subscriber 2:" + data));

    processor.onNext("Update 3");
    processor.onComplete();

    //获取当前值
    String currentValue = processor.getValue();
}

Subscriber 1:Initial
Subscriber 1:Update 1
Subscriber 1:Update 2
Subscriber 2:Update 2
Subscriber 1:Update 3
Subscriber 2:Update 3

背压处理示例:

java 复制代码
public class BehaviorProcessorBackpressure {
    
    public static void demoWithBackpressure() {
        BehaviorProcessor<Integer> processor = BehaviorProcessor.create();
        
        // 快速生产者
        Flowable.interval(1, TimeUnit.MILLISECONDS)
            .take(10000)
            .map(Long::intValue)
            .subscribe(processor);
        
        // 慢速消费者
        processor
            .onBackpressureBuffer(
                100,
                () -> System.out.println("Buffer overflow"),
                BackpressureOverflowStrategy.DROP_OLDEST
            )
            .observeOn(Schedulers.io(), false, 16)
            .subscribe(
                value -> {
                    Thread.sleep(10);  // 慢速处理
                    System.out.println("Processed: " + value);
                }
            );
    }
    
    // 状态管理示例
    public static class StateManager<T> {
        private final BehaviorProcessor<T> stateProcessor;
        private final Flowable<T> stateStream;
        
        public StateManager(T initialState) {
            this.stateProcessor = BehaviorProcessor.createDefault(initialState);
            this.stateStream = stateProcessor
                .onBackpressureLatest()  // 状态只需要最新值
                .distinctUntilChanged()
                .share();
        }
        
        public void setState(T newState) {
            stateProcessor.onNext(newState);
        }
        
        public Flowable<T> getStateStream() {
            return stateStream;
        }
        
        public T getCurrentState() {
            return stateProcessor.getValue();
        }
        
        public boolean hasState() {
            return stateProcessor.hasValue();
        }
    }
}

3.2、使用场景

java 复制代码
// 1. 应用状态管理
public class AppStateManager {
    private final BehaviorProcessor<AppState> stateProcessor = 
        BehaviorProcessor.createDefault(AppState.IDLE);
    private final Flowable<AppState> stateStream;
    
    public AppStateManager() {
        this.stateStream = stateProcessor
            .onBackpressureLatest()
            .distinctUntilChanged()
            .share();
    }
    
    public void transitionTo(AppState newState) {
        if (isValidTransition(stateProcessor.getValue(), newState)) {
            stateProcessor.onNext(newState);
        } else {
            throw new IllegalStateException("Invalid state transition");
        }
    }
    
    public Flowable<AppState> getStateStream() {
        return stateStream;
    }
    
    public AppState getCurrentState() {
        return stateProcessor.getValue();
    }
    
    public Flowable<Boolean> isState(AppState targetState) {
        return stateStream.map(state -> state == targetState);
    }
}

// 2. 配置管理
public class ConfigManager {
    private final BehaviorProcessor<Config> configProcessor = 
        BehaviorProcessor.create();
    private final Flowable<Config> configStream;
    
    public ConfigManager(Config initialConfig) {
        configProcessor.onNext(initialConfig);
        this.configStream = configProcessor
            .onBackpressureLatest()
            .share();
    }
    
    public void updateConfig(Config newConfig) {
        configProcessor.onNext(newConfig);
    }
    
    public Flowable<Config> getConfigStream() {
        return configStream;
    }
    
    public Config getCurrentConfig() {
        return configProcessor.getValue();
    }
    
    public <T> Flowable<T> observeConfigValue(
        Function<Config, T> extractor, 
        Class<T> type
    ) {
        return configStream
            .map(extractor)
            .distinctUntilChanged()
            .filter(Objects::nonNull);
    }
}

// 3. 实时价格更新
public class StockPriceFeed {
    private final BehaviorProcessor<StockPrice> priceProcessor = 
        BehaviorProcessor.create();
    private final Map<String, Flowable<Double>> symbolStreams = new ConcurrentHashMap<>();
    
    public void updatePrice(String symbol, double price) {
        priceProcessor.onNext(new StockPrice(symbol, price, System.currentTimeMillis()));
    }
    
    public Flowable<Double> getPriceStream(String symbol) {
        return symbolStreams.computeIfAbsent(symbol, sym -> 
            priceProcessor
                .onBackpressureLatest()
                .filter(p -> p.getSymbol().equals(sym))
                .map(StockPrice::getPrice)
                .distinctUntilChanged()
                .share()
        );
    }
    
    public Double getCurrentPrice(String symbol) {
        StockPrice current = priceProcessor.getValue();
        if (current != null && current.getSymbol().equals(symbol)) {
            return current.getPrice();
        }
        return null;
    }
}

四、ReplayProcessor(重放处理器)

4.1、核心特性

  • 类似 ReplaySubject,但支持背压
  • 缓存所有发射的数据
  • 可配置缓冲区大小和时间窗口
  • 新订阅者立即收到所有缓存数据
java 复制代码
@Test
public void testReplayProcessor() throws InterruptedException {
    // 1. 无限缓存
    ReplayProcessor<String> unlimited = ReplayProcessor.create();
    unlimited.onNext("Data 1");
    unlimited.onNext("Data 2");
    unlimited.subscribe(data -> System.out.println("Subscriber: " + data));
    // 输出: Data 1, Data 2

    // 2. 限制缓存大小
    ReplayProcessor<Integer> sizeLimited = ReplayProcessor.create(2);
    sizeLimited.onNext(1);
    sizeLimited.onNext(2);
    sizeLimited.onNext(3);  // 1被丢弃
    sizeLimited.subscribe(System.out::println);
    // 输出: 2, 3

    // 3. 限制时间窗口
    ReplayProcessor<String> timeLimited = ReplayProcessor.createWithTime(
            1, TimeUnit.SECONDS, Schedulers.computation()
    );
    timeLimited.onNext("Data 1");
    Thread.sleep(500);
    timeLimited.onNext("Data 2");
    Thread.sleep(600);  // Data 1过期
    timeLimited.subscribe(System.out::println);
    // 输出: Data 2

    // 4. 限制大小和时间
    ReplayProcessor<String> bounded = ReplayProcessor.createWithTimeAndSize(
            1, TimeUnit.SECONDS,  // 时间窗口
            Schedulers.computation(),                  // 最大缓存数量
            100
    );
}


Subscriber: Data 1
Subscriber: Data 2
1
2
3
Data 2

背压处理示例:

java 复制代码
public class ReplayProcessorBackpressure {
    
    public static void demoWithBackpressure() {
        // 创建有界重放处理器
        ReplayProcessor<Integer> processor = ReplayProcessor.createWithSize(1000);
        
        // 快速生产者
        Flowable.interval(1, TimeUnit.MILLISECONDS)
            .take(10000)
            .map(Long::intValue)
            .subscribe(processor);
        
        // 慢速消费者
        processor
            .onBackpressureBuffer(100)
            .observeOn(Schedulers.io(), false, 16)
            .subscribe(
                value -> {
                    Thread.sleep(10);
                    System.out.println("Processed: " + value);
                }
            );
        
        // 新订阅者会收到最近1000个值
        Thread.sleep(1000);
        processor.subscribe(value -> 
            System.out.println("New subscriber: " + value)
        );
    }
    
    // 历史数据查询
    public static class HistoricalDataStore<T> {
        private final ReplayProcessor<T> dataProcessor;
        private final Flowable<T> liveStream;
        
        public HistoricalDataStore(int historySize) {
            this.dataProcessor = ReplayProcessor.createWithSize(historySize);
            this.liveStream = dataProcessor
                .onBackpressureBuffer(100)
                .share();
        }
        
        public void addData(T data) {
            dataProcessor.onNext(data);
        }
        
        public Flowable<T> getLiveStream() {
            return liveStream;
        }
        
        public Flowable<T> getHistory() {
            return dataProcessor.take(dataProcessor.getBufferSize());
        }
        
        public Flowable<T> getRecentData(int count) {
            return dataProcessor.takeLast(count);
        }
        
        public int getStoredCount() {
            return dataProcessor.getBufferSize();
        }
    }
}

4.2、使用场景

java 复制代码
// 1. 日志记录系统
public class LoggingSystem {
    private final ReplayProcessor<LogEntry> logProcessor = 
        ReplayProcessor.createWithTime(1, TimeUnit.HOURS, Schedulers.io());
    private final Flowable<LogEntry> logStream;
    
    public LoggingSystem() {
        this.logStream = logProcessor
            .onBackpressureBuffer(10000)
            .share();
    }
    
    public void log(Level level, String message, Throwable throwable) {
        LogEntry entry = new LogEntry(
            level, 
            message, 
            System.currentTimeMillis(), 
            Thread.currentThread().getName(),
            throwable
        );
        logProcessor.onNext(entry);
    }
    
    public Flowable<LogEntry> getLogStream() {
        return logStream;
    }
    
    public Flowable<LogEntry> getLogsByLevel(Level level) {
        return logStream.filter(entry -> entry.getLevel() == level);
    }
    
    public Flowable<LogEntry> getRecentLogs(int count) {
        return logProcessor.takeLast(count);
    }
    
    public Flowable<LogEntry> getLogsSince(long timestamp) {
        return logStream.filter(entry -> entry.getTimestamp() >= timestamp);
    }
}

// 2. 传感器数据历史
public class SensorDataRecorder {
    private final ReplayProcessor<SensorData> dataProcessor = 
        ReplayProcessor.createWithTimeAndSize(
            10, TimeUnit.MINUTES,
            10000,
            Schedulers.computation()
        );
    private final Flowable<SensorData> dataStream;
    
    public SensorDataRecorder() {
        this.dataStream = dataProcessor
            .onBackpressureBuffer(1000)
            .share();
    }
    
    public void recordData(SensorData data) {
        dataProcessor.onNext(data);
    }
    
    public Flowable<SensorData> getDataStream() {
        return dataStream;
    }
    
    public Flowable<SensorData> getDataBySensor(String sensorId) {
        return dataStream.filter(data -> data.getSensorId().equals(sensorId));
    }
    
    public Flowable<Double> getAverageValue(String sensorId, long duration, TimeUnit unit) {
        long window = System.currentTimeMillis() - unit.toMillis(duration);
        return dataStream
            .filter(data -> 
                data.getSensorId().equals(sensorId) && 
                data.getTimestamp() >= window
            )
            .map(SensorData::getValue)
            .reduce(0.0, (sum, val) -> sum + val)
            .toFlowable()
            .flatMap(sum -> 
                dataStream
                    .filter(data -> 
                        data.getSensorId().equals(sensorId) && 
                        data.getTimestamp() >= window
                    )
                    .count()
                    .map(count -> count > 0 ? sum / count : 0.0)
            );
    }
}

// 3. 聊天消息历史
public class ChatRoom {
    private final ReplayProcessor<Message> messageProcessor = 
        ReplayProcessor.createWithSize(1000);
    private final Flowable<Message> messageStream;
    
    public ChatRoom() {
        this.messageStream = messageProcessor
            .onBackpressureBuffer(100)
            .share();
    }
    
    public void sendMessage(User sender, String content) {
        Message message = new Message(
            sender,
            content,
            System.currentTimeMillis()
        );
        messageProcessor.onNext(message);
    }
    
    public Flowable<Message> getMessageStream() {
        return messageStream;
    }
    
    public Flowable<Message> getMessagesByUser(User user) {
        return messageStream.filter(msg -> msg.getSender().equals(user));
    }
    
    public void replayHistoryFor(User user, int messageCount) {
        messageProcessor
            .takeLast(messageCount)
            .subscribe(msg -> sendToUser(user, msg));
    }
}

五、UnicastProcessor(单播处理器)

5.1、核心特性

  • 类似 UnicastSubject,但支持背压
  • 只允许一个订阅者
  • 支持缓冲区
  • 如果没有订阅者,会缓存数据直到有订阅者
java 复制代码
// 基本用法
UnicastProcessor<String> processor = UnicastProcessor.create();

// 发射数据(还没有订阅者,数据被缓存)
processor.onNext("Cached 1");
processor.onNext("Cached 2");

// 订阅者(立即收到缓存的数据)
processor.subscribe(
    data -> System.out.println("Received: " + data),
    error -> System.err.println("Error: " + error),
    () -> System.out.println("Completed")
);

processor.onNext("Live 1");
processor.onComplete();

// 输出:
// Received: Cached 1
// Received: Cached 2
// Received: Live 1
// Completed

// 尝试第二个订阅者会抛异常
try {
    processor.subscribe(data -> System.out.println("Second: " + data));
} catch (IllegalStateException e) {
    System.out.println("只能有一个订阅者: " + e.getMessage());
}

背压处理示例:

java 复制代码
public class UnicastProcessorBackpressure {
    
    public static void demoWithBackpressure() {
        // 创建带缓冲区的单播处理器
        UnicastProcessor<Integer> processor = UnicastProcessor.create(100);
        
        // 快速生产者
        Flowable.interval(1, TimeUnit.MILLISECONDS)
            .take(1000)
            .map(Long::intValue)
            .subscribe(processor);
        
        // 慢速消费者
        processor
            .onBackpressureBuffer(50)
            .observeOn(Schedulers.io(), false, 16)
            .subscribe(
                value -> {
                    Thread.sleep(10);
                    System.out.println("Processed: " + value);
                }
            );
    }
    
    // 队列式处理器
    public static class QueueProcessor<T> {
        private final UnicastProcessor<T> processor;
        private final Flowable<T> outputStream;
        
        public QueueProcessor(int bufferSize) {
            this.processor = UnicastProcessor.create(bufferSize);
            this.outputStream = processor
                .onBackpressureBuffer(bufferSize)
                .share();
        }
        
        public void enqueue(T item) {
            processor.onNext(item);
        }
        
        public void complete() {
            processor.onComplete();
        }
        
        public void error(Throwable throwable) {
            processor.onError(throwable);
        }
        
        public Flowable<T> getOutputStream() {
            return outputStream;
        }
        
        public boolean hasSubscriber() {
            return processor.hasSubscribers();
        }
        
        public int getBufferSize() {
            return processor.getBufferSize();
        }
    }
}

5.2、使用场景

java 复制代码
// 1. 单消费者任务队列
public class TaskQueue {
    private final UnicastProcessor<Task> taskProcessor = UnicastProcessor.create(1000);
    private final Flowable<Task> taskStream;
    private Disposable workerDisposable;
    
    public TaskQueue() {
        this.taskStream = taskProcessor
            .onBackpressureBuffer(100)
            .observeOn(Schedulers.io(), false, 16);
        
        // 启动工作线程
        this.workerDisposable = taskStream.subscribe(this::processTask);
    }
    
    public void submitTask(Task task) {
        taskProcessor.onNext(task);
    }
    
    public void submitTasks(List<Task> tasks) {
        tasks.forEach(taskProcessor::onNext);
    }
    
    public void shutdown() {
        taskProcessor.onComplete();
        if (workerDisposable != null && !workerDisposable.isDisposed()) {
            workerDisposable.dispose();
        }
    }
    
    public boolean isActive() {
        return workerDisposable != null && !workerDisposable.isDisposed();
    }
    
    private void processTask(Task task) {
        try {
            task.execute();
        } catch (Exception e) {
            System.err.println("Task failed: " + e.getMessage());
        }
    }
}

// 2. 串行写入器
public class SerialWriter {
    private final UnicastProcessor<byte[]> writeProcessor = UnicastProcessor.create(100);
    private final OutputStream outputStream;
    private Disposable writeDisposable;
    
    public SerialWriter(OutputStream outputStream) {
        this.outputStream = outputStream;
        
        this.writeDisposable = writeProcessor
            .onBackpressureBuffer(50)
            .observeOn(Schedulers.io())
            .subscribe(
                data -> {
                    synchronized (outputStream) {
                        outputStream.write(data);
                        outputStream.flush();
                    }
                },
                error -> {
                    try {
                        outputStream.close();
                    } catch (IOException e) {
                        error.addSuppressed(e);
                    }
                },
                () -> {
                    try {
                        outputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            );
    }
    
    public void write(byte[] data) {
        writeProcessor.onNext(data);
    }
    
    public void close() {
        writeProcessor.onComplete();
        if (writeDisposable != null && !writeDisposable.isDisposed()) {
            writeDisposable.dispose();
        }
    }
    
    public boolean isClosed() {
        return writeProcessor.hasComplete() || writeProcessor.hasThrowable();
    }
}

// 3. 命令处理器
public class CommandProcessor {
    private final UnicastProcessor<Command> commandProcessor = UnicastProcessor.create(100);
    private final Map<Class<? extends Command>, CommandHandler> handlers = new ConcurrentHashMap<>();
    private Disposable processingDisposable;
    
    public CommandProcessor() {
        this.processingDisposable = commandProcessor
            .onBackpressureBuffer(50)
            .observeOn(Schedulers.io())
            .subscribe(this::processCommand);
    }
    
    public void registerHandler(Class<? extends Command> commandType, CommandHandler handler) {
        handlers.put(commandType, handler);
    }
    
    public void execute(Command command) {
        commandProcessor.onNext(command);
    }
    
    public void shutdown() {
        commandProcessor.onComplete();
        if (processingDisposable != null && !processingDisposable.isDisposed()) {
            processingDisposable.dispose();
        }
    }
    
    private void processCommand(Command command) {
        CommandHandler handler = handlers.get(command.getClass());
        if (handler != null) {
            try {
                handler.handle(command);
            } catch (Exception e) {
                System.err.println("Command execution failed: " + e.getMessage());
            }
        } else {
            System.err.println("No handler for command: " + command.getClass());
        }
    }
}
相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
paopaokaka_luck2 天前
智慧社区综合服务小程序(人脸识别、AI问答、Echarts图形化分析)
前端·javascript·spring boot·spring·数据分析·echarts
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas