《Android 核心组件深度系列 · 第 3 篇 BroadcastReceiver》

《Android 核心组件深度系列 · 第 3 篇 BroadcastReceiver》

如果说 Activity 是「眼睛」,Service 是「心脏」, 那 BroadcastReceiver(广播接收器)就是 Android 系统的「神经系统」。

它负责感知系统或应用间的事件变化: 网络变化、电量变化、应用安装、音乐播放完毕......都离不开它。

今天这篇,我们彻底讲懂------BroadcastReceiver 到底是什么、怎么工作、以及怎么优雅地用。


一、什么是 BroadcastReceiver?

一句话定义:

BroadcastReceiver 是 Android 中的消息接收机制,用于在应用或系统之间传递广播事件。

它可以理解为一个订阅-发布(Pub/Sub)模型

  • 发布者(Sender) :发送广播事件
  • 接收者(Receiver) :监听并响应事件

举个例子

当用户连接 WiFi 时:

  • 系统发送广播:android.net.conn.CONNECTIVITY_CHANGE
  • 你的 App 收到 → 可以弹提示 "网络已连接"

二、广播的分类

类型 说明 是否跨进程
系统广播 由系统发送(如电量、网络)
应用广播 App 内部自定义 否(默认)
有序广播(Ordered) 按优先级逐个接收,可中断
无序广播(Normal) 同时接收,互不影响
本地广播(Local) 仅在当前 App 内发送

三、动态注册 vs 静态注册

3.1 动态注册(代码中注册)

动态注册需要在代码中手动注册和注销,适合需要灵活控制生命周期的场景。

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    private val networkReceiver = NetworkReceiver()

    override fun onStart() {
        super.onStart()
        val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
        
        // Android 14+ 需要指定 flag
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            registerReceiver(networkReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
        } else {
            registerReceiver(networkReceiver, filter)
        }
    }

    override fun onStop() {
        super.onStop()
        unregisterReceiver(networkReceiver)
    }
}

NetworkReceiver 实现:

kotlin 复制代码
class NetworkReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) 
            as ConnectivityManager
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            val network = cm.activeNetwork
            if (network == null) {
                Log.d("NetworkReceiver", "网络已断开")
                return
            }
            
            val capabilities = cm.getNetworkCapabilities(network)
            if (capabilities == null) {
                Log.d("NetworkReceiver", "无法获取网络能力")
                return
            }
            
            when {
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> 
                    Log.d("NetworkReceiver", "WiFi 已连接")
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> 
                    Log.d("NetworkReceiver", "移动网络已连接")
                else -> 
                    Log.d("NetworkReceiver", "其他网络类型")
            }
        } else {
            // Android 6.0 以下的旧方法
            @Suppress("DEPRECATION")
            val networkInfo = cm.activeNetworkInfo
            if (networkInfo?.isConnected == true) {
                Log.d("NetworkReceiver", "网络已连接:${networkInfo.typeName}")
            } else {
                Log.d("NetworkReceiver", "网络已断开")
            }
        }
    }
}

特点:

  • App 运行时才能接收
  • 适合 UI 相关场景
  • 必须在对应的生命周期方法中注销,否则会内存泄漏

3.2 静态注册(写在 Manifest)

静态注册可以让 App 在未启动时也能接收广播,但 Android 8.0 以后对此做了严格限制。

xml 复制代码
<manifest>
    <!-- 需要的权限 -->
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    
    <application>
        <!-- Android 12+ 必须显式声明 exported -->
        <receiver 
            android:name=".BootReceiver"
            android:enabled="true"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
    </application>
</manifest>

BootReceiver 实现:

kotlin 复制代码
class BootReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
            Log.d("BootReceiver", "设备启动完成")
            // 可以启动你的后台服务
            // context.startForegroundService(Intent(context, YourService::class.java))
        }
    }
}

特点:

  • App 未启动也能接收(Android 8.0 以前)
  • Android 8.0+ 对大多数隐式广播做了限制
  • 仍可接收的系统广播包括:BOOT_COMPLETEDLOCALE_CHANGEDTIMEZONE_CHANGED

3.3 Android 版本适配重点

Android 8.0(API 26):

  • 禁止在 Manifest 中静态注册大部分隐式广播
  • 解决方案:改用动态注册

Android 12(API 31):

  • 必须显式声明 android:exported 属性
  • 否则会导致 App 安装失败

