当前位置: 首页 > news >正文

Android Framework AMS(14)ContentProvider分析-1(CP组件应用及开机启动注册流程解读)

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


本章关键点总结 & 说明:

说明:本章节主要解读ContentProvider组件的基本知识。关注思维导图中左上侧部分即可。

有了前面activity组件分析、service组件分析、广播组件分析的基础,基于此,接下来我们来分析ContentProvider组件的基本流程,ContentProvider主要涉及2个:

  1. ContentProvider的注册,开即启动后解析和处理ContentProvider组件。
  2. getContentResolver,然后执行对应ContentProvider.query方法。

本章,我们先对ContentProvider组件的应用有基本的了解,再详细分析第一个流程。

1 ContentProvider组件解读

1.1 理解ContentProvider组件

ContentProvider 是 Android 框架中的一个核心组件,它专门设计用于在应用之间共享数据。它通过URI标识数据,并提供一组标准的CRUD操作(创建、读取、更新、删除),同时支持数据封装、安全性控制和多用户管理,使得数据访问变得抽象化和标准化。

ContentProvider组件具体内容解读如下:

  • 数据共享机制:ContentProvider 提供了一种标准化的接口,允许不同应用或应用的不同部分访问和共享数据。
  • URI 访问:每个 ContentProvider 都有一个唯一的标识符(authority),客户端可以通过构建 content 类型的 Uri 来访问提供的数据。
  • MIME 类型:ContentProvider 能够返回数据的 MIME 类型,这有助于客户端理解数据的性质并相应地处理。
  • CRUD 操作:ContentProvider 实现了创建(Create)、读取(Retrieve)、更新(Update)和删除(Delete)数据的基本操作。
  • 数据封装:它封装了数据存储的细节,客户端无需了解数据是如何存储和维护的。
  • 安全性:通过权限控制,ContentProvider 可以限制对敏感数据的访问,确保只有授权的应用或用户可以访问。
  • 多用户支持:在多用户环境中,ContentProvider 可以管理不同用户的数据隔离。
  • 系统服务集成:许多系统服务,如联系人、日历和媒体库,都是通过 ContentProvider 暴露给应用的。

那么ContentProvider为什么要这样设计呢?解读如下:

  • 数据共享与封装:设计ContentProvider的核心目的是为了在不同的应用之间共享数据,同时隐藏数据的具体存储细节。这种封装允许数据提供者改变数据存储方式而不影响数据消费者。
  • 统一的数据访问接口:提供一个统一的接口(CRUD操作)来访问数据,使得不同的应用可以使用相同的方式与数据交互,无论数据存储在SQLite数据库、文件系统还是其他存储介质中。
  • 安全性与权限管理:通过ContentProvider,Android可以控制对敏感数据的访问。使用URI授权和权限系统,只有获得授权的应用才能访问特定的数据,这增强了数据的安全性。
  • 解耦组件:ContentProvider允许数据提供者和数据消费者之间保持松耦合关系。消费者无需知道数据是如何生成或存储的,只需知道如何通过ContentProvider访问数据。
  • 支持内容URI和MIME类型:内容URI提供了一种标准化的方式来标识和访问数据。MIME类型则允许客户端根据数据类型采取适当的操作,这增加了数据交换的灵活性和多样性。
  • 跨应用功能:ContentProvider使得不同的应用可以协同工作,共享数据和功能,为用户提供更加丰富的体验。
  • 系统服务集成:许多系统服务,如联系人、日历和媒体库,都是通过ContentProvider暴露给应用的,这使得开发者可以轻松地在自己的应用中集成这些服务。
  • 多用户支持:在多用户环境中,ContentProvider可以管理不同用户的数据隔离,确保每个用户只能访问自己的数据。
  • 数据同步:ContentProvider可以用于实现数据同步,尤其是在处理需要与网络服务或云端数据同步的场景。

ContentProvider的设计反映了Android平台对于数据访问和共享的深刻理解,它提供了一种强大而灵活的方式来管理应用内和应用间的数据流动。

1.2 ContentProvider被启动和使用的过程

ContentProvider 是 Android 系统中的一个组件,它不像 Activity 或 Service 那样可以直接被启动。相反,ContentProvider 是在系统启动时注册,并在需要时被调用的。ContentProvider 是在系统启动时注册,并在需要时被调用的。以下是 ContentProvider 被启动和使用的过程:

@1 声明 ContentProvider:在应用的 AndroidManifest.xml 文件中声明 ContentProvider,包括它的 authorities(唯一标识)和其他必要的属性。

