Android 11 PackageManagerService源码分析(二):Packages.xml详解

2023-05-16

1、开篇

在上一篇文章中提到Settings类会在PackageManagerService启动过程中对packages.xml等一些列xml文件进行解析。那么有以下问题:

  1. 这些文件记录了什么内容?
  2. 为什么需要这些文件?

让我们一起通过阅读源码解决这些问题吧。

2、packages.xml文件详解

要在真机上拿到packages.xml殊为不易,所以我这里是在模拟器上通过adb命令拉取了一份:

adb pull /data/system/packages.xml

文件内容精简后如下:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<packages>
    <version sdkVersion="24" databaseVersion="3" fingerprint="Android/sdk_phone_x86/generic_x86:7.0/NYC/4174735:userdebug/test-keys" />
    <version volumeUuid="primary_physical" sdkVersion="24" databaseVersion="3" fingerprint="Android/sdk_phone_x86/generic_x86:7.0/NYC/4174735:userdebug/test-keys" />
    <permission-trees />
    <permissions>
        <item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
        <item name="android.permission.ACCESS_CACHE_FILESYSTEM" package="android" protection="18" />
        ...
    </permissions>

    <package name="com.android.providers.media" codePath="/system/priv-app/MediaProvider" nativeLibraryPath="/system/priv-app/MediaProvider/lib" primaryCpuAbi="x86" publicFlags="944291397" privateFlags="8" ft="15d38697a58" it="15d38697a58" ut="15d38697a58" version="800" sharedUserId="10010">
        <sigs count="1">
            <cert index="2" key="308..." />
        </sigs>
        <perms>
            <item name="android.permission.ACCESS_CACHE_FILESYSTEM" granted="true" flags="0" />
            ...
        </perms>
        <proper-signing-keyset identifier="4" />
    </package>

    ...

    <shared-user name="com.android.emergency.uid" userId="10011">
        <sigs count="1">
            <cert index="1" />
        </sigs>
        <perms>
            <item name="android.permission.MANAGE_USERS" granted="true" flags="0" />
        </perms>
    </shared-user>
    <keyset-settings version="1">
        <keys>
            <public-key identifier="1" value="MIIBIDAN..." />
            <public-key identifier="2" value="MIIBI..." />
            <public-key identifier="3" value="MIIBI..." />
            <public-key identifier="4" value="MIIBID..." />
        </keys>
        <keysets>
            <keyset identifier="1">
                <key-id identifier="1" />
            </keyset>
            <keyset identifier="2">
                <key-id identifier="2" />
            </keyset>
            <keyset identifier="3">
                <key-id identifier="3" />
            </keyset>
            <keyset identifier="4">
                <key-id identifier="4" />
            </keyset>
        </keysets>
        <lastIssuedKeyId value="4" />
        <lastIssuedKeySetId value="4" />
    </keyset-settings>
</packages>

可以看到,packages.xml主要记录了以下几方面的信息:

  1. 权限信息,permission-trees标签和permissions标签。这里记录的是系统里所有的权限条目
  2. 安装的App信息,包括系统App和用户自行安装的App,package和updated-package标签。其中package标签用户记录一般App的信息;而updated-package通常用于被用户手动升级了的系统App,比如说手机自带了计算器App,这个自带的App的版本是1.0,而厂商又在它的应用商店发布了计算器2.0,当我们在应用商店更新成2.0版本的时候,在系统分区和用户安装分区会各存在一个计算器App。这也是为什么我们在系统应用管理界面删除更新后的计算器2.0后,手机上还是会有计算器1.0版本,而不是彻底消失的原因。
    package标签的属性记录了包名、代码路径、版本等各项信息。另外,package还会有一些子标签,sigs记录App的签名信息,perms记录App的权限信息,注意这里的权限是manifest中声明的权限而不是已经授予的权限。
  3. 共享用户信息,shared-user标签。一般来说一个Android系统会为每一个App分配一个user id,但是我们也可以在manifest元素中指定android:sharedUserId属性,使多个App具有同样的user id从而可以共享数据等等。但是除了系统App,Android强烈建议我们不要使用它,并且在未来的版本可能会被移除,所以了解即可。
  4. 签名信息,keyset-settings标签,记录的是应用签名的公钥。

