ANR底层源码分析之Service篇

news2024/11/19 8:25:37

ANR底层源码分析之Service篇

    • 一、前言
    • 二、Service-ANR原理
      • 2.1 Service启动ANR原理简述
      • 2.2 前台Service VS 后台Service的区别
        • 2.2.1 前台Service
        • 2.2.3 后台Service
      • 2.3 Service启动ANR源码执行过程
        • 2.3.1 ActiveServices#bringUpServiceLocked
        • 2.3.2 ActiveServices#realStartServiceLocked
        • 2.3.3 埋炸弹过程:ActiveServices#bumpServiceExecutingLocked
        • 2.3.4 拆炸弹过程:ActiveServices#serviceDoneExecutingLocked
        • 2.3.5 炸弹爆炸出发ANR弹窗过程

一、前言

    在Service组件StartService()方式启动流程分析文章中,针对Context#startService()启动Service流程分析了源码,其实关于Service启动还有一个比较重要的点是Service启动的ANR,因为因为线上出现了上百例的"executing service " + service.shortName的异常。

二、Service-ANR原理

2.1 Service启动ANR原理简述

    Service的ANR触发原理,是在启动Service前使用Handler发送一个延时的Message(埋炸弹过程),然后在Service启动完成后remove掉这个Message(拆炸弹过程)。如果在指定的延迟时间内没有remove掉这个Message,那么就会触发ANR(没有在炸弹爆炸前拆掉就会爆炸),弹出AppNotResponding的弹窗。
Android-应用程序无响应
    其实这个机制跟Windows/MacOS的应用程序无响应,是类似的交互设计。
Windows-应用程序无响应
Mac-应用程序无响应

2.2 前台Service VS 后台Service的区别

2.2.1 前台Service

    前台Service是一种在通知栏中显示持续通知的服务,它通常用于执行用户明确知晓的任务,比如音乐播放器、定位服务等。前台Service在系统内部被视为用户正在主动使用的组件,因此它具有更高的优先级和较低的系统资源限制。在使用前台Service时,必须在通知栏中显示一个通知,以告知用户有一个正在运行的Service,并且通常还应该提供一些与该Service相关的有用信息。

2.2.3 后台Service

    后台Service是一种不会在通知栏中显示通知的服务。它用于执行一些不需要用户直接交互或注意的任务,例如数据同步、网络请求等。后台Service具有较低的系统优先级,系统可能会在资源紧张的情况下终止这些服务,以释放资源。

2.3 Service启动ANR源码执行过程

    ps: 还是基于Android SDK28源码分析
    基于文章:Service组件StartService()方式启动流程分析的总结,我们已经很清楚,通过startService的方式启动Service的源码过程。因此,本文直接从com.android.server.am.ActiveServices#bringUpServiceLocked方法的源码开始分析,如有不清楚前置的启动流程的同学,可以参考我之前的文章,然后打开AS对照看下这部分的代码。

2.3.1 ActiveServices#bringUpServiceLocked

    private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
            boolean whileRestarting, boolean permissionsReviewRequired)
            throws TransactionTooLargeException {
        if (r.app != null && r.app.thread != null) {
            sendServiceArgsLocked(r, execInFg, false);
            return null;
        }
        realStartServiceLocked(r, app, execInFg);
    }

2.3.2 ActiveServices#realStartServiceLocked