Android 13(API 33):

  • 如果在 Receiver 中发送通知,需要申请 POST_NOTIFICATIONS 权限
kotlin 复制代码
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    if (ContextCompat.checkSelfPermission(
            context,
            Manifest.permission.POST_NOTIFICATIONS
        ) != PackageManager.PERMISSION_GRANTED
    ) {
        // 在 Activity 中请求权限
        ActivityCompat.requestPermissions(
            activity,
            arrayOf(Manifest.permission.POST_NOTIFICATIONS),
            REQUEST_CODE_NOTIFICATION
        )
    }
}

Android 14(API 34):

  • 动态注册时必须指定 RECEIVER_EXPORTEDRECEIVER_NOT_EXPORTED flag

四、自定义广播实战

4.1 普通广播(无序广播)

定义接收器:

kotlin 复制代码
class MyReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val msg = intent.getStringExtra("msg")
        Log.d("MyReceiver", "收到广播:$msg")
        Toast.makeText(context, "收到广播:$msg", Toast.LENGTH_SHORT).show()
    }
}

Manifest 注册:

ini 复制代码
<receiver 
    android:name=".MyReceiver"
    android:exported="false">
    <intent-filter>
        <action android:name="com.tony.broadcast.TEST" />
    </intent-filter>
</receiver>

发送广播:

kotlin 复制代码
val intent = Intent("com.tony.broadcast.TEST")
intent.putExtra("msg", "Hello Broadcast!")
sendBroadcast(intent)

4.2 有序广播(Ordered Broadcast)

有序广播允许多个接收器按优先级顺序接收,并且:

  • 高优先级可以修改数据后传递给下一个
  • 高优先级可以中断传递(调用 abortBroadcast()

定义两个接收器:

kotlin 复制代码
class ReceiverA : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Log.d("ReceiverA", "我是高优先级,先收到")
        
        // 修改数据并传递
        setResultData("ReceiverA 处理过的数据")
        
        // 如果取消注释下面这行,ReceiverB 将收不到广播
        // abortBroadcast()
    }
}

class ReceiverB : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val data = getResultData()
        Log.d("ReceiverB", "我是低优先级,收到数据:$data")
    }
}

Manifest 配置(注意 priority):

ini 复制代码
<receiver android:name=".ReceiverA" android:exported="false">
    <intent-filter android:priority="100">
        <action android:name="com.tony.broadcast.ORDERED" />
    </intent-filter>
</receiver>

<receiver android:name=".ReceiverB" android:exported="false">
    <intent-filter android:priority="50">
        <action android:name="com.tony.broadcast.ORDERED" />
    </intent-filter>
</receiver>

发送有序广播:

kotlin 复制代码
val intent = Intent("com.tony.broadcast.ORDERED")
sendOrderedBroadcast(
    intent,
    null,  // receiverPermission
    null,  // resultReceiver(最终接收器)
    null,  // scheduler
    Activity.RESULT_OK,  // initialCode
    "初始数据",  // initialData
    null   // initialExtras
)

执行顺序:

  1. ReceiverA 先收到(priority=100)
  2. ReceiverA 可以修改数据或中断
  3. ReceiverB 后收到(priority=50)

4.3 本地广播替代方案

LocalBroadcastManager 已在 API 33(Android 13)废弃,官方推荐使用以下方案:

方案一:使用 LiveData

kotlin 复制代码
// 定义事件总线
object EventBus {
    private val _event = MutableLiveData<String>()
    val event: LiveData<String> = _event
    
    fun post(msg: String) {
        _event.postValue(msg)
    }
}

// 发送事件
EventBus.post("Hello LiveData!")

// 接收事件
EventBus.event.observe(this) { msg ->
    Log.d("EventBus", "收到:$msg")
}

方案二:使用 SharedFlow(推荐)

kotlin 复制代码
// 定义事件总线
object EventBus {
    private val _events = MutableSharedFlow<String>()
    val events = _events.asSharedFlow()
    
    suspend fun emit(event: String) {
        _events.emit(event)
    }
}

// 发送事件
lifecycleScope.launch {
    EventBus.emit("Hello SharedFlow!")
}

// 接收事件
lifecycleScope.launch {
    EventBus.events.collect { msg ->
        Log.d("EventBus", "收到:$msg")
    }
}

对比:

diff 复制代码
BroadcastReceiver:
- 优点:系统级支持,可跨进程
- 缺点:性能开销大,需要序列化

