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

news2024/11/17 23:50:46

该系列文章总纲链接:专题总纲目录 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(唯一标识)和其他必要的属性。

<provider
    android: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;

    @Override
    public boolean onCreate() {
        dbHelper = new MyDatabaseHelper(getContext());
        return true;
    }

    @Override
    public 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中注册该组件,具体内容为:

<provider
    android: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");

    @Override
    protected 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 database
                String 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,像这样:

<provider
    android: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
    //关键流程:step1
    private 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 packages
                continue;
            }
            try {
                scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
                        scanFlags, currentTime, null);
            } catch (PackageManagerException e) {
                //...
            }
        }
    }
    //关键流程:step2
    private 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;
    }
    //关键流程:step3
    private 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 {
            //...
        }
    }
    //关键流程:step4
    private 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; // 重置authority
                    for (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);
                            // 设置或更新authority
                            if (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
    @Override
    public List<ProviderInfo> queryContentProviders(String processName,
            int uid, int flags) {
        ArrayList<ProviderInfo> finalList = null; // 用于存储最终的ContentProvider信息列表
        // reader
        synchronized (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(); // 获取下一个Provider
                PackageSetting ps = mSettings.mPackages.get(p.owner.packageName); // 获取PackageSetting
                if (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); // 生成ProviderInfo
                    if (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) {
    // 检查指定的用户是否存在,如果不存在则返回null
    if (!sUserManager.exists(userId)) return null;

    // 同步代码块,确保线程安全
    synchronized (mPackages) {
        // 从mProvidersByAuthority中获取指定名称的Provider
        final PackageParser.Provider provider = mProvidersByAuthority.get(name);
        // 如果provider不为空,则获取其所属包的PackageSetting
        PackageSetting 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,两者主要是针对不同的查询需求。

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

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

相关文章

计算机视觉 1-8章 (硕士)

文章目录 零、前言1.先行课程&#xff1a;python、深度学习、数字图像处理2.查文献3.环境安装 第一章&#xff1a;概论1.计算机视觉的概念2.机器学习 第二章&#xff1a;图像处理相关基础1.图像的概念2.图像处理3.滤波器4.卷积神经网络CNN5.图像的多层表示&#xff1a;图像金字…

Vue基础(1)_模板语法、数据绑定

模板语法 Vue模板语法有2大类&#xff1a; 1、插值语法&#xff1b; 功能&#xff1a;用于解析标签体内内容。 写法&#xff1a;{{xxx}}&#xff0c;xxx是js表达式&#xff0c;且可以直接读取到data中的所有属性。 2、指令语法&#xff1a; 功能&#xff1a;用于解析标签(包括…

《生成式 AI》课程 第3講 CODE TASK 任务2:角色扮演的机器人

课程 《生成式 AI》课程 第3講&#xff1a;訓練不了人工智慧嗎&#xff1f;你可以訓練你自己-CSDN博客 我们希望你设计一个机器人服务&#xff0c;你可以用LM玩角色扮演游戏。 与LM进行多轮对话 提示:告诉聊天机器人扮演任意角色。 后续输入:与聊天机器人交互。 Part 2: Role…

【软件工程】一篇入门UML建模图(类图)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;软件开发必练内功_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前…

展会邀约|加速科技与您相约IC China 2024!

第二十一届中国国际半导体博览会&#xff08; IC China 2024&#xff09;将于 2024 年11月18日—11月20日在北京国家会议中心举行。加速科技将携高性能测试机ST2500EX、ST2500E、eATE及全系测试解决方案亮相E2馆B150展位。博览会期间&#xff0c;将同期举办"半导体产业前沿…

用python中的tkinter包实现进度条

python中的tkinter包是一种常见的设计程序的GUI界面用的包。本文主要介绍这里面的一个组件&#xff1a;进度条&#xff08;Progressbar&#xff09;。Tkinter Progressbar里面对进度条组件已经做了一定的介绍&#xff0c;但比较抽象。本文以另一种方式介绍这个组件及其常用用法…

蓝桥杯每日真题 - 第15天

题目&#xff1a;&#xff08;钟表&#xff09; 题目描述&#xff08;13届 C&C B组B题&#xff09; 解题思路&#xff1a; 理解钟表指针的运动&#xff1a; 秒针每分钟转一圈&#xff0c;即每秒转6度。 分针每小时转一圈&#xff0c;即每分钟转6度。 时针每12小时转一圈…

rust高级特征

文章目录 不安全的rust解引用裸指针裸指针与引用和智能指针的区别裸指针使用解引用运算符 *&#xff0c;这需要一个 unsafe 块调用不安全函数或方法在不安全的代码之上构建一个安全的抽象层 使用 extern 函数调用外部代码rust调用C语言函数rust接口被C语言程序调用 访问或修改可…

ArcGIS Pro属性表乱码与字段名3个汉字解决方案大总结

01 背景 我们之前在使用ArcGIS出现导出Excel中文乱码及shp添加字段3个字被截断的情况&#xff0c;我们有以下应对策略&#xff1a; 推荐阅读&#xff1a;ArcGIS导出Excel中文乱码及shp添加字段3个字被截断&#xff1f; 那如果我们使用ArGIS Pro出现上述问题&#xff0c;该如何…

GOLANG+VUE后台管理系统

1.截图 2.后端工程截图 3.前端工程截图

STM32设计防丢防摔智能行李箱

目录 目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 1.电路图采用Altium Designer进行设计&#xff1a; 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着科技的不断发展&#xff0c;嵌入式系统、物联网技术、智能设备…

论文阅读 - Causally Regularized Learning with Agnostic Data Selection

代码链接&#xff1a; GitHub - HMTTT/CRLR: CRLR尝试实现 https://arxiv.org/pdf/1708.06656v2 目录 摘要 INTRODUCTION 2 RELATED WORK 3 CAUSALLY REGULARIZED LOGISTIC REGRESSION 3.1 Problem Formulation 3.2 Confounder Balancing 3.3 Causally Regularized Lo…

探索Python文档自动化的奥秘:`python-docx`库全解析

文章目录 探索Python文档自动化的奥秘&#xff1a;python-docx库全解析1. 背景&#xff1a;为何选择python-docx&#xff1f;2. python-docx是什么&#xff1f;3. 如何安装python-docx&#xff1f;4. 简单库函数使用方法创建文档添加段落添加标题添加表格插入图片 5. 应用场景自…

Vue3 -- element-plus【项目集成1】

本次项目采用的UI组件库为element-plus&#xff0c;请各位看官根据实际情况进行观看。 集成element-plus&#xff1a; 官网直达车&#xff1a;element-plus 官网明确指出如何引入使用。 安装element-plus&#xff1a; 选择一个你喜欢的包管理器&#xff1a; npm install el…

MySQL 中的集群部署方案

文章目录 MySQL 中的集群部署方案MySQL ReplicationMySQL Group ReplicationInnoDB ClusterInnoDB ClusterSetInnoDB ReplicaSetMMMMHAGalera ClusterMySQL ClusterMySQL Fabric 总结参考 MySQL 中的集群部署方案 MySQL Replication MySQL Replication 是官方提供的主从同步方…

【功耗现象】com.gorgeous.lite后台Camera 使用2小时平均电流200mA耗电量400mAh现象

现象 轻颜相机(com.gorgeous.lite)后台Camera 使用2小时平均电流200mA(BugReport提供的电流参考数据),耗电量400mAh 即耗电占比(200mA*2h)/(12.83h*52.68mA )400mAh/623mAh62% CameraOct 10 202321:03:08 - 23:03:372h16m15s859ms to 4h16m44s984msactive duration: 2h 0m 29…

Unix信号

文章目录 信号概念及产生键盘事件eg软件中断eg硬件中断eg 信号处理方式PCB中关于信号的数据结构信号捕捉 信号集sigset_tsigprocmasksigpending 信号处理程序signal、sigaction可重入函数可靠信号 kill、raise 信号概念及产生 信号是一种异步通知机制&#xff0c;内核通过信号…

如何解决由于找不到d3dx9_43.dll导致游戏启动失败?这里是如何解决的完整指南

遇到“由于找不到d3dx9_43.dll”错误时&#xff0c;很多用户可能会感到困惑和无助。这个问题通常发生在尝试启动游戏或使用基于DirectX的应用程序时。d3dx9_43.dll是Microsoft DirectX软件的一部分&#xff0c;专门用于处理复杂的图形计算&#xff0c;缺少它意味着某些图形功能…

任务函数分析

一、页面存储栈 PageStack 1、头文件 #include "ui.h"#define MAX_DEPTH 6typedef long long int StackData_t;typedef struct {StackData_t Data[MAX_DEPTH];uint8_t Top_Point;}user_Stack_T;uint8_t user_Stack_Push(user_Stack_T* stack, StackData_t datain)…

Springboot 微信小程序定位后将坐标转换为百度地图坐标,在百度地图做逆地址解析

问题解析以及解决思路 业务:微信小程序定位后,将坐标转换为百度地图坐标,在百度地图做逆地址解析 问题:微信小程序的定位是拿的腾讯地图的经纬度,但是我们app端这边使用的百度地图,如果直接使用腾讯地图的经纬度再使用腾讯地图的逆地址解析需要腾讯和百度商业授权,为了减少授权…