<providerandroid:name=".MyContentProvider"android:authorities="com.example.myprovider"android:exported="true" />

@2 实现 ContentProvider:创建一个类继承自 ContentProvider,并实现其抽象方法,如 onCreate(), query(), insert(), delete(), update() 和 getType()。

@3 系统启动时注册:当应用安装到设备上时,AndroidManifest.xml 中声明的 ContentProvider 会被系统读取并注册。这意味着系统知道了这个 ContentProvider 的存在和如何与其通信。

@4 调用 ContentProvider:其他应用或组件可以通过构建一个 ContentProvider 的 Uri 来与其交互。这个 Uri 通常是基于 ContentProvider 的 authorities 构建的。例如,使用 ContentResolver 来查询 ContentProvider:

ContentResolver contentResolver = getContentResolver();
Uri uri = Uri.parse("content://com.example.myprovider/items");
Cursor cursor = contentResolver.query(uri, null, null, null, null);

@5 ContentProvider 的生命周期:ContentProvider 的 onCreate() 方法在 ContentProvider 第一次被调用时执行,而不是在应用启动时执行。这类似于 Activity 的 onCreate() 方法。ContentProvider 通常在它们的 Uri 被查询时“启动”,这意味着它们的相关方法(如 query())被调用。

@6 跨应用通信:如果 ContentProvider 的 exported 属性设置为 true,则其他应用可以访问它。如果设置为 false,则只有声明它的应用可以访问。

@7 多用户环境:在多用户环境中,ContentProvider 可以管理不同用户的数据隔离,确保每个用户只能访问自己的数据。

ContentProvider 的启动和使用是被动的,它们在需要时被调用,而不是主动启动。这种设计使得 ContentProvider 可以作为数据共享的桥梁,同时保持应用组件的解耦和灵活性。

1.3 ContentProvider应用解读

以下是两个简单的Android应用示例,一个演示如何通过ContentProvider提供数据,另一个演示如何通过ContentResolver获取数据。

@示例1 通过ContentProvider提供数据

参考文件MyContentProvider.java的内容为:

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;public class MyContentProvider extends ContentProvider {public static final String AUTHORITY = "com.example.myprovider";public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/items");private static final int ITEMS = 1;private static final int ITEM_ID = 2;private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);static {uriMatcher.addURI(AUTHORITY, "items", ITEMS);uriMatcher.addURI(AUTHORITY, "items/#", ITEM_ID);}private MyDatabaseHelper dbHelper;@Overridepublic boolean onCreate() {dbHelper = new MyDatabaseHelper(getContext());return true;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {Cursor cursor;switch (uriMatcher.match(uri)) {case ITEMS:cursor = dbHelper.getReadableDatabase().query("MyTable", projection, selection, selectionArgs, null, null, sortOrder);break;case ITEM_ID:String id = uri.getLastPathSegment();cursor = dbHelper.getReadableDatabase().query("MyTable", projection, "_id=?", new String[]{id}, null, null, sortOrder);break;default:throw new IllegalArgumentException("Unknown URI: " + uri);}cursor.setNotificationUri(getContext(), uri);return cursor;}// Implement other required ContentProvider methods (insert, update, delete, getType)
}

需要在AndroidManifest.xml中注册该组件,具体内容为:

<providerandroid:name=".MyContentProvider"android:authorities="com.example.myprovider"android:exported="true"android:grantUriPermissions="true" />

@示例2 通过ContentResolver获取数据

参考文件MainActivity.java的内容为:

import android.database.Cursor;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ContentResolver;
import android.content.UriMatcher;
import android.net.Uri;
import android.provider.BaseColumns;public class MainActivity extends AppCompatActivity {private static final String AUTHORITY = "com.example.myprovider";private static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/items");@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ContentResolver contentResolver = getContentResolver();Cursor cursor = contentResolver.query(CONTENT_URI, null, null, null, null);if (cursor != null) {while (cursor.moveToNext()) {// Assuming there's a column named "name" in your databaseString name = cursor.getString(cursor.getColumnIndex("name"));// Do something with the data, e.g., display it in a ListView}cursor.close();}}
}

这两个示例展示了ContentProvider和ContentResolver的基本用法。

  • 第一个示例中,MyContentProvider提供了访问数据库的能力,通过定义AUTHORITY和CONTENT_URI来标识数据的来源。
  • 第二个示例中,MainActivity使用ContentResolver来查询由MyContentProvider提供的数据。