private final void realStartServiceLocked(ServiceRecord r,
        ProcessRecord app, boolean execInFg) throws RemoteException {
    // ...
    // 会转调到scheduleServiceTimeoutLocked(r.app)方法进行埋炸弹操作
	bumpServiceExecutingLocked(r, execInFg, "create");
	mAm.updateLruProcessLocked(app, false, null);
	updateServiceForegroundLocked(r.app, /* oomAdj= */ false);
	// 调整oomAdj优先级
	mAm.updateOomAdjLocked();
	boolean created = false;
	try {
		mAm.notifyPackageUse(r.serviceInfo.packageName,
                                 PackageManager.NOTIFY_PACKAGE_USE_SERVICE);
        app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
		app.thread.scheduleCreateService(r, r.serviceInfo,
        mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
        app.repProcState);
        r.postNotification();
        created = true;
    } catch (DeadObjectException e) {
    	// 进程已经kill了,或者binder通信失败了,直接kill掉进程,
    	// 通过app.thread向ActivityThread.H发消息报告程序奔溃了
    	mAm.appDiedLocked(app);
    	throw e;
    } finally {
    	if (!created) {
           // Keep the executeNesting count accurate.
           final boolean inDestroying = mDestroyingServices.contains(r);
           // serviceDoneExecutingLocked方法内会拆除炸弹
           serviceDoneExecutingLocked(r, inDestroying, inDestroying);

           // Cleanup.
           if (newService) {
               app.services.remove(r);
               r.app = null;
           }

           // Retry.
           if (!inDestroying) {
              scheduleServiceRestartLocked(r, false);
           }
       }
    	// ...
    }
}
  • 从如上realStartSreviceLocked()实现逻辑看,如果是新创建(非首次启动的Serive,冷启动)Service,ANR超时时间的计算是从创建Service实例->

2.3.3 埋炸弹过程:ActiveServices#bumpServiceExecutingLocked

private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
	// ...
	scheduleServiceTimeoutLocked(r.app);
	// ...
}
  • com.android.server.am.ActiveServices#scheduleServiceTimeoutLocked
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
    if (proc.executingServices.size() == 0 || proc.thread == null) {
        return;
    }
    Message msg = mAm.mHandler.obtainMessage(
            ActivityManagerService.SERVICE_TIMEOUT_MSG);
    msg.obj = proc;
    mAm.mHandler.sendMessageDelayed(msg,
            proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}
  • 这里需要知道,炸弹的爆炸时间在ActiveServices中定义了三个:
// 前台Service的超时时间是20s
static final int SERVICE_TIMEOUT = 20*1000;
// 后台Service的超时时间是200s,是前台超时时间的10倍
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
// How long the startForegroundService() grace period is to get around to
// calling startForeground() before we ANR + stop it.
static final int SERVICE_START_FOREGROUND_TIMEOUT = 10*1000;

2.3.4 拆炸弹过程:ActiveServices#serviceDoneExecutingLocked

private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
        boolean finishing) {
    // ...
	mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); 
	// ...       
}
  • 通过以上源码分析过程,我们可以简单总结成如下时序图,供理解整体过程:
    Service-ANR过程源码时序图

2.3.5 炸弹爆炸出发ANR弹窗过程

    如上文分析的,ANR弹窗其实就是一个sendMessageDelayed()方式发送的一个Message,想要了解ANR炸弹这么爆炸的,其实检索这个what值为ActivityManagerService.SERVICE_TIMEOUT_MSG的消息处理过程即可。
这个消息的Handler对应的handleMessage方法实现代码在AMS.java中。

  • com.android.server.am.ActivityManagerService.MainHandler#handleMessage
final class MainHandler extends Handler {
    public MainHandler(Looper looper) {
        super(looper, null, true);
    }
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            // ...
        	case SERVICE_TIMEOUT_MSG: {
                mServices.serviceTimeout((ProcessRecord)msg.obj);
            } break;
            case SERVICE_FOREGROUND_TIMEOUT_MSG: {
                mServices.serviceForegroundTimeout((ServiceRecord)msg.obj);
            } break;
            case SERVICE_FOREGROUND_CRASH_MSG: {
                mServices.serviceForegroundCrash(
                    (ProcessRecord) msg.obj, msg.getData().getCharSequence(SERVICE_RECORD_KEY));
            } break;
            // ...
        }
    }
  • com.android.server.am.ActiveServices#serviceTimeout
