Android Framework AMS(10)广播组件分析-1

news2024/11/23 8:28:49

该系列文章总纲链接:专题总纲目录 Android Framework 总纲


本章关键点总结 & 说明:

说明:本章节主要解读应用层广播组件的发送广播和接收处理广播 2个过程,以及从APP层到AMS调用之间的打通。关注思维导图中左上部分即可。

有了前面startActivity流程和service组件启动的流程的过程,我们基于此继续分析广播组件的发送和接收处理流程过程。

我们先对广播组件的基本概念、分类、以及基本的使用方式有所了解。再基于应用的API入手,对关键API进行分析。

1 Android系统中广播的解读

1.1 广播的分类

在Android中,广播(Broadcast)是一种在不同组件之间传递信息的机制。广播可以分为以下几种类型:

  • 普通广播(Normal Broadcasts):异步发送,系统会同时将广播发送给所有注册接收该类型广播的接收器。接收器之间没有执行顺序,几乎同时接收到广播消息。不能被截断。
  • 有序广播(Ordered Broadcasts):同步发送,系统会按照接收器的优先级顺序逐个发送广播。每个接收器都有机会处理广播消息,并且可以决定是否继续传递广播。如果接收器决定不再传递广播(通过调用abortBroadcast()),那么后续的接收器将不会收到该广播。
  • 粘性广播(Sticky Broadcasts):属于标准广播,在发送后就一直存在于系统的消息容器里面,等待对应的处理器去处理。如果暂时没有处理器处理这个消息则一直在消息容器里面处于等待状态。粘性广播的Receiver如果被销毁,那么下次重建时会自动接收到消息数据。
  • 系统广播(System Broadcasts):Android内置了很多系统级别的广播,我们可以在应用中通过监听这些广播来得到各种系统的状态信息。例如手机开机后会发送一条广播,电池的电量发生变化会发出一条广播,时间或时区发生改变也会发出一条广播等。
  • App应用内广播(Local Broadcast):用于应用内部组件之间的通信,使用LocalBroadcastManager来管理。相比全局广播,应用内广播更高效,不需要跨进程通信,也不需要考虑安全性问题。

当然,随着Android版本发展的迭代,一些广播和一些广播发送的方法不再使用,参考信息如下:

  1. Android 5.0(API级别21)粘性广播不再支持sendStickyBroadcast 被打上了 Deprecated 标签,逐渐被弃用
  2. Android 7.0 (API 级别 24) 及更高版本不再发送以下系统广播ACTION_NEW_PICTURE和ACTION_NEW_VIDEO
  3. Android 8.0 (API 级别 26) 开始对清单声明的接收器施加额外限制:如果应用以 Android 8.0 或更高版本为目标平台,则不能使用清单来声明接收器,对于大多数隐式广播(并非针对你的应用)。
  4. Android 9 (API 级别 28) 开始NETWORK_STATE_CHANGED_ACTION 广播不再接收关于用户所在位置或个人身份信息可识别身份的数据。
  5. Android 12 开始ACTION_CLOSE_SYSTEM_DIALOGS 被弃用,为了改善用户控制体验,在应用程序和系统进行交互时。

这些变更意味着开发者需要根据目标Android版本调整广播的使用,以确保应用的兼容性和功能性。

1.2 广播接收器分类

在Android中,广播接收器(BroadcastReceiver)用于接收和处理广播消息。根据广播的类型和用途,广播接收器可以分为以下几种:

1.2.1 静态注册广播接收器

在AndroidManifest.xml文件中声明,系统会在应用启动时自动为这些接收器注册广播。适用于接收系统广播,如电池电量变化、屏幕关闭等。当然,静态声明接收器也可以接收感兴趣的广播类型,参考配置如下:

<receiver
    android:name=".YourBroadcastReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="your.action.YOUR_ACTION" />
    </intent-filter>
</receiver>

这里的your.action.YOUR_ACTION是你自定义的广播动作,用于IntentIntentFilter匹配对应动作。

1.2.2 动态注册广播接收器

一般在代码中通过registerReceiver()方法注册,需要在onDestroy()方法中调用unregisterReceiver()来注销。适用于接收应用内部广播或者需要根据应用状态动态注册和注销的广播。在Java代码中动态注册广播接收器代码实现参考如下:

IntentFilter filter = new IntentFilter();
filter.addAction("your.action.YOUR_ACTION");
registerReceiver(yourBroadcastReceiver, filter);

1.3 发送/接收广播代码实现解读

1.3.1 普通广播

发送普通广播方法如下:

// 创建一个Intent,包含要发送的动作
Intent intent = new Intent("your.action.YOUR_ACTION");
// 可以添加一些额外的数据
intent.putExtra("key", "value");

// 发送普通广播
sendBroadcast(intent);

接收普通广播:

public class NormalReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 检查Intent的动作是否是我们期望的
        if ("your.action.YOUR_ACTION".equals(intent.getAction())) {
            // 处理接收到的数据
            String value = intent.getStringExtra("key");
            // ...
        }
    }
}

 在AndroidManifest.xml中静态注册普通广播接收器

<receiver android:name=".NormalReceiver">
    <intent-filter>
        <action android:name="your.action.YOUR_ACTION" />
    </intent-filter>
</receiver>

或者动态注册:

IntentFilter filter = new IntentFilter("your.action.YOUR_ACTION");
registerReceiver(new NormalReceiver(), filter);

1.3.2 有序广播

发送有序广播方法如下:

// 创建一个Intent,包含要发送的动作
Intent intent = new Intent("your.action.YOUR_ACTION");
// 可以添加一些额外的数据
intent.putExtra("key", "value");

// 发送有序广播,第二个参数是权限字符串,null表示没有权限限制
sendOrderedBroadcast(intent, null);

在发送有序广播时,可以通过abortBroadcast()方法来截断广播,阻止其继续传递给其他接收器。接收有序广播:

public class OrderedReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 检查Intent的动作是否是我们期望的
        if ("your.action.YOUR_ACTION".equals(intent.getAction())) {
            // 处理接收到的数据
            String value = intent.getStringExtra("key");
            // ...

            // 如果需要继续传递广播,不调用abortBroadcast(),否则调用abortBroadcast()来截断广播
            // abortBroadcast();
        }
    }
}

注册普通广播接收器参考1.3.1即可。

请注意,有序广播可能会在未来的Android版本中被进一步限制或弃用,因为它们可能被滥用来执行恶意行为。因此,使用时需要谨慎,并考虑应用的目标API级别。

1.3.3 粘性广播(Sticky Broadcasts)

发送粘性广播:

// 创建一个Intent,包含要发送的数据
Intent intent = new Intent("com.example.myapp.MY_STICKY_BROADCAST");
intent.putExtra("key", "value");

// 发送粘性广播
sendStickyBroadcast(intent);

接收粘性广播:

public class StickyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 处理接收到的数据
        String value = intent.getStringExtra("key");
        // ...
    }
}

// 在代码中动态注册粘性广播接收器
IntentFilter filter = new IntentFilter("com.example.myapp.MY_STICKY_BROADCAST");
registerReceiver(new StickyReceiver(), filter);

注意: 如之前描述,从Android 5.0(API 级别 21)开始,sendStickyBroadcastsendStickyOrderedBroadcast 方法被标记为不推荐使用,因为它们可能会引起安全和隐私问题。建议使用其他方式来传递数据。

1.3.4 系统广播(System Broadcasts)

关于系统广播的发送,我们不需要过渡关心,更在于如何接收。

接收系统广播(例如电池电量变化):

public class SystemReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
            int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
            int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
            float batteryPct = level / (float) scale;
            // 处理电池电量变化
        }
    }
}

// 在AndroidManifest.xml中静态注册系统广播接收器
<receiver android:name=".SystemReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BATTERY_CHANGED" />
    </intent-filter>
</receiver>

注册广播接收器参考1.3.1即可。

1.3.5 App应用内广播(Local Broadcast)

发送应用内广播:

// 创建LocalBroadcastManager实例
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);

// 创建一个Intent,包含要发送的数据
Intent intent = new Intent("com.example.myapp.MY_LOCAL_BROADCAST");
intent.putExtra("key", "value");

// 发送应用内广播
localBroadcastManager.sendBroadcast(intent);

接收应用内广播:

public class LocalReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 处理接收到的数据
        String value = intent.getStringExtra("key");
        // ...
    }
}

// 在代码中动态注册应用内广播接收器
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
IntentFilter filter = new IntentFilter("com.example.myapp.MY_LOCAL_BROADCAST");
localBroadcastManager.registerReceiver(new LocalReceiver(), filter);

1.4 广播发送和接收案例

以下是一个简单的 Android 应用示例,它演示了如何发送和接收普通广播。这个示例包含两个部分:一个用于发送广播的 Activity 和一个广播接收器 BroadcastReceiver

首先,你需要在AndroidManifest.xml文件中声明Activity

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.dynamicbroadcastdemo">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

这是一个简单的Activity,它将发送一个自定义的广播,并动态注册一个广播接收器:

package com.example.dynamicbroadcastdemo;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private Button sendBroadcastButton;
    private MyBroadcastReceiver myBroadcastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sendBroadcastButton = findViewById(R.id.sendBroadcastButton);
        sendBroadcastButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                sendBroadcast();
            }
        });

        // 动态注册广播接收器
        myBroadcastReceiver = new MyBroadcastReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction("com.example.dynamicbroadcastdemo.MY_CUSTOM_ACTION");
        registerReceiver(myBroadcastReceiver, filter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 注销广播接收器
        unregisterReceiver(myBroadcastReceiver);
    }

    private void sendBroadcast() {
        Intent intent = new Intent("com.example.dynamicbroadcastdemo.MY_CUSTOM_ACTION");
        // 可以添加额外的数据
        intent.putExtra("key", "value");
        sendBroadcast(intent);
    }

    public class MyBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            // 检查Intent的动作是否是我们期望的
            if ("com.example.dynamicbroadcastdemo.MY_CUSTOM_ACTION".equals(intent.getAction())) {
                // 处理接收到的数据
                String value = intent.getStringExtra("key");
                Toast.makeText(context, "Received broadcast: " + value, Toast.LENGTH_SHORT).show();
            }
        }
    }
}

编写一个简单的布局文件,包含一个按钮用于发送广播:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/sendBroadcastButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Send Broadcast"
        android:layout_centerInParent="true" />
</RelativeLayout>

这个示例展示了如何发送和接收一个简单的广播。当用户点击按钮时,MainActivity 会发送一个广播,MyBroadcastReceiver 接收到广播后会显示一个 Toast 消息。这个示例涵盖了广播的发送和接收的基本流程。

基于此,我们对于广播组件的研究起点就从2个关键方法入手:

  • 动态注册/注销:registerReceiver unregisterReceiver
  • 发送普通/有序广播:sendBroadcast sendOrderedBroadcast

2 从activity场景到AMS调用的流程

2.1 动态注册/注销流程

2.1.1 从activity.registerReceiver到AMS.registerReceiver

activity.registerReceiver方法是从Context中的registerReceiver开始调用的,接口代码实现如下:

//Context 
@Nullable
public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver,IntentFilter filter);

@Nullable
public abstract Intent registerReceiver(BroadcastReceiver receiver,IntentFilter filter, @Nullable String broadcastPermission,@Nullable Handler scheduler);

注册时,用户的第一个 参数receiver 是继承于 BroadcastReceiver 的。其真正的实现是在ContextImpl中,代码实现如下:

//ContextImpl
    //关键流程:step1
    @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        return registerReceiver(receiver, filter, null, null);
    }
    //关键流程:step2
    @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
            String broadcastPermission, Handler scheduler) {
        return registerReceiverInternal(receiver, getUserId(),
                filter, broadcastPermission, scheduler, getOuterContext());
    }
    //关键流程:step3
    private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
                IntentFilter filter, String broadcastPermission,
                Handler scheduler, Context context) {
        // 创建一个IIntentReceiver对象,用于与ActivityManagerService通信
        IIntentReceiver rd = null;
        if (receiver != null) {
            if (mPackageInfo != null && context != null) {
                if (scheduler == null) {
                    // 如果scheduler(调度器)为null,则使用主线程的Handler
                    scheduler = mMainThread.getHandler();
                }
                // 创建一个ReceiverDispatcher对象,用于调度广播
                rd = mPackageInfo.getReceiverDispatcher(
                    receiver, context, scheduler,
                    mMainThread.getInstrumentation(), true);
            } else {
                if (scheduler == null) {
                    // 如果scheduler为null,则使用主线程的Handler
                    scheduler = mMainThread.getHandler();
                }
                // 创建一个LoadedApk.ReceiverDispatcher对象,用于调度广播
                rd = new LoadedApk.ReceiverDispatcher(
                        receiver, context, scheduler, null, true).getIIntentReceiver();
            }
        }
        try {
            // 调用AMS的registerReceiver方法注册广播接收器
            return ActivityManagerNative.getDefault().registerReceiver(
                    mMainThread.getApplicationThread(), mBasePackageName,
                    rd, filter, broadcastPermission, userId);
        } catch (RemoteException e) {
            return null;
        }
    }

