PMS(PackageManagerService)原理简单介绍,启动过程源码简单解析

2023-05-16

文章目录

  • 前言
  • 1. PMS
  • 2. 源码和关键方法
      • SystemServer
      • PackageManagerService
      • ParallelPackageParser
      • PackageParser
  • 3. 细节总结
  • 4. 时序图startuml代码
  • 参考材料

前言

先想一个最直接的问题:
我们在写Android项目的时候,为什么要去写AndroidMenifest.xml这个清单文件?除了IDE强制你写以外,还有没有别的什么理由?

假如我们的项目很大,有上千个类,那么手机系统想要从项目里面遍历把启动类找出来是非常耗时的,所以我们通过将Android一些重要的资源(四大组件,Application还有权限)声明在AndroidManifest.xml里面,这样就省的系统一个个去找这些重要资源的时间。

顺便一提,在Windows或者别的系统里面不存在这样的问题,因为每个应用都会有个很明显的exe文件,只要通过运行exe(或者对应的快捷方式)文件就可以拉起整个应用,而Android系统中不同应用是根据包来区分的,而我们用的时候又没办法像Windows系统一样每个文件夹去翻,所以才会有这样的机制。

1. PMS

刚刚说了AndroidManifest.xml的作用是为了让手机系统明确自己应用中重要资源的位置,接下来就是重点了,具体是让手机系统中的哪个模块明确呢?这里就是我们今天的主角PMS登场了。

PMS(Package Manager Service):用于定位手机系统中所有的APK文件。

这个东西是伴随着Android系统而运行的,也就是说,当你的手机开启的时候,PMS就开始运行了。

工作原理简介
在手机系统开机的时候,PMS就会将你手机里面的存着不所有APP的目录遍历一遍,将其中的apk文件加载到内存里面,并且解析apk里面的androidMenifst.xml,以便于在用户点击应用的时候,快速的根据androidMenifest.xml中声明的内容来快速拉起APP(虽然实际上还是要消耗一定时间)。

todo:APK介绍和两个存放APK的地址

2. 源码和关键方法

PMS启动的关键流程时序图如下,在下面还会贴出关键代码,启动流程涉及到三个重要的类:

  • SystemServer:系统服务类,由zygote进程所启动的一个新的进程,我们常说的PMS,AMS和WMS都在SystemServer里面启动。
  • PackageManagerService:今天的主角,主要功能上文已经说过了。
  • PackageParser:用于解析APK和AndroidManifest.xml的工具类。
    请添加图片描述

从宏观的看完这个服务启动图之后,还需要从具体代码重新入手,把里面的关键方法都再看一遍,顺便整理一下里面的一些细节。

我会只截取有意义的代码部分,并且调整不同方法的顺序,让我们看的时候可以直接从上往下按顺序看。

如果某个方法中,我有进行省略代码的行为,我会注释表明,反之就是没有省略代码。
如果因为省略代码而看到了意义不明的参数,请忽略他,他并不会影响源码的阅读。

SystemServer

public final class SystemServer {
	/**
     * The main entry point from zygote.
     */
    public static void main(String[] args) {
        new SystemServer().run();
    }

	private void run() {
		// 省略无关代码
		TimingsTraceAndSlog t = new TimingsTraceAndSlog(); // 记录各种操作的耗时用的,这里不用管
		startBootstrapServices(t); // AMS,PMS等都在这里启动
        startCoreServices(t);
        startOtherServices(t);
	}
	
	private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) {
		// 省略无关代码,主要看启动PMS服务的这行
		mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
                    mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
	}
}
  1. 这是一个有静态main方法的类,这就意味着他可以是一个单独的进程,事实上SystemServer在开机的时候可以单独启动一个进程来操作的。
  2. 可以看到在run方法里面,启动了很多的服务,我们常说的AMS,PMS,WMS等都是在这里启动的(所以这些服务都是系统开机的时候启动的)

PackageManagerService