SharedFlow:
- 优点:轻量级,性能好,支持背压
- 缺点:仅限同一进程

五、安全性最佳实践

5.1 防止广播劫持

恶意 App 可能伪装成你的广播发送者,注入恶意数据。

解决方案:使用自定义权限

定义权限:

xml 复制代码
<manifest>
    <!-- 定义一个签名级权限 -->
    <permission 
        android:name="com.tony.CUSTOM_PERMISSION"
        android:protectionLevel="signature" />
    
    <!-- 声明使用该权限 -->
    <uses-permission android:name="com.tony.CUSTOM_PERMISSION" />
</manifest>

限制接收器:

ini 复制代码
<receiver 
    android:name=".SecureReceiver"
    android:permission="com.tony.CUSTOM_PERMISSION"
    android:exported="true">
    <intent-filter>
        <action android:name="com.tony.SECURE_ACTION" />
    </intent-filter>
</receiver>

限制发送者:

less 复制代码
// 发送时指定权限
sendBroadcast(
    Intent("com.tony.SECURE_ACTION"),
    "com.tony.CUSTOM_PERMISSION"
)

这样,只有具有相同签名的 App 才能发送/接收此广播。


5.2 避免数据泄露

不要在广播中传递敏感信息(密码、token 等),因为:

  • 其他 App 可能监听到(如果 exported=true)
  • 广播内容可能被系统日志记录

错误示例:

scss 复制代码
val intent = Intent("com.tony.LOGIN")
intent.putExtra("password", "123456")  // 危险!
sendBroadcast(intent)

正确做法:

scss 复制代码
// 使用 EncryptedSharedPreferences 或其他加密方案
val sharedPreferences = EncryptedSharedPreferences.create(
    context,
    "secure_prefs",
    masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

// 只通过广播发送通知,敏感数据另外存储
val intent = Intent("com.tony.LOGIN_SUCCESS")
sendBroadcast(intent)

5.3 正确设置 exported 属性

Android 12+ 要求显式声明:

xml 复制代码
<!-- 仅内部使用 -->
<receiver 
    android:name=".InternalReceiver"
    android:exported="false">
    ...
</receiver>

<!-- 需要被其他 App 调用 -->
<receiver 
    android:name=".PublicReceiver"
    android:exported="true"
    android:permission="com.tony.CUSTOM_PERMISSION">
    ...
</receiver>

六、性能优化

6.1 onReceive() 中的时间限制

onReceive() 方法必须在 10 秒内完成,否则会触发 ANR(Application Not Responding)。

错误示例:

kotlin 复制代码
override fun onReceive(context: Context, intent: Intent) {
    // 这会导致 ANR!
    Thread.sleep(15000)
}

正确做法:启动后台任务

kotlin 复制代码
override fun onReceive(context: Context, intent: Intent) {
    // 方案一:使用 WorkManager(推荐)
    val workRequest = OneTimeWorkRequestBuilder<MyWorker>().build()
    WorkManager.getInstance(context).enqueue(workRequest)
    
    // 方案二:使用 goAsync()(适合短时异步任务)
    val pendingResult = goAsync()
    Thread {
        try {
            // 执行异步任务(不超过 10 秒)
            doSomeWork()
        } finally {
            pendingResult.finish()
        }
    }.start()
}

6.2 避免频繁发送广播

问题场景:

scss 复制代码
// 在循环中频繁发送(性能差)
repeat(1000) {
    sendBroadcast(Intent("com.tony.UPDATE"))
}

优化方案:使用 SharedFlow

kotlin 复制代码
object EventBus {
    private val _events = MutableSharedFlow<String>(
        replay = 0,
        extraBufferCapacity = 100,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    val events = _events.asSharedFlow()
    
    fun emit(event: String) {
        _events.tryEmit(event)
    }
}

// 发送方
repeat(1000) {
    EventBus.emit("Update $it")
}

// 接收方
lifecycleScope.launch {
    EventBus.events.collect { event ->
        // 处理事件
    }
}

性能对比:

diff 复制代码
BroadcastReceiver:
- 每次发送都需要跨进程通信(如果 exported=true)
- 需要序列化/反序列化 Intent
- 系统需要查找所有匹配的接收器

SharedFlow:
- 内存操作,无需跨进程
- 无需序列化
- 直接方法调用

七、常见使用场景

7.1 监听网络状态变化

kotlin 复制代码
class NetworkReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) 
            as ConnectivityManager
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            val network = cm.activeNetwork
            val capabilities = cm.getNetworkCapabilities(network)
            
            val isConnected = capabilities?.hasCapability(
                NetworkCapabilities.NET_CAPABILITY_INTERNET
            ) == true
            
            if (isConnected) {
                Toast.makeText(context, "网络已连接", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(context, "网络已断开", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

// 动态注册
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
registerReceiver(networkReceiver, filter)

权限:

ini 复制代码
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

7.2 监听应用安装/卸载

kotlin 复制代码
class AppReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val packageName = intent.data?.schemeSpecificPart
        
        when (intent.action) {
            Intent.ACTION_PACKAGE_ADDED -> {
                Log.d("AppReceiver", "应用已安装:$packageName")
            }
            Intent.ACTION_PACKAGE_REMOVED -> {
                Log.d("AppReceiver", "应用已卸载:$packageName")
            }
        }
    }
}

Manifest 注册:

ini 复制代码
<receiver 
    android:name=".AppReceiver"
    android:exported="false">
    <intent-filter>
        <action android:name="android.intent.action.PACKAGE_ADDED" />
        <action android:name="android.intent.action.PACKAGE_REMOVED" />
        <data android:scheme="package" />
    </intent-filter>
</receiver>

7.3 监听电池电量

注意:ACTION_BATTERY_CHANGED 是特殊广播,只能动态注册

kotlin 复制代码
class BatteryReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
        val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
        val batteryPct = (level / scale.toFloat() * 100).toInt()
        
        val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
        val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING
        
        Log.d("BatteryReceiver", "电量:$batteryPct%,充电中:$isCharging")
    }
}

