Android面试题(32)-android编译过程和安装流程
android app的编译过程
从网上拷了一张图
这张图很好的讲述了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;
我们先分析SystemServer是怎样启动PackageManagerService的:
(1)由SystemServer.main方法开始
SystemServer组件是由Zygote进程负责启动的,启动的时候就会调用它的main函数,这个函数主要调用了JNI方法init1来做一些系统初始化的工作。
(2)SystemServer.init1()方法:(JNI方法)
这个函数很简单,只是调用了system_init函数来进一步执行操作。
(3)system_init()方法(c++方法)
这个函数创建了一个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
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; }
我们如果继续跟踪scanPackageLI方法,发现最终程序经过很多次的if else 的筛选,最后判定可以安装后调用了 mInstaller.install:
PackageManagerService通过套接字的方式访问installd服务进程,在Android启动脚本init.rc中通过服务配置启动了installd服务进程,具体的installd过程是调用底层的C代码,这里就不做分析了,如果需要可以看这篇文章 链接
这里我们把PMS的作用总结一下:
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); }这里通过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:
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的加载流程
上一篇: 华为麦芒9值得买吗 华为麦芒9介绍