注意:这两个示例仅提供了核心代码部分,实际应用中需要实现ContentProvider的所有必需方法(如insert、update、delete和getType)。

基于此,我们分析ContentProvider组件主要从两个大的方面进行分析:

  • 作为内容提供者,ContentProvider随着开机启动启动注册和解析流程。我们主要关注Provider信息的存储和查询方式。也是本文接下来要解读的部分。
  • 作为数据获取者,通过getContentResolver获取ContentResolver对应,进而通过query方法获取数据流程。这里我们关注2个关键流程:getContentResolver获取ContentProvider的流程和通过ContentProvider来查询的query流程。这部分我们放到下一篇文章来解读。

2 ContentProvider开机启动注册流程解读

ContentProvider组件是android应用在 AndroidManifest.xml 文件中声明的 receiver,像这样:

<providerandroid:name=".MyContentProvider"android:authorities="com.example.myprovider"android:exported="true"android:grantUriPermissions="true" />

它们的信息会在系统启动时,由PMS解析并记录下来。

当 AMS 调用 PMS 的相关接口来查询 对应的Provider 时,PMS 内部就会去查询当初记录下来的数据,并把结果返回 AMS。

这里PMS在初始化时其中一部分是通过scanDirLI相关方法来初始化成员变量mProviders和mProvidersByAuthority,这两个成员变量都是PMS中存储provider的关键变量,关于PMS的初始化部分内容,想有更多了解可参考文章:

Android Framework 包管理子系统(01)PackageManagerService启动分析

这里主要看以scanDirLI为入口,分析mReceivers和mProvidersByAuthority的初始化部分逻辑,代码实现如下所示:

//PMS//关键流程:step1private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {final File[] files = dir.listFiles();//...for (File file : files) {final boolean isPackage = (isApkFile(file) || file.isDirectory())&& !PackageInstallerService.isStageName(file.getName());if (!isPackage) {// Ignore entries which are not packagescontinue;}try {scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,scanFlags, currentTime, null);} catch (PackageManagerException e) {//...}}}//关键流程:step2private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,long currentTime, UserHandle user) throws PackageManagerException {//...PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags| SCAN_UPDATE_SIGNATURE, currentTime, user);//...return scannedPkg;}//关键流程:step3private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags,int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {boolean success = false;try {final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags,currentTime, user);success = true;return res;} finally {//...}}//关键流程:step4private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {//...synchronized (mPackages) {//...// 获取应用包中ContentProvider的数量int N = pkg.providers.size();int i;for (i = 0; i < N; i++) {PackageParser.Provider p = pkg.providers.get(i); // 获取ContentProvider的Provider对象// 修复ContentProvider的进程名称p.info.processName = fixProcessName(pkg.applicationInfo.processName,p.info.processName, pkg.applicationInfo.uid);// 将ContentProvider添加到全局ContentProvider列表mProviders.addProvider(p);// 设置是否可同步p.syncable = p.info.isSyncable;// 如果ContentProvider有authority声明if (p.info.authority != null) {// 分割authority,因为可能声明了多个String names[] = p.info.authority.split(";");p.info.authority = null; // 重置authorityfor (int j = 0; j < names.length; j++) {// 如果是第二个authority并且ContentProvider是可同步的,则创建一个新的Provider对象if (j == 1 && p.syncable) {p = new PackageParser.Provider(p);p.syncable = false; // 新的Provider对象不可同步}// 如果mProvidersByAuthority中没有这个authority,则添加进去if (!mProvidersByAuthority.containsKey(names[j])) {mProvidersByAuthority.put(names[j], p);// 设置或更新authorityif (p.info.authority == null) {p.info.authority = names[j];} else {p.info.authority = p.info.authority + ";" + names[j];}} else {// 如果已经有相同的authority,获取已经存在的Provider对象PackageParser.Provider other = mProvidersByAuthority.get(names[j]);//...}}}}//...}//...//返回处理后的应用包对象return pkg;}

