欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android面试题(32)-android编译过程和安装流程

程序员文章站 2022-06-28 21:54:47
...

android app的编译过程

从网上拷了一张图

Android面试题(32)-android编译过程和安装流程

这张图很好的讲述了android的编译打包过程,接下来就具体的分析分析,大致分为7步:

(1)aapt(Android Asset Packaging Tool,android构建工具,在android-sdk的build-tool目录下)它的主要工作就是把项目中使用到的资源文件打包成R.java文件;

(2)aidl工具会将aidl接口转换为java接口

(3)java编译器就会将上述准备好的文件和我们在项目敲得java源文件打包成.class文件

       R.java文件+aidl接口+java源文件===>.class字节码文件;

(4)如果是java程序,把.class文件交给java虚拟机就可以了,但是android使用的不是java虚拟机,是davlik虚拟机,所以编译成.class文件还不行,还需要通过dex工具把.class文件打包成.dex文件,这里如果你项目中使用了第三方的库,也会在这里一起打包成.dex文件;

(5)通过apkbuilder工具将编译过的文件和那些没有编译过的文件(图片,视频等)加上上述的.dex文件一起打包成.apk文件;

(6)这时候的.apk文件还无法去使用,还需要通过Jarsigner这个工具对.apk进行签名,至于签名的原因:为了保证每个应用程序开发商合法ID,防止部分开放商可能通过使用相同的Package Name来混淆替换已经安装的程序,我们需要对我们发布的APK文件进行唯一签名,保证我们每次发布的版本的一致性(如自动更新不会因为版本不一致而无法安装)。

(7)签名过后的.apk文件其实就可以使用了,但是这时候的.apk文件太过杂乱,还需要Zipalign工具进行.apk文件的对其,减少内存,整理apk文件;

通过这七大步骤,一个apk文件就完整的生成出来了,那么我们知道apk文件是怎么生成的了,那么他又是怎么安装到我们的手机上呢?

android app的安装流程

android app的安装方式大致分为四种:

(1)系统应用安装---开机完成,没有安装界面;

(2)网络下载应用安装----通过mark应用完成,没有安装界面

(3)adb工具安装----没有安装界面

(4)第三方应用安装----通过sd卡中的apk文件安装,有安装界面,由Packageinstaller.apk应用处理完成及卸载

四大目录:

system/app-----系统自带的应用程序,获得adb root权限才能够访问

data /app-----用户程序安装目录,用户安装时将apk文件拷贝至此目录

data/data-----存放应用程序的数据

data/dalvik-cache----将apk中的dex文件安装到此目录下;

安装过程(用户程序):复制APK安装包到data/app目录下,解压并扫描安装包,把dex文件(Dalvik字节码)保存到dalvik-cache目录,并data/data目录下创建对应的应用数据目录。

安装步骤大致分为四步:

(1) 拷贝apk文件到指定目录
在Android系统中,apk安装文件是会被保存起来的,默认情况下,用户安装的apk首先会被拷贝到 /data/app 目录下。
/data/app目录是用户有权限访问的目录,在安装apk的时候会自动选择该目录存放用户安装的文件,而系统出厂的apk文件则被放到了 /system 分区下,包括 /system/app,/system/vendor/app,以及 /system/priv-app 等等,该分区只有Root权限的用户才能访问,这也就是为什么在没有Root手机之前,我们无法删除系统出厂的app的原因了。
(2) 解压apk,拷贝文件,创建应用的数据目录
为了加快app的启动速度,apk在安装的时候,会首先将app的可执行文件(dex)拷贝到 /data/dalvik-cache 目录,缓存起来。
然后,在/data/data/目录下创建应用程序的数据目录(以应用的包名命名),存放应用的相关数据,如数据库、xml文件、cache、二进制的so动态库等等。
(3) 解析apk的AndroidManifinest.xml文件
Android系统中,也有一个类似注册表的东西,用来记录当前所有安装的应用的基本信息,每次系统安装或者卸载了任何apk文件,都会更新这个文件。这个文件位于如下目录:
/data/system/packages.xml
系统在安装apk的过程中,会解析apk的AndroidManifinest.xml文件,提取出这个apk的重要信息写入到packages.xml文件中,这些信息包括:权限、应用包名、APK的安装位置、版本、userID等等。
由此,我们就知道了为啥一些应用市场和软件管理类的app能够很清楚地知道当前手机所安装的所有的app,以及这些app的详细信息了。
另外一件事就是Linux的用户Id和用户组Id,以便他可以获得合适的运行权限。
以上这些都是由PackageServiceManager完成的,下面我们会重点介绍PackageServiceManager。
(4) 显示快捷方式
这些应用程序只是相当于在PackageManagerService服务注册好了,如果我们想要在Android桌面上看到这些应用程序,还需要有一个Home应用程序,负责从PackageManagerService服务中把这些安装好的应用程序取出来,并以友好的方式在桌面上展现出来,例如以快捷图标的形式。在Android系统中,负责把系统中已经安装的应用程序在桌面中展现出来的Home应用程序就是Launcher了

