ActivityManagerService 分发广播(6)

news2024/9/21 6:35:08

ActivityManagerService 分发广播

简述

上一节我们看了发送广播流程,主要是将广播信息封装至BroadcastRecord,然后通过BroadcastQueueImpl/BroadcastQueueModernImpl的enqueueBroadcastLocked来发送广播。
这一节我们来看一下AMS是怎么分发广播的流程,BroadcastQueueImpl/BroadcastQueueModernImpl是两套策略,只是分发的模式不同,我们主要关心流程,策略细节就不去关注了,我们这里就挑BroadcastQueueImpl来看一下分发的流程。

广播分发

前面提到过BroadcastQueueImpl模式下,会有四个队列来发送广播,流程上四个队列都是一样的,只是有不同的优先级。
我们从BroadcastQueueImpl.enqueueBroadcastLocked开始看。
在这里插入图片描述

1.1 BroadcastQueueImpl.enqueueBroadcastLocked
区分有序广播和无序广播,如果有序广播,可能还需要替换队列中之前的BroadcastRecord。
有序和无序广播会添加到BroadcastDispatcher不同的队列中。
最终都会通过调用scheduleBroadcastsLocked来触发广播的分发。

public void enqueueBroadcastLocked(BroadcastRecord r) {
    r.applySingletonPolicy(mService);
    // flag如果有FLAG_RECEIVER_REPLACE_PENDING,则说明新的广播需要替代之前的
    final boolean replacePending = (r.intent.getFlags()
            & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;

    // 有序广播需要顺序分发
    boolean serialDispatch = r.ordered;
    if (!serialDispatch) {
        final int N = (r.receivers != null) ? r.receivers.size() : 0;
        for (int i = 0; i < N; i++) {
            if (r.receivers.get(i) instanceof ResolveInfo) {
                serialDispatch = true;
                break;
            }
        }
    }

    if (serialDispatch) {
        // BroadcastDispatcher里面有一个mOrderedBroadcasts,如果之前已经发送过一样的广播,需要replace mOrderedBroadcasts里面之前的。
        final BroadcastRecord oldRecord =
                replacePending ? replaceOrderedBroadcastLocked(r) : null;
        if (oldRecord != null) {
            // 如果有替换,调用performReceiveLocked
            if (oldRecord.resultTo != null) {
                try {
                    oldRecord.mIsReceiverAppRunning = true;
                    performReceiveLocked(oldRecord, oldRecord.resultToApp, oldRecord.resultTo,
                            oldRecord.intent,
                            Activity.RESULT_CANCELED, null, null,
                            false, false, oldRecord.shareIdentity, oldRecord.userId,
                            oldRecord.callingUid, r.callingUid, r.callerPackage,
                            SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0, 0,
                            oldRecord.resultToApp != null
                                    ? oldRecord.resultToApp.mState.getCurProcState()
                                    : ActivityManager.PROCESS_STATE_UNKNOWN);
                } catch (RemoteException e) {
                    // ...
                }
            }
        } else {
            // 否则添加到BroadcastDispatcher的队列中去。
            enqueueOrderedBroadcastLocked(r);
            // 最终需要调用scheduleBroadcastsLocked来发送广播,详见1.2
            scheduleBroadcastsLocked();
        }
    } else {
        final boolean replaced = replacePending
                && (replaceParallelBroadcastLocked(r) != null);
        // 无序广播
        if (!replaced) {
            // 添加到BroadcastDispatcher的队列中去
            enqueueParallelBroadcastLocked(r);
            // 最终需要调用scheduleBroadcastsLocked来发送广播,详见1.2
            scheduleBroadcastsLocked();
        }
    }
}

1.2 BroadcastQueueImpl.scheduleBroadcastsLocked
发送一个BROADCAST_INTENT_MSG消息

public void scheduleBroadcastsLocked() {
    // ...log
    // 防重入
    if (mBroadcastsScheduled) {
        return;
    }
    // 发送BROADCAST_INTENT_MSG消息
    mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
    mBroadcastsScheduled = true;
}

1.3 BroadcastHandler.handleMessage
调用processNextBroadcast

private final class BroadcastHandler extends Handler {

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case BROADCAST_INTENT_MSG: {
                if (DEBUG_BROADCAST) Slog.v(
                        TAG_BROADCAST, "Received BROADCAST_INTENT_MSG ["
                        + mQueueName + "]");
                // 调用 processNextBroadcast,详见1.4
                processNextBroadcast(true);
            } break;
            case BROADCAST_TIMEOUT_MSG: {
                synchronized (mService) {
                    // 这里是广播超时,触发ANR的地方,我们不是在说稳定性,这里就不关注了
                    broadcastTimeoutLocked(true);
                }
            } break;
        }
    }
}

1.4 BroadcastQueueImpl.processNextBroadcast
加了AMS大锁,然后调用processNextBroadcastLocked

private void processNextBroadcast(boolean fromMsg) {
    synchronized (mService) {
        processNextBroadcastLocked(fromMsg, false);
    }
}

1.5 BroadcastQueueImpl.processNextBroadcastLocked
首先先通过deliverToRegisteredReceiverLocked分发无序广播,然后再来处理有序广播。
有序广播需要一个一个receiver来分发,所以先要找到下一个receiver。这里有一个延迟策略,有一些广播app侧处理起来比较耗时,例如需要启动进程。
这种情况下就会将广播进行拆分,新建一个BroadcastRecord,它的receiver只有刚刚需要延迟的那一个,然后在原来的BroadcastRecord中删除这个receiver。
找到下一个receiver后,先设置广播的Timeout超时逻辑,这里相当于ANR埋雷,然后获取receiver的进程信息,检查是否需要skip该receiver,如果不需要则检查进程是否已经运行,如果已经运行就调用processCurBroadcastLocked来分发广播,否则会先启动目标进程,启动进程的逻辑和之前Activity启动进程逻辑类似,就不说了。

public void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
    BroadcastRecord r;

    // ...

    // 先分发无序广播
    while (mParallelBroadcasts.size() > 0) {
        r = mParallelBroadcasts.remove(0);
        r.dispatchTime = SystemClock.uptimeMillis();
        r.dispatchRealTime = SystemClock.elapsedRealtime();
        r.dispatchClockTime = System.currentTimeMillis();
        r.mIsReceiverAppRunning = true;

        // ...

        final int N = r.receivers.size();
        if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
                + mQueueName + "] " + r);
        for (int i=0; i<N; i++) {
            Object target = r.receivers.get(i);
            // 分发无序广播
            deliverToRegisteredReceiverLocked(r,
                    (BroadcastFilter) target, false, i);
        }
        addBroadcastToHistoryLocked(r);
    }

    // 如果有mPendingBroadcast,正在发送的广播,什么都不做,只是检查一下对应进程是否还活着,如果活着就直接return。
    // 否则需要继续发送广播的流程
    if (mPendingBroadcast != null) {
        boolean isDead;
        if (mPendingBroadcast.curApp.getPid() > 0) {
            synchronized (mService.mPidsSelfLocked) {
                ProcessRecord proc = mService.mPidsSelfLocked.get(
                        mPendingBroadcast.curApp.getPid());
                isDead = proc == null || proc.mErrorState.isCrashing();
            }
        } else {
            final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get(
                    mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
            isDead = proc == null || !proc.isPendingStart();
        }
        if (!isDead) {
            // 进程还活着,什么都不做直接return
            return;
        } else {
            mPendingBroadcast.state = BroadcastRecord.IDLE;
            mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
            mPendingBroadcast = null;
        }
    }

    boolean looped = false;
    // 循环处理广播,寻找下一个我们需要发送的广播以及receiver
    do {
        final long now = SystemClock.uptimeMillis();
        r = mDispatcher.getNextBroadcastLocked(now);

        if (r == null) {
            // ...
            // 没有广播需要分发,直接return
            return;
        }

        boolean forceReceive = false;

        // 
        int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
        if (mService.mProcessesReady && !r.timeoutExempt && r.dispatchTime > 0) {
            if ((numReceivers > 0) &&
                    (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) {
                // ... 如果广播处理太慢超时,强制停止广播
                broadcastTimeoutLocked(false); // forcibly finish this broadcast
                forceReceive = true;
                r.state = BroadcastRecord.IDLE;
            }
        }

        if (r.state != BroadcastRecord.IDLE) {
            // ...如果当前广播不是IDLE状态,则直接返回
            return;
        }

        // 检查广播发送是否已经完成
        if (r.receivers == null || r.nextReceiver >= numReceivers
                || r.resultAbort || forceReceive) {
            if (r.resultTo != null) {
                boolean sendResult = true;
                // ...
                if (sendResult) {
                    // ...
                        // 如果需要发送结果
                        performReceiveLocked(r, r.resultToApp, r.resultTo,
                                new Intent(r.intent), r.resultCode,
                                r.resultData, r.resultExtras, false, false, r.shareIdentity,
                                r.userId, r.callingUid, r.callingUid, r.callerPackage,
                                r.dispatchTime - r.enqueueTime,
                                now - r.dispatchTime, 0,
                                r.resultToApp != null
                                        ? r.resultToApp.mState.getCurProcState()
                                        : ActivityManager.PROCESS_STATE_UNKNOWN);
                    // ...
                }
            }

            // ...清除广播超时计时器
            cancelBroadcastTimeoutLocked();

            // ... 
            mDispatcher.retireBroadcastLocked(r);
            r = null;
            looped = true;
            // 当前广播已经完成,则进入下一次循环
            continue;
        }

        // 检查下一个receiver是否在defer策略下。
        if (!r.deferred) {
            final int receiverUid = r.getReceiverUid(r.receivers.get(r.nextReceiver));
            if (mDispatcher.isDeferringLocked(receiverUid)) {
                // 如果这个是最后一个receiver,就没有必要做拆分操作了,就正常推迟即可。
                BroadcastRecord defer;
                if (r.nextReceiver + 1 == numReceivers) {
                    defer = r;
                    mDispatcher.retireBroadcastLocked(r);
                } else {
                    // 进行分割操作,这里会新构建一个BroadcastRecord,接收者只有这一个uid,单独来发送
                    defer = r.splitRecipientsLocked(receiverUid, r.nextReceiver);
                    // ...
                    // Track completion refcount as well if relevant
                    if (r.resultTo != null) {
                        int token = r.splitToken;
                        // 分割的计数。
                        if (token == 0) {
                            r.splitToken = defer.splitToken = nextSplitTokenLocked();
                            mSplitRefcounts.put(r.splitToken, 2);
                        } else {
                            final int curCount = mSplitRefcounts.get(token);
                            mSplitRefcounts.put(token, curCount + 1);
                        }
                    }
                }
                // 添加分割后需要延迟的广播
                mDispatcher.addDeferredBroadcast(receiverUid, defer);
                r = null;
                looped = true;
                continue;
            }
        }
    } while (r == null);

    int recIdx = r.nextReceiver++;

    r.receiverTime = SystemClock.uptimeMillis();
    r.scheduledTime[recIdx] = r.receiverTime;
    if (recIdx == 0) {
        r.dispatchTime = r.receiverTime;
        r.dispatchRealTime = SystemClock.elapsedRealtime();
        r.dispatchClockTime = System.currentTimeMillis();
        // ... trace/log
    }
    if (! mPendingBroadcastTimeoutMessage) {
        long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
        // 设置广播超时
        setBroadcastTimeoutLocked(timeoutTime);
    }

    final BroadcastOptions brOptions = r.options;
    final Object nextReceiver = r.receivers.get(recIdx);

    //...
    // ...获取receiver进程信息

    // 判断是否需要跳过当前的receiver
    boolean skip = mSkipPolicy.shouldSkip(r, info);

    Bundle filteredExtras = null;
    if (!skip && r.filterExtrasForReceiver != null) {
        final Bundle extras = r.intent.getExtras();
        if (extras != null) {
            filteredExtras = r.filterExtrasForReceiver.apply(receiverUid, extras);
            if (filteredExtras == null) {
                skip = true;
            }
        }
    }
    // 如果需要跳过,就调用scheduleBroadcastsLocked进入下一次广播分发逻辑
    if (skip) {
        r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
        r.curFilter = null;
        r.state = BroadcastRecord.IDLE;
        r.manifestSkipCount++;
        scheduleBroadcastsLocked();
        return;
    }
    r.manifestCount++;

    r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
    r.state = BroadcastRecord.APP_RECEIVE;
    r.curComponent = component;
    r.curReceiver = info.activityInfo;
    r.curFilteredExtras = filteredExtras;

    final boolean isActivityCapable =
            (brOptions != null && brOptions.getTemporaryAppAllowlistDuration() > 0);
    maybeScheduleTempAllowlistLocked(receiverUid, r, brOptions);

    // ...

    // 检查receiver目标app是否已经启动
    if (app != null && app.getThread() != null && !app.isKilled()) {
        try {
            app.addPackage(info.activityInfo.packageName,
                    info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
            maybeAddBackgroundStartPrivileges(app, r);
            r.mIsReceiverAppRunning = true;
            // 发送广播,详见1.6
            processCurBroadcastLocked(r, app);
            return;
        }
        // ...
    }

    r.mWasReceiverAppStopped =
            (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
    //  如果目标进程没有启动,这里需要先启动进程。
    r.curApp = mService.startProcessLocked(targetProcess,
            info.activityInfo.applicationInfo, true,
            r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
            new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
                    r.intent.getAction(), r.getHostingRecordTriggerType()),
            isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
            (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
    r.curAppLastProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
    if (r.curApp == null) {
        // 如果启动进程失败,结束当前的广播
        logBroadcastReceiverDiscardLocked(r);
        finishReceiverLocked(r, r.resultCode, r.resultData,
                r.resultExtras, r.resultAbort, false);
        // 重新调用scheduleBroadcastsLocked发送广播
        scheduleBroadcastsLocked();
        r.state = BroadcastRecord.IDLE;
        return;
    }

    maybeAddBackgroundStartPrivileges(r.curApp, r);
    mPendingBroadcast = r;
    mPendingBroadcastRecvIndex = recIdx;
}

1.6 BroadcastQueueImpl.processCurBroadcastLocked
通过ActivityThread.scheduleReceiver binder回调回app侧执行广播。

private final void processCurBroadcastLocked(BroadcastRecord r,
        ProcessRecord app) throws RemoteException {
    // ...
    // 跟新adj
    mService.enqueueOomAdjTargetLocked(app);
    mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);

    // ...

    boolean started = false;
    try {
        mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
                                  PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
        final boolean assumeDelivered = false;
        // 通过binder回调App侧,分发广播,详见1.7
        thread.scheduleReceiver(
                prepareReceiverIntent(r.intent, r.curFilteredExtras),
                r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
                r.resultCode, r.resultData, r.resultExtras, r.ordered, assumeDelivered,
                r.userId, r.shareIdentity ? r.callingUid : Process.INVALID_UID,
                app.mState.getReportedProcState(),
                r.shareIdentity ? r.callerPackage : null);
        started = true;
    } finally {
        // ...
    }

    if (app.isKilled()) {
        throw new RemoteException("app gets killed during broadcasting");
    }
}

1.7 ActivityThread.scheduleReceiver
将广播封装到ReceiverData,发送H.RECEIVER消息

public final void scheduleReceiver(Intent intent, ActivityInfo info,
        CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
        boolean ordered, boolean assumeDelivered, int sendingUser, int processState,
        int sendingUid, String sendingPackage) {
    updateProcessState(processState, false);
    // 封装ReceiverData
    ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
            ordered, false, assumeDelivered, mAppThread.asBinder(), sendingUser,
            sendingUid, sendingPackage);
    r.info = info;
    // 发送H.RECEIVER消息,详见1.8
    sendMessage(H.RECEIVER, r);
}

1.8 H.handleMessage
调用handleReceiver

public void handleMessage(Message msg) {
    // ...  
        case RECEIVER:
            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                ReceiverData rec = (ReceiverData) msg.obj;
            // ...
            // 详见1.9
            handleReceiver((ReceiverData)msg.obj);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            break;
    // ...
}

1.9 ActivityThread.handleReceiver
这里通过反射构造出对应BroadcastReceiver,然后调用了Receiver的onReceive,后续就是我们应用开发时候定义广播接收器,复写onReceive自己定义的逻辑了。

private void handleReceiver(ReceiverData data) {
    // ...
    try {
        // ...
        java.lang.ClassLoader cl = context.getClassLoader();
        data.intent.setExtrasClassLoader(cl);
        data.intent.prepareToEnterProcess(
                isProtectedComponent(data.info) || isProtectedBroadcast(data.intent),
                context.getAttributionSource());
        data.setExtrasClassLoader(cl);
        // 通过反射构造对应是BroadcastReceiver
        receiver = packageInfo.getAppFactory()
                .instantiateReceiver(cl, data.info.name, data.intent);
    } catch (Exception e) {
        // ...
    }

    try {
        // ...
        // 调用广播的onReceive
        sCurrentBroadcastIntent.set(data.intent);
        receiver.setPendingResult(data);
        receiver.onReceive(context.getReceiverRestrictedContext(),
                data.intent);
    } catch (Exception e) {
        // ...
    } finally {
        sCurrentBroadcastIntent.set(null);
    }

    if (receiver.getPendingResult() != null) {
        data.finish();
    }
}

小结

本章主要介绍了一下广播分发的流程,虽然有一些方法比较长,但是核心的逻辑是比较简单的,主要是一些分发的策略逻辑比较多。
发送广播时会把广播数据放到队列中,然后分发广播会不断调用scheduleBroadcastsLocked来处理广播的分发。
广播分发分为无序广播和有序广播,无序广播分发逻辑比较简单,直接分发即可。而有序广播有一些额外策略,例如如果有处理广播时间较长的情况,如目标进程未启动需要现在启动进程,会将广播单独拆分开,防止单个应用处理广播太慢广播影响其他应用。
分发到应用通过ActivityThread.scheduleReceiver的binder调用通知到应用,而应用会通过反射构造对应的BroadcastReceiver,然后调用它的onReceiver,之后就是app侧复写onReceive的逻辑了,这里的其实和Activity的onCreate操作很像。

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

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

相关文章

【论文笔记】Are Large Kernels Better Teacheres than Transformers for ConvNets

Abstract 本文提出蒸馏中小核ConvNet做学生时&#xff0c;与Transformer相比&#xff0c;大核ConvNet因其高效的卷积操作和紧凑的权重共享&#xff0c;使得其做教师效果更好&#xff0c;更适合资源受限的应用。 用蒸馏从Transformers蒸到小核ConvNet的效果并不好&#xff0c;原…

视频去噪技术分享

视频去噪是一种视频处理技术&#xff0c;旨在从视频帧中移除噪声和干扰&#xff0c;提高视频质量。噪声可能由多种因素引起&#xff0c;包括低光照条件、高ISO设置、传感器缺陷等。视频去噪对于提升视频内容的可视性和可用性至关重要&#xff0c;特别是在安全监控、医疗成像和视…

001.从0开始实现线性回归(pytorch)

000动手从0实现线性回归 0. 背景介绍 我们构造一个简单的人工训练数据集&#xff0c;它可以使我们能够直观比较学到的参数和真实的模型参数的区别。 设训练数据集样本数为1000&#xff0c;输入个数&#xff08;特征数&#xff09;为2。给定随机生成的批量样本特征 X∈R10002 …

正点原子阿尔法ARM开发板-IMX6ULL(八)——串口通信(寄存器解释)(补:有源蜂鸣器)

文章目录 一、蜂鸣器&#xff08;待&#xff0c;理解&#xff09;1.1 第一行1.2 第二行1.3 第三行 二、串口原理2.1 通信格式2.2 UART寄存器 一、蜂鸣器&#xff08;待&#xff0c;理解&#xff09; 1.1 第一行 对于第一行&#xff0c;首先先到fsl_iomuxc文件里面寻找IOMUXC_S…

探索C语言与Linux编程:获取当前用户ID与进程ID

探索C语言与Linux编程:获取当前用户ID与进程ID 一、Linux系统概述与用户、进程概念二、C语言与系统调用三、获取当前用户ID四、获取当前进程ID五、综合应用:同时获取用户ID和进程ID六、深入理解与扩展七、结语在操作系统与编程语言的交汇点,Linux作为开源操作系统的典范,为…

01-Mac OS系统如何下载安装Python解释器

目录 Mac安装Python的教程 mac下载并安装python解释器 如何下载和安装最新的python解释器 访问python.org&#xff08;受国内网速的影响&#xff0c;访问速度会比较慢&#xff0c;不过也可以去我博客的资源下载&#xff09; 打开历史发布版本页面 进入下载页 鼠标拖到页面…

安装Kali Linux后8件需要马上安排的事

目录 一、更新升级 二、 编辑器 三、用户与权限 四、 下载TOR 五、下载终端 一、更新升级 sudo apt update -y && sudo apt upgrade -y && sudo apt autoremove 二、 编辑器 VScode或者vim&#xff1b;点击.deb就会下载了 一般都会下载到Downloads文件夹中…

煤矸石检测数据集(yolo)

yolo煤矸石检测 数据集 pt模型 界面&#xff0c; ✓3091张图片和txt标签&#xff0c;标签类别两类&#xff1a;“coal”、“rock”。 ✓适用于煤矸石识别&#xff0c;深度学习&#xff0c;机器学习&#xff0c;yolov5 yolov6 yolov7 yolov8 yolov9 yolov10&#xff0c;Python 煤…

YOLOv5模型部署教程

一、介绍 YOLOv5模型是一种以实时物体检测闻名的计算机视觉模型&#xff0c;由Ultralytics开发&#xff0c;并于2020年年中发布。它是YOLO系列的升级版&#xff0c;继承了YOLO系列以实时物体检测能力而著称的特点。 二、基础环境 系统&#xff1a;Ubuntu系统&#xff0c;显卡…

企业内网安全

企业内网安全 1.安全域2.终端安全3.网络安全网络入侵检测系统异常访问检测系统隐蔽信道检测系统 4.服务器安全基础安全配置入侵防护检测 5.重点应用安全活动目录邮件系统VPN堡垒机 6.蜜罐体系建设蜜域名蜜网站蜜端口蜜服务蜜库蜜表蜜文件全民皆兵 1.安全域 企业出于不同安全防…

详读西瓜书+南瓜书第3章——线性回归

在这里&#xff0c;我们来深入探讨线性模型的相关内容&#xff0c;这章涵盖了从基础线性回归到更复杂的分类任务模型。我们会逐步分析其数学公式和实际应用场景。 3.1 基本形式 线性模型的核心是通过属性的线性组合来预测结果。具体形式为&#xff1a; 其中&#xff0c;w 是…

基于深度学习的花卉智能分类识别系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 传统的花卉分类方法通常依赖于专家的知识和经验&#xff0c;这种方法不仅耗时耗力&#xff0c;而且容易受到主观因素的影响。本系统利用 TensorFlow、Keras 等深度学习框架构建卷积神经网络&#…

PLC通信协议的转化

在自动化程序设计中&#xff0c;常常需要对通信协议进行相互转化。例如&#xff0c;某个控制器需要通过PLC控制设备的某个部件的运动&#xff0c;但PLC只支持ModbusTCP协议&#xff0c;而控制器只支持CanOpen通讯协议。这时&#xff0c;就需要一个网关进行通信协议的转化。网关…

Thymeleaf模版引擎

Thymeleaf是面向Web和独立环境的现代服务器端Java模版引擎&#xff0c;能够处理HTML、XML、JavaScript、CSS甚至纯文本。Thymeleaf旨在提供一个优雅的、高度可维护的创建模版的方式。为了实现这一目标&#xff0c;Thymeleaf建立在自然模版的概念上&#xff0c;将其逻辑注入到模…

VUE3配置路由(超级详细)

第一步创建vue3的项目

(八)使用Postman工具调用WebAPI

访问WebAPI的方法&#xff0c;Postman工具比SoapUI好用一些。 1.不带参数的get请求 [HttpGet(Name "GetWeatherForecast")] public IEnumerable<WeatherForecast> Get() {return Enumerable.Range(1, 5).Select(index > new WeatherForecast{Date DateT…

【TabBar嵌套Navigation案例-JSON的简单使用 Objective-C语言】

一、JSON的简单使用 1.我们先来看一下示例程序里边,产品推荐页面, 在我们这个产品推荐页面里面, 它是一个CollectionViewController,注册的是一个xib的一个类型,xib显示这个cell,叫做item,然后,这个邮箱大师啊,包括这个图标,以及这些东西,都是从哪儿来的呢,都是从…

NLP 主要语言模型分类

文章目录 ngram自回归语言模型TransformerGPTBERT&#xff08;2018年提出&#xff09;基于 Transformer 架构的预训练模型特点应用基于 transformer&#xff08;2017年提出&#xff0c;attention is all you need&#xff09;堆叠层数与原transformer 的差异bert transformer 层…

SpringBoot 项目如何使用 pageHelper 做分页处理 (含两种依赖方式)

分页是常见大型项目都需要的一个功能&#xff0c;PageHelper是一个非常流行的MyBatis分页插件&#xff0c;它支持多数据库分页&#xff0c;无需修改SQL语句即可实现分页功能。 本文在最后展示了两种依赖验证的结果。 文章目录 一、第一种依赖方式二、第二种依赖方式三、创建数…

低空经济刚需篇:各种道路不畅地区无人机吊装详解

低空经济作为近年来备受关注的新兴经济形态&#xff0c;其核心在于利用3000米以下的低空空域进行各种飞行活动&#xff0c;以无人机、电动垂直起降飞行器(eVTOL)等为载体&#xff0c;推动交通、物流、巡检、农林植保、应急救援等多领域的变革。在道路不畅的地区&#xff0c;无人…