这里准备一个IIntentReceiver对象,调用ActivityManagerNative.getDefault().registerReceiver,实际上最终就是调用到AMS的registerReceiver方法中。这一部分参考binder系列文章即可,有了或者基础分析起来就较为简单了。系列文章链接为:专题分纲目录 android 系统核心机制 binder,尤其是这2篇偏实操的:

android 系统核心机制binder(11)binder java层 TestServer分析

android 系统核心机制binder(12)binder java层 TestClient  分析

2.1.2 从activity.unregisterReceiver到AMS.unregisterReceiver

activity.unregisterReceiver方法是从Context中的unregisterReceiver开始调用的,接口代码实现如下:

//Context
public abstract void unregisterReceiver(BroadcastReceiver receiver);

其真正的实现是在ContextImpl中,代码实现如下:

//ContextImpl
    @Override
    public void unregisterReceiver(BroadcastReceiver receiver) {
        if (mPackageInfo != null) {
            IIntentReceiver rd = mPackageInfo.forgetReceiverDispatcher(
                    getOuterContext(), receiver);
            try {
                ActivityManagerNative.getDefault().unregisterReceiver(rd);
            } catch (RemoteException e) {
            }
        } else {
            throw new RuntimeException("Not supported in system context");
        }
    }

这里直接调用到ActivityManagerNative.getDefault().unregisterReceiver,实际上最终就是调用到AMS的unregisterReceiver方法中。其中过程参考2.1节即可。

2.2 发送普通/有序广播流程

2.2.1 从activity.sendBroadcast到AMS.broadcastIntent

activity.sendBroadcast方法是从Context中的sendBroadcast开始调用的,接口代码实现如下:

//Context
public abstract void sendBroadcast(Intent intent);

public abstract void sendBroadcast(Intent intent,@Nullable String receiverPermission);

public abstract void sendBroadcast(Intent intent,String receiverPermission, int appOp);

其真正的实现是在ContextImpl中,代码实现如下:

//ContextImpl
    @Override
    public void sendBroadcast(Intent intent) {
        // 如果这个方法是从系统进程调用的,则发出警告。
        // 说明:这是为了限制系统进程滥用发送广播的能力,因为这可能会影响到整个系统的稳定性。
        warnIfCallingFromSystemProcess();
        // 确定Intent的类型,并使用getContentResolver()进行解析。
        // 说明:这一步是为了确保Intent中的数据类型是正确的,并且可以被系统正确理解。
        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
        try {
            // 准备Intent,使其可以离开当前进程。
            // 说明:这是为了确保Intent在发送给其他应用或组件时,包含所有必要的信息,并且是安全的。
            intent.prepareToLeaveProcess();
            // 调用AMS的broadcastIntent方法发送广播
            ActivityManagerNative.getDefault().broadcastIntent(
                mMainThread.getApplicationThread(), intent, resolvedType, null,
                Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, false, false,
                getUserId());
        } catch (RemoteException e) {
        }
    }

    //解读同上,内容逻辑基本一致,仅传递参数不同
    @Override
    public void sendBroadcast(Intent intent, String receiverPermission) {
        warnIfCallingFromSystemProcess();
        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
        try {
            intent.prepareToLeaveProcess();
            ActivityManagerNative.getDefault().broadcastIntent(
                mMainThread.getApplicationThread(), intent, resolvedType, null,
                Activity.RESULT_OK, null, null, receiverPermission, AppOpsManager.OP_NONE,
                false, false, getUserId());
        } catch (RemoteException e) {
        }
    }

    //解读同上,内容逻辑基本一致,仅传递参数不同
    @Override
    public void sendBroadcast(Intent intent, String receiverPermission, int appOp) {
        warnIfCallingFromSystemProcess();
        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
        try {
            intent.prepareToLeaveProcess();
            ActivityManagerNative.getDefault().broadcastIntent(
                mMainThread.getApplicationThread(), intent, resolvedType, null,
                Activity.RESULT_OK, null, null, receiverPermission, appOp, false, false,
                getUserId());
        } catch (RemoteException e) {
        }
    }