对于ContentProvider注册的解析,实际上到此就结束了。这里我们主要分析和关注卡年我们提到的2个成员变量mProvidersByAuthority和mProviders,详细解读如下:

  • mProvidersByAuthority:通过URI中的authority部分来快速查找ContentProvider的。ArrayMap是Android提供的一个优化版的HashMap,它在存储键值对时更加高效,特别是在键和值都是对象的情况下。mProvidersByAuthority是一个ArrayMap,它将ContentProvider的authority(即URI的authority部分)映射到PackageParser.Provider对象,这样可以快速通过authority查找对应的ContentProvider信息。这个映射表主要用于快速访问和检索ContentProvider,特别是当你需要根据authority来获取ContentProvider的详细信息时,这个映射表提供了一种快速查找的方式。
  • mProviders:通过解析Intent的URI和MIME类型来确定哪个ContentProvider应该响应这个Intent的。ProviderIntentResolver是一个用于解析和匹配Intent与ContentProvider的类。它主要负责处理Intent和ContentProvider之间的关系,即根据Intent的URI和MIME类型来确定应该由哪个ContentProvider来处理这个请求。它内部使用了一些数据结构来存储和查找ContentProvider的信息,以便快速匹配和解析Intent。ProviderIntentResolver通常用于动态查找和处理ContentProvider,特别是在处理Intent时,需要根据Intent的URI和MIME类型来确定具体的ContentProvider。

两者都是管理ContentProvider的重要机制,但它们的查找依据和工作方式有所不同。接下来针对查询的流程分别进行解读。接下来针对查询的流程做更详细的解读。

2.1 通过解析intent的Uri和MIME来查找对应provider的流程

当有数据获取端想通过Uri和MIME来快速查找ContentProvider时,则会通过PMS的queryContentProviders方法来查询。接下来我们来查看该方法的实现,代码如下:

//PMS@Overridepublic List<ProviderInfo> queryContentProviders(String processName,int uid, int flags) {ArrayList<ProviderInfo> finalList = null; // 用于存储最终的ContentProvider信息列表// readersynchronized (mPackages) {final Iterator<PackageParser.Provider> i = mProviders.mProviders.values().iterator();final int userId = processName != null ?UserHandle.getUserId(uid) : UserHandle.getCallingUserId();while (i.hasNext()) {final PackageParser.Provider p = i.next(); // 获取下一个ProviderPackageSetting ps = mSettings.mPackages.get(p.owner.packageName); // 获取PackageSettingif (ps != null && p.info.authority != null // 确保PackageSetting不为空且Provider有authority&& (processName == null // 如果processName为null,或者|| (p.info.processName.equals(processName) // Provider的processName匹配,并且&& UserHandle.isSameApp(p.info.applicationInfo.uid, uid))) // UID匹配&& mSettings.isEnabledLPr(p.info, flags, userId) // Provider是可用的&& (!mSafeMode // 如果不在安全模式,或者Provider是系统应用|| (p.info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)) {if (finalList == null) {finalList = new ArrayList<ProviderInfo>(3); // 初始化最终列表}ProviderInfo info = PackageParser.generateProviderInfo(p, flags,ps.readUserState(userId), userId); // 生成ProviderInfoif (info != null) {finalList.add(info); // 将ProviderInfo添加到最终列表}}}}if (finalList != null) {Collections.sort(finalList, mProviderInitOrderSorter); // 对最终列表进行排序}return finalList; // 返回ContentProvider信息列表}

queryContentProviders方法通过遍历所有已知的ContentProvider,根据给定的条件过滤和生成ProviderInfo对象,最终返回一个包含所有匹配ContentProvider信息的列表。这个过程确保了只有符合条件的ContentProvider被返回,同时考虑了进程名、UID、可用性和安全模式等因素。

这里本质上是基于mProviders来进行处理。

2.2 通过Uri的authority来查找对应provider的流程

当有数据获取端想通过URI中的authority部分来快速查找ContentProvider时,则会通过PMS的resolveContentProvider方法来查询。接下来我们来查看该方法的实现,代码如下:

@Override
public ProviderInfo resolveContentProvider(String name, int flags, int userId) {// 检查指定的用户是否存在,如果不存在则返回nullif (!sUserManager.exists(userId)) return null;// 同步代码块,确保线程安全synchronized (mPackages) {// 从mProvidersByAuthority中获取指定名称的Providerfinal PackageParser.Provider provider = mProvidersByAuthority.get(name);// 如果provider不为空,则获取其所属包的PackageSettingPackageSetting ps = provider != null? mSettings.mPackages.get(provider.owner.packageName): null;// 如果PackageSetting不为空,且provider是启用的,且在安全模式下只允许系统应用return ps != null&& mSettings.isEnabledLPr(provider.info, flags, userId)&& (!mSafeMode || (provider.info.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0)? PackageParser.generateProviderInfo(provider, flags,ps.readUserState(userId), userId): null;}
}