public class PackageManagerService extends IPackageManager.Stub
        implements PackageSender {
    
	/** 存储已安装应用程序的目录 */
    private static final File sAppInstallDir = new File(Environment.getDataDirectory(), "app");

	/** 在这个静态方法中,会实例化一个新的PMS */
	public static PackageManagerService main(Context context, Installer installer,
	            boolean factoryTest, boolean onlyCore) {
        // 省略无关代码
		PackageManagerService m = new PackageManagerService(injector, onlyCore, factoryTest);
	}

	/** PMS的构造方法 */
	public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) {
		// 省略无关代码,顺便一提这个构造方法算上注释有2000+行了
		if (!mOnlyCore) {
			scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0,
                        packageParser, executorService);
		}
	}

	/** 用Trace对初始化的过程做个耗时记录,*/
	private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags,
            long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
        try {
            scanDirLI(scanDir, parseFlags, scanFlags, currentTime, packageParser, executorService);
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }

	/** */
	private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime,
            PackageParser2 packageParser, ExecutorService executorService) {
        // 省略无关代码
        final File[] files = scanDir.listFiles(); // 获得所有的文件列表
        ParallelPackageParser parallelPackageParser =new ParallelPackageParser(packageParser, executorService); // 创建一个用于处理apk文件的对象

 		for (File file : files) {
	        final boolean isPackage = (isApkFile(file) || file.isDirectory())
	                && !PackageInstallerService.isStageName(file.getName());
	        if (!isPackage) {
	        	// 不是APK文件就置之不理
	            continue;
	        }
	        parallelPackageParser.submit(file, parseFlags); // 解析每个APK文件
    	}
    }
}
  1. 可以看到,PMS是用最基本的File类的API,对着整个APP的安装目录扫了一圈,并且将其中的APK文件抓去解析。
  2. 在源码过程中,可以看到多次Trace类的出镜,他的主要作用就是记录某段时间内系统的运行情况,一般用于做性能优化(没有数据的话谷歌工程师也不会知道哪里需要优化,这个就是记数据的)。

ParallelPackageParser

class ParallelPackageParser {
	private static final int QUEUE_CAPACITY = 30;
	// 线程池的最大线程数量
    private static final int MAX_THREADS = 4;

	private final BlockingQueue<ParseResult> mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);
	
	/** 线程池,用于处理并发任务 */
	private final ExecutorService mExecutorService;

	private final PackageParser mPackageParser;
	
	/**
     * 提交用于解析的文件
     */
    public void submit(File scanFile, int parseFlags) {
        mExecutorService.submit(() -> {
            ParseResult pr = new ParseResult();
            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");
            try {
                pr.scanFile = scanFile;
                pr.parsedPackage = parsePackage(scanFile, parseFlags);  // 解析APK
            } catch (Throwable e) {
                pr.throwable = e;
            } finally {
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
            }
            try {
                mQueue.put(pr);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                mInterruptedInThread = Thread.currentThread().getName();
            }
        });
    }

	 @VisibleForTesting
    protected ParsedPackage parsePackage(File scanFile, int parseFlags)
            throws PackageParser.PackageParserException {
        return mPackageParser.parsePackage(scanFile, parseFlags, true);
    }
}
  1. 他是个多线程任务,通过并行处理来同时对多个APK文件来进行解析
  2. 顺便一提,在android的早期版本6.0,这部分是没有多线程的,是在高版本才改为并行处理。

PackageParser

核心的解析类,内容会比较多

public class PackageParser {

	public Package parsePackage(File packageFile, int flags, boolean useCaches)
	        throws PackageParserException {
	    if (packageFile.isDirectory()) {
	    	// 这次不看文件是目录的情况
	        return parseClusterPackage(packageFile, flags);
	    } else {
	        return parseMonolithicPackage(packageFile, flags);
	    }
	}