这里直接调用到ActivityManagerNative.getDefault().broadcastIntent,实际上最终就是调用到AMS的broadcastIntent方法中。其中过程参考2.1节即可。

2.2.2 从activity.sendOrderedBroadcast到AMS.broadcastIntent

activity.sendOrderedBroadcast方法是从Context中的sendOrderedBroadcast开始调用的,接口代码实现如下:

//Context
    public abstract void sendOrderedBroadcast(Intent intent,@Nullable String receiverPermission);

    public abstract void sendOrderedBroadcast(@NonNull Intent intent,
            @Nullable String receiverPermission, BroadcastReceiver resultReceiver,
            @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
            @Nullable Bundle initialExtras);

    public abstract void sendOrderedBroadcast(Intent intent,
            String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
            Handler scheduler, int initialCode, String initialData,
            Bundle initialExtras);

其真正的实现是在ContextImpl中,代码实现如下:

//ContextImpl
    //解读同sendBroadcast,内容逻辑基本一致,仅传递参数不同
    @Override
    public void sendOrderedBroadcast(Intent intent,
            String receiverPermission) {
        warnIfCallingFromSystemProcess();
        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
        try {
            intent.prepareToLeaveProcess();
            ActivityManagerNative.getDefault().broadcastIntent(
                mMainThread.getApplicationThread(), intent, resolvedType, null,
                Activity.RESULT_OK, null, null, receiverPermission, AppOpsManager.OP_NONE, true, false,
                getUserId());
        } catch (RemoteException e) {
        }
    }
    //sendOrderedBroadcast 关键流程:step1
    @Override
    public void sendOrderedBroadcast(Intent intent,
            String receiverPermission, BroadcastReceiver resultReceiver,
            Handler scheduler, int initialCode, String initialData,
            Bundle initialExtras) {
        sendOrderedBroadcast(intent, receiverPermission, AppOpsManager.OP_NONE,
                resultReceiver, scheduler, initialCode, initialData, initialExtras);
    }
    //sendOrderedBroadcast 关键流程:step2
    @Override
    public void sendOrderedBroadcast(Intent intent,
            String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
            Handler scheduler, int initialCode, String initialData,
            Bundle initialExtras) {
        warnIfCallingFromSystemProcess();

        // 初始化IIntentReceiver,用于接收广播结果
        IIntentReceiver rd = null;
        if (resultReceiver != null) {
            // 如果提供了resultReceiver,则需要创建一个IIntentReceiver对象
            if (mPackageInfo != null) {
                // 如果mPackageInfo不为空,使用它来创建ReceiverDispatcher
                if (scheduler == null) {
                    // 如果scheduler为null,则使用主线程的Handler
                    scheduler = mMainThread.getHandler();
                }
                rd = mPackageInfo.getReceiverDispatcher(
                    resultReceiver, getOuterContext(), scheduler,
                    mMainThread.getInstrumentation(), false);
            } else {
                // 如果mPackageInfo为空,使用LoadedApk来创建ReceiverDispatcher
                if (scheduler == null) {
                    // 如果scheduler为null,则使用主线程的Handler
                    scheduler = mMainThread.getHandler();
                }
                rd = new LoadedApk.ReceiverDispatcher(
                        resultReceiver, getOuterContext(), scheduler, null, false).getIIntentReceiver();
            }
        }

        // 解析Intent的类型
        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());

        try {
            intent.prepareToLeaveProcess();
            
            // 调用ActivityManagerService的broadcastIntent方法发送有序广播
            ActivityManagerNative.getDefault().broadcastIntent(
                mMainThread.getApplicationThread(), intent, resolvedType, rd,
                initialCode, initialData, initialExtras, receiverPermission, appOp,
                    true, false, getUserId());
        } catch (RemoteException e) {
        }
    }