接下来详细说说这几个步骤,再说之前,我们先弄清楚一个概念,在之前的app的启动流程中,我们说到,所有的app组件启动都是由AMS去实现的 ,那么安装当然也会有一个对应的服务,也就是PackageManagerService;

Android系统在启动的过程中,会启动一个应用程序管理服务PackageManagerService,这个服务负责扫描系统中特定的目录,找到里面的应用程序文件,即以Apk为后缀的文件,然后对这些文件进解析,得到应用程序的相关信息。应用程序管理服务PackageManagerService安装应用程序的过程,其实就是解析析应用程序配置文件AndroidManifest.xml的过程,并从里面得到得到应用程序的相关信息,例如得到应用程序的组件Activity、Service、Broadcast Receiver和Content Provider等信息,有了这些信息后,通过ActivityManagerService这个服务,我们就可以在系统中正常地使用这些应用程序了。应用程序管理服务PackageManagerService是系统启动的时候由SystemServer组件启动的,启后它就会执行应用程序安装的过程,因此,本文将从SystemServer启动PackageManagerService服务的过程开始分析系统中的应用程序安装的过程。

我们先分析SystemServer是怎样启动PackageManagerService的:
(1)由SystemServer.main方法开始
SystemServer组件是由Zygote进程负责启动的,启动的时候就会调用它的main函数,这个函数主要调用了JNI方法init1来做一些系统初始化的工作。
(2)SystemServer.init1()方法:(JNI方法)
这个函数很简单,只是调用了system_init函数来进一步执行操作。

(3)system_init()方法(c++方法)

这个函数首先会初始化SurfaceFlinger、SensorService、AudioFlinger、MediaPlayerService、CameraService和AudioPolicyService这几个服务,然后就通过系统全局唯一的AndroidRuntime实例变量runtime的callStatic来调用SystemServer的init2函数了。
(4)AndroidRuntime.callStatic方法(c++方法)

这个函数调用由参数className指定的java类的静态成员函数,这个静态成员函数是由参数methodName指定的。上面传进来的参数className的值为"com/android/server/SystemServer",而参数methodName的值为"init2",因此,接下来就会调用SystemServer类的init2函数了。
(5)SystemServer.init2()方法
这个函数创建了一个ServerThread线程,PackageManagerService服务就是这个线程中启动的了。这里调用了ServerThread实例thr的start函数之后,下面就会执行这个实例的run函数了。

(6)SystemServer.run()方法(PackageManagerService就是在这里启动的)

这个函数除了启动PackageManagerService服务之外,还启动了其它很多的服务

我们知道了PackageManagerService的启动流程之后,接下来就针对以上的四种情况一一进行分析,看看PMS是如何去进行app安装的;

(1)系统应用安装

A.扫描安装app

我们之前说过apk文件都是放在对应的文件目录下的,所以我们第一步做的事就是需要在对应文件夹中扫描对应的apk文件,PMS是通过调用它的scanDirLI方法:

private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
    final File[] files = dir.listFiles();
    if (ArrayUtils.isEmpty(files)) {
        Log.d(TAG, "No files in app dir " + dir);
        return;
    }

    if (DEBUG_PACKAGE_SCANNING) {
        Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags
                + " flags=0x" + Integer.toHexString(parseFlags));
    }
    ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
            mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
            mParallelPackageParserCallback);

    // Submit files for parsing in parallel
    int fileCount = 0;
    for (File file : files) {
        final boolean isPackage = (isApkFile(file) || file.isDirectory())
                && !PackageInstallerService.isStageName(file.getName());
        if (!isPackage) {
            // Ignore entries which are not packages
            continue;
        }
        parallelPackageParser.submit(file, parseFlags);
        fileCount++;
    }

    // Process results one by one
    for (; fileCount > 0; fileCount--) {
        ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
        Throwable throwable = parseResult.throwable;
        int errorCode = PackageManager.INSTALL_SUCCEEDED;

        if (throwable == null) {
            // Static shared libraries have synthetic package names
            if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {
                renameStaticSharedLibraryPackage(parseResult.pkg);
            }
            try {
                if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
                    scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags,
                            currentTime, null);
                }
            } catch (PackageManagerException e) {
                errorCode = e.error;
                Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage());
            }
        } else if (throwable instanceof PackageParser.PackageParserException) {
            PackageParser.PackageParserException e = (PackageParser.PackageParserException)
                    throwable;
            errorCode = e.error;
            Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage());
        } else {
            throw new IllegalStateException("Unexpected exception occurred while parsing "
                    + parseResult.scanFile, throwable);
        }

        // Delete invalid userdata apps
        if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
                errorCode == PackageManager.INSTALL_FAILED_INVALID_APK) {
            logCriticalInfo(Log.WARN,
                    "Deleting invalid package at " + parseResult.scanFile);
            removeCodePathLI(parseResult.scanFile);
        }
    }
    parallelPackageParser.close();
}

分别扫描以下五个文件夹:

    /system/framework

    /system/app

    /vendor/app

    /data/app

    /data/app-private
由于Android每次启动的时候都需要安装一次信息,但是有些信息是保持不变的,例如Linux用户组Id,PackageManagerService 每次安装程序之后,都会把这些程序的信息保存下来,以便下次使用,恢复上一次程序的安装信息是通过PackageManagerService 的成员变量mSetting的readLP()来实现的,恢复信息之后就开始扫描和安装app了。对于目录中的每一个文件,如果是以后Apk作为后缀名,那么就调用scanPackageLI函数来对它进行解析和安装。

B.调用PMS的scanPackageLI方法

private PackageParser.Package scanPackageLI(PackageParser.Package pkg, File scanFile,
        final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
        throws PackageManagerException {
    // If the package has children and this is the first dive in the function
    // we scan the package with the SCAN_CHECK_ONLY flag set to see whether all
    // packages (parent and children) would be successfully scanned before the
    // actual scan since scanning mutates internal state and we want to atomically
    // install the package and its children.
    if ((scanFlags & SCAN_CHECK_ONLY) == 0) {
        if (pkg.childPackages != null && pkg.childPackages.size() > 0) {
            scanFlags |= SCAN_CHECK_ONLY;
        }
    } else {
        scanFlags &= ~SCAN_CHECK_ONLY;
    }

    // Scan the parent
    PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, scanFile, policyFlags,
            scanFlags, currentTime, user);

    // Scan the children
    final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
    for (int i = 0; i < childCount; i++) {
        PackageParser.Package childPackage = pkg.childPackages.get(i);
        scanPackageInternalLI(childPackage, scanFile, policyFlags, scanFlags,
                currentTime, user);
    }


    if ((scanFlags & SCAN_CHECK_ONLY) != 0) {
        return scanPackageLI(pkg, scanFile, policyFlags, scanFlags, currentTime, user);
    }

    return scannedPkg;
}
这个函数首先会为这个Apk文件创建一个PackageParser实例,接着调用这个实例的parsePackage函数来对这个Apk文件进行解析。这个函数最后还会调用另外一个版本的scanPackageLI函数把来解析后得到的应用程序信息保存在PackageManagerService中。
这有一个PackageParser,只需要记住这个类主要是对apk文件进行解析,主要就是解析AndroidManifest.xml文件;每一个Apk文件都是一个归档文件,它里面包含了Android应用程序的配置文件AndroidManifest.xml,这里主要就是要对这个配置文件就行解析了,从Apk归档文件中得到这个配置文件后,就调用另一外版本的parsePackage函数对这个应用程序进行解析了;
我们如果继续跟踪scanPackageLI方法,发现最终程序经过很多次的if else 的筛选,最后判定可以安装后调用了 mInstaller.install:
PackageManagerService通过套接字的方式访问installd服务进程,在Android启动脚本init.rc中通过服务配置启动了installd服务进程,具体的installd过程是调用底层的C代码,这里就不做分析了,如果需要可以看这篇文章   链接