	public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
		// 省略无关代码
		final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);
		return package
	}

	private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
            throws PackageParserException {
        // 省略无关代码
        final String apkPath = apkFile.getAbsolutePath();
        XmlResourceParser parser = null;
        // 创建一个用于解析xml文件的实例,就是
        parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); 
        final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);  //解析androidMenifest.xml
        if (pkg == null) {
                throw new PackageParserException(mParseError,
                        apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
            }
        return pkg;
    }

	private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
            String[] outError) throws XmlPullParserException, IOException {
        // 省略无关代码	
        
        // 这里只是获取APK的版本号等信息
       	TypedArray sa = res.obtainAttributes(parser,
               com.android.internal.R.styleable.AndroidManifest);
        pkg.mVersionCode = sa.getInteger(
                com.android.internal.R.styleable.AndroidManifest_versionCode, 0);
        pkg.mVersionCodeMajor = sa.getInteger(
                com.android.internal.R.styleable.AndroidManifest_versionCodeMajor, 0);
        pkg.applicationInfo.setVersionCode(pkg.getLongVersionCode());
        pkg.baseRevisionCode = sa.getInteger(
                com.android.internal.R.styleable.AndroidManifest_revisionCode, 0);
        pkg.mVersionName = sa.getNonConfigurationString(
                com.android.internal.R.styleable.AndroidManifest_versionName, 0);
        if (pkg.mVersionName != null) {
            pkg.mVersionName = pkg.mVersionName.intern();
        }

		return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
    }

	private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,
            XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException,
            IOException {
        // 省略无关代码

		// 遍历androidManifest中的所有的标签
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
        	String tagName = parser.getName();
        	// 标签是application标签的情况
        	if (tagName.equals(TAG_APPLICATION)) { 
    		    if (!parseBaseApplication(pkg, res, parser, flags, outError)) {
                    return null;
                }
            // 标签是permission的情况
        	} else if (tagName.equals(TAG_PERMISSION)) {
                if (!parsePermission(pkg, res, parser, outError)) {
                    return null;
                }
            }
       	}
    }

	private boolean parseBaseApplication(Package owner, Resources res,
            XmlResourceParser parser, int flags, String[] outError) 
            throws XmlPullParserException, IOException {

		// 再去找Application标签里面的标签,Activity等标签都在里面
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { 
            String tagName = parser.getName();
            if (tagName.equals("activity")) {
            	Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,
                        owner.baseHardwareAccelerate);
                owner.activities.add(a);
            } else if (tagName.equals("receiver") {
            	Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
                        true, false);
                owner.receivers.add(a);
            } else if (tagName.equals("service")) {
            	Service s = parseService(owner, res, parser, flags, outError, cachedArgs);
            	owner.services.add(s);
            } else if (tagName.equals("provider")) {
            	Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);
            	owner.providers.add(p);
            }
        }    
        return true;
    }
}
  1. 基本上就是将xml里面的标签全部保存下来
  2. 要注意的是,这里的Activity,Service和Provider不是真的四大组件,只是一个用于记录组件信息的缩略类。
/** 这个类不仅是个缩略类,也是PackageParser的内部类,主要用于记录组件的信息 */
public final static class Activity extends Component<ActivityIntentInfo> implements Parcelable {
        @UnsupportedAppUsage
        public final ActivityInfo info;
        private boolean mHasMaxAspectRatio;
        private boolean mHasMinAspectRatio;
}

3. 细节总结

  1. SystemService是一个单独的进程,AMS,PMS等服务都由它管理
  2. PMS用多线程去解析我们的安装文件目录
  3. 解析的过程本质就是将androidMenifest.xml中的标签取下来保存到一个信息类里面,并不会对数据做实际的校验(所以会有可能出现androidMenifest.xml和实际的项目对不上的情况)。

4. 时序图startuml代码

@startuml

participant SystemServer 
participant PackageManagerService
participant ParallelPackageParser
participant PackageParser

SystemServer -> SystemServer : main
SystemServer -> SystemServer : run

activate SystemServer
	SystemServer -> SystemServer : startBootstrapServices
	activate SystemServer
	SystemServer -> PackageManagerService : main
	activate PackageManagerService
	PackageManagerService -> PackageManagerService : new PackageManagerService
	PackageManagerService -> PackageManagerService : ScanDirTracedLI 
	PackageManagerService -> PackageManagerService : ScanDirLI
	loop 遍历文件夹内所有的APK文件
		PackageManagerService --> ParallelPackageParser : submit
		activate ParallelPackageParser
			ParallelPackageParser -> PackageParser : parsePackage
			activate PackageParser
			PackageParser -> PackageParser : parseMonolithicPackage
			PackageParser -> PackageParser : parseBaseApk(这里参数传的是路径)
			PackageParser -> PackageParser : parseBaseApk(这里参数传的是androidMenifest)
			PackageParser -> PackageParser : parseBaseApkCommon(这里解析androidMenifest中定义的各种资源)
			PackageParser -> PackageParser : parseBaseApplication(解析application标签内的子标签)
			alt 标签名为Activity或者receiver
				PackageParser -> PackageParser : parseActivity
			else 标签名为Service
				PackageParser -> PackageParser : parseService
			else 标签名为Provider
				PackageParser -> PackageParser : parseProvider
			end 
		deactivate ParallelPackageParser
	end
	deactivate PackageManagerService
	deactivate SystemServer
