Android11 framework 禁止三方应用开机自启动

Android11应用自启动限制

大纲

  • Android11应用自启动限制
  • 分析
  • 验证猜想:Android11 AOSP是否自带禁止三方应用监听`BOOT_COMPLETED`​
  • 方案
    • 禁止执行非系统应用监听到`BOOT_COMPLETED`​后的代码逻辑
    • 在执行启动时判断其启动的广播接收器
    • 一棍子打死方案(慎用)
    • 补充!!!!!!!

分析

按理说三方应用应该收不到开机启动广播(后文会证实这个说法是假的),但是很神奇的是还是有应用能自启动,体现为比如秋秋,启动后通过ps命令能看到其进程存活,但是静置后进程会启动失败,从而导致被清理

在这里插入图片描述

07-18 05:59:02.132367 938 967 I ActivityManager: Start proc 1930:com.tencent.mobileqq:MSF/u0a110 for broadcast {com.tencent.mobileqq/com.tencent.mobileqq.msf.core.NetConnInfoCenter}

log如上

参照这篇博客Android系统层面限制应用开机自启动详解,此时通过Android Studio查看其apk的manifest,其NetConnInfoCenter​内容如下

可以看到其监听了开机广播外,还监听了各种各样的广播,比如TIMEZONE_CHANGED​,看起来是时区变换的广播,这边尝试了一下在没有启动秋秋的情况下,去切换时区,果然打印了启动秋秋的log,虽然没有启动成功,但是之前在这个项目中,自己遇到过秋秋启动成功的情况,所以还是要处理

在这里插入图片描述

<receiverandroid:name="com.tencent.mobileqq.msf.core.NetConnInfoCenter"android:exported="false"android:process=":MSF"><intent-filterandroid:priority="2147483647"><actionandroid:name="android.intent.action.BOOT_COMPLETED" /></intent-filter><intent-filter><actionandroid:name="android.intent.action.MY_PACKAGE_REPLACED" /></intent-filter><intent-filter><actionandroid:name="android.net.conn.CONNECTIVITY_CHANGE" /></intent-filter><intent-filter><actionandroid:name="android.intent.action.TIME_SET" /></intent-filter><intent-filter><actionandroid:name="android.intent.action.TIMEZONE_CHANGED" /></intent-filter><intent-filter><actionandroid:name="com.tencent.mobileqq.rdm.report" /></intent-filter><intent-filter><actionandroid:name="com.tencent.mobileqq.msf.receiveofflinepush" /></intent-filter><intent-filter><actionandroid:name="com.tencent.mobileqq.msf.offlinepushclearall" /></intent-filter><intent-filter><actionandroid:name="com.tencent.mobileqq.msf.receiveofflinepushav" /></intent-filter><intent-filter><actionandroid:name="com.tencent.mobileqq.msf.offlinepushclearallav" /></intent-filter><intent-filter><actionandroid:name="com.tencent.mobileqq.msf.startmsf" /></intent-filter><intent-filter><actionandroid:name="android.intent.action.MEDIA_BAD_REMOVAL" /><actionandroid:name="android.intent.action.MEDIA_EJECT" /><actionandroid:name="android.intent.action.MEDIA_MOUNTED" /><actionandroid:name="android.intent.action.MEDIA_REMOVED" /><actionandroid:name="android.intent.action.MEDIA_SCANNER_FINISHED" /><actionandroid:name="android.intent.action.MEDIA_SCANNER_STARTED" /><actionandroid:name="android.intent.action.MEDIA_SHARED" /><actionandroid:name="android.intent.action.MEDIA_UNMOUNTED" /><dataandroid:scheme="file" /></intent-filter><intent-filter><actionandroid:name="android.net.wifi.WIFI_STATE_CHANGED" /></intent-filter></receiver>

验证猜想:Android11 AOSP是否自带禁止三方应用监听BOOT_COMPLETED

因为前面提到Android11三方应用到底能不能监听开机广播,这边写了个demo,仅监听BOOT_COMPLETED​,重启后验证,进程会正常被创建,所以Android11并没有原生的禁止三方监听BOOT_COMPLETED​的策略。至少AOSP没有,国内手机带此功能是手机厂商自己实现的

public class BootReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {Intent mIntent = new Intent(context, MainActivity.class);mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(mIntent);          // 启动你的应用程序}}
}<uses-permissionandroid:name="android.permission.RECEIVE_BOOT_COMPLETED" /><receiver android:name=".BootReceiver"android:exported="false"><intent-filter><action android:name="android.intent.action.BOOT_COMPLETED" /></intent-filter>
</receiver>