这里直接调用到ActivityManagerNative.getDefault().broadcastIntent,实际上最终就是调用到AMS的broadcastIntent方法中。其中过程参考2.1节即可。

从这里也可以看到,对于有序广播的发送,最终和普通广播调用的broadcastIntent方法是一致的。只不过过程中的参数不同。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2230355.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

磁盘空间不足导致postgreSQL启动失败

背景&#xff1a; 智慧庭审平台安装了ivr/xvr等vr应用后&#xff0c;磁盘空间不足导致postgreSQL数据库一直重启 排查 到服务器下使用 systemctl status hik.postgresql96linux64.rdbms.1.service 查看进程报错信息 这次报的是 FATAL: could not write lock file "po…

C++进阶:C++11的新特性

✨✨所属专栏&#xff1a;C✨✨ ✨✨作者主页&#xff1a;嶔某✨✨ C11的发展历史 2011年&#xff0c;C标准委员会发布了C11标准&#xff0c;这是C的一次巨大飞跃&#xff0c;引入了许多重要的新特性&#xff0c;如智能指针、lambda表达式、并发编程支持等。这一版本的发布对C社…

LongVU :Meta AI 的解锁长视频理解模型,利用自适应时空压缩技术彻底改变视频理解方式

Meta AI在视频理解方面取得了令人瞩目的里程碑式成就&#xff0c;推出了LongVU&#xff0c;这是一种开创性的模型&#xff0c;能够理解以前对人工智能系统来说具有挑战性的长视频。 研究论文 "LongVU&#xff1a;用于长视频语言理解的时空自适应压缩 "提出了一种革命…

Ubuntu 22.04安装部署

一、部署环境 表 1‑1 环境服务版本号系统Ubuntu22.04 server lts运行环境1JDK1.8前端WEBNginx1.8数据库postgresqlpostgresql13postgis3.1pgrouting3.1消息队列rabbitmq3.X(3.0以上)运行环境2erlang23.3.3.1 二、安装系统 2.1安装 1.安装方式&#xff0c;选第一条。 2.选择…

无需手动部署的正式版comfyUI是否就此收费?开源等同免费?

​ ​ 关于ComfyUI的正式版是否会收费的问题是很多AI玩家都关心的问题。 一旦ComfyUI正式版发布&#xff0c;我们是否需要为它买单&#xff1f;不再开源 同时这也引出了一个核心问题&#xff1a;开源究竟等不等于免费&#xff1f; ComfyUI正式版到底是什么&#xff1f;它会收…

云计算作业二Spark:问题解决备忘

安装spark 教程源地址&#xff1a;https://blog.csdn.net/weixin_52564218/article/details/141090528 镜像下载 教程给的官网下载地址很慢&#xff0c;https://archive.apache.org/dist/spark/spark-3.1.1/ 这里的镜像快很多&#xff1a; 清华软件源&#xff1a;https://mi…

C语言 | Leetcode C语言题解之第524题通过删除字母匹配到字典里最长单词

题目&#xff1a; 题解&#xff1a; char * findLongestWord(char * s, char ** d, int dSize){char *result "";int max -1;for (int i 0; i < dSize; i) {char *p s, *q d[i];int j 0, k 0;while (p[j] ! \0 && q[k] ! \0) {if (p[j] q[k]) {k…