resolveContentProvider 方法是 PackageManagerService 中用于根据提供的 authority 名称、标志位和用户 ID 来解析特定 ContentProvider 的方法。这个方法主要用于在需要确定特定 authority 对应的 ContentProvider 信息时使用。

这里本质上是基于mProvidersByAuthority来进行处理。

总结下,开机启动后,PMS解析AndroidManifest.xml并处理,将ContentProvider存储在PMS的变量中,关键变量一个是mProvidersByAuthority,另一个是mProviders,两者主要是针对不同的查询需求。

相关文章:

Android Framework AMS(14)ContentProvider分析-1(CP组件应用及开机启动注册流程解读)

该系列文章总纲链接&#xff1a;专题总纲目录 Android Framework 总纲 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;本章节主要解读ContentProvider组件的基本知识。关注思维导图中左上侧部分即可。 有了前面activity组件分析、service组件分析、广播组件分析的基…...

Three.js PBR材质

本文将详细介绍Three.js中的PBR&#xff08;Physically Based Rendering&#xff09;材质&#xff0c;包括PBR的基本概念、适用场景、PBR材质的构建以及一些高级应用技巧。 1. PBR&#xff08;Physically Based Rendering&#xff09;基本概念 PBR&#xff0c;即Physically B…...

智谱AI清影升级:引领AI视频进入音效新时代

前几天智谱推出了新清影,该版本支持4k、60帧超高清画质、任意尺寸&#xff0c;并且自带音效的10秒视频,让ai生视频告别了"哑巴时代"。 智谱AI视频腾空出世&#xff0c;可灵遭遇强劲挑战&#xff01;究竟谁是行业翘楚&#xff1f;(附测评案例)之前智谱出世那时体验了一…...

嵌入式硬件电子电路设计(五)MOS管详解(NMOS、PMOS、三极管跟mos管的区别)

引言&#xff1a;在我们的日常使用中&#xff0c;MOS就是个纯粹的电子开关&#xff0c;虽然MOS管也有放大作用&#xff0c;但是几乎用不到&#xff0c;只用它的开关作用&#xff0c;一般的电机驱动&#xff0c;开关电源&#xff0c;逆变器等大功率设备&#xff0c;全部使用MOS管…...

Centos 9 安装 PostgreSQL 16 并支持远程访问

仅列出核心操作&#xff0c;可以解决使用过程中遇到的访问问题。 1 安装 使用dnf源安装 sudo dnf module -y install postgresql:16 2 配置文件夹权限 使用root权限操作 sudo chown postgres:postgres /var/lib/pgsql/datasudo chmod -R 0750 /var/lib/pgsql/data 3 初…...

Dubbo源码解析(三)

一、Dubbo整合Spring启动流程 Dubbo的使用可以不依赖Spring&#xff0c;但是生产环境中Dubbo都是整合到Spring中一起使用&#xff0c;所以本章就解析Dubbo整合Spring的启动流程 一、传统的xml解析方式 一、Dubbo配置解析流程 在Java 中&#xff0c;一切皆对象。在JDK 中使用…...

HarmonyOS Next星河版笔记--界面开发(5)

1.字符串 1.1.字符串拼接 作用&#xff1a;把两个或多个字符串&#xff0c;拼成一个字符串。&#xff08;通常是用来拼接字符串和变量&#xff09; hello world > helloworld 加好作用&#xff1a;拼接 let name:string 小明 console.log(简介信息,名字是 name) …...

Spring Boot3 实战案例合集上线了

Spring Boot3实战案例合集...

在Ubuntu 24.04 LTS上安装飞桨PaddleX

前面我们介绍了《在Windows用远程桌面访问Ubuntu 24.04.1 LTS》本文接着介绍安装飞桨PaddleX。 PaddleX 3.0 是基于飞桨框架构建的一站式全流程开发工具&#xff0c;它集成了众多开箱即用的预训练模型&#xff0c;可以实现模型从训练到推理的全流程开发&#xff0c;支持国内外多…...

Homebrew 命令大全

Homebrew 是 macOS 和 Linux 系统上的一个流行的包管理器&#xff0c;它可以帮助用户轻松地安装、更新和管理软件包。以下是一些常用的 Homebrew 命令&#xff1a; 安装 Homebrew 如果你还没有安装 Homebrew&#xff0c;可以使用以下命令在 macOS 上进行安装&#xff1a; /b…...

Docker+Django项目部署-从Linux+Windows实战