// 必须动态注册
val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
registerReceiver(batteryReceiver, filter)

八、常见问题与避坑指南

问题 1:收不到系统广播

原因:

  • Android 8.0+ 限制了静态注册
  • 广播已被系统弃用
  • 缺少必要权限

解决方案:

kotlin 复制代码
// 1. 改用动态注册
override fun onStart() {
    super.onStart()
    val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
        registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED)
    } else {
        registerReceiver(receiver, filter)
    }
}

// 2. 检查权限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

// 3. 使用替代 API(如 ConnectivityManager.NetworkCallback)
val networkCallback = object : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network: Network) {
        Log.d("Network", "网络已连接")
    }
    
    override fun onLost(network: Network) {
        Log.d("Network", "网络已断开")
    }
}

connectivityManager.registerDefaultNetworkCallback(networkCallback)

问题 2:App 未启动收不到广播

原因:

  • 使用了动态注册(必须 App 运行才能注册)
  • Android 8.0+ 限制了静态注册

解决方案:

xml 复制代码
<!-- 使用允许静态注册的系统广播 -->
<receiver android:name=".BootReceiver" android:exported="false">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>
kotlin 复制代码
class BootReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        // App 启动后可以启动服务、设置闹钟等
        val serviceIntent = Intent(context, MyService::class.java)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(serviceIntent)
        } else {
            context.startService(serviceIntent)
        }
    }
}

问题 3:内存泄漏

原因: 忘记调用 unregisterReceiver()

错误示例:

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    private val receiver = MyReceiver()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        registerReceiver(receiver, IntentFilter("com.tony.TEST"))
        // 忘记注销!
    }
}

正确做法:

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    private val receiver = MyReceiver()
    private var isReceiverRegistered = false
    
    override fun onStart() {
        super.onStart()
        if (!isReceiverRegistered) {
            registerReceiver(receiver, IntentFilter("com.tony.TEST"))
            isReceiverRegistered = true
        }
    }
    
    override fun onStop() {
        super.onStop()
        if (isReceiverRegistered) {
            unregisterReceiver(receiver)
            isReceiverRegistered = false
        }
    }
}

问题 4:onReceive() 不执行

排查步骤:

  1. 检查 Action 是否匹配:
scss 复制代码
// 发送
sendBroadcast(Intent("com.tony.TEST"))

// 接收(必须完全一致)
<intent-filter>
    <action android:name="com.tony.TEST" />
</intent-filter>
  1. 检查 exported 设置:
xml 复制代码
<!-- 如果是自定义广播且不跨进程,设为 false -->
<receiver 
    android:name=".MyReceiver"
    android:exported="false">
    ...