packages.xml也不止以上这些标签,具体可以参考Settings类对packages.xml的解析:

boolean readLPw(@NonNull List<UserInfo> users) {
    FileInputStream str = null;
    if (mBackupSettingsFilename.exists()) {
        try {
            str = new FileInputStream(mBackupSettingsFilename);
            mReadMessages.append("Reading from backup settings file\n");
            PackageManagerService.reportSettingsProblem(Log.INFO,
                    "Need to read from backup settings file");
            if (mSettingsFilename.exists()) {
                // 两者都存在的时候,说明上次更新packages.xml时发生了异常
                Slog.w(PackageManagerService.TAG, "Cleaning up settings file "
                        + mSettingsFilename);
                mSettingsFilename.delete();
            }
        } catch (java.io.IOException e) {
            // We'll try for the normal settings file.
        }
    }

    mPendingPackages.clear();
    mPastSignatures.clear();
    mKeySetRefs.clear();
    mInstallerPackages.clear();

    try {
        if (str == null) {
            if (!mSettingsFilename.exists()) {
                mReadMessages.append("No settings file found\n");
                PackageManagerService.reportSettingsProblem(Log.INFO,
                        "No settings file; creating initial state");
                // It's enough to just touch version details to create them
                // with default values
                findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL).forceCurrent();
                findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL).forceCurrent();
                return false;
            }
            str = new FileInputStream(mSettingsFilename);
        }
        XmlPullParser parser = Xml.newPullParser();
        parser.setInput(str, StandardCharsets.UTF_8.name());

        int type;
        while ((type = parser.next()) != XmlPullParser.START_TAG
                && type != XmlPullParser.END_DOCUMENT) {
            ;
        }

        if (type != XmlPullParser.START_TAG) {
            mReadMessages.append("No start tag found in settings file\n");
            PackageManagerService.reportSettingsProblem(Log.WARN,
                    "No start tag found in package manager settings");
            Slog.wtf(PackageManagerService.TAG,
                    "No start tag found in package manager settings");
            return false;
        }

        int outerDepth = parser.getDepth();
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            String tagName = parser.getName();
            if (tagName.equals("package")) {
                readPackageLPw(parser);
            } else if (tagName.equals("permissions")) {
                mPermissions.readPermissions(parser);
            } else if (tagName.equals("permission-trees")) {
                mPermissions.readPermissionTrees(parser);
            } else if (tagName.equals("shared-user")) {
                readSharedUserLPw(parser);
            } else if (tagName.equals("preferred-packages")) {
                // 不再使用了,所以不做任何操作
            } else if (tagName.equals("preferred-activities")) {
                // Upgrading from old single-user implementation;
                // these are the preferred activities for user 0.
                readPreferredActivitiesLPw(parser, 0);
            } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
                // TODO: check whether this is okay! as it is very
                // similar to how preferred-activities are treated
                readPersistentPreferredActivitiesLPw(parser, 0);
            } else if (tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) {
                // TODO: check whether this is okay! as it is very
                // similar to how preferred-activities are treated
                readCrossProfileIntentFiltersLPw(parser, 0);
            } else if (tagName.equals(TAG_DEFAULT_BROWSER)) {
                readDefaultAppsLPw(parser, 0);
            } else if (tagName.equals("updated-package")) {
                // 注意这里,updated-package记录的package视为disabled
                readDisabledSysPackageLPw(parser);
            } else if (tagName.equals("renamed-package")) {
                String nname = parser.getAttributeValue(null, "new");
                String oname = parser.getAttributeValue(null, "old");
                if (nname != null && oname != null) {
                    mRenamedPackages.put(nname, oname);
                }
            } else if (tagName.equals("restored-ivi")) {
                readRestoredIntentFilterVerifications(parser);
            } else if (tagName.equals("last-platform-version")) {
                // Upgrade from older XML schema
                final VersionInfo internal = findOrCreateVersion(
                        StorageManager.UUID_PRIVATE_INTERNAL);
                final VersionInfo external = findOrCreateVersion(
                        StorageManager.UUID_PRIMARY_PHYSICAL);

                internal.sdkVersion = XmlUtils.readIntAttribute(parser, "internal", 0);
                external.sdkVersion = XmlUtils.readIntAttribute(parser, "external", 0);
                internal.fingerprint = external.fingerprint =
                        XmlUtils.readStringAttribute(parser, "fingerprint");

            } else if (tagName.equals("database-version")) {
                // Upgrade from older XML schema
                final VersionInfo internal = findOrCreateVersion(
                        StorageManager.UUID_PRIVATE_INTERNAL);
                final VersionInfo external = findOrCreateVersion(
                        StorageManager.UUID_PRIMARY_PHYSICAL);

                internal.databaseVersion = XmlUtils.readIntAttribute(parser, "internal", 0);
                external.databaseVersion = XmlUtils.readIntAttribute(parser, "external", 0);

            } else if (tagName.equals("verifier")) {
                final String deviceIdentity = parser.getAttributeValue(null, "device");
                try {
                    mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity);
                } catch (IllegalArgumentException e) {
                    Slog.w(PackageManagerService.TAG, "Discard invalid verifier device id: "
                            + e.getMessage());
                }
            } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
                final String enforcement = parser.getAttributeValue(null, ATTR_ENFORCEMENT);
                mReadExternalStorageEnforced =
                        "1".equals(enforcement) ? Boolean.TRUE : Boolean.FALSE;
            } else if (tagName.equals("keyset-settings")) {
                mKeySetManagerService.readKeySetsLPw(parser, mKeySetRefs);
            } else if (TAG_VERSION.equals(tagName)) {
                final String volumeUuid = XmlUtils.readStringAttribute(parser,
                        ATTR_VOLUME_UUID);
                final VersionInfo ver = findOrCreateVersion(volumeUuid);
                ver.sdkVersion = XmlUtils.readIntAttribute(parser, ATTR_SDK_VERSION);
                ver.databaseVersion = XmlUtils.readIntAttribute(parser, ATTR_DATABASE_VERSION);
                ver.fingerprint = XmlUtils.readStringAttribute(parser, ATTR_FINGERPRINT);
            } else {
                Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: "
                        + parser.getName());
                XmlUtils.skipCurrentTag(parser);
            }
        }

        str.close();

    } catch (XmlPullParserException e) {
        ...

    } catch (java.io.IOException e) {
        ...
    }

    ...

    return true;
}

