Android 四大组件——BroadcastReceiver(广播)

目录

一、广播接收器概述

[1. 是什么?](#1. 是什么?)

[2. 核心设计思想:发布-订阅模式](#2. 核心设计思想:发布-订阅模式)

[3. 主要用途](#3. 主要用途)

二、广播的工作原理

三、广播的类型

[1. 标准广播](#1. 标准广播)

[2. 有序广播](#2. 有序广播)

[2.1 在 AndroidManifest.xml 中注册,并设置优先级:](#2.1 在 AndroidManifest.xml 中注册,并设置优先级:)

[2.2 定义三个广播接收器](#2.2 定义三个广播接收器)

[2.3 在 Activity 或 Service 中发送有序广播:](#2.3 在 Activity 或 Service 中发送有序广播:)

[2.4 结果](#2.4 结果)

[四、 广播接收器的注册方式](#四、 广播接收器的注册方式)

[1. 静态注册](#1. 静态注册)

[2. 动态注册](#2. 动态注册)

[五、 本地广播(应用内广播)](#五、 本地广播(应用内广播))

[六、 安全考量](#六、 安全考量)

[七、 最佳实践与注意事项](#七、 最佳实践与注意事项)

八、常用的系统广播


我们来对 Android 四大组件之一的 BroadcastReceiver(广播接收器) 进行一次全面而深入的解析。本文将涵盖其定义、工作原理、类型、注册方式、使用方法、安全考量、最佳实践以及进化历程。

一、广播接收器概述

1. 是什么?

BroadcastReceiver(广播接收器)是一个用于响应系统范围或应用内广播消息 的组件。它充当应用程序与系统事件或其他应用程序事件之间的"事件监听器"。

可以把它理解为一种全局的监听器,它不包含用户界面,但在后台工作,当它感兴趣的特定事件(广播)发生时,就会被系统激活并执行相应的代码。

2. 核心设计思想:发布-订阅模式

广播机制完美体现了发布-订阅模式:

  • 发布者: 发送一个广播Intent

  • 订阅者: 注册一个 BroadcastReceiver来监听特定类型的广播。

  • 系统(作为中介): 当有广播发出时,系统会寻找所有订阅了该广播的接收器,并调用它们的**onReceive()** 方法。

3. 主要用途

  1. 监听系统状态变化: 如开机完成、电量低、网络状态改变、屏幕开关、时区变化等。

  2. 应用内组件通信: 在不同 Activity、Service 等组件之间传递消息,实现解耦。

  3. 应用间通信: 一个应用可以发送广播,让其他应用(如果它们注册了相应的接收器)收到并处理。

二、广播的工作原理

  1. 发送广播: 某个组件(如 Activity、Service 或其他 Receiver)使用 Intent 对象,通过 Context.sendBroadcast() 等方法发送广播。

  2. 系统匹配: Android 系统接收到广播 Intent 后,会根据 Intent 中的信息(如 Action、Category 等)去查找所有已注册 并且与该 Intent 匹配的 BroadcastReceiver

  3. 实例化与执行: 系统会实例化匹配的 Receiver(如果它还没运行),并调用其 onReceive(Context context, Intent intent) 方法。

  4. 生命周期: Receiver 对象的生命周期非常短暂,仅在 onReceive() 方法执行期间存活。方法执行完毕后,该实例就会被销毁

重要限制:onReceive()不能执行异步操作,也不能开启一个长时间运行的后台线程。因为一旦 onReceive() 返回,进程可能随时被系统杀死。如果需要进行耗时操作,必须使用 goAsync() 或启动一个 JobIntentService / JobScheduler

三、广播的类型

1. 标准广播

  • 特点: 完全异步。发送后,所有匹配的接收器几乎同时收到广播,它们之间没有顺序,也无法中止广播。

  • 发送方法: sendBroadcast(Intent)

2. 有序广播

  • 特点: 同步执行。发送后,同一时间只有一个接收器能收到广播。该接收器执行完后,可以将结果传递给下一个接收器 ,也可以完全中止广播,使其不再继续传播。

  • 发送方法: sendOrderedBroadcast(Intent, String)

  • 优先级: 通过在注册时设置 android:priority 属性来决定接收顺序,数值越大优先级越高(范围:-1000 到 1000)。

  • 结果传播: 可以使用 **setResultExtras(Bundle)**存放数据,下一个接收器可以用 getResultExtras(true) 获取并修改。

  • 中止广播: abortBroadcast()

接下来通过示例来演示有序广播的流程:这里用一个链式处理的场景,如:权限验证 → 数据验证 → 业务处理 → 结果上报等流程。

2.1 在 AndroidManifest.xml 中注册,并设置优先级:

XML 复制代码
<receiver
    android:name=".PermissionCheckReceiver"
    android:enabled="true"
    android:exported="false">
    <intent-filter android:priority="1000"> <!-- 最高优先级 -->
        <action android:name="com.example.ORDERED_BROADCAST_ACTION" />
    </intent-filter>
</receiver>

<receiver
    android:name=".ContentFilterReceiver"
    android:enabled="true"
    android:exported="false">
    <intent-filter android:priority="500"> <!-- 中等优先级 -->
        <action android:name="com.example.ORDERED_BROADCAST_ACTION" />
    </intent-filter>
</receiver>

<receiver
    android:name=".FinalProcessorReceiver"
    android:enabled="true"
    android:exported="false">
    <intent-filter android:priority="100"> <!-- 最低优先级 -->
        <action android:name="com.example.ORDERED_BROADCAST_ACTION" />
    </intent-filter>
</receiver>

2.2 定义三个广播接收器

java 复制代码
//------------------------PermissionCheckReceiver---------------------------------

public class PermissionCheckReceiver extends BroadcastReceiver {
    private static final String TAG = "PermissionCheck";
    
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "【权限检查】开始执行...");
        
        // 模拟权限检查
        boolean hasPermission = checkUserPermission();
        
        if (!hasPermission) {
            Log.w(TAG, "【权限检查】权限不足,中止广播!");
            abortBroadcast(); // 中止广播,后续接收器不会执行
            return;
        }
        
        // 添加处理结果
        Bundle resultBundle = getResultExtras(true);
        resultBundle.putString("permission_status", "PASS");
        setResultExtras(resultBundle);
        
        Log.d(TAG, "【权限检查】权限验证通过,继续传递");
    }
    
    private boolean checkUserPermission() {
        // 模拟权限检查逻辑,这里简单返回true
        return true;
    }
}



//--------------------------ContentFilterReceiver -----------------------------------

public class ContentFilterReceiver extends BroadcastReceiver {
    private static final String TAG = "ContentFilter";
    
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "【内容过滤】开始执行...");
        
        // 获取上一个接收器的处理结果
        Bundle previousResult = getResultExtras(false);
        String permissionStatus = previousResult != null ? 
            previousResult.getString("permission_status") : "UNKNOWN";
        
        Log.d(TAG, "【内容过滤】接收到权限状态: " + permissionStatus);
        
        // 模拟内容过滤
        String originalMessage = intent.getStringExtra("message");
        String filteredMessage = filterContent(originalMessage);
        
        // 更新结果数据
        Bundle resultBundle = getResultExtras(true);
        resultBundle.putString("filtered_message", filteredMessage);
        resultBundle.putString("content_filter_status", "PROCESSED");
        setResultExtras(resultBundle);
        
        Log.d(TAG, "【内容过滤】内容过滤完成,继续传递");
    }
    
    private String filterContent(String message) {
        // 简单的敏感词过滤
        if (message == null) return "";
        return message.replace("敏感词", "***");
    }
}


//------------------------FinalProcessorReceiver ---------------------------------

public class FinalProcessorReceiver extends BroadcastReceiver {
    private static final String TAG = "FinalProcessor";
    
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "【最终处理】开始执行...");
        
        // 获取前面所有接收器的处理结果
        Bundle finalResult = getResultExtras(false);
        
        String originalMessage = intent.getStringExtra("message");
        String filteredMessage = finalResult != null ? 
            finalResult.getString("filtered_message") : originalMessage;
        String permissionStatus = finalResult != null ? 
            finalResult.getString("permission_status") : "UNKNOWN";
        
        Log.d(TAG, "======= 最终处理结果 =======");
        Log.d(TAG, "原始消息: " + originalMessage);
        Log.d(TAG, "过滤后消息: " + filteredMessage);
        Log.d(TAG, "权限状态: " + permissionStatus);
        Log.d(TAG, "【最终处理】消息处理流程完成");
        
        // 可以在这里更新UI或进行其他操作
        Toast.makeText(context, "消息处理完成: " + filteredMessage, Toast.LENGTH_SHORT).show();
    }
}

2.3 在 Activity 或 Service 中发送有序广播:

java 复制代码
public class MainActivity extends AppCompatActivity {
    
    private void sendOrderedBroadcastExample() {
        Log.d("BroadcastSender", "准备发送有序广播...");
        
        // 创建广播Intent
        Intent orderedIntent = new Intent("com.example.ORDERED_BROADCAST_ACTION");
        orderedIntent.putExtra("message", "这是一条包含敏感词的重要消息");
        
        // 发送有序广播
        // 参数说明:
        // 1. Intent
        // 2. 权限字符串(可为null)
        // 3. 最终接收器(可选)
        // 4. Handler(可为null)
        // 5. 初始结果码
        // 6. 初始数据(可为null)
        // 7. 初始Bundle
        sendOrderedBroadcast(
            orderedIntent,
            null, // 权限
            new FinalProcessorReceiver(), // 可选的最终接收器
            null, // Handler
            Activity.RESULT_OK,
            null, // initialData
            null  // initialExtras
        );
        
        Log.d("BroadcastSender", "有序广播已发送");
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button sendButton = findViewById(R.id.send_button);
        sendButton.setOnClickListener(v -> sendOrderedBroadcastExample());
    }
}

详细解释下sendOrderedBroadcast:

java 复制代码
public abstract void sendOrderedBroadcast(
    //要发送的广播Intent,包含action、data、category等信息。
    Intent intent, 

    //接收器必须拥有的权限。只有声明了该权限的接收器才能接收此广播。
    //如:String permission = "com.example.MY_PERMISSION";
    //String systemPermission = android.Manifest.permission.INTERNET;
    String receiverPermission, 

    //最终接收器。无论广播是否被中止,这个接收器都会最后执行。
    //即使前面的接收器调用了 abortBroadcast(),最终接收器仍会执行
    BroadcastReceiver resultReceiver, 

    //指定处理广播的Handler。控制 onReceive() 方法在哪个线程执行。
    //如果为 null:在主线程执行
    Handler scheduler, 

    //初始结果码。接收器可以通过 setResultCode(int code) 修改这个值。
    int initialCode, 

    //初始结果数据。接收器可以通过 setResultData(String data) 修改这个值。
    String initialData, 

    //初始附加数据。接收器可以通过 getResultExtras() 和 setResultExtras() 访问和修改。
    Bundle initialExtras
)

2.4 结果

java 复制代码
BroadcastSender: 准备发送有序广播...
BroadcastSender: 有序广播已发送
PermissionCheck: 【权限检查】开始执行...
PermissionCheck: 【权限检查】权限验证通过,继续传递
ContentFilter: 【内容过滤】开始执行...
ContentFilter: 【内容过滤】接收到权限状态: PASS
ContentFilter: 【内容过滤】内容过滤完成,继续传递
FinalProcessor: 【最终处理】开始执行...
FinalProcessor: ======= 最终处理结果 =======
FinalProcessor: 原始消息: 这是一条包含敏感词的重要消息
FinalProcessor: 过滤后消息: 这是一条包含***的重要消息
FinalProcessor: 权限状态: PASS
FinalProcessor: 【最终处理】消息处理流程完成

四、 广播接收器的注册方式

有两种方式将 BroadcastReceiver 注册到系统中,使其开始监听。

1. 静态注册

AndroidManifest.xml 文件中进行声明。

XML 复制代码
<receiver
    android:name=".MyBootCompletedReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <!-- 监听系统启动完成广播 -->
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>
  • 特点:

    • 应用安装后即注册,即使应用未启动,Receiver 也能被激活(系统会先启动你的应用进程)。

    • 常用于监听系统事件(如开机启动)。

    • 从 Android 8.0 (API 26) 开始,大部分隐式广播不能再使用静态注册 ,以防止应用过度消耗资源。BOOT_COMPLETED 是少数例外之一。

2. 动态注册

在代码中(如 Activity 或 Service 中)通过调用 **registerReceiver()**方法进行注册。

java 复制代码
public class MainActivity extends AppCompatActivity {
    private MyNetworkReceiver networkReceiver;

    @Override
    protected void onResume() {
        super.onResume();
        // 1. 创建Receiver实例
        networkReceiver = new MyNetworkReceiver();
        // 2. 创建IntentFilter
        IntentFilter filter = new IntentFilter();
        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        // 3. 注册Receiver
        registerReceiver(networkReceiver, filter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 4. 必须反注册!防止内存泄漏和不必要的系统开销
        if (networkReceiver != null) {
            unregisterReceiver(networkReceiver);
            networkReceiver = null;
        }
    }
}

特点:

  • 注册与组件(如 Activity)生命周期绑定,灵活性强。

  • 必须在合适的时机(如 onPause()onDestroy())调用 unregisterReceiver() 进行反注册

  • 不受 Android 8.0 隐式广播限制的影响,可以监听任何广播

五、 本地广播(应用内广播)

为了安全和效率,如果广播只在应用内部使用,强烈推荐使用 LocalBroadcastManager (现已并入 AndroidX)或类似的机制。本地广播无法通过静态注册方式来接受,相比起系统全局广播更加高效

  • 优点:

    1. 安全: 数据不会泄露给其他应用,其他应用也无法向你发送广播

    2. 高效: 不需要系统介入,进程内通信,开销小

    3. 便捷: 不需要担心 Android 8.0 的静态注册限制。

  • 使用方法:

java 复制代码
// 1. 获取LocalBroadcastManager实例
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this);

// 2. 注册接收器(动态注册方式)
localBroadcastManager.registerReceiver(myLocalReceiver, new IntentFilter("LOCAL_ACTION"));

// 3. 发送广播
Intent localIntent = new Intent("LOCAL_ACTION");
localIntent.putExtra("data", "Local message");
localBroadcastManager.sendBroadcast(localIntent);

// 4. 反注册
localBroadcastManager.unregisterReceiver(myLocalReceiver);

六、 安全考量

广播是跨进程通信的,因此安全性至关重要。

  1. 权限:

    • 在发送时指定权限: sendBroadcast(Intent, String)。只有拥有该权限的接收器才能收到广播。

    • 在注册时指定权限: 在静态注册的 <receiver> 标签或动态注册的 registerReceiver() 方法中指定权限。只有拥有该权限的发送者发出的广播才能被接收。

  2. android:exported 属性:

    • 在静态注册中,此属性决定该 Receiver 是否能接收来自其他应用的广播。

    • false:仅接收同一应用内或同一用户ID下的组件发出的广播。

    • true可以接收来自任何应用的广播

    • 最佳实践: 如果 Receiver 不需要接收外部广播,显式设置为 false

  3. 避免敏感信息: 不要在隐式广播的 Intent 中携带敏感数据,因为任何注册了该广播的应用都能收到。

七、 最佳实践与注意事项

  • onReceive() 中不要做耗时操作: 重申这一点。如果需要,请使用 goAsync() 获取一个 PendingResult 对象,然后在后台线程中完成工作并调用 finish()
java 复制代码
@Override
public void onReceive(Context context, Intent intent) {
    final PendingResult pendingResult = goAsync();
    AsyncTask<String, Integer, String> asyncTask = new AsyncTask<String, Integer, String>() {
        @Override
        protected String doInBackground(String... params) {
            // 在这里执行耗时操作
            return "Done";
        }
        @Override
        protected void onPostExecute(String s) {
            // 必须调用finish()
            pendingResult.finish();
        }
    };
    asyncTask.execute();
}
  • 精确的 IntentFilter: 尽量使用更具体的 Action,避免使用过于宽泛的 Filter,以减少不必要的激活和资源浪费。

  • 生命周期管理: 动态注册一定要配对反注册。

  • 优先使用 LocalBroadcastManager: 对于应用内通信,它是首选。

推荐替代方案:

  • JobScheduler / WorkManager 用于调度后台任务,是执行延迟、异步任务的首选。

  • Foreground Service + 通知: 用于需要用户感知的长时间运行任务。

  • LocalBroadcastManager 用于应用内通信。

  • 第三方事件总线: 如 EventBus、RxJava,用于组件间解耦通信,但它们只在进程内有效。

八、常用的系统广播

最后给大家提供下我们平常可能会用到的一些系统广播:

java 复制代码
intent.action.AIRPLANE_MODE;
//关闭或打开飞行模式时的广播

Intent.ACTION_BATTERY_CHANGED;
//充电状态,或者电池的电量发生变化
//电池的充电状态、电荷级别改变,不能通过组建声明接收这个广播,只有通过Context.registerReceiver()注册

Intent.ACTION_BATTERY_LOW;
//表示电池电量低

Intent.ACTION_BATTERY_OKAY;
//表示电池电量充足,即从电池电量低变化到饱满时会发出广播

Intent.ACTION_BOOT_COMPLETED;
//在系统启动完成后,这个动作被广播一次(只有一次)。

Intent.ACTION_CAMERA_BUTTON;
//按下照相时的拍照按键(硬件按键)时发出的广播

Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
//当屏幕超时进行锁屏时,当用户按下电源按钮,长按或短按(不管有没跳出话框),进行锁屏时,android系统都会广播此Action消息

Intent.ACTION_CONFIGURATION_CHANGED;
//设备当前设置被改变时发出的广播(包括的改变:界面语言,设备方向,等,请参考Configuration.java)

Intent.ACTION_DATE_CHANGED;
//设备日期发生改变时会发出此广播

Intent.ACTION_DEVICE_STORAGE_LOW;
//设备内存不足时发出的广播,此广播只能由系统使用,其它APP不可用?

Intent.ACTION_DEVICE_STORAGE_OK;
//设备内存从不足到充足时发出的广播,此广播只能由系统使用,其它APP不可用?

Intent.ACTION_DOCK_EVENT;
//
//发出此广播的地方frameworks\base\services\java\com\android\server\DockObserver.java

Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE;
////移动APP完成之后,发出的广播(移动是指:APP2SD)

Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
//正在移动APP时,发出的广播(移动是指:APP2SD)

Intent.ACTION_GTALK_SERVICE_CONNECTED;
//Gtalk已建立连接时发出的广播

Intent.ACTION_GTALK_SERVICE_DISCONNECTED;
//Gtalk已断开连接时发出的广播

Intent.ACTION_HEADSET_PLUG;
//在耳机口上插入耳机时发出的广播

Intent.ACTION_INPUT_METHOD_CHANGED;
//改变输入法时发出的广播

Intent.ACTION_LOCALE_CHANGED;
//设备当前区域设置已更改时发出的广播

Intent.ACTION_MANAGE_PACKAGE_STORAGE;
//

Intent.ACTION_MEDIA_BAD_REMOVAL;
//未正确移除SD卡(正确移除SD卡的方法:设置--SD卡和设备内存--卸载SD卡),但已把SD卡取出来时发出的广播
//广播:扩展介质(扩展卡)已经从 SD 卡插槽拔出,但是挂载点 (mount point) 还没解除 (unmount)

Intent.ACTION_MEDIA_BUTTON;
//按下"Media Button" 按键时发出的广播,假如有"Media Button" 按键的话(硬件按键)

Intent.ACTION_MEDIA_CHECKING;
//插入外部储存装置,比如SD卡时,系统会检验SD卡,此时发出的广播?
Intent.ACTION_MEDIA_EJECT;
//已拔掉外部大容量储存设备发出的广播(比如SD卡,或移动硬盘),不管有没有正确卸载都会发出此广播?
//广播:用户想要移除扩展介质(拔掉扩展卡)。
Intent.ACTION_MEDIA_MOUNTED;
//插入SD卡并且已正确安装(识别)时发出的广播
//广播:扩展介质被插入,而且已经被挂载。
Intent.ACTION_MEDIA_NOFS;
//
Intent.ACTION_MEDIA_REMOVED;
//外部储存设备已被移除,不管有没正确卸载,都会发出此广播?
// 广播:扩展介质被移除。
Intent.ACTION_MEDIA_SCANNER_FINISHED;
//广播:已经扫描完介质的一个目录
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE;
//
Intent.ACTION_MEDIA_SCANNER_STARTED;
//广播:开始扫描介质的一个目录

Intent.ACTION_MEDIA_SHARED;
// 广播:扩展介质的挂载被解除 (unmount),因为它已经作为 USB 大容量存储被共享。
 Intent.ACTION_MEDIA_UNMOUNTABLE;
//
Intent.ACTION_MEDIA_UNMOUNTED
// 广播:扩展介质存在,但是还没有被挂载 (mount)。
Intent.ACTION_NEW_OUTGOING_CALL;

Intent.ACTION_PACKAGE_ADDED;
//成功的安装APK之后
//广播:设备上新安装了一个应用程序包。
//一个新应用包已经安装在设备上,数据包括包名(最新安装的包程序不能接收到这个广播)
 Intent.ACTION_PACKAGE_CHANGED;
//一个已存在的应用程序包已经改变,包括包名
Intent.ACTION_PACKAGE_DATA_CLEARED;
//清除一个应用程序的数据时发出的广播(在设置--应用管理--选中某个应用,之后点清除数据时?)
//用户已经清除一个包的数据,包括包名(清除包程序不能接收到这个广播)

Intent.ACTION_PACKAGE_INSTALL;
//触发一个下载并且完成安装时发出的广播,比如在电子市场里下载应用?
//
Intent.ACTION_PACKAGE_REMOVED;
//成功的删除某个APK之后发出的广播
//一个已存在的应用程序包已经从设备上移除,包括包名(正在被安装的包程序不能接收到这个广播)

Intent.ACTION_PACKAGE_REPLACED;
//替换一个现有的安装包时发出的广播(不管现在安装的APP比之前的新还是旧,都会发出此广播?)
Intent.ACTION_PACKAGE_RESTARTED;
//用户重新开始一个包,包的所有进程将被杀死,所有与其联系的运行时间状态应该被移除,包括包名(重新开始包程序不能接收到这个广播)
Intent.ACTION_POWER_CONNECTED;
//插上外部电源时发出的广播
Intent.ACTION_POWER_DISCONNECTED;
//已断开外部电源连接时发出的广播
Intent.ACTION_PROVIDER_CHANGED;
//

Intent.ACTION_REBOOT;
//重启设备时的广播

Intent.ACTION_SCREEN_OFF;
//屏幕被关闭之后的广播

Intent.ACTION_SCREEN_ON;
//屏幕被打开之后的广播

Intent.ACTION_SHUTDOWN;
//关闭系统时发出的广播

Intent.ACTION_TIMEZONE_CHANGED;
//时区发生改变时发出的广播

Intent.ACTION_TIME_CHANGED;
//时间被设置时发出的广播

Intent.ACTION_TIME_TICK;
//广播:当前时间已经变化(正常的时间流逝)。
//当前时间改变,每分钟都发送,不能通过组件声明来接收,只有通过Context.registerReceiver()方法来注册

Intent.ACTION_UID_REMOVED;
//一个用户ID已经从系统中移除发出的广播
//

Intent.ACTION_UMS_CONNECTED;
//设备已进入USB大容量储存状态时发出的广播?

Intent.ACTION_UMS_DISCONNECTED;
//设备已从USB大容量储存状态转为正常状态时发出的广播?

Intent.ACTION_USER_PRESENT;
//

Intent.ACTION_WALLPAPER_CHANGED;
//设备墙纸已改变时发出的广播
相关推荐
大傻^38 分钟前
LangChain4j Spring Boot Starter:自动配置与声明式 Bean 管理
java·人工智能·spring boot·spring·langchain4j
沐硕39 分钟前
《基于改进协同过滤与多目标优化的健康饮食推荐系统设计与实现》
java·python·算法·fastapi·多目标优化·饮食推荐·改进协同过滤
愣头不青1 小时前
560.和为k的子数组
java·数据结构
共享家95271 小时前
Java入门(String类)
java·开发语言
Georgewu1 小时前
如何判断应用在鸿蒙卓易通或者出境易环境下?
android·harmonyos
l软件定制开发工作室1 小时前
Spring开发系列教程(34)——打包Spring Boot应用
java·spring boot·后端·spring·springboot
0xDevNull1 小时前
Spring Boot 循环依赖解决方案完全指南
java·开发语言·spring
爱丽_1 小时前
GC 怎么判定“该回收谁”:GC Roots、可达性分析、四种引用与回收算法
java·jvm·算法
bbq粉刷匠1 小时前
Java--多线程--单例模式
java·开发语言·单例模式
随风,奔跑1 小时前
Spring MVC
java·后端·spring