【含文档】基于ssm+jsp的学科竞赛系统(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: apache tomcat 主要技术: Java,Spring,SpringMvc,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定义了四个…

【5.5】指针算法-三指针解决颜色分类

一、题目 给定一个包含红色、白色和蓝色&#xff0c;一共n个元素的数组&#xff0c;原地对它们进行排序&#xff0c;使得相同颜色的元素相邻&#xff0c;并按照红色、白色、蓝色顺序排列。 此题中&#xff0c;我们使用整数0、1和2分别表示红色、白色和蓝色。 示例 1&#xff1…

双向链表及如何使用GLib的GList实现双向链表

双向链表是一种比单向链表更为灵活的数据结构&#xff0c;与单向链表相比可以有更多的应用场景&#xff0c;本文讨论双向链表的基本概念及实现方法&#xff0c;并着重介绍使用GLib的GList实现单向链表的方法及步骤&#xff0c;本文给出了多个实际范例源代码&#xff0c;旨在帮助…

web——warmup——攻防世界

这道题还是没有做出来。。&#xff0c;来总结一下 1.ctrlU显示源码 2.看见body里有source.php 打开这个source.php 看见了源码 highlight_file(FILE); 这行代码用于高亮显示当前文件的源码&#xff0c;适合调试和学习&#xff0c;但在生产环境中通常不需要。 class emmm 定义…

【MATLAB代码】三个CT模型的IMM例程,各CT旋转速率不同,适用于定位、导航、目标跟踪

三个CT模型&#xff0c;各CT模型下的运动旋转速率不同&#xff0c;适用于定位、导航、目标跟踪 文章目录 代码构成运行结果源代码代码讲解概述代码结构1. 初始化2. 仿真参数设置3. 生成量测数据4. IMM迭代5. 绘图 主要功能函数部分1. 卡尔曼滤波函数2. 模型综合函数3. 模型概率…

sklearn 实现随机森林分类器 - python 实现

python sklearn 实现随机森林分类器 from sklearn.ensemble import RandomForestClassifier from sklearn.datasets import load_iris # 加载数据集 irisload_iris() x,yiris.data,iris.target print("x y shape:",x.shape,y.shape) # 创建并训练模型 model Random…

GetX的一些高级API

目录 前言 一、一些常用的API 二、局部状态组件 1.可选的全局设置和手动配置 2.局部状态组件 1.ValueBuilder 1.特点 2.基本用法 2.ObxValue 1.特点 2.基本用法 前言 这篇文章主要讲解GetX的一些高级API和一些有用的小组件。 一、一些常用的API GetX提供了一些高级…

WPF+MVVM案例实战(十一)- 环形进度条实现

文章目录 1、运行效果2、功能实现1、文件创建与代码实现2、角度转换器实现3、命名空间引用3、源代码下载1、运行效果 2、功能实现 1、文件创建与代码实现 打开 Wpf_Examples 项目,在Views 文件夹下创建 CircularProgressBar.xaml 窗体文件。 CircularProgressBar.xaml 代码实…

SYN590RH

一般描述 SYN590RH是SYNOXO全新开发设计的一款宽电压范围&#xff0c;低功耗&#xff0c;高性能&#xff0c;无需外置AGC电容&#xff0c;灵敏度达到典型-110 dBm,400MHz~450MHz频率范围应用的单芯片ASK或00 K射频接收器。 SYN590RH是一款典型的即插即用型单片高…

kafka里的consumer 是推还是拉?

大家好&#xff0c;我是锋哥。今天分享关于【kafka里的consumer 是推还是拉&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; kafka里的consumer 是推还是拉&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在Kafka中&#xff0c;消费者&…

C语言的数组地址 数组的遍历与练习

1.int main(void) { int a[5] { 10,20,30,40,50 };//数组间的元素地址相连的 int* p; printf("%d\n", &a[0]); printf("%d\n", &a[1]); printf("%d\n", &a[2]); printf("%d\n", &a[3]); …

基于springboot + vue的网上订餐系统的设计与实现(附源码)

一、项目背景 随着互联网技术的飞速发展和智能手机的普及&#xff0c;人们的生活方式发生了翻天覆地的变化&#xff0c;其中之一便是网上订餐系统的兴起。这种系统通过在线平台连接消费者和餐饮服务提供商&#xff0c;使得用户可以随时随地浏览菜单、下单并支付&#xff0c;极…

Redis安装及运维

源码安装 Redis安装前建议要进行残留数据检查&#xff0c;排除后期存在的各种隐患 官网&#xff1a;https://redis.io/&#xff0c;Linux版本只会提供源码&#xff0c;不提供二进制安装包&#xff0c;因此需要编译源码进行安装&#xff0c;本次使用CentOS8 VMware虚拟机进行安…