3、Packages.xml的作用

在上篇文章中我们可以看到,packages.xml文件最终被解析和保存到了Settings的mPackages属性里了。来看一下PMS的构造方法里,它都发挥了什么作用吧

public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) {
    ...
    mSettings = injector.getSettings();
    ...
    t.traceBegin("addSharedUsers");
    // 创建一些列系统shared user id
    mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
            ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
            ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    ...

    // CHECKSTYLE:OFF IndentationCheck
    synchronized (mInstallLock) {
    // writer
    synchronized (mLock) {
        ...

        // 读取packages.xml或packages-backup.xml,两者的格式一样,上次更新packages.xml出现异常的时候才会出现packages-backup.xml
        t.traceBegin("read user settings");
        mFirstBoot = !mSettings.readLPw(mInjector.getUserManagerInternal().getUsers(false));
        t.traceEnd();

        
        // 清除代码路径不存在的package
        final int packageSettingCount = mSettings.mPackages.size();
        for (int i = packageSettingCount - 1; i >= 0; i--) {
            PackageSetting ps = mSettings.mPackages.valueAt(i);
            if (!isExternal(ps) && (ps.codePath == null || !ps.codePath.exists())
                    && mSettings.getDisabledSystemPkgLPr(ps.name) != null) {
                mSettings.mPackages.removeAt(i);
                mSettings.enableSystemPackageLPw(ps.name);
            }
        }

        if (!mOnlyCore && mFirstBoot) {
            requestCopyPreoptedFiles();
        }

        ...

        // Save the names of pre-existing packages prior to scanning, so we can determine
        // which system packages are completely new due to an upgrade.
        // 在扫描之前保存预先存在的App的名称,以便确定哪些系统App由于升级完全新加的
        if (isDeviceUpgrading()) {
            mExistingPackages = new ArraySet<>(mSettings.mPackages.size());
            for (PackageSetting ps : mSettings.mPackages.values()) {
                mExistingPackages.add(ps.name);
            }
        }

        // 扫描系统App,这里代码省略
        ...

        // Prune any system packages that no longer exist.
        final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
        // Stub packages must either be replaced with full versions in the /data
        // partition or be disabled.
        final List<String> stubSystemApps = new ArrayList<>();
        if (!mOnlyCore) {
            ...

            final Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
            while (psit.hasNext()) {
                PackageSetting ps = psit.next();

                // 非系统App跳过
                if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
                    continue;
                }

                
                final AndroidPackage scannedPkg = mPackages.get(ps.name);
                if (scannedPkg != null) { // packages.xml和即时扫描的结果都存在这个package
                    
                    // 如果系统App扫描到了,而且是disabled状态,也就是被记录在updated-package标签里,把这个记录删除,以期使用用户安装的版本
                    // 后面如果没有找到用户安装的版本,会恢复系统自带的版本
                    if (mSettings.isDisabledSystemPackageLPr(ps.name)) {
                        logCriticalInfo(Log.WARN,
                                "Expecting better updated system app for " + ps.name
                                + "; removing system app.  Last known"
                                + " codePath=" + ps.codePathString
                                + ", versionCode=" + ps.versionCode
                                + "; scanned versionCode=" + scannedPkg.getLongVersionCode());
                        removePackageLI(scannedPkg, true);
                        mExpectingBetter.put(ps.name, ps.codePath);
                    }

                    continue;
                }

                // packages.xml中存在的package如果没有被扫描到执行接下来的代码


                if (!mSettings.isDisabledSystemPackageLPr(ps.name)) { // 不是disabled状态说明用户没有手动更新过,直接删除
                    psit.remove();
                    logCriticalInfo(Log.WARN, "System package " + ps.name
                            + " no longer exists; it's data will be wiped");

                    // Assume package is truly gone and wipe residual permissions.
                    mPermissionManager.updatePermissions(ps.name, null);

                    // 真正删除代码和数据的操作会在后面执行
                } else {
                    // 在disabled list里,判断代码路径是不是还存在,存在的话可能是升级的时候改了包名,不存在则可能删除了
                    final PackageSetting disabledPs =
                            mSettings.getDisabledSystemPkgLPr(ps.name);
                    if (disabledPs.codePath == null || !disabledPs.codePath.exists()
                            || disabledPs.pkg == null) {
                        possiblyDeletedUpdatedSystemApps.add(ps.name);
                    } else {
                        // 加到mExpectingBetter,以便后续扫描到对应的升级版本的时候继续保持系统版本disabled,而使用用户版本,没有扫描到则再处理是删除还是保留
                        mExpectingBetter.put(disabledPs.name, disabledPs.codePath);
                    }
                }
            }
        }

        final int cachedSystemApps = PackageCacher.sCachedPackageReadCount.get();

        // 移除那些没有package关联的shared user id
        mSettings.pruneSharedUsersLPw();
        ...

        // 扫描用户安装的App
        if (!mOnlyCore) {
            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                    SystemClock.uptimeMillis());
            scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0,
                    packageParser, executorService);

        }

        packageParser.close();

        List<Runnable> unfinishedTasks = executorService.shutdownNow();
        if (!unfinishedTasks.isEmpty()) {
            throw new IllegalStateException("Not all tasks finished before calling close: "
                    + unfinishedTasks);
        }

        if (!mOnlyCore) {
            // Remove disable package settings for updated system apps that were
            // removed via an OTA. If the update is no longer present, remove the
            // app completely. Otherwise, revoke their system privileges.
            // 系统升级中移除了App,如果App还存在于用户区(用户手动安装过新版本),剥夺App的系统级权限,否则完全删除
            for (int i = possiblyDeletedUpdatedSystemApps.size() - 1; i >= 0; --i) {
                final String packageName = possiblyDeletedUpdatedSystemApps.get(i);
                final AndroidPackage pkg = mPackages.get(packageName);
                final String msg;

                // remove from the disabled system list; do this first so any future
                // scans of this package are performed without this state
                mSettings.removeDisabledSystemPackageLPw(packageName);

                if (pkg == null) {
                    // 这里仍然没找到扫描结果,直接删除
                    msg = "Updated system package " + packageName
                            + " no longer exists; removing its data";
                    // 真正删除代码和数据的操作会在后面执行
                } else {
                    // 扫描到了,剥夺系统级权限
                    msg = "Updated system package " + packageName
                            + " no longer exists; rescanning package on data";

                    // NOTE: We don't do anything special if a stub is removed from the
                    // system image. But, if we were [like removing the uncompressed
                    // version from the /data partition], this is where it'd be done.

                    // remove the package from the system and re-scan it without any
                    // special privileges
                    // 先删除,后重新扫描
                    removePackageLI(pkg, true);
                    try {
                        final File codePath = new File(pkg.getCodePath());
                        // 重新扫描
                        scanPackageTracedLI(codePath, 0, scanFlags, 0, null);
                    } catch (PackageManagerException e) {
                        Slog.e(TAG, "Failed to parse updated, ex-system package: "
                                + e.getMessage());
                    }
                }

                // 最终确认结果
                final PackageSetting ps = mSettings.mPackages.get(packageName);
                if (ps != null && mPackages.get(packageName) == null) {
                    removePackageDataLIF(ps, null, null, 0, false);

                }
                logCriticalInfo(Log.WARN, msg);
            }

            /*
                * Make sure all system apps that we expected to appear on
                * the userdata partition actually showed up. If they never
                * appeared, crawl back and revive the system version.
                */
            // 确保应该在用户区出现的系统App存在,不存在则使用系统区的版本
            for (int i = 0; i < mExpectingBetter.size(); i++) {
                final String packageName = mExpectingBetter.keyAt(i);
                if (!mPackages.containsKey(packageName)) {
                    final File scanFile = mExpectingBetter.valueAt(i);

                    logCriticalInfo(Log.WARN, "Expected better " + packageName
                            + " but never showed up; reverting to system");

                    @ParseFlags int reparseFlags = 0;
                    @ScanFlags int rescanFlags = 0;
                    for (int i1 = mDirsToScanAsSystem.size() - 1; i1 >= 0; i1--) {
                        final ScanPartition partition = mDirsToScanAsSystem.get(i1);
                        if (partition.containsPrivApp(scanFile)) {
                            reparseFlags = systemParseFlags;
                            rescanFlags = systemScanFlags | SCAN_AS_PRIVILEGED
                                    | partition.scanFlag;
                            break;
                        }
                        if (partition.containsApp(scanFile)) {
                            reparseFlags = systemParseFlags;
                            rescanFlags = systemScanFlags | partition.scanFlag;
                            break;
                        }
                    }
                    if (rescanFlags == 0) {
                        Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile);
                        continue;
                    }
                    mSettings.enableSystemPackageLPw(packageName);

                    try {
                        scanPackageTracedLI(scanFile, reparseFlags, rescanFlags, 0, null);
                    } catch (PackageManagerException e) {
                        Slog.e(TAG, "Failed to parse original system package: "
                                + e.getMessage());
                    }
                }
            }

            ...
        }
        mExpectingBetter.clear();

        ...

        for (SharedUserSetting setting : mSettings.getAllSharedUsersLPw()) {
            // NOTE: We ignore potential failures here during a system scan (like
            // the rest of the commands above) because there's precious little we
            // can do about it. A settings error is reported, though.
            final List<String> changedAbiCodePath =
                    applyAdjustedAbiToSharedUser(setting, null /*scannedPackage*/,
                    mInjector.getAbiHelper().getAdjustedAbiForSharedUser(
                            setting.packages, null /*scannedPackage*/));
            if (changedAbiCodePath != null && changedAbiCodePath.size() > 0) {
                for (int i = changedAbiCodePath.size() - 1; i >= 0; --i) {
                    final String codePathString = changedAbiCodePath.get(i);
                    try {
                        mInstaller.rmdex(codePathString,
                                getDexCodeInstructionSet(getPreferredInstructionSet()));
                    } catch (InstallerException ignored) {
                    }
                }
            }
            // Adjust seInfo to ensure apps which share a sharedUserId are placed in the same
            // SELinux domain.
            setting.fixSeInfoLocked();
            setting.updateProcesses();
        }

        // Now that we know all the packages we are keeping,
        // read and update their last usage times.
        mPackageUsage.read(mSettings.mPackages);
        
        ...

        t.traceBegin("write settings");
        mSettings.writeLPr();
        
        ...

        mSettings.setPermissionControllerVersion(
                getPackageInfo(mRequiredPermissionControllerPackage, 0,
                        UserHandle.USER_SYSTEM).getLongVersionCode());

        ...
    } // synchronized (mLock)
    } // synchronized (mInstallLock)
    ...
}