deactivate SystemServer

@enduml

参考材料

码牛学院VIP24-2020.8.24-PMS服务启动原理详解,从开机到APP启动PMS服务处理机制与流程-david

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

PMS(PackageManagerService)原理简单介绍,启动过程源码简单解析 的相关文章

  • Xstart远程连接Linux图形用户界面

    目标 xff1a 在自己的Windows桌面打开Linux的firefox浏览器 工具 xff1a Windows Xmanager的Xstart工具 Linux xterm firefox 说明 xff1a 使用Xstart远程连接Lin
  • 微信内置小程序在线客服功能

    在小程序中加入客服消息按钮 小程序接入微信 客服消息 功能模块 xff0c 开发者只需要调用按钮 xff0c 触发微信的客服消息功能即可 xff0c 不需要自行在小程序中实现 加入客服消息按钮有两个方法 xff0c 大家可以根据自己的实际需
  • Java实例化泛型

    public D newUsr D newUsr try 通过反射获取model的真实类型 ParameterizedType pt 61 ParameterizedType this getClass getGenericSupercla
  • Windows桌面下面任务栏无法点击(卡住)的解决办法

    Windows桌面下面任务栏无法点击 卡住 的解决办法 大家再使用Windows的时候 xff0c 有时候会碰到桌面卡住无法点击下面的任何图标的现象 xff0c 若不知道如何解决 xff0c 可能就开始重启电脑了 xff0c 其实不必要 x
  • 剑指offer03

    数组中的重复数字 题目 在一个长度为 n 的数组 nums 里的所有数字都在 0 xff5e n 1 的范围内 数组中某些数字是重复的 xff0c 但不知道有几个数字重复了 xff0c 也不知道每个数字重复了几次 请找出数组中任意一个重复的
  • 如何pycharm与jupyter lab/notebook结合使用

    如何pycharm与jupyter lab notebook结合使用 原因效果教程 原因 jupyter lab的自动补全并不好用 xff0c 使用了kite后总是存在卡顿的现象 xff0c 正好在pycharm中支持jupyter lab
  • 【杭电100题】2073 无限的路

    题目链接 xff1a http acm hdu edu cn showproblem php pid 61 2073 xff08 c语言的double类型printf lf 显示0 00000问题 xff09 xff1a https blo
  • 【杭电100题】2094 产生冠军

    原题 xff1a http acm hdu edu cn showproblem php pid 61 2094 最近很喜欢用map 把成功者 失败者都存起来 然后在成功者里把曾经失败的划掉 最后成功者里如果只剩一个人 xff0c 冠军产生
  • 逆变电路

    逆变的概念 与整流相对应 xff0c 直流电 变成 交流电 交流侧接电网 xff0c 为 有源逆变 交流侧接负载 xff0c 为 无源逆变 xff0c 本章主要讲述无源逆变 逆变与变频 变频电路 xff1a 分为 交交变频 和 交直交变频
  • 博客搭建教程1-Archlinux环境配置

    文章目录 1 前言2 archlinux镜像下载3 archlinux安装 1 前言 这个教程主要讲解linux环境下博客的搭建 xff0c 这里的linux系统选择archlinux xff0c 博客的框架基于hexo框架 参考博客 xf
  • 如何在git bash中启用复制粘贴快捷键

    方法一 xff1a 1 鼠标右键点击左上角 xff0c 选择属性 xff08 如果是英文就选择properties xff09 xff1a 2 在选项中勾选相应选项 xff1a 然后就完事了 方法二 xff1a 从其他地方复制一段文本 2
  • 栈,队列(纸牌游戏,小猫钓鱼)

    文章目录 队列 xff1a FIFO实现顺序队列 xff1a 1 顺序循环队基本操作2 链队 栈1 顺序栈栈的元素初始化操作入栈操作判断顺序栈是否为空栈的长度出栈清空一个栈销毁顺序栈 2 链式栈 应用栈1 xff1a 判断回文字符串栈与队列
  • SELinux

    domain https www cnblogs com ly565911158 p 3622942 html coredomain https www cnblogs com liwugang p 12433028 html xff08
  • 电脑任务栏无法点击

    解决方法 1 启动任务管理器 xff08 Ctrl 43 alt 43 找到windows 资源管理器重新启动 xff09 2 win10 更新后右下方有个天气资讯的推送点击全部选择关闭 xff0c 在任务栏上也点击取消
  • 如何快速获取网页源码(直接把网站的 js css html 扒下来的)

    如何快速获取网页源码 xff1f 我们在学习和研究的时候 或者看到非常酷炫的页面效果 xff0c 需要网站的源代码进行借鉴 xff0c 但每次需要下载网站源代码 xff0c 我们都需要找到一个 xff0c 下载一个 xff0c 每次只能下载
  • 人工智能大作业——人脸识别系统(最终)

    写在前面 时间过得飞快 xff0c 时间已经距离我发第一篇文章过去2年多了 xff0c 也不再从事代码工作 xff0c 偶然间上到csdn翻看文章经过 xff0c 看到还是有些人关注人脸识别系统的后续的 xff0c 我猜大概率是学弟学妹们正
  • JAVA json 三种格式

    json三种格式 span class token class name JSONObject span jsonParam span class token operator 61 span span class token keywor
  • java.lang.NumberFormatException: For input string: ""解决方案

    引起异常的主要原因如下 xff1a 1 传参字段和映射字段不一致2 传参类型和映射类型不一致3 时间类型转换时间戳长度不一致4 参数长度和数据库不一致 Service 层代码 span class token keyword public
  • 个性化命令提示符CMD,不止简单地美化,Dos命令让你的命令提示符cmd花里胡哨

    自重温了下注册表知道了autorun子项后 我把cmd重新设计了一遍 先上图 win10系统 cmd版本为10 0 17763 1 原理 添加注册表命令行的自启动项 使启动cmd时会自动运行项的命令值 打开注册表 win R 输入reged
  • Pandas入门第二章之数据的读取

    本节主要介绍pandas经常读取的两种数据格式 xff0c 其分别是CSV和JSON本节使用两个数据集分别是2019腾讯算法大赛和中国AI创新创业大赛的数据集 没有标签的原始数据的格式 带标题的数据格式 本节在介绍pandas读取CSV文件