一、概述 1. 什么是Docker Docker 是一个开源的应用容器引擎&#xff0c;支持在win、mac、Linux系统上进行安装。可以帮助我们在一台电脑上创建出多个隔离的环境&#xff0c;比传统的虚拟机极大的节省资源 。 为什么要创建隔离的环境&#xff1f; 假设你先在有一个centos7.…...

前端 JS 实用操作总结

目录 1、重构解构 1、数组解构 2、对象解构 3、...展开 2、箭头函数 1、简写 2、this指向 3、没有arguments 4、普通函数this的指向 3、数组实用方法 1、map和filter 2、find 3、reduce 1、重构解构 1、数组解构 const arr ["唐僧", "孙悟空&quo…...

11.15 机器学习-集成学习方法-随机森林

# 机器学习中有一种大类叫**集成学习**&#xff08;Ensemble Learning&#xff09;&#xff0c;集成学习的基本思想就是将多个分类器组合&#xff0c;从而实现一个预测效果更好的集成分类器。集成算法可以说从一方面验证了中国的一句老话&#xff1a; # 三个臭皮匠&#xff0c…...

【SQL】E-R模型(实体-联系模型)

目录 一、介绍 1、实体集 定义和性质 属性 E-R图表示 2. 联系集 定义和性质 属性 E-R图表示 一、介绍 实体-联系数据模型&#xff08;E-R数据模型&#xff09;被开发来方便数据库的设计&#xff0c;它是通过允许定义代表数据库全局逻辑结构的企业模式&#xf…...

C/C++静态库引用过程中出现符号未定义的处理方式

问题背景&#xff1a; 在接入新库&#xff08;静态库&#xff09;时遇到了符号未定义问题&#xff0c;并发现改变静态库的链接顺序可以解决问题。 问题根源&#xff1a; 静态库是由 .o 文件拼接而成的&#xff0c;链接静态库时&#xff0c;链接器以 .o 文件为单位进行处理。链接…...

『VUE』27. 透传属性与inheritAttrs(详细图文注释)

目录 什么是透传属性&#xff08;Forwarding Attributes&#xff09;使用条件唯一根节点禁用透传属性继承总结 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 什么是透传属性&#xff08;Forwarding Attributes&#xff09; 在 V…...

借助Excel实现Word表格快速排序

实例需求&#xff1a;Word中的表格如下图所示&#xff0c;为了强化记忆&#xff0c;希望能够将表格内容随机排序&#xff0c;表格第一列仍然按照顺序编号&#xff0c;即编号不跟随表格行内容调整。 乱序之后的效果如下图所示&#xff08;每次运行代码的结果都不一定相同&#x…...

数据结构 ——— 层序遍历链式二叉树

目录 链式二叉树示意图​编辑 何为层序遍历 手搓一个链式二叉树 实现层序遍历链式二叉树 链式二叉树示意图 何为层序遍历 和前中后序遍历不同&#xff0c;前中后序遍历链式二叉树需要利用递归才能遍历 而层序遍历是非递归的形式&#xff0c;如上图&#xff1a;层序遍历的…...

使用 Prompt API 与您的对象聊天

tl;dr&#xff1a;GET、PUT、PROMPT。现在&#xff0c;可以使用新的 PromptObject API 仅使用自然语言对存储在 MinIO 上的对象进行总结、交谈和提问。在本文中&#xff0c;我们将探讨这个新 API 的一些用例以及代码示例。 赋予动机&#xff1a; 对象存储和 S3 API 的无处不在…...

SpringBoot整合Mybatis-Plus实践汇总

相关依赖 MyBatis-Plus涉及的依赖主要是Mybatis-start、和分页插件的依赖&#xff0c;不考虑使用额外分页插件的前提下&#xff0c;只需要mybatis-plus-boot-starter一个依赖即可与SpringBoot集成&#xff1a; <!--Mybatis-plugs--><dependency><groupId>co…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

python打卡day49

知识点回顾&#xff1a; 通道注意力模块复习空间注意力模块CBAM的定义 作业&#xff1a;尝试对今天的模型检查参数数目&#xff0c;并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】

1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件&#xff08;System Property Definition File&#xff09;&#xff0c;用于声明和管理 Bluetooth 模块相…...

C# SqlSugar:依赖注入与仓储模式实践

C# SqlSugar&#xff1a;依赖注入与仓储模式实践 在 C# 的应用开发中&#xff0c;数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护&#xff0c;许多开发者会选择成熟的 ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;SqlSugar 就是其中备受…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列&#xff0c;以便知晓哪些列包含有价值的数据&#xff0c;…...