这里删除了非常多的代码,只列出了关键性的。可以看到packages.xml的主要作用存储上一次启动时扫描和更新的结果,和本次启动扫描的结果进行比较,判断哪些该更新,哪些该删除。这就是它的主要作用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android 11 PackageManagerService源码分析(二):Packages.xml详解 的相关文章

  • 我是否需要在我的 Firebase 项目中添加 SHA-1 指纹?

    在 Firebase 上有一个弹出窗口 显示我的项目可以添加指纹 SHA1 因为我只想使用一些 Firebase 服务 例如实时数据库 身份验证 我需要在我的项目中添加指纹吗 该图显示 仅在使用某些 Google Play 服务 如 OAu
  • HttpURLConnection getResponseCode 未返回

    我尝试在 Android 上使用 HttpURLConnection 将文件发布到我们的服务器 但 getResponseCode 调用只是挂起并且永远不会返回 该问题源于文件对于服务器来说太大 因此服务器发回 HTTP 错误代码 413
  • 喷气背包组合中的波纹效果无法正常工作

    我正在研究jetpack compose中的连锁反应 我提供了我的颜色 单击视图后 它会在一段时间后显示不同类型的颜色 在按下状态时显示为深灰色 binding itemComposable setContent Column modifi
  • 修改 Settings.apk 以与 Project Glass 配合使用

    我正在尝试构建要在 Google I O 的 Hacking Glass 会议上在 Glass 上使用的 Settings apk 他提到 为了让设置 apk 正常工作 需要修改清单中的一行 这是 AOSP 清单 http pastebin
  • Android - Firebase - 保存新数据而不覆盖旧数据

    创建此问题是因为我之前的问题包含 2 个问题 而不是将其缩小到 1 Aim 用户将能够存储新数据而不会覆盖之前提交的数据 描述 目前 当用户输入新报告时 事件报告节点中的用户事件报告数据将被覆盖 用户发送的旧事件报告中的数据应与新数据一起保
  • 带有电子墨水显示屏的 Android

    我有兴趣使用 AndroidE Ink http www eink com technology howitworks html为基础的平台 我知道已经是证明了 http vimeo com 3162590MOTO 曾经使用过 但我有兴趣将
  • 如何防止机器人程序和垃圾邮件 API 请求?

    我正在使用react native 开发一个Android 应用程序 该应用程序与我正在为该应用程序开发的API 进行通信 该 API 是使用 Laravel 和 Laravel Passport 构建的 我知道 Android 应用程序可
  • 如何在 Android 上禁用 HTML 输入中的自动更正?

    我无法在 Android 上的网络浏览器中禁用文本输入的自动更正 我试过这个
  • 无法在 PowerShell 中完全解析 XML

    我有一个 XML 文件 我想解析该文件并检索特定信息 为了便于理解 下面是 XML 文件的屏幕截图 我想解析 XML 并为每个Item节点 检索屏幕截图中指示的字段 检索到的每个值都需要针对每个项目节点进行格式化 最后 我希望能够指定一个要
  • Google 的新地点库(实现“com.google.android.libraries.places:1.0.0”)无法解析

    我正在尝试迁移到新的 Places SDK 客户端 但我被告知要安装的依赖项文档 https developers google com places android sdk client migration给我一个 无法解决 错误 我确保
  • 如何使用 gradle 动态切换/更改 testInstrumentationRunner

    我的项目有两组不同的测试 一组仅使用默认值运行AndroidJUnitRunner另一个必须使用自定义实现来运行TestRunner extends MonitoringInstrumentation 目前我切换testInstrument
  • jQuery:评估 ajax 响应中的脚本

    来自我的 web 应用程序的 XML 响应既有要添加到页面的 HTML 也有要运行的脚本 我正在尝试从我的网络应用程序发回 XML 例如
  • 如何从其他 Activity 类访问 Activity 的视图

    我的问题是当我更改文本时TextView在我的主类中 它返回 nullPointerException 这是我的代码 main xml
  • 处理网络视图中的链接

    我有我的WebView加载网络视图中的所有链接 但是当我选择电子邮件链接时 它会尝试将其加载到网络视图中 而不是在手机上启动电子邮件应用程序 我该如何解决这个问题 链接是mailto 电子邮件受保护 cdn cgi l email prot
  • 为什么 Kotlin 数据类对象有反引号?

    这是我使用 Kotlin 数据类创建器插件创建的数据类 data class ResponseHealthInisghts val data List
  • 现已弃用使用 Google Places API 获取多种类型

    谷歌最近宣布 自 2016 年 2 月 16 日起 types 参数已被弃用 取而代之的是新的类型参数 每个搜索请求仅支持一种类型 我的问题是 现在有什么方法 不使用已弃用的参数 从单个 API 调用中获取多个地点类型吗 谢谢 None
  • 使用 libgdx 裁剪图像

    I need to crop image like this 我需要从中心绘制部分图像 我知道有一个带有很多参数的批处理的draw 方法 但是没有关于所有这些参数的良好文档 所以我不知道如何使用它 这是我实现的 public class T
  • 如何创建一个类似“隐形”的Android应用程序?

    我想让我的应用程序以某种 隐形 模式运行 我想做的两件主要事情 不以编程方式在已安装的应用程序列表 抽屉 中显示应用程序图标 通过拨号盘启动应用程序 一些特殊的数字组合 我知道我可以删除启动器意图过滤器来隐藏图标
  • AS更新到1.0后,项目中出现“method ID not in [0, 0xffff]: 65536”错误

    我将 Android Studio 更新到最新版本 并让它 修复项目 之类的 但现在我的项目无法编译 给了我 FAILED FAILURE Build failed with an exception What went wrong Exe
  • 处理带有两个片段的操作栏

    我有一个包含两个片段的布局 两个片段都有自己的操作栏 每个操作栏都有自己的操作项和菜单 当我的应用程序处于横向模式并且两个片段都显示在屏幕上时 看起来框架正在选择在 右侧 或第二个片段 显示操作栏 这意味着左侧的片段 第一个片段 缺少其操作

