Android 应用安装过程源码解析

本文是 Android 系统学习系列文章中的第一章节的内容,介绍了 PackageManagerService 在启动时如何去加载已安装的应用,通过一个新的应用是如何在 PackageManagerService 的帮助下完成安装过程的。对此系列感兴趣的同学,可以收藏这个链接 Android 系统学习,也可以点击 RSS订阅 进行订阅。

接下来的分析,如果没有特别提及,是基于 SDK-23 版本进行的。

系统解析安装包过程

自行解析 Android APK 信息该如何入手呢?从这里入手的话,就得完整地知道系统是如何完成这个过程的,那么自然而然地就能想到通过 PackageManageService 入手进行分析,毕竟是掌管所有应用的大执行官。通过前面 Binder 系列的学习,我们了解到 PackageManageService 是将自己注入到 SystemManager 中去的,其后其他应用就可以通过 SystemServer 来访问 PackageManageService 了。

在 SystemServer 启动后,执行了 PackageManagerService 的 main 方法,后面的 isFirstBoot 方法是避免 PackageManangerService 被重复启动。

// Start the package manager.
Slog.i(TAG, "Package Manager");
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
        mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
mFirstBoot = mPackageManagerService.isFirstBoot();
mPackageManager = mSystemContext.getPackageManager();

main 方法中,调用了构造函数,并将自己注入到 SystemServer 去,看起来大部分逻辑都在构造函数里,接着往下分析。

public static PackageManagerService main(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    PackageManagerService m = new PackageManagerService(context, installer,
            factoryTest, onlyCore);
    ServiceManager.addService("package", m);
    return m;
}

构造函数相当复杂,这里只说明,对我们分析有用的东西。


// ... other code

// Collect vendor overlay packages.
// (Do this before scanning any apps.)
// For security and version matching reason, only consider
// overlay packages if they reside in VENDOR_OVERLAY_DIR.
File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
        | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);

// Find base frameworks (resource packages without code).
scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
        | PackageParser.PARSE_IS_SYSTEM_DIR
        | PackageParser.PARSE_IS_PRIVILEGED,
        scanFlags | SCAN_NO_DEX, 0);

// Collected privileged system packages.
final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
        | PackageParser.PARSE_IS_SYSTEM_DIR
        | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);

// Collect ordinary system packages.
final File systemAppDir = new File(Environment.getRootDirectory(), "app");
scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
        | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

// Collect all vendor packages.
File vendorAppDir = new File("/vendor/app");
try {
    vendorAppDir = vendorAppDir.getCanonicalFile();
} catch (IOException e) {
    // failed to look up canonical path, continue with original one
}
scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
        | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

// Collect all OEM packages.
final File oemAppDir = new File(Environment.getOemDirectory(), "app");
scanDirLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM
        | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

// ... other code

如上代码,可以看到 PackageManagerService 在构造函数的时候,会扫描对应目录下的 APK,保证这些 APK 的信息被预先加载进来。我在 root 的手机下,在 /system/priv-app 目录下截图,这些就是系统高优先级加载的系统应用。

priv-app-tele

接下来看看 scanDirLI 这个函数,看看这里面进行了什么勾当。

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) {
            // ...
        }
    }
}

看起来,这里面只是做了对 APK 文件的过滤,并没有实际对于 APK 解析的代码,那么接着看看 scanPackageLI 的实现。

private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
        long currentTime, UserHandle user) throws PackageManagerException {

    // ...

    final PackageParser.Package pkg;
    try {
        pkg = pp.parsePackage(scanFile, parseFlags);
    } catch (PackageParserException e) {
        throw PackageManagerException.from(e);
    }

    // ...

}

scanPackageLI 的实现也相对复杂,比如设计对供应商内置 APP 的更新逻辑等等,这里只关心是如何解析 APK 的,那么看看 parsePackage 是怎么实现的,我们能否通过 parsePackage 来达到我们自动解析 APK 的目的了?

parsePackage 方法,在不同 SDK 的版本里面实现各不一样,有兴趣的读者可以从这个链接 http://grepcode.com/search?query=packageParser 里面查看各个版本的实现,这里只看 SDK 23 版本的实现,这里针对 APK 目录和单一的 APK 文件分别进行处理。

public Package parsePackage(File packageFile, int flags) throws PackageParserException {
    if (packageFile.isDirectory()) {
        return parseClusterPackage(packageFile, flags);
    } else {
        return parseMonolithicPackage(packageFile, flags);
    }
}

接着看看 parseMonolithicPackage 的实现,前面的 mOnlyCoreApps 参数是对 Core APP 进行的处理,这边可以不看,主要看 parseBaseApk 方法的实现。

public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
    if (mOnlyCoreApps) {
        final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
        if (!lite.coreApp) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                    "Not a coreApp: " + apkFile);
        }
    }

    final AssetManager assets = new AssetManager();
    try {
        final Package pkg = parseBaseApk(apkFile, assets, flags);
        pkg.codePath = apkFile.getAbsolutePath();
        return pkg;
    } finally {
        IoUtils.closeQuietly(assets);
    }
}