在这里插入图片描述

在这里插入图片描述

结论:Android11上普通三方应用是可以接收到开机广播的,此时如果应用去自启动,后台会有进程存活,因此需要做处理

方案

禁止执行非系统应用监听到BOOT_COMPLETED​后的代码逻辑

在处理广播的地方 过滤掉所有的三方应用,并预留白名单

在AMS的broadcastIntentLocked​方法中

diff --git a/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java b/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
index f26ae929ef..6b906ced4f 100644
--- a/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16736,7 +16736,30 @@ public class ActivityManagerService extends IActivityManager.Stubfinal int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,@Nullable String callerFeatureId, Intent intent, String resolvedType,IIntentReceiver resultTo, int resultCode, String resultData,Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,boolean ordered, boolean sticky, int callingPid, int callingUid, int realCallingUid,int realCallingPid, int userId, boolean allowBackgroundActivityStarts,@Nullable int[] broadcastWhitelist) {...if (receivers != null) {String skipPackages[] = null;if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())|| Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())|| Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {Uri data = intent.getData();if (data != null) {String pkgName = data.getSchemeSpecificPart();if (pkgName != null) {skipPackages = new String[] { pkgName };}}} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-            }
+            // add by xumaoxin start
+            } else if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+                //Resources res = mContext.getResources();
+                //String[] blacklist = res.getStringArray(com.android.internal.R.array.blacklist_boot_receiver);
+                List<String> skipList = new ArrayList<>();
+                Set<String> whitelist = new ArraySet<>();
+                whitelist.add("com.example.test11");
+
+                for (int i = 0, j = receivers.size(); i < j; i++) {
+                    ResolveInfo curt = (ResolveInfo) receivers.get(i);
+                    String curPkgName = curt.activityInfo.packageName;
+                    if ((curt.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                        //Slog.i("ActivityManager", "xumaoxin: " + curPkgName + " is system app, allow!");
+                    } else if (!whitelist.contains(curPkgName)) {
+                        Slog.i("ActivityManager", "xumaoxin: " + curPkgName + " is not system app and not whitelist, not allow!");
+                        skipList.add(curPkgName);
+                    }else {
+                        Slog.i("ActivityManager", "xumaoxin: " + curPkgName + " is not system app but is whitelist, allow!");
+                    }
+                }
+                skipPackages = skipList.toArray(new String[0]);
+                Slog.i("ActivityManager", "xumaoxin: skipPackages= " + Arrays.toString(skipPackages));
+                //add by xumaoxin end
+            } if (skipPackages != null && (skipPackages.length > 0)) {for (String skipPackage : skipPackages) {if (skipPackage != null) {

判断是不是系统应用

curt.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 //是系统应用

注意:此处只能禁止执行skipPackages中的应用去监听到BOOT_COMPLETED后的执行逻辑,如果应用只做了这一个监听来自启,比如我前面写的demo,这样就足够了,但是国内三方app一般会想方设法地自启动,这边验证了秋秋还是会启动起来,会有进程存在,继续处理。

在执行启动时判断其启动的广播接收器

注意:同样需要处理秋秋的同学请一定看到最后,这一节的方案不完善

基于log

ActivityManager: Start proc 2962:com.tencent.mobileqq:MSF/u0a112 for broadcast {com.tencent.mobileqq/com.tencent.mobileqq.msf.core.NetConnInfoCenter}

这里秋秋自己的广播接收器叫com.tencent.mobileqq/com.tencent.mobileqq.msf.core.NetConnInfoCenter​,我们先找到这行log打印的地方

ActivityManager为tag的,在server/am下去grep就好,找到其文件

frameworks\base\services\core\java\com\android\server\am\ProcessList.java

    boolean handleProcessStartedLocked(ProcessRecord app, int pid, boolean usingWrapper,long expectedStartSeq, boolean procAttached) {mPendingStarts.remove(expectedStartSeq);final String reason = isProcStartValidLocked(app, expectedStartSeq);if (reason != null) {Slog.w(TAG_PROCESSES, app + " start not valid, killing pid=" +pid+ ", " + reason);app.pendingStart = false;killProcessQuiet(pid);Process.killProcessGroup(app.uid, app.pid);noteAppKill(app, ApplicationExitInfo.REASON_OTHER,ApplicationExitInfo.SUBREASON_INVALID_START, reason);return false;}......StringBuilder buf = mStringBuilder;buf.setLength(0);buf.append("Start proc ");buf.append(pid);buf.append(':');buf.append(app.processName);buf.append('/');UserHandle.formatUid(buf, app.startUid);if (app.isolatedEntryPoint != null) {buf.append(" [");buf.append(app.isolatedEntryPoint);buf.append("]");}buf.append(" for ");buf.append(app.hostingRecord.getType());if (app.hostingRecord.getName() != null) {buf.append(" ");buf.append(app.hostingRecord.getName());}

能看到buf.append("Start proc ");​这里开始的buf记录和我们log是一样的,所以后面的app.hostingRecord.getType()​对应log中的广播接收器broadcast​,而app.hostingRecord.getName()​对应log中的{com.tencent.mobileqq/com.tencent.mobileqq.msf.core.NetConnInfoCenter}

那就好处理了,判断其name,如果是NetConnInfoCenter​,那就不允许启动。

代码往前,能看到原生有一个退出的逻辑,通过final String reason = isProcStartValidLocked(app, expectedStartSeq);​赋值

isProcStartValidLocked​方法中加入秋秋的判断,返回一个reason,这样此次启动能通过原生的逻辑进行拦截,比较安全

diff --git a/frameworks/base/services/core/java/com/android/server/am/ProcessList.java b/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
index 9d4deb4d5b..271f99138f 100644
--- a/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
+++ b/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
@@ -2462,6 +2462,14 @@ public final class ProcessList {@GuardedBy("mService")private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {StringBuilder sb = null;
+        if (app.hostingRecord.getName() != null) {
+            String name = app.hostingRecord.getName();
+            Slog.i("ActivityManager", "xumaoxin: broadcast= " + name);
+            if (name.contains("NetConnInfoCenter")) {
+                if (sb == null) sb = new StringBuilder();
+                sb.append("dont allow qq auto start");
+            }
+        }if (app.killedByAm) {if (sb == null) sb = new StringBuilder();sb.append("killedByAm=true;");

编译push验证,秋秋在设备重启后不会再重启,会被拦截

在这里插入图片描述

抑制秋秋自启动成功!同时不会影响用户手动点击图标等方式的启动

应用多了可以写成白名单,并配置到xml中

注意:此方案的弊端就是只能对特定的应用单独加判断逻辑,不能适用于所有应用

一棍子打死方案(慎用)

观察三方应用是否都是通过broadcast​类型进行自启动的,能否判断type​是否是broadcast​来进行通用过滤?风险就是这样一来一棍子打死了,等于不再允许三方应用通过广播方式进行启动

diff --git a/frameworks/base/services/core/java/com/android/server/am/ProcessList.java b/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
index 9d4deb4d5b..03a4c1ddb2 100644
--- a/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
+++ b/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
@@ -2462,6 +2462,19 @@ public final class ProcessList {@GuardedBy("mService")private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {StringBuilder sb = null;
+        /*if (app.hostingRecord.getName() != null) {
+            String name = app.hostingRecord.getName();
+            Slog.i("ActivityManager", "xumaoxin: broadcast= " + name);
+            if (name.contains("NetConnInfoCenter")) {
+                if (sb == null) sb = new StringBuilder();
+                sb.append("dont allow qq auto start");
+            }
+        }*/
+        if (((app.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) && "broadcast".equals(app.hostingRecord.getType())) {
+            String name = app.hostingRecord.getName();
+            if (sb == null) sb = new StringBuilder();
+            sb.append("dont allow thirdapp auto start by broadcast: " + name);
+        }if (app.killedByAm) {if (sb == null) sb = new StringBuilder();sb.append("killedByAm=true;");

编译后验证秋秋确实是没有问题,主要不确定其他的广播场景会不会被拦截下来,毕竟一棍子打死的办法杀伤面有点大

在这里插入图片描述

尽量还是使用第一种+第二种,不让三方应用执行开机广播逻辑+特殊应用特殊过滤。第三种方案慎用!

补充!!!!!!!

对于秋秋前面通过广播想启动的只是其一个子进程,在登陆的时候会用到,如果一直不让其自动启动,会导致登陆超时。
所以还需要加入判断,启动的时候秋秋主进程是否存活,如果存活,我们认为秋秋不是在后台想自启动,而是通过正常途径启动子进程。
而进行是否存活的判断调用较频繁,而我们的系统应用不需要进行此判断,所以调用前再加入是否系统应用的判断。实现代码diff如下

--- a/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
+++ b/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
@@ -2459,14 +2459,27 @@ public final class ProcessList {return success ? app : null;}+    public boolean isAppRunning(String packageName) {
+        List<ActivityManager.RunningAppProcessInfo> runningProcesses = mService.getRunningAppProcesses();
+        if (runningProcesses.size() > 0) {
+            for (ActivityManager.RunningAppProcessInfo info : runningProcesses) {
+                if (info.processName.equals(packageName)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+@GuardedBy("mService")private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {StringBuilder sb = null;// add by sprocomm start
-        if (app.hostingRecord.getName() != null) {
+		 //非系统应用执行if中的逻辑
+        if (((app.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) && app.hostingRecord.getName() != null) {String name = app.hostingRecord.getName();
-            Slog.i("ActivityManager", "xumaoxin: broadcast= " + name);
-            if (name.contains("NetConnInfoCenter")) {
+			 //秋秋是否存活,这个包名,可以动态到白名单中,和NetConnInfoCenter这个组件名放一起
+            boolean isRunning = isAppRunning("com.tencent.mobileqq");
+            Slog.i("ActivityManager", "xumaoxin: com.tencent.mobileqq isRunning= "+ isRunning + ", broadcast= " + name);
+            if (!isRunning && name.contains("NetConnInfoCenter")) {if (sb == null) sb = new StringBuilder();sb.append("dont allow qq auto start");}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DevExpress WPF中文教程 - 为项目添加GridControl并将其绑定到数据

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

【思科】链路聚合实验配置和背景

【思科】链路聚合实验配置和背景 背景链路聚合基本概念链路聚合聚合接口 思科链路聚合协议01.PAgP协议02.LACP协议 思科链路聚合模式LACP协议模式PAgP协议模式ON模式 实验准备配置二层链路聚合LACP协议模式SW1SW2PC1PC2查看LACP聚合组建立情况查看LACP聚合端口情况查看逻辑聚合…

2-40 基于Matlab编写的3维FDTD(时域有限差分算法)计算了球的RCS经典散射问题

基于Matlab编写的3维FDTD(时域有限差分算法)计算了球的RCS经典散射问题&#xff0c;采用PEC作边界&#xff0c;高斯波束激励。程序已调通&#xff0c;可直接运行。 2-40 3维FDTD 时域有限差分算法 - 小红书 (xiaohongshu.com)

Python自动化DevOps任务入门

目录 Python自动化DevOps任务入门 一、环境和工具配置 1. 系统环境与Python版本 2. 虚拟环境搭建 3. 必要的库安装 二、自动化部署 1. 使用Fabric进行流式部署 2. 使用Ansible编写部署剧本 三、持续集成和测试 1. 配置CI/CD工具 选择工具 配置工具 构建和测试自动…

深入理解设计模式:六大经典模式解析

深入理解设计模式&#xff1a;六大经典模式解析 1. 单例模式&#xff08;Singleton Pattern&#xff09;1.1 概述1.2 示例场景1.3 实现要点 2. 工厂模式&#xff08;Factory Pattern&#xff09;2.1 简单工厂2.2 抽象工厂2.3 示例场景2.4 实现要点 3. 观察者模式&#xff08;Ob…

从输入 URL 到页面展示到底发生了什么

从输入 URL 到页面展示到底发生了什么呢&#xff1f; 1.在浏览器输入一个域名回车 2.首先会先检查浏览器缓存里是否有缓存资源&#xff0c;如果缓存中有&#xff0c;会直接在浏览器上显示页面内容&#xff0c;如果没有&#xff0c;在发送http请求之前&#xff0c;浏览器会向D…

设计模式-抽象工厂

抽象工厂属于创建型模式。 抽象工厂和工厂设计模式的区别&#xff1a; 工厂模式的是设计模式中最简单的一种设计模式&#xff0c;主要设计思想是&#xff0c;分离对象的创建和使用&#xff0c;在Java中&#xff0c;如果需要使用一个对象时&#xff0c;需要new Class()&#xff…

Step-DPO 论文——数学大语言模型理解

论文题目&#xff1a;STEP-DPO: STEP-WISE PREFERENCE OPTIMIZATION FOR LONG-CHAIN REASONING OF LLMS 翻译为中文就是&#xff1a;“LLMs长链推理的逐步偏好优化” 论文由港中文贾佳亚团队推出&#xff0c;基于推理步骤的大模型优化策略&#xff0c;能够像老师教学生一样优…

Vulnhub靶场DC-7练习

目录 0x00 准备0x01 主机信息收集0x02 站点信息收集1. 获取用户名/密码2. ssh连接目标主机3. drush命令修改Drupal密码 0x03 漏洞查找与利用1. Drupal写入php木马2. 连接shell3. 反弹shell并提权 0x04 总结 0x00 准备 下载链接&#xff1a;https://download.vulnhub.com/dc/DC-…

深入理解Linux网络(四):TCP接收阻塞

TCP socket 接收函数 recv 发出 recvfrom 系统调用。 进⼊系统调⽤后&#xff0c;⽤户进程就进⼊到了内核态&#xff0c;通过执⾏⼀系列的内核协议层函数&#xff0c;然后到 socket 对象的接收队列中查看是否有数据&#xff0c;没有的话就把⾃⼰添加到 socket 对应的等待队列⾥…

c++网络编程实战——开发基于ftp协议的文件传输模块(二) 配置ftp服务与手动执行ftp命令

配置FTP服务 一.前言 博主的环境是阿里云服务器&#xff0c;操作系统版本为 ubuntu20.04,一下所有操作都基于以上环境下进行的操作&#xff0c;同时为了简化操作我将开放同一个云服务器的不同端口&#xff0c;让它同时充当服务端和客户端&#xff0c;大家如果想测试效果更好且…

巧用Vue3 composition api的计算属性实现扁平化tree连线

本示例节选自vue3最新开源组件实战教程大纲&#xff08;持续更新中&#xff09;的tree组件开发部分。将进一步把基于Vue3 composition api的computed计算属性特性应用到组件开发实战中&#xff0c;继续以最佳实践的方式呈现给大家。 下面我们要实现的是扁平化的dom结构所呈现的…

【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理

初阶数据结构相关知识点可以通过点击以下链接进行学习一起加油&#xff01;时间与空间复杂度的深度剖析深入解析顺序表:探索底层逻辑深入解析单链表:探索底层逻辑深入解析带头双向循环链表:探索底层逻辑深入解析栈:探索底层逻辑深入解析队列:探索底层逻辑深入解析循环队列:探索…

最新缺失msvcp140.dll的多种解决方法,有效解决电脑dll问题

msvcp140.dll 是一个关键的动态链接库&#xff08;DLL&#xff09;文件&#xff0c;属于 Microsoft Visual C 2015 Redistributable 的一部分。它为使用 Microsoft Visual C 编译的应用程序提供了运行时支持&#xff0c;确保这些应用程序能够正常运行。以下是对 msvcp140.dll 的…

RPA鼠标按键使用技巧

RPA鼠标按键使用技巧 Mouse.MouseAuto.Action命令出错&#xff0c;调用的目标发生了异常&#xff0c;Exception in Mouse.Action元素不可用怎么解决 出现问题 1.想要实现的效果鼠标移动到录屏工具的小球上2.点击开始按钮开始录屏现象&#xff0c;鼠标没有移动痕迹&#xff0c…

Docker无法拉取镜像!如何解决?

问题现象 继去年Docker Hub被xxx后&#xff0c;各大NAS的注册表均出现问题&#xff0c;例如群晖的Docker套件注册表无法连接&#xff08;更新至DSM7.2版本后恢复&#xff09;。而在今年2024年6月初&#xff08;约2024.06.06&#xff09;&#xff0c;NAS中最重要的工具Docker又…

Flink源码学习资料

Flink系列文档脑图 由于源码分析系列文档较多&#xff0c;本人绘制了Flink文档脑图。和下面的文档目录对应。各位读者可以选择自己感兴趣的模块阅读并参与讨论。 此脑图不定期更新中…… 文章目录 以下是本人Flink 源码分析系列文档目录&#xff0c;欢迎大家查阅和参与讨论。…

爬取百度图片,想爬谁就爬谁

前言 既然是做爬虫&#xff0c;那么肯定就会有一些小心思&#xff0c;比如去获取一些自己喜欢的资料等。 去百度图片去抓取图片吧 打开百度图片网站&#xff0c;点击搜索xxx&#xff0c;打开后&#xff0c;滚动滚动条&#xff0c;发现滚动条越来越小&#xff0c;说明图片加载…

springboot 配置 spring data redis

1、在pom.xml引入父依赖spring-boot-starter-parent&#xff0c;其中2.7.18是最后一版支持java8的spring <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.18</…

详解数据结构之二叉树(堆)

详解数据结构之二叉树(堆) 树 树的概念 树是一个非线性结构的数据结构&#xff0c;它是由 n(n>0)个有限节点组成的一个具有层次关系的集合&#xff0c;它的外观形似一颗倒挂着的树&#xff0c;根朝上&#xff0c;叶朝下&#xff0c;所以称呼为树。每颗子树的根节点有且只…