随机推荐

  • 使用Javascript 创建枚举类型(enum)

    使用Javascript 创建枚举类型 xff08 enum xff09 1 枚举类型的定义 是指将变量的值一一列出来 变量的值只限于列举出来的值的范围内 2 typescript中的枚举类型 span class token keywor
  • 一个七年Java女程序员的年终总结,写给过去一年的自己

    简单先说一下 xff0c 坐标杭州 xff0c 14届本科毕业 xff0c 算上年前在阿里巴巴B2B事业部的面试 xff0c 一共有面试了有6家公司 xff08 因为不想请假 xff0c 因此只是每个晚上去其他公司面试 xff0c 所以面试
  • HTML初识

    文章目录 思维导图HTML标签浏览器内核Web标准骨架标签VScode的使用网页开发工具解释标签图像标签注意点路径视频格式 xff08 后续会补充 xff09 链接 思维导图HTML标签 xff08 表示后面有相应解释 xff09 浏览器内
  • 建造者模式

    建造者模式 建造者模式也属于创建型模式 xff0c 它提供了一种创建对象的最佳方式 定义 将一个复杂对象的构建与它的表示分离 xff0c 使得同样的构建过程可以创建不同的表示 主要作用 在用户不知道对象的建造过程和细节的情况下就可以直接创建
  • 作为一名Web前端开发人员和设计师,2018告诉你如何正确的学习前端

    第一步 掌握HTML CSS 这是你最初必须 掌握的是网站的构建元素没得选 随着你前端的学习进程 熟练掌握HTML CSS简单易学这里还是要推荐下小编的web前端学习群 606加721加798 xff0c 不管你是小白还是大牛 xff0c
  • R语言对正交实验结果(含交互作用)进行极差分析与方差分析实例

    题目 某工厂为了提高某产品的收率 xff0c 根据经验和分析 xff0c 认为反应温度A 反应时间B 碱用量C和催化剂种类D可能对产品的收率造成较大的影响 并考虑交互作用AB xff0c AC 用正交表L8 27 安排试验 xff0c 试验
  • git突然pull push不了 一直fetching

    4 14 今天改完代码之后在idea中push的时候一直fetching xff0c 提交不了代码 改用命令push被拒绝 xff0c pull可以 xff0c 但是特别慢 首先考虑是公司要求定期更改密码 xff0c 但是排除 因为已经改了
  • 配置VNC图形界面服务

    第一步 xff1a 安装Gnome图形化界面 要能远程访问图形化界面 xff0c 首先服务器自身要安装图形化界面 xff0c 在此我们还要安装中文支持套件 yum groupinstall 34 X window System 34 34
  • Activity四种启动模式及onNewIntent()方法

    1 Standard xff1a 是活动默认的启动模式 xff0c 在不进行显式指定的情况下 xff0c 所有活动都会自动使用这种启动模式 系统不在乎这个活动是否已经在返回栈中存在 xff0c 每次启动都会创建该活动的一个新的实例 2 Si
  • Lnuix中查看pytorch和python安装版本和路径

    Lnuix中查看pytorch和python安装版本和路径 1 查看pytorch安装版本和路径 conda activate pytorch环境名称输入python查看版本号 span class token function impor
  • Python之FileNotFoundError: [Errno 2] No such file or directory问题处理

    错误信息 xff1a FileNotFoundError Errno 2 No such file or directory 39 AutoFrame temp report xlsx 39 相对于当前文件夹的路径 xff0c 其实就是你写
  • 基于centos7学习总结 -- shell脚本

    shell 脚本必须要以 34 bin bash 34 开头 脚本建议内容 xff1a 脚本的功能脚本的版本信息脚本的作者与联系方式脚本的版权声明方式脚本的History脚本内特殊的命令 xff0c 使用 绝对路径 的方式来执行脚本运行时需
  • 关于java里的Collections工具类的max和min以及Arrays工具的二分查找。

    标题和沙雕 xff0c 很乱 xff1a 本文主要介绍两个在Java util里的工具类里的一小部分小小的方法 xff1a Collections类的max 和min Arrays类的asList 和二分查找 数组和集合的转换 一 Coll
  • js基本输入输出,变量,数据类型,案例。

    文章目录 1 计算机编程基础 xff1a 2 JS3 变量4 数据类型a 5种简单数据类型 xff1a 案例 b typeof获取变量类型 xff1a c 转化为数值型的放法 xff1a d 转化为字符型的方法 案例 xff1a 5 扩展阅
  • Android 7.0Settings加载主界面流程

    新人一枚 xff0c 没有整机环境 xff0c 有什么写的不对欢迎批评指正 xff0c 万分感谢 xff01 Settings主界面加载时序图 xff08 这里很多判断逻辑我省略掉了 更多的是想把加载主界面流程跑通 xff09 这张流程图将
  • C# 获取图片,Pdf中的文字

    识别图片中的文字 首先把下载好的tessdata放在自己项目的bin Debug tessdata文件夹中 附一个tessdata的下载地址 xff1a https github com tesseract ocr tessdata 命名空
  • 1449-The user specified as a definer (‘mysql.infoschema‘@‘localhost‘) does not exit

    navicat连接MySQL数据库时1449 The user specified as a definer 把本机mysql数据库5 6版本的数据备份后 xff0c 卸载5 6 版本 xff0c 安装了最新的8 0 27版本 xff0c
  • 最新版Docker Desktop安装在windows10上会出现的WSL2错误

    有科技的可以去这个帖子看 xff0c 解决WSL是最新版也无法运行docker的情况 查了很多帖子都是牛头不对马嘴 xff0c 不说废话直接上解决方案 1 Docker运行出现的问题 Docker Core HttpBadResponseE
  • C#各种官网文档链接

    目录 1 WinFrom NET Framework 2 TeeChart 3 C 教程 xff1a C 入门经典教程 4 C语言中文网 1 WinFrom NET Framework 官方文档YYDS xff0c 以前忽略了 xff0c
  • PMS(PackageManagerService)原理简单介绍,启动过程源码简单解析

    文章目录 前言1 PMS2 源码和关键方法SystemServerPackageManagerServiceParallelPackageParserPackageParser 3 细节总结4 时序图startuml代码参考材料 前言 先想