在 parseBaseApk 中开始根据各个具体的节点(如 Application、Activity 等等),进行解析,最后得到整个 Package 的信息。

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

        // ...

        if (tagName.equals("application")) {
            if (!parseBaseApplication(pkg, res, parser, attrs, flags, outError)) {
                return null;
            }
        } else if (tagName.equals("overlay")) {
          // ...
        } else if (tagName.equals("permission")) {
            if (parsePermission(pkg, res, parser, attrs, outError) == null) {
                return null;
            }
        }

        // ...
        return pkg;
}

这些得到的各种信息,最后都会存储在 PackageManagerService 中,这些信息就此保留下来,可用用来响应各种 IntentFilter ,等待启动命令。

经过上面的代码分析,可以看出,如果想要获取相应的包信息,可以调用 Package.parsePackage 方法来进行解析,这里唯一需要注意的地方在于这个方法的签名在不同 SDK 版本是不同的,需要针对不同版本做处理。

APK 包安装过程

我们知道怎么获取包信息了,但这还不够,我们在安装应用的时候,还弹出了一个安装界面,这个安装界面背后有什么逻辑了?这些逻辑应该也对我们实现加载插件很有帮助吧,我们来仔细地分析下。

实际在处理安装应用 Intent 的是 PackageInstallerActivity,但这个类厂商可以随意修改,这个类也并没有在 android.jar 中,这里就不做分析了。PackageInstallerActivity 在安装过程中,实际调用的是 ApplicationPackageManager 里面的代码。

private void installCommon(Uri packageURI,
        PackageInstallObserver observer, int flags, String installerPackageName,
        VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
    if (!"file".equals(packageURI.getScheme())) {
        throw new UnsupportedOperationException("Only file:// URIs are supported");
    }
    if (encryptionParams != null) {
        throw new UnsupportedOperationException("ContainerEncryptionParams not supported");
    }

    final String originPath = packageURI.getPath();
    try {
        mPM.installPackage(originPath, observer.getBinder(), flags, installerPackageName,
                verificationParams, null);
    } catch (RemoteException ignored) {
    }
}

在 ApplicationPackageManager 中是通过 Binder 机制调用了 PackageManagerService 中的 installPackage 方法,让我们一探究竟。

@Override
public void installPackage(String originPath, IPackageInstallObserver2 observer,
        int installFlags, String installerPackageName, VerificationParams verificationParams,
        String packageAbiOverride) {
    installPackageAsUser(originPath, observer, installFlags, installerPackageName,
            verificationParams, packageAbiOverride, UserHandle.getCallingUserId());
}

接着调用了 installPackageAsUser 方法。

public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
        int installFlags, String installerPackageName, VerificationParams verificationParams,
        String packageAbiOverride, int userId) {
          final File originFile = new File(originPath);

    // 权限校验

    final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile);

    final Message msg = mHandler.obtainMessage(INIT_COPY);
    msg.obj = new InstallParams(origin, null, observer, installFlags, installerPackageName,
            null, verificationParams, user, packageAbiOverride, null);
    mHandler.sendMessage(msg);
}

原来是通过 Handler 方式发送消息的,那么看看 PackageHandler 是如何处理这个消息的。在接受到 INIT_COPY 消息后,将要安装的参数信息加入到 PendingInstalls 中去,如果是第一个安装,还需要发送 MCS_BOUND 消息,用于触发实际安装过程。

case INIT_COPY: {
    HandlerParams params = (HandlerParams) msg.obj;
    int idx = mPendingInstalls.size();
    if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
    // If a bind was already initiated we dont really
    // need to do anything. The pending install
    // will be processed later on.
    if (!mBound) {
        // If this is the only one pending we might
        // have to bind to the service again.
        if (!connectToService()) {
            Slog.e(TAG, "Failed to bind to media container service");
            params.serviceError();
            return;
        } else {
            // Once we bind to the service, the first
            // pending request will be processed.
            mPendingInstalls.add(idx, params);
        }
    } else {
        mPendingInstalls.add(idx, params);
        // Already bound to the service. Just make
        // sure we trigger off processing the first request.
        if (idx == 0) {
            mHandler.sendEmptyMessage(MCS_BOUND);
        }
    }
    break;
}

在 MCS_BOUND 消息中取出第一个安装请求,并调用 startCopy 方法。

HandlerParams params = mPendingInstalls.get(0);
if (params != null) {
   if (params.startCopy()) {
     // ...
   }
   // ...
}

在 startCopy 中调用 handleStartCopy 方法,由于这个类,需要与 MCS (MediaContainerService) 进行通信,有可能发生异常,因而这里设置了重试机制。

if (++mRetries > MAX_RETRIES) {
    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
    mHandler.sendEmptyMessage(MCS_GIVE_UP);
    handleServiceError();
    return false;
} else {
    handleStartCopy();
    res = true;
}
handleReturnCode();