这里我们把PMS的作用总结一下:

1)从apk, xml中载入pacakge信息, 存储到内部成员变量中, 用于后面的查找. 关键的方法是scanPackageLI().
2)各种查询操作, 包括query Intent操作.
3)install package和delete package的操作. 还有后面的关键方法是installPackageLI().

(2)从网络上下载应用:
其实这种安装方式最后都是调用installer的接口去进行安装,只不过在之前会进行一些逻辑处理:
首先,下载完成后,会自动调用PackageManager的installPackage方法:
public void installPackage( final Uri packageURI, final IPackageInstallObserver observer, final int flags, final String installerPackageName) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.INSTALL_PACKAGES, null); Message msg = mHandler.obtainMessage(INIT_COPY); msg.obj = new InstallParams(packageURI, observer, flags, installerPackageName); mHandler.sendMessage(msg); } 

public void installPackage( final Uri packageURI, final IPackageInstallObserver observer, final int flags, final String installerPackageName) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.INSTALL_PACKAGES, null); Message msg = mHandler.obtainMessage(INIT_COPY); msg.obj = new InstallParams(packageURI, observer, flags, installerPackageName); mHandler.sendMessage(msg); }

public void installPackage(final Uri packageURI,final IPackageInstallObserver observer,final int flags,final String installerPackageName){
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,null);
        Message msg=mHandler.obtainMessage(INIT_COPY);
        msg.obj=new InstallParams(packageURI,observer,flags,installerPackageName);
        mHandler.sendMessage(msg);
}
这里通过PackageHandler的实例mhandler.sendMessage(msg)把信息发给继承Handler的类PackageHandler的HandleMessage()方法

在HandleMessage方法中调用doHandleMessage方法,doHandleMessage中使用了switch对传入的message进行判断,在其中会调用抽象类HandlerParams中的一个startCopy()方法,在startCopy()中会调用handleReturnCode()方法,这个方法复写了两次其中有一次是删除时要调用的,只列出安装调用的一个方法,在它里面调用了processPendingInstall方法,processPendingInstall的run方法中会调用installPacakgeLI,最后判断如果以前不存在那么调用installNewPackageLI(),这时候你会惊奇的发现,在installNewPackageLI方法中调用了PMS的scanPackageLI,这就回到了上面说的;

整个调用链:

下载完成自动调用->PackageManager.installPackage()->packageHandler.sendMessage(msg)将信息发送->PackageHandler.handlerMessage()->doHandlerMessage()->HandlerParams.startCopy()->handlerReturnCode()->ProcessPendingInstall()->installPackgeLI()->installerNewPackageLI()->scanPackageLI();

(3)从ADB工具安装:

其入口函数源文件为pm.java,安装时候会调用其 runInstall()方法