</receiver>
  1. 检查权限:
xml 复制代码
<!-- 如果接收器设置了权限要求 -->
<receiver 
    android:name=".MyReceiver"
    android:permission="com.tony.PERMISSION"
    android:exported="true">
    ...
</receiver>

<!-- 发送方必须声明该权限 -->
<uses-permission android:name="com.tony.PERMISSION" />
  1. 添加日志调试:
kotlin 复制代码
override fun onReceive(context: Context, intent: Intent) {
    Log.d("MyReceiver", "onReceive called")
    Log.d("MyReceiver", "Action: ${intent.action}")
    Log.d("MyReceiver", "Extras: ${intent.extras}")
}

问题 5:触发 ANR

原因: onReceive() 执行时间超过 10 秒

解决方案:

kotlin 复制代码
override fun onReceive(context: Context, intent: Intent) {
    // 方案一:使用 goAsync()(适合 10 秒内能完成的任务)
    val pendingResult = goAsync()
    
    CoroutineScope(Dispatchers.IO).launch {
        try {
            // 执行异步任务
            val result = doNetworkRequest()
            Log.d("Receiver", "Result: $result")
        } finally {
            pendingResult.finish()
        }
    }
    
    // 方案二:使用 WorkManager(推荐,适合长时间任务)
    val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
        .setInputData(workDataOf("key" to "value"))
        .build()
    WorkManager.getInstance(context).enqueue(workRequest)
}

九、总结

BroadcastReceiver 是 Android 世界的"信号员"。 它让应用之间、系统与应用之间得以沟通。

核心要点回顾:

  1. 注册方式选择

    • 需要 App 未启动时接收 → 静态注册(但受 Android 8.0+ 限制)
    • 灵活控制生命周期 → 动态注册
  2. 安全性

    • 使用自定义权限防止劫持
    • 不在广播中传递敏感信息
    • 正确设置 exported 属性
  3. 性能优化

    • onReceive() 必须 10 秒内完成
    • 避免频繁发送广播
    • 考虑用 SharedFlow/LiveData 替代本地广播
  4. 版本适配

    • Android 8.0:静态注册限制
    • Android 12:必须声明 exported
    • Android 13:通知权限
    • Android 14:动态注册 flag

什么时候用 BroadcastReceiver?

  • 需要响应系统事件(开机、网络变化等)
  • 需要跨进程通信
  • 需要一对多的消息分发

什么时候不用?

  • 仅在同一 App 内通信 → 用 SharedFlow/LiveData
  • 需要高频通信 → 用 EventBus 或响应式框架
  • 需要持久化数据 → 用数据库或文件

十、互动与扩展

你在使用 BroadcastReceiver 时踩过哪些坑?

常见问题:

  • "收不到广播"
  • "onReceive() 不执行"
  • "App 安装失败(Android 12+)"
  • "触发 ANR"

欢迎在评论区分享你的经验和解决方案。

系列文章:

  • 《Android 核心组件深度系列 · 第 1 篇 Activity》
  • 《Android 核心组件深度系列 · 第 2 篇 Service》
  • 《Android 核心组件深度系列 · 第 3 篇 BroadcastReceiver》(本篇)
  • 《Android 核心组件深度系列 · 第 4 篇 ContentProvider》(敬请期待)
相关推荐
用户17345666963473 小时前
Android 日志库:高性能压缩加密日志系统
android
恋猫de小郭3 小时前
React 和 React Native 不再直接归属 Meta,React 基金会成立
android·前端·ios
bst@微胖子4 小时前
鸿蒙实现滴滴出行项目之侧边抽屉栏以及权限以及搜索定位功能
android·华为·harmonyos
zcz16071278214 小时前
Docker Compose 搭建 LNMP 环境并部署 WordPress 论坛
android·adb·docker
fundroid13 小时前
Android Studio 新功能 Journey Test:借助 AI 实现基于自然语言的 UI 测试用例编写
android studio
Pika14 小时前
深入浅出 Compose 测量机制
android·android jetpack·composer
木易 士心19 小时前
MPAndroidChart 用法解析和性能优化 - Kotlin & Java 双版本
android·java·kotlin
消失的旧时光-194319 小时前
Kotlin Flow 与“天然背压”(完整示例)
android·开发语言·kotlin
ClassOps19 小时前
Kotlin invoke 函数调用重载
android·开发语言·kotlin