随机推荐

  • STL的确很好用

    STL的确很好用 以前只听过没用过 用了几次后 xff0c 发现的确方便 现在会自觉不自觉地用到 边用边学吧 map insert 返回 pair multimap insert 返回 iterator 来自 ITPUB博客 xff0c 链
  • ROS-学习笔记-04-( Ubuntu20.04编译ecto,boost1.7环境)

    目录 Ubuntu20 04编译ecto xff08 boost1 7环境 xff09 编译ecto简介1 下载源码安装依赖2 修改源码BuildInstall 参考 Ubuntu20 04编译ecto xff08 boost1 7环境 x
  • Windows设置SSH出错,Permission denied (publickey),Error connecting to agent

    最近在设置nginx配置 xff0c 由于云服务器是密钥登陆的 xff0c 所以有一些问题 前置环境 xff1a 安装Chocolatey 以管理员权限打开PowerShell运行Set ExecutionPolicy AllSigned
  • 其他笔记 - 关于Fast DDS 和rtps的介绍

    简述 分布式实时数据分发服务中间件协议Data Distribution Service xff08 DDS xff09 是OMG于2003年发布并于2007年修订的开放标准 该标准定义了用于分布式系统的 发布 订阅 通信中间件的API规范
  • 其他笔记 - InfluxDB与MongoDB的对比

    InfluxDB与MongoDB对比 文章来源 本文将比较InfluxDB与MongoDB在常见时间序列工作负载下的性能和特性 xff0c 特别是两者在数据传输率 xff0c 磁盘上数据压缩率和查询性能上的差异 InfluxDB是一个Go实
  • 其他笔记 - Electron代替品探索过程

    目录 前言替代品一览C Net C 43 43 DartGoJavaJSPython sciter测试 前言 Electron是基于 Node js 和 Chromium的 xff0c 可以把前端代码打包成桌面应用的工具 xff0c 使得J
  • 【其他笔记】 双屏显示分辨率低下、屏幕闪烁、暗沉等问题。

    1 我在以前学校的电脑 xff0c 设置单屏的时候dell屏幕很亮 xff0c 双屏 xff08 hdmi和VGA线 xff09 时 xff0c dell屏幕会变暗 这个时期我链接的主机 xff0c 有一张N卡 xff0c 所以一个屏幕接到
  • 【读书笔记】 如何进行Python性能分析

    文章目录 Python解释器性能分析方法1 time计时2 标准库内建分析工具3 逐行分析4 诊断内存其他建议 参考目录 Python解释器 python 解释器有很多 xff1a CPython IPython Jython PyPy x
  • 自编译的pytorch出现OSError: libmkl_intel_lp64.so.1: cannot open shared object file: No such file or direct

    我的Pytorch是本机编译安装 运行时出现 xff1a span class token function Traceback span span class token punctuation span most recent call
  • 【深度笔记】模型理论性能计算调研

    定义 1 计算量 FLOP xff1a 浮点运算次数 xff08 Floating Point Operations xff09 MAC xff1a 乘加累积操作数 FLOPs S xff1a 每秒的FLOP xff0c 可视作性能 公式
  • 分布式学习 - MPICH编译与实践

    下载release编译 参考官方README 源码编译 准备工作 xff1a git clone https github com pmodels mpich git bash cd mpich git submodule update i
  • 07誓师大会有感

    07财年的誓师大会如约在中国剧院举行了 xff0c 纪律显得比以往更加严格 xff0c 迟到或者服装不合格者都将予与重罚 开场一段精彩的电影片断 xff0c 然后是高唱神码之歌 林总代表总裁室总结了06财年的营收状况 xff0c 已经07年
  • ROS 进阶之 tf变换

    TF变换 一 http www guyuehome com 355 http www guyuehome com 279 world 坐标系 二 广播TF变换 1 广播tf变换 向系统中广播参考系之间的坐标变换关系 系统中更可能会存在多个不
  • 易扩展的SLAM框架-OpenVSLAM

    本文介绍了一种具有较高可用性和可扩展性的可视化SLAM框架 OpenVSLAM 视觉SLAM系统对于AR设备 机器人和无人机的自主控制等是必不可少的 然而 传统的开源视觉SLAM框架并没有像从第三方程序调用的库那样进行适当的设计 为了克服这
  • GVINS:基于GNSS视觉惯性紧耦合的平滑状态估计方法

    文章 xff1a GVINS Tightly Coupled GNSS Visual Inertial Fusion for Smooth and Consistent State Estimation 作者 xff1a Shaozu Ca
  • opencv中ArUco模块实践(1)

    论文阅读模块将分享点云处理 xff0c SLAM xff0c 三维视觉 xff0c 高精地图相关的文章 公众号致力于理解三维视觉领域相关内容的干货分享 xff0c 欢迎各位加入我 xff0c 我们一起每天一篇文章阅读 xff0c 开启分享之
  • ROS2入门之基本介绍

    论文阅读模块将分享点云处理 SLAM 三维视觉 高精地图相关的文章 公众号致力于理解三维视觉领域相关内容的干货分享 欢迎各位加入我 我们一起每天一篇文章阅读 开启分享之旅 有兴趣的可联系微信dianyunpcl 163 com 前言 201
  • ikd-Tree:增量KD树在机器人中的应用

    文章 xff1a ikd Tree An Incremental K D Tree for Robotic Applications 作者 xff1a Yixi Cai Wei Xu and Fu Zhang 编译 xff1a 点云PCL
  • 视觉SLAM从传统到语义方法的概述

    文章 xff1a An Overview on Visual SLAM From Tradition to Semantic 作者 xff1a Weifeng Chen Guangtao Shang Aihong Ji Chengjun Z
  • Android 11 PackageManagerService源码分析(二):Packages.xml详解

    1 开篇 在上一篇文章中提到Settings类会在PackageManagerService启动过程中对packages xml等一些列xml文件进行解析 那么有以下问题 xff1a 这些文件记录了什么内容 xff1f 为什么需要这些文件