private int runInstall() throws RemoteException {
    long startedTime = SystemClock.elapsedRealtime();
    final InstallParams params = makeInstallParams();
    final String inPath = nextArg();
    if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
        File file = new File(inPath);
        if (file.isFile()) {
            try {
                ApkLite baseApk = PackageParser.parseApkLite(file, 0);
                PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
                        null, null);
                params.sessionParams.setSize(
                        PackageHelper.calculateInstalledSize(pkgLite, false,
                        params.sessionParams.abiOverride));
            } catch (PackageParserException | IOException e) {
                System.err.println("Error: Failed to parse APK file: " + e);
                return 1;
            }
        } else {
            System.err.println("Error: Can't open non-file: " + inPath);
            return 1;
        }
    }

    final int sessionId = doCreateSession(params.sessionParams,
            params.installerPackageName, params.userId);

    try {
        if (inPath == null && params.sessionParams.sizeBytes == -1) {
            System.err.println("Error: must either specify a package size or an APK file");
            return 1;
        }
        if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
                false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
            return 1;
        }
        Pair<String, Integer> status = doCommitSession(sessionId, false /*logSuccess*/);
        if (status.second != PackageInstaller.STATUS_SUCCESS) {
            return 1;
        }
        Log.i(TAG, "Package " + status.first + " installed in " + (SystemClock.elapsedRealtime()
                - startedTime) + " ms");
        System.out.println("Success");
        return 0;
    } finally {
        try {
            mInstaller.abandonSession(sessionId);
        } catch (Exception ignore) {
        }
    }
}
其中
IPackageManager mPm;
mPm = IpackageManager.Stub.asInterface(ServiceManager.getService("package"));

Stub是接口IPackageManage的静态抽象类,asInterface是返回IPackageManager代理的静态方法。

因为class PackageManagerService extends IPackageManager.Stub

所以mPm.installPackage 调用网络入口下载的installPackage方法;

(4)从sd卡中安装

系统调用PackageInstallerActivity.java,进入这个Activity会判断信息是否有错,然后调用initiateinstaller方法去判断是否曾经有过同名包或者包已经安装过,通过后执行private void startInstallConfirm() 点击OK按钮后经过一系列的安装信息的判断Intent跳转到InstallAppProcess这个活动,在其中调用了initView方法,方法再次调用安装接口完成安装。

讲到这里,我想我们可以回答一个android的一个面试题了:

Dalvik和art虚拟机有什么区别?

首先我们看看Dalvik和JVM有什么区别:

(1)Dalvik虚拟机支持的是.dex文件,而JVM支持的是.class文件

(2)Dalvik是基于寄存器的,而JVM是基于栈的。

(3)相比之下,Dalvik虚拟机占用更少的空间;

(4)Dalvik常量池只采用32位索引;

(5)标准Java字节码实行8位堆栈指令,Dalvik使用16位指令集直接作用于局部变量。局部变量通常来自4位的“虚拟寄存器”区。这样减少了Dalvik的指令计数,提高了翻译速度。

在来看看上面是art:

即Android Runtime
ART 的机制与 Dalvik 不同。在Dalvik下,应用每次运行的时候,字节码都需要通过即时编译器(just in time ,JIT)转换为机器码,这会拖慢应用的运行效率,而在ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。这个过程叫做预编译(AOT,Ahead-Of-Time)。这样的话,应用的启动(首次)和执行都会变得更加快速。
ART有什么优缺点呢?
优点:
1、系统性能的显著提升。
2、应用启动更快、运行更快、体验更流畅、触感反馈更及时。
3、更长的电池续航能力。
4、支持更低的硬件。
缺点:
1.机器码占用的存储空间更大,字节码变为机器码之后,可能会增加10%-20%(不过在应用包中,可执行的代码常常只是一部分。比如最新的 Google+ APK 是 28.3 MB,但是代码只有 6.9 MB。)
2.应用的安装时间会变长。


另外关于Activity的加载流程和Android系统的启动流程的文章有两篇,可以看看;
Android系统的启动流程
Activity的加载流程