接着看看 handleStartCopy 里面是如何进行的,为啥还有可能失败?

public void handleStartCopy() throws RemoteException {

  // 安装在哪里?
  final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
  final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;

  pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,
          packageAbiOverride);

  // 如果安装空间不够了,尝试释放一些空间
  // 校验等逻辑

  final InstallArgs args = createInstallArgs(this);
  // ...
  args.copyApk();

}

private InstallArgs createInstallArgs(InstallParams params) {
    if (params.move != null) {
        return new MoveInstallArgs(params);
    } else if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) {
        return new AsecInstallArgs(params);
    } else {
        return new FileInstallArgs(params);
    }
}

看起来失败的可能性,大多来自于 Media 服务,空间不足,当时不能写等等都可能导致失败。在空间判断、校验通过后,根据不同的情况创建不同的 InstallArgs,这里只看 FileInstallArgs。copyApk 的主要任务是拷贝 APK 文件和对应的 lib 文件到 /data/app/{packageName} 目录下。

data_app_package.png
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
  // ...
  final IParcelFileDescriptorFactory target = new IParcelFileDescriptorFactory.Stub() {
    @Override
    public ParcelFileDescriptor open(String name, int mode) throws RemoteException {
        if (!FileUtils.isValidExtFilename(name)) {
            throw new IllegalArgumentException("Invalid filename: " + name);
        }
        try {
            final File file = new File(codeFile, name);
            final FileDescriptor fd = Os.open(file.getAbsolutePath(),
                    O_RDWR | O_CREAT, 0644);
            Os.chmod(file.getAbsolutePath(), 0644);
            return new ParcelFileDescriptor(fd);
        } catch (ErrnoException e) {
            throw new RemoteException("Failed to open: " + e.getMessage());
        }
    }
  };
  ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);
  // ...
  final File libraryRoot = new File(codeFile, LIB_DIR_NAME);
  NativeLibraryHelper.Handle handle = null;
  try {
      handle = NativeLibraryHelper.Handle.create(codeFile);
      ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
              abiOverride);
  } catch (IOException e) {
      Slog.e(TAG, "Copying native libraries failed", e);
      ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
  } finally {
      IoUtils.closeQuietly(handle);
  }
}

在 handleStartCopy 后,就可以继续执行 handleReturnCode 后的代码。这部分会不会和前面 PackageManageService 在启动时候扫描系统 APK 的逻辑相同了?让我们拭目以待。

@Override
void handleReturnCode() {
    // If mArgs is null, then MCS couldn't be reached. When it
    // reconnects, it will try again to install. At that point, this
    // will succeed.
    if (mArgs != null) {
        processPendingInstall(mArgs, mRet);
    }
}

接着看 processPendingInstall 的实现。

if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
    args.doPreInstall(res.returnCode);
    synchronized (mInstallLock) {
        installPackageLI(args, res);
    }
    args.doPostInstall(res.returnCode, res.uid);
}

installPackageLI 中会一些列复杂的逻辑,这里只看针对新安装包的逻辑。

if (replace) {
    replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
            installerPackageName, volumeUuid, res);
} else {
    installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
            args.user, installerPackageName, volumeUuid, res);
}

在 installNewPackageLI 中会继续调用 scanPackageLI 方法,这和第一章节讲述的一直,这里就不再赘述了。

try {
    PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanFlags,
            System.currentTimeMillis(), user);

    updateSettingsLI(newPackage, installerPackageName, volumeUuid, null, null, res, user);
    // delete the partially installed application. the data directory will have to be
    // restored if it was already existing
    if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
        // remove package from internal structures.  Note that we want deletePackageX to
        // delete the package data and cache directories that it created in
        // scanPackageLocked, unless those directories existed before we even tried to
        // install.
        deletePackageLI(pkgName, UserHandle.ALL, false, null, null,
                dataDirExists ? PackageManager.DELETE_KEEP_DATA : 0,
                        res.removedInfo, true);
    }

} catch (PackageManagerException e) {
    res.setError("Package couldn't be installed in " + pkg.codePath, e);
}

可能读者会想安装包的资源怎么处理的?总不会只处理 lib 文件和 dex 文件吧,这是前面一个疏漏的地方,在文末进行下简单的补充。

在 Packageparser 进行解析的时候,会通过 AssetMananger 进行资源的加载。

XmlResourceParser parser = null;
AssetManager assmgr = null;
Resources res = null;
boolean assetError = true;
try {
  assmgr = new AssetManager();
  int cookie = assmgr.addAssetPath(mArchiveSourcePath);
  if (cookie != 0) {
    res = new Resources(assmgr, metrics, null);
    assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        Build.VERSION.RESOURCES_SDK_INT);
    parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
    assetError = false;
  } else {
    VLog.w(TAG, "Failed adding asset path:" + mArchiveSourcePath);
  }
} catch (Exception e) {
  VLog.w(TAG, "Unable to read AndroidManifest.xml of " + mArchiveSourcePath, e);
}

文档信息


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容