Android T PackageInstaller解析

news/2025/3/15 15:36:46/文章来源:https://www.cnblogs.com/kato-T/p/18773737

PackageInstallerActivity 解析

Apk安装方式

  • 通过adb命令安装 adb install -r
  • 用户下载的Apk,通过系统安装器packageinstaller安装该Apk
  • 系统内置应用,开机时自动安装系统应用

执行到最后都是由PackageManagerService来进行处理.

解析系统安装器PackageInstaller安装Apk

InstallStart

InstallStart是PackageInstaller中的入口Activity,其中PackageInstaller是系统内置的应用程序,用于安装和卸载应用。
当我们调用PackageInstaller来安装应用时会跳转到InstallStart,并调用它的onCreate方法。

packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {...Intent intent = getIntent();...final boolean isSessionInstall =PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());// If the activity was started via a PackageInstaller session, we retrieve the calling// package from that session//判断intent的action是否等于PackageInstaller.ACTION_CONFIRM_INSTALL,//如果是,这次安装为PackageInstaller session安装 会去intent中取出SESSION_ID,//并且得到SessionInfo信息,并从中得到callingPackage、callingAttributionTag。//当前Activity是运行在安装应用进程中,PackageInstaller session是在系统进程中。//在系统进程中,如果检测到权限不够,需要用户来确认,这时,会启动InstallStart以便进入用户确认界面。  final int sessionId = (isSessionInstall? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1): -1);if (callingPackage == null && sessionId != -1) {PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);callingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;callingAttributionTag =(sessionInfo != null) ? sessionInfo.getInstallerAttributionTag() : null;}...//判断是否勾选“未知来源”选项,若未勾选跳转到设置安装未知来源界面    if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);if (targetSdkVersion < 0) {Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);// Invalid originating uid supplied. Abort install.mAbortInstall = true;} else if (targetSdkVersion >= Build.VERSION_CODES.O && !isUidRequestingPermission(originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {//对于大于等于 Android 8.0 版本,会先检查是否申请安装权限,若没有则中断安装    Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "+ Manifest.permission.REQUEST_INSTALL_PACKAGES);mAbortInstall = true;}}...Intent nextActivity = new Intent(intent);nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT| Intent.FLAG_GRANT_READ_URI_PERMISSION);...//isSessionInstall 为true,是PackageInstaller session安装,直接跳转PackageInstallerActivityif (isSessionInstall) {nextActivity.setClass(this, PackageInstallerActivity.class);} else {Uri packageUri = intent.getData();//如果得到的URI的getScheme()是ContentResolver.SCHEME_CONTENT,则会跳转到InstallStagingif (packageUri != null && packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {// [IMPORTANT] This path is deprecated, but should still work. Only necessary// features should be added.// Copy file to prevent it from being changed underneath this processnextActivity.setClass(this, InstallStaging.class);//如果得到的URI的getScheme()是PackageInstallerActivity.SCHEME_PACKAGE,//则会跳转到PackageInstallerActivity中		        } else if (packageUri != null && packageUri.getScheme().equals(PackageInstallerActivity.SCHEME_PACKAGE)) {nextActivity.setClass(this, PackageInstallerActivity.class);} else {//如果不是以上情况,则返回PackageManager.INSTALL_FAILED_INVALID_URI给调用者Activity。Intent result = new Intent();result.putExtra(Intent.EXTRA_INSTALL_RESULT,PackageManager.INSTALL_FAILED_INVALID_URI);setResult(RESULT_FIRST_USER, result);nextActivity = null;}}if (nextActivity != null) {startActivity(nextActivity);}finish();        }

判断 Uri 的 Scheme 协议,若是 SCHEME_CONTENT 则调用 InstallStaging。
若是 SCHEME_PACKAGE 则调用 PackageInstallerActivity。

PS:file协议的Uri通常以"file://"开头,用于标识本地文件系统上的文件路径。
而content协议的Uri则以"content://"开头,用于访问通过内容提供者(Content Provider)暴露的数据。

Android7.0以及更高版本我们会使用FileProvider来处理URI ,FileProvider会隐藏共享文件的真实路径,将路径转换成content://Uri路径,
这样就会跳转到InstallStaging::onResum()方法内,大致流程如下:

packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java/** Currently running task that loads the file from the content URI into a file */private @Nullable StagingAsyncTask mStagingTask;/** The file the package is in */private @Nullable File mStagedFile;@Overrideprotected void onResume() {super.onResume();// This is the first onResume in a single life of the activityif (mStagingTask == null) {// File does not exist, or became invalidif (mStagedFile == null) {// Create file delayed to be able to show errortry {mStagedFile = TemporaryFileManager.getStagedFile(this);} catch (IOException e) {showError();return;}}mStagingTask = new StagingAsyncTask();mStagingTask.execute(getIntent().getData());}} 

mStagedFile用于存储临时数据,mStagingTask拿到了content协议的Uri,追踪StagingAsyncTask拿到这个Uri后会做啥?

StagingAsyncTask是InstallStaging的内部类。

private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {@Overrideprotected Boolean doInBackground(Uri... params) {if (params == null || params.length <= 0) {return false;}Uri packageUri = params[0];try (InputStream in = getContentResolver().openInputStream(packageUri)) {// Despite the comments in ContentResolver#openInputStream the returned stream can// be null.if (in == null) {return false;}try (OutputStream out = new FileOutputStream(mStagedFile)) {byte[] buffer = new byte[1024 * 1024];int bytesRead;while ((bytesRead = in.read(buffer)) >= 0) {// Be nice and respond to a cancellationif (isCancelled()) {return false;}out.write(buffer, 0, bytesRead);}}} catch (IOException | SecurityException | IllegalStateException e) {Log.w(LOG_TAG, "Error staging apk from content URI", e);return false;}return true;}@Overrideprotected void onPostExecute(Boolean success) {if (success) {// Now start the installation again from a fileIntent installIntent = new Intent(getIntent());installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);installIntent.setData(Uri.fromFile(mStagedFile));if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);}installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);startActivity(installIntent);InstallStaging.this.finish();} else {showError();}}}

doInBackground方法中将packageUri(content协议的Uri)的内容写入到mStagedFile中,
如果写入成功,onPostExecute方法中会跳转到PackageInstallerActivity中,并将mStagedFile传进去。
绕了一圈又回到了PackageInstallerActivity,这里可以看出InstallStaging主要起了转换的作用,将content协议的Uri转换为File协议,
然后跳转到PackageInstallerActivity,这样就可以像此前版本(Android7.0之前)一样启动安装流程了。

PackageInstallerActivity

PackageInstallerActivity才是应用安装器PackageInstaller真正的入口Activity。
简单说一下几个函数的作用。

    @Overrideprotected void onCreate(Bundle icicle) {...//根据Uri的Scheme进行预处理,对apk进行解析boolean wasSetUp = processPackageUri(packageUri);... }@Overrideprotected void onResume() {super.onResume();...if (mAppSnippet != null) {// load dummy layout with OK button disabled until we override this layout in// startInstallConfirmbindUi();checkIfAllowedAndInitiateInstall();//检查是否允许安装该软件包,如果允许,则启动安装。如果不允许,会弹出对应的对话框。}...private void initiateInstall() {String pkgName = mPkgInfo.packageName;//检查设备上是否已存在具有此名称的包// Check if there is already a package on the device with this name// but it has been renamed to something else.String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });if (oldName != null && oldName.length > 0 && oldName[0] != null) {pkgName = oldName[0];mPkgInfo.packageName = pkgName;mPkgInfo.applicationInfo.packageName = pkgName;}// Check if package is already installed. display confirmation dialog if replacing pkg//检查包是否已安装。如果替换pkg,则显示确认对话框try {// This is a little convoluted because we want to get all uninstalled// apps, but this may include apps with just data, and if it is just// data we still want to count it as "installed".mAppInfo = mPm.getApplicationInfo(pkgName,PackageManager.MATCH_UNINSTALLED_PACKAGES);if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {mAppInfo = null;}} catch (NameNotFoundException e) {mAppInfo = null;}//初始化安装确认界面startInstallConfirm();}   

在PackageInstallerActivity调用startInstallConfirm方法初始化安装确认界面后。
之后的流程:

PackageInstallerActivity::startInstall()-->InstallInstalling::onCreate()--->InstallInstalling::onResume()--->InstallInstalling::onPostExecute(){...//PackageInstaller.Session sessionsession.commit(pendingIntent.getIntentSender());...
}./frameworks/base/core/java/android/content/pm/PackageInstaller.java说明要通过IPackageInstallerSession来进行进程间的通信,
最终会调用PackageInstallerSession的commit方法,
这样代码逻辑就到了Java框架层的。

1.将APK的信息通过IO流的形式写入到PackageInstaller.Session中。
2.调用PackageInstaller.Session的commit方法,将APK的信息交由PMS处理。

禁止安装三方应用
PackageManagerService 作用

PackageInstaller 静默安装实现

MTK-Android13-包安装器PackageInstaller 静默安装实现-需要改动文件过多
MTK Android12静默安装接口-只能用于系统签名apk

 /*** 根据包名判断app是否具有系统签名*/private static boolean isSystemSign (Context context){return context.getPackageManager().checkSignatures(Binder.getCallingUid(), android.os.Process.SYSTEM_UID) == PackageManager.SIGNATURE_MATCH;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/899226.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Contest3898 - 计科23级算法设计与分析平时作业-01

题目链接 A.DNA Sorting 题面思路 题目意思就是说,如果一个字符串中前面的字符比后面的字符大,那么它的无序度就+1,根据这个给一组字符串从最有序到最无序依次输出。那么明白题目意思之后直接模拟即可。 示例代码 #include<bits/stdc++.h>using namespace std;#define…

web56笔记(甚⾄把数字都给过滤掉了,还有部分的特殊字符)

<?php/* # -*- coding: utf-8 -*- # @Author: Lazzaro # @Date: 2020-09-05 20:49:30 # @Last Modified by: h1xa # @Last Modified time: 2020-09-07 22:02:47 # @email: h1xa@ctfer.com # @link: https://ctfer.com*/// 你们在炫技吗? if(isset($_GET[c])){$c=$_GET…

Odoo17中套件追溯

基于序列号的套件产品追溯 我们知道odoo原生的套件BOM类型并不会产生真正的库存,从而导致了某些情况下我们想要对套件进行追溯的困难性。基于这样的一个背景,我们在欧姆生产解决方案中新增了对套件产品的追溯拓展,下面我们就来看看如何使用吧。 产品设置 假设我们这里一台组…

Linux | 堆内存管理

from pixiv进程的地址空间jyy 进程的地址空间 Linux 堆内存管理深入分析如何查看Linux进程的地址空间? 答:pmap /proc/$PID/maps/proc文件系统 动态内核信息: /proc 是一个虚拟文件系统,主要提供内核和正在运行的进程的信息。它不是存储在磁盘上的真实文件,而是在运行时动…

如何使用Wled控制RGB LED

要装饰您的照明设置,WS2812B RGB led是最好的选择之一。为了控制这些可寻址的led,我们需要一个像ESP32这样的微控制器,还必须上传代码。编写代码来控制可寻址led并不难,但如果你只是想在客厅或办公室添加一些环境照明,并通过智能手机来管理它呢?目前最好的选择,毫无疑问…

将树莓派Pico变成带有触摸屏界面的USB-HID设备

将您的树莓派Pico变成带有触摸屏界面的USB-HID设备!在这个项目中,我们将在Arduino IDE中设置编程环境,为3.5”Pico Touch LCD屏蔽配置库,测试基本功能,创建简单的按钮(用于复制和粘贴),显示自定义图标,甚至使用Windows环境变量来启动程序。设置编程环境 安装Arduino IDE …

20250315

1. 20号胶 3-5的细分

语音交友app源码,优化白屏降低用户负面情绪

语音交友app源码,优化白屏降低用户负面情绪目标: 缩小打包体积,优化白屏时间路由懒加载const Home = () => import(/* webpackChunkName: "home" */"@/views/home/index.vue"); const MetricGroup = () => import(/* webpackChunkName: "met…

Visio绘制时间轴安排图的方法

本文介绍基于Visio软件绘制时间轴、日程安排图、时间进度图等的方法~本文介绍基于Visio软件绘制时间轴、日程安排图、时间进度图等的方法。在很多学习、工作场合中,我们往往需要绘制如下所示的一些带有具体时间进度的日程安排、工作流程、项目进展等可视化图表。而基于Visio软…

华为---MUX VLAN简介及示例配置

https://blog.csdn.net/lehe99/article/details/1426209886.4 配置及解析 system-view [Huawei]sysname R [R]interface GigabitEthernet 0/0/0 [R-GigabitEthernet0/0/0]ip address 192.168.11.254 24 [R]interface LoopBack 11 [R-LoopBack11]ip address 11.11.11.11 32 syst…

SQL Server 列存储HTAP介绍

本篇文章是我线下活动分享的一个活动PPT,现在分享在我的个人博客中

基于QWidget打造的进度条控件(等待控件、加载控件)

效果图功能支持设置加载进度条颜色 支持设置中间显示文字、文字颜色、文字大小 基于QWidget开发, 开箱即用。全部代码 CircleWaitingWidget.h #pragma once #include <QWidget>/// /// 环状等待控件。 /// class CircleWaitingWidget : public QWidget {Q_OBJECTpubli…