void serviceTimeout(ProcessRecord proc) {
    String anrMessage = null;
    synchronized(mAm) {
        if (proc.executingServices.size() == 0 || proc.thread == null) {
            return;
        }
        final long now = SystemClock.uptimeMillis();
        final long maxTime =  now -
                (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
        ServiceRecord timeout = null;
        long nextTime = 0;
        for (int i=proc.executingServices.size()-1; i>=0; i--) {
            ServiceRecord sr = proc.executingServices.valueAt(i);
            if (sr.executingStart < maxTime) {
                timeout = sr;
                break;
            }
            if (sr.executingStart > nextTime) {
                nextTime = sr.executingStart;
            }
        }
        if (timeout != null && mAm.mLruProcesses.contains(proc)) {
            Slog.w(TAG, "Timeout executing service: " + timeout);
            StringWriter sw = new StringWriter();
            PrintWriter pw = new FastPrintWriter(sw, false, 1024);
            pw.println(timeout);
            timeout.dump(pw, "    ");
            pw.close();
            mLastAnrDump = sw.toString();
            mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
            mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
            // 这一句对于我们分析问题比较关键,如果有Service的ANR,
            // 就会在log中有这样的前缀打印:executing service Service.shortName
            anrMessage = "executing service " + timeout.shortName;
        } else {
            Message msg = mAm.mHandler.obtainMessage(
                    ActivityManagerService.SERVICE_TIMEOUT_MSG);
            msg.obj = proc;
            mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg
                    ? (nextTime+SERVICE_TIMEOUT) : (nextTime + SERVICE_BACKGROUND_TIMEOUT));
        }
    }
    // 真正触发ANR弹窗的位置
    if (anrMessage != null) {
        mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
    }
}

这里记住anrMessage的格式是:executing service Service.shortName,代表的是Service的启动超时。

  • com.android.server.am.AppErrors#appNotResponding
final void appNotResponding(ProcessRecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation) {
	if (mService.mController != null) {
    try {
        // 0 == continue, -1 = kill process immediately
        // !!关键:mService.mController的实现类是:
       // com.android.server.am.ActivityManagerShellCommand.MyActivityController
        int res = mService.mController.appEarlyNotResponding(
                app.processName, app.pid, annotation);
        if (res < 0 && app.pid != MY_PID) {
            app.kill("anr", true);
        }
    } catch (RemoteException e) {
        mService.mController = null;
        Watchdog.getInstance().setActivityController(null);
    }
    long anrTime = SystemClock.uptimeMillis();
    if (ActivityManagerService.MONITOR_CPU_USAGE) {
       mService.updateCpuStatsNow();
    }
    // Unless configured otherwise, swallow ANRs in background processes 
    // & kill the process.
    // 读取开发者选项中的“显示后台ANR”开关
    boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
    Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
    boolean isSilentANR;
    // Don't dump other PIDs if it's a background ANR
    isSilentANR = !showBackground && !isInterestingForBackgroundTraces(app);
    // Log the ANR to the main log.
	StringBuilder info = new StringBuilder();
	info.setLength(0);
	// 这里可以看出写入到日志文件中的格式是:ANR in processName,
	// 比如:我们应用包名是com.techmix.myapp,
	// 那可在搜索时,直接输入ANR in com.techminx.myapp搜索,可更高效定位到ANR的trace位置
	info.append("ANR in ").append(app.processName);
	if (activity != null && activity.shortComponentName != null) {
    	info.append(" (").append(activity.shortComponentName).append(")");
	}
    info.append("\n");
    info.append("PID: ").append(app.pid).append("\n");
    if (annotation != null) {
      // 这里的reason还是serviceTimeout中定义的Service启动的anrMessage字符串:
      // "executing service Service.shortName",没有多余的更明细的分类了,具体是哪一步ANR了。
      info.append("Reason: ").append(annotation).append("\n");
    }
    if (parent != null && parent != activity) {
      info.append("Parent: ").append(parent.shortComponentName).append("\n");
    }
    ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
    // For background ANRs, don't pass the ProcessCpuTracker to
    // avoid spending 1/2 second collecting stats to rank lastPids.
    File tracesFile = ActivityManagerService.dumpStackTraces(true, firstPids,
    (isSilentANR) ? null : processCpuTracker, 
    (isSilentANR) ? null : lastPids, nativePids);
    
    // 写入cpu占用信息到anr log中
    String cpuInfo = null;
    if (ActivityManagerService.MONITOR_CPU_USAGE) {
       mService.updateCpuStatsNow();
       synchronized (mService.mProcessCpuTracker) {
           cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime);
       }
       info.append(processCpuTracker.printCurrentLoad());
       info.append(cpuInfo);
    }
    info.append(processCpuTracker.printCurrentState(anrTime));
    // ANR log写入到dropbox文件夹中,annotation变量就是
    // com.android.server.am.ActiveServices#serviceTimeout中传入的anrMessage变量
    mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
                cpuInfo, tracesFile, null);
    synchronized (mService) {
    	// 静默ANR的定义?
    	if (isSilentANR) {
           app.kill("bg anr", true);
           return;
        }
    	// 通过Handler发送ANR弹窗的dialog,这里直接跟进
    	// ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG这个消息的handleMessage处理逻辑即可
    	Message msg = Message.obtain();
    	msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
    	msg.obj = new AppNotRespondingDialog.Data(app, activity, aboveSystem);
    	// 注意这里的mService是AMS
    	mService.mUiHandler.sendMessage(msg);
    }
}
  • 静默ANR的定义?
  • 弹出ANR弹窗的逻辑代码:com.android.server.am.ActivityManagerService.UiHandler#handleMessage
case SHOW_NOT_RESPONDING_UI_MSG: {
	// 还是转调到AppErrors中的方法去实现了,所以这里只是用AMS中的UiHandler切换了一下线程而已
	// 最开始的startService方法,从应用主线程
	// ContextImpl#startService->AMS#startService(),后者其实是执行在binder线程池的线程里
	// 面的,是子线程。所以这里通过消息的方式切到主线程
    mAppErrors.handleShowAnrUi(msg);
    ensureBootCompleted();
   } break;
  • com.android.server.am.AppErrors#handleShowAnrUi
	// 跟ANR相关的变量直接存储在了ProcessRecord.java类中,每个进程单独维护一个
	boolean notResponding;      // does the app have a not responding dialog?
	Dialog anrDialog;           // dialog being displayed due to app not resp.

    void handleShowAnrUi(Message msg) {
        Dialog dialogToShow = null;
        synchronized (mService) {
            AppNotRespondingDialog.Data data = (AppNotRespondingDialog.Data) msg.obj;
            final ProcessRecord proc = data.proc;
            if (proc == null) {
                Slog.e(TAG, "handleShowAnrUi: proc is null");
                return;
            }
            if (proc.anrDialog != null) {
                Slog.e(TAG, "App already has anr dialog: " + proc);
                MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
                        AppNotRespondingDialog.ALREADY_SHOWING);
                return;
            }
			// 这个ANR的广播,应用进程如果注册了能接收到吗?
            Intent intent = new Intent("android.intent.action.ANR");
            if (!mService.mProcessesReady) {
                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                        | Intent.FLAG_RECEIVER_FOREGROUND);
            }
            mService.broadcastIntentLocked(null, null, intent,
                    null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                    null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */);

            boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
                    Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
            if (mService.canShowErrorDialogs() || showBackground) {
                dialogToShow = new AppNotRespondingDialog(mService, mContext, data);
                proc.anrDialog = dialogToShow;
            } else {
                MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
                        AppNotRespondingDialog.CANT_SHOW);
                // 如果ANR弹窗是关闭状态下,直接kill当前应用进程了
                // 跟ANR弹窗中点关闭应用一样,多是调用AMS#killAppAtUsersRequest方法
                // 关闭当前进程
                mService.killAppAtUsersRequest(proc, null);
            }
        }
        // If we've created a crash dialog, show it without the lock held
        if (dialogToShow != null) {
            dialogToShow.show();
        }
    }
  • com.android.server.am.AppErrors#killAppAtUserRequestLocked
  void killAppAtUserRequestLocked(ProcessRecord app, Dialog fromDialog) {
        app.crashing = false;
        app.crashingReport = null;
        app.notResponding = false;
        app.notRespondingReport = null;
        if (app.anrDialog == fromDialog) {
            app.anrDialog = null;
        }
        if (app.waitDialog == fromDialog) {
            app.waitDialog = null;
        }
        // 这里的MY_PID是定义在AMS中的:static final int MY_PID = myPid();
        // 所以这里只要是有效的应用pid,都是能进入if逻辑分支中的
        if (app.pid > 0 && app.pid != MY_PID) {
            handleAppCrashLocked(app, "user-terminated" /*reason*/,
                    null /*shortMsg*/, null /*longMsg*/, null /*stackTrace*/, null /*data*/);
            app.kill("user request after error", true);
        }
    }

app.kill()调用的是ProcessRecord#kill(),最终转调到AMS#killProcessGroup()方法了。这里AMS方式kill掉进程的,在Android的logcat中其实都能搜索到,Activity Manager killing的字样的。

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

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

相关文章

数据安全

数据的备份与恢复 1. 数据备份技术 任何数据在长期使用过程中&#xff0c;都存在一定的安全隐患。由于认为操作失误或系统故障&#xff0c;例如认为错误、程序出错、计算机失效、灾难和偷窃&#xff0c;经常造成数据丢失&#xff0c;给个人和企业造成灾难性的影响。在这种情况…

LeetCode 刷题 数据结构 数组 27 移除元素

难度&#xff1a;简单 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考虑…

SpringBoot自动装配介绍

SpringBoot是对Spring的一种扩展&#xff0c;其中比较重要的扩展功能就是自动装配&#xff1a;通过注解对常用的配置做默认配置&#xff0c;简化xml配置内容。本文会对Spring的自动配置的原理和部分源码进行解析&#xff0c;本文主要参考了Spring的官方文档。 自动装配的组件 …

西安科技大学:励志图存,自强不息

今天就让我带着大家一起来看“采矿冶金历史悠久&#xff0c;安全工程国重学科”的西安科技大学吧&#xff01; 一、高校概况 西安科技大学&#xff08;Xi’an University of Science and Technology&#xff09;&#xff0c;简称西科大、西安科大&#xff0c;位于陕西省西安市…

算法通关村第二关——指定区间反转的问题解析

题目类型 指定区间反转 题目描述 给你单链表的头指针 head 和两个整数left 和right &#xff0c;其中left < right 。请反转从位置left到位置right的链表节点&#xff0c;返回反转后的链表 示例 输入&#xff1a;head [1,2,3,4,5] , left 2 , right 4 输出&#xff1a;[…

「网络编程」传输层协议_ TCP协议学习_及原理深入理解(二 - 完结)[万字详解]

「前言」文章内容大致是传输层协议&#xff0c;TCP协议讲解的第二篇&#xff0c;续上篇TCP。 「归属专栏」网络编程 「主页链接」个人主页 「笔者」枫叶先生(fy) 目录 二、TCP协议2.9 TCP连接管理机制2.9.1 三次握手2.9.2 四次挥手2.9.3 演示查看TIME_WAIT和CLOSE_WAIT状态2.9.…

基于SpringBoot+Vue的车辆充电桩管理系统设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

MOS管的体二极管是怎么形成的

一般MOS管的源极和漏极之间会存在体二极管&#xff0c;对于N沟道的MOS管&#xff0c;体二极管由源极指向漏极。 对于PMOS管&#xff0c;体二极管由漏极指向源极&#xff0c;那么MOS管为什么会有这个体二极管呢&#xff1f; 我们看到这个N沟道MOS管的结构&#xff0c;源极和漏极…

单片机第一季:零基础12——I2C和EEPROM

目录 1&#xff0c;EEPROM 2&#xff0c;I2C 2.1&#xff0c;I2C物理层 2.2&#xff0c;I2C协议层 3&#xff0c;AT24C02介绍 4&#xff0c;代码 1&#xff0c;EEPROM 为什么需要EEPROM&#xff1f; 单片机内部的ROM只能在程序下载时进行擦除和改写&#xff0c;但是…

西安电子科技大学计算机考研分析

关注我们的微信公众号 姚哥计算机考研 更多详情欢迎咨询 西安电子科技大学&#xff08;A-&#xff09;考研难度&#xff08;☆☆☆☆&#xff09; 西安电子科技大学计算机科学与技术学院&#xff08;国家示范性软件学院&#xff09;始于1958年中央军委批复设立的导弹系统专用…

17 反显、修改、提交图书信息功能实战

前言 上节回顾 上一节&#xff0c;我们针对图书列表做了实战练习&#xff0c;主体内容包括顶部的检索区域&#xff0c;中间的el-table数据区域&#xff0c;和底部的el-pagination分页区域&#xff0c;并针对每一块做了讲解&#xff0c;还不太明白上下文的同学可以回过头去看一…

Linux标准库API

目录 1.字符串函数 2.数据转换函数 3.格式化输入输出函数 4.权限控制函数 5.IO函数 6.进程控制函数 7.文件和目录函数 1.字符串函数 2.数据转换函数 3.格式化输入输出函数 #include<stdarg.h>void test(const char * format , ...){va_list ap;va_start(ap,format…

自监督去噪:Noise2Noise原理及实现(Pytorch)

文章地址&#xff1a;https://arxiv.org/abs/1803.04189 ICML github 代码: https://github.com/NVlabs/noise2noise 本文整理和参考代码: https://github.com/shivamsaboo17/Deep-Restore-PyTorch 文章目录 1. 理论背景2. 实验结果3. 代码实现(1) 网络结构(2) 数据加载(3) 网络…

Linux--验证命令行上运行的程序的父进程是bash

1.输入以下代码&#xff1a; #include <stdio.h> #include <unistd.h> int main() {printf("hello world: pid: %d, ppid: %d\n",getpid(),getppid());return 0; }2.编译得到可执行程序​​​ 3.运行得到ppid 4.输入指令 ps axj | head -1 &&am…

Stable Diffusion ControlNet 完全指南

ControlNet 是 Stable Diffusion中的一种扩展模型&#xff0c;通过这种扩展模型&#xff0c;我们能够将参考图像的构图&#xff08;compositions &#xff09;或者人体姿势迁移到目标图像。 资深 Stable Diffusion 用户都知道&#xff0c;很难精准控制Stable Diffusion生成的图…

protobuf配置过程

一、配置过程 vs2022 第一次下载cmake 3.17 x64.msi &#xff0c; 发现没有vs2022选项。 第二次下载最新版本cmake 3.27 x64.msi &#xff0c; 发现不兼容vs2022 &#xff0c; 会闪退&#xff1b; 第三次下载了倒数第二新的版本cmake 3.26 x64.msi &#xff0c; 这次完美gen…

简单认识redis高可用实现方法

文章目录 一、redis群集三种模式二、 Redis 主从复制1、简介2、作用&#xff1a;3、流程&#xff1a;4.配置主从复制 三、Redis 哨兵模式1、简介2、原理:3、作用&#xff1a;4、哨兵结构由两部分组成&#xff0c;哨兵节点和数据节点&#xff1a;5、故障转移机制&#xff1a;6、…

C/C++ 线程池工作原理 C代码实现

1. 线程池作用 如果多次使用线程&#xff0c;那么就需要多次的创建并撤销线程。但是创建/撤销的过程会消耗资源。线程池是一种数据结构&#xff0c;其中维护着多个线程&#xff0c;这避免了在处理短时间任务时&#xff0c;创建与销毁线程的代价。即在程序开始运行前预先创建一…

day15 | 110.平衡二叉树 257.二叉树的所有路径 404.左叶子之和

文章目录 一、平衡二叉树二、[回溯小难]二叉树的所有路径三、左叶子之和 一、平衡二叉树 110.平衡二叉树 依旧是使用后序遍历来统计高度。 递归过程中&#xff0c;发现某节点的左右子树的高度差超过了1&#xff0c;我们就直接返回-1&#xff0c;不返回节点的高度了。 递归函…

C/C++开发,opencv与qt结合播放视频

目录 一、qt_ui创建 1.1 ui设置 1.2 ui及代码输出保存 二、创建工程 2.1 工程目录及编译设置 2.2 源码设计 三、编译及测试 3.1 程序编译 3.2 程序运行 首先声明&#xff0c;这是一个OpenCV 3学习文档的案例&#xff0c;但是说明有些过于省略&#xff0c;只有一些简短的代码…