【Android 系统开发】_“核心服务”篇 -- PMS(4)- PackageInstaller

开篇

PackageManagerService 系列文章以下(基于 Android 9.0 源码)

         🍁   Framework 核心服务之 PackageManagerService 钻研(1)- 启动流程                          
         🍁   Framework 核心服务之 PackageManagerService 钻研(2)- 构造函数
         🍁   Framework 核心服务之 PackageManagerService 钻研(3)- PackageManager
         🍁   Framework 核心服务之 PackageManagerService 钻研(4)- PackageInstaller
         🍁   Framework 核心服务之 PackageManagerService 钻研(5)- APK 安装流程(PackageInstaller)
         🍁   Framework 核心服务之 PackageManagerService 钻研(6)- APK 安装流程(PMS)
         🍁   Framework 核心服务之 PackageManagerService 钻研(7)- PackageParserhtml

核心源码

关键类 路径
PackageInstaller.java frameworks/base/core/java/android/content/pm/PackageInstaller.java
InstallStart.java packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
InstallStaging.java packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
PackageInstallerActivity.java packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java

PackageManager 简介

在详细讲解包管理机制如何安装 APK 以前,咱们须要了解 PackageManagerjava

与 ActivityManagerService(AMS) 和 ActivityManager 的关系相似,PackageManagerService(PMS) 也有一个对应的管理类 PackageManager ,用于向应用程序进程提供一些功能。android

PackageManager 是一个抽象类,它的具体实现类为 ApplicationPackageManager ,ApplicationPackageManager 中的方法会经过 IPackageManager 与 PMS 进行进程间通讯,所以 PackageManager 所提供的功能最终是由 PMS 来实现的,这么设计的主要用意是为了不系统服务 PMS 直接被访问。segmentfault

/**
 * Class for retrieving various kinds of information related to the application
 * packages that are currently installed on the device.
 *
 * You can find this class through {@link Context#getPackageManager}.
 */
// PackageManager 是一个抽象类
public abstract class PackageManager {}     

/** @hide */
// ApplicationPackageManager 继承自 PackageManager
public class ApplicationPackageManager extends PackageManager {}

抽象类 PackageManager 提供了的功能,主要有如下几点:微信

        ✨ 一、获取一个应用程序的全部信息(ApplicationInfo)
        ✨ 二、获取四大组件的信息
        ✨ 三、查询 permission 相关信息
        ✨ 四、获取包的信息
        ✨ 五、安装、卸载 APKsession

APK 文件结构和安装方式

APK 是 AndroidPackage 的缩写,即 Android 安装包,它其实是 zip 格式的压缩文件,通常状况下,解压后的文件结构以下表所示。app

目录/文件 描述
assert 存放的原生资源文件,经过 AssetManager 类访问。
lib 存放库文件。
META-INF 保存应用的签名信息,签名信息能够验证 APK 文件的完整性。
res 存放资源文件。res 中除了 raw 子目录,其余的子目录都参与编译,这些子目录下的资源是经过编译出的 R 类在代码中访问。
AndroidManifest.xml 用来声明应用程序的包名称、版本、组件和权限等数据。 apk 中的 AndroidManifest.xml 通过压缩,能够经过 AXMLPrinter2 工具解开。
classes.dex Java 源码编译后生成的 Java 字节码文件。
resources.arsc 编译后的二进制资源文件。

APK 的安装场景

目前,咱们常见的安装 APK 的场景主要分为如下 四种ide

✨ 一、经过 adb 命令安装:adb 命令包括 adb push/install。
✨ 二、用户下载的 Apk,经过系统安装器 packageinstaller(系统内置的应用程序,用于安装和卸载应用程序)安装该 Apk。
✨ 三、系统开机时安装系统应用。
✨ 四、电脑或者手机上的应用商店自动安装。函数

其实,这4种方式最终都是由 PMS 来进行处理,只是在此以前的调用链是不一样的。咱们在接下来的分析中,会选择第二种方式,由于对于开发者来讲,这是调用链比较长的安装方式(利于咱们分析源码)。工具

咱们看下这种安装方式的 实际操做图 :(后面咱们分析的代码流程会紧密联系着这几张图,你们能够对比理解!)
微信截图_20181129131739.png

APK 安装相关目录

目录/文件 描述
/system/app 系统自带的应用程序,得到 adb root 权限才能删除。
/data/app 用户程序安装的目录,安装时把 apk 文件复制到此目录。
/data/data 存放应用程序的数据。
/data/dalvik-cache 将 apk 中的 dex 文件安装到 dalvik-cache 目录下(dex 文件是 dalvik 虚拟机的可执行文件,固然,ART-Android Runtime 的可执行文件格式为 .oat ,启动 ART 时,系统会执行 dex 文件转换至 oat 文件)。
/data/system 该目录下的 packages.xml 文件相似于 Window 的注册表,这个文件是解析 apk 时由 writeLP() 建立的,里面记录了系统的 permissons ,以及每一个 apk 的 name,codePath,flag,ts,version ,userid 等信息,这些信息主要经过 apk 的 AndroidManifest 解析获取,解析完 apk 后将更新信息写入这个文件并保存到 flash ,下次开机的时候直接从里面读取相关信息并添加到内存相关列表中。当有 apk 升级,安装或删除时会更新这个文件。
/data/system/package.xml 包含了该应用申请的权限、签名和代码所在的位置等信息系,而且二者都有同一个userld。之因此每一个应用都要一个userId,是由于Android在系统设计上把每一个应用当作Linux系统上的一个用户对待,这样就能够利用已有的Linux用户管理机制来设计Android应用,好比应用目录,应用权限,应用进程管理等。
/data/system/package.list 指定了应用的默认存储位置/data/data/com.xxx.xxx。

PackageInstaller 初始化

首先,咱们须要指出的是:从 Android 8.0 开始系统是经过以下代码安装指定路径中的 APK:

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("file://" 
                      + apkfile.toString()), "application/vnd.android.package-archive");

Intent 的 Action 属性为 ACTION_VIEW,Type 属性指定 Intent 的数据类型为 application/vnd.android.package-archive。

"application/vnd.android.package-archive" 是什么?

final String[][] MIME_MapTable={ 
            //{后缀名,MIME类型} 
            {".3gp",    "video/3gpp"}, 
            {".apk",    "application/vnd.android.package-archive"}, 
            {".asf",    "video/x-ms-asf"}, 
            {".avi",    "video/x-msvideo"}, 
            ... ...

咱们发现 "application/vnd.android.package-archive" 其实就是文件类型,具体对应 apk 类型。

那么这个 Intent 隐式匹配的 Activity 是什么?这边咱们直接告诉你:InstallStart!(其实,7.0能隐式匹配的 Activity 为 PackageInstallerActivity ,二者的区别咱们后面会讲解到!)。

// packages/apps/PackageInstaller/AndroidManifest.xml
<activity android:name=".InstallStart"
        android:exported="true"
        android:excludeFromRecents="true">
    <intent-filter android:priority="1">
        <action android:name="android.intent.action.VIEW" />
        <action android:name="android.intent.action.INSTALL_PACKAGE" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="file" />
        <data android:scheme="content" />
        <data android:mimeType="application/vnd.android.package-archive" />
    </intent-filter>
    ... ...
</activity>

InstallStart

其实,InstallStart 是 PackageInstaller 中的入口 Activity。当咱们调用 PackageInstaller 来安装应用时会跳转到 InstallStart,并调用它的 onCreate 方法,咱们来看看:

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mIPackageManager = AppGlobals.getPackageManager();
        Intent intent = getIntent();
        String callingPackage = getCallingPackage();

        ... ...

        /**
         * public static final String 
         *          ACTION_CONFIRM_PERMISSIONS = "android.content.pm.action.CONFIRM_PERMISSIONS";
         * 判断 Intent 的 Action 是否为 CONFIRM_PERMISSIONS ;
         */
        if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
            nextActivity.setClass(this, PackageInstallerActivity.class);
        } else {
            // 咱们此时的情景会走这边
            Uri packageUri = intent.getData();

            // 保证 packageUri 不为 null,判断 packageUri 的 Scheme 协议是不是 content
            if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE)
                    || packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) {
                // 跳转到 InstallStaging (Android 8.0)
                nextActivity.setClass(this, InstallStaging.class);
            } else if (packageUri != null && packageUri.getScheme().equals(
                    PackageInstallerActivity.SCHEME_PACKAGE)) {
                // 跳转到 PackageInstallerActivity(Android 7.0)
                nextActivity.setClass(this, PackageInstallerActivity.class);
            } else {
                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) {
            // 启动相应的 Activity(InstallStaging、PackageInstallerActivity)
            startActivity(nextActivity);
        }
        finish();
    }

InstallStaging

咱们是基于 Android 8.0 的代码进行的分析,因此会走到 InstallStaging 分支,咱们继续看源码:

@Override
    protected void onResume() {
        super.onResume();

        // This is the first onResume in a single life of the activity
        if (mStagingTask == null) {
            if (mStagedFile == null) {
                // Create file delayed to be able to show error
                try {
                    // 若是 File 类型的 mStagedFile 为 null,
                    // 则建立 mStagedFile,mStagedFile 用于存储临时数据
                    mStagedFile = TemporaryFileManager.getStagedFile(this);
                } catch (IOException e) {
                    showError();
                    return;
                }
            }

            mStagingTask = new StagingAsyncTask();
            // 启动 StagingAsyncTask 线程,并传入了 content 协议的 Uri
            mStagingTask.execute(getIntent().getData());
        }
    }

咱们能够看到, InstallStaging 主要作了两部分工做

        ✨ 一、判断 mStagingTask 是否为空,主要用于存储临时数据;

        ✨ 二、建立并启动 StagingAsyncTask 线程。

StagingAsyncTask

接下来,咱们看看这个线程所作的工做:

private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {
        @Override
        protected Boolean doInBackground(Uri... params) {
            if (params == null || params.length <= 0) {
                return false;
            }
            Uri packageUri = params[0];
            try (InputStream in = getContentResolver().openInputStream(packageUri)) {
                if (in == null) {
                    return false;
                }
                /*
                 * 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥
                 * 将 packageUri(content协议的Uri)的内容写入到 mStagedFile 中
                 * 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥
                 */                 
                try (OutputStream out = new FileOutputStream(mStagedFile)) {
                    byte[] buffer = new byte[1024 * 1024];
                    int bytesRead;
                    while ((bytesRead = in.read(buffer)) >= 0) {
                        if (isCancelled()) {
                            return false;
                        }
                        out.write(buffer, 0, bytesRead);
                    }
                }
            } catch (IOException | SecurityException e) {
                Log.w(LOG_TAG, "Error staging apk from content URI", e);
                return false;
            }
            return true;
        }

        @Override
        protected void onPostExecute(Boolean success) {
            if (success) {
                Intent installIntent = new Intent(getIntent());

                /*
                 * 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥
                 * 若是写入成功,跳转到 PackageInstallerActivity
                 * 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥
                 */                
                installIntent.setClass(InstallStaging.this, PackageInstallerActivity.class);
                
                // 并将 mStagedFile 传进去
                installIntent.setData(Uri.fromFile(mStagedFile));
                installIntent
                        .setFlags(installIntent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
                installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
                startActivityForResult(installIntent, 0);
            } else {
                showError();
            }
        }
    }

【总结】:doInBackground 方法中将 packageUri(content 协议的 Uri)的内容写入到 mStagedFile 中,若是写入成功,onPostExecute 方法中会跳转到 PackageInstallerActivity 中,并将 mStagedFile 传进去。

绕了一圈又回到了 PackageInstallerActivity,这里能够看出 InstallStaging 主要起了转换的做用,将 content 协议的 Uri 转换为 File 协议,而后跳转到 PackageInstallerActivity,接下来的安装流程就和 Android 7.0 同样了。

PackageInstallerActivity

接下来,咱们就要重点分析 PackageInstallerActivity !从功能上来讲, PackageInstallerActivity 才是应用安装器 PackageInstaller 真正的入口 Activity

咱们看下官方对于这个类的说明:

/**
 * This activity is launched when a new application is installed via side loading
 * The package is first parsed and the user is notified of parse errors via a dialog.
 * If the package is successfully parsed, the user is notified to turn on the install unknown
 * applications setting. A memory check is made at this point and the user is notified of out
 * of memory conditions if any. If the package is already existing on the device,
 * a confirmation dialog (to replace the existing package) is presented to the user.
 * Based on the user response the package is then installed by launching InstallAppConfirm
 * sub activity. All state transitions are handled in this activity
 */
 
/**
 * 当经过渠道安装一个应用程序的时候,会启动这个 Activity。
 * 若是在首次解析这个安装包的时候出现解析错误,会经过对话框的形式告诉用户。
 * 若是首次解析安装包的时候,成功解析了,则会通知用户去打开"安装未知应用程序设置"。
 * 在启动 Activity 的时候会进行内存检查,若是内存不足会通知用户。
 * 若是这个应用程序已经在这个设备安装过了,则会向用户弹出一个对话框询问用户是否"替换现有应用程序的安装包"。
 * 基于用户的回应,而后经过 InstallAppConfirm 的子类来安装应用程序。
 * 全部状态的转换都是在这 Activity 中处理。
 */

public class PackageInstallerActivity extends OverlayTouchActivity implements OnClickListener {
    ... ...
}

了解完了官方说明,接下来咱们查看它的 onCreate 方法:

onCreate

public class PackageInstallerActivity extends OverlayTouchActivity implements OnClickListener {
    ... ...

    PackageManager mPm;
    IPackageManager mIpm;
    AppOpsManager mAppOpsManager;
    PackageInstaller mInstaller;
    UserManager mUserManager;
    
    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        if (icicle != null) {
            mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY);
        }

        /**
         * 初始话 PackageManager 对象:具体用来执行安装操做,最终的功能是由 PMS 来实现的;
         */
        mPm = getPackageManager();
        
        /**
         * 初始话 IPackageManager 对象:一个AIDL的接口,用于和 PMS 进行进程间通讯;
         */
        mIpm = AppGlobals.getPackageManager();
        
        /**
         * 初始化 AppOpsManager 对象:用于权限动态检测,在,Android 4.3 中被引入;
         */
        mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
        
        /**
         * 初始化 PackageInstaller 对象:在该对象中包含了安装 APK 的基本信息;
         */
        mInstaller = mPm.getPackageInstaller();
        
        /**
         * 初始化 UserManager 对象:用于多用户管理;
         */
        mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
        
        final Intent intent = getIntent();
        
        ... ...

        final Uri packageUri;

        if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
            // 多是系统级别的应用安装时,须要受权走这个流程
            final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
            final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
            if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
                Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
                finish();
                return;
            }

            mSessionId = sessionId;
            packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
            mOriginatingURI = null;
            mReferrerURI = null;
        } else {
            // 若是是用户本身拉起来的安装,则默认 sessionId 为 -1,而且获取 packageUri
            mSessionId = -1;
            packageUri = intent.getData();
            mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
            mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
        }

        // 返回 URI 解析错误
        if (packageUri == null) {
            Log.w(TAG, "Unspecified source");
            setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
            finish();
            return;
        }

        // 若是设备为手表,则不支持
        if (DeviceUtils.isWear(this)) {
            showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
            return;
        }

        // 根据 Uri 的 Scheme 进行预处理
        boolean wasSetUp = processPackageUri(packageUri);      // 💥 💥 💥 重点方法 1 💥 💥 💥 
        if (!wasSetUp) {
            return;
        }

        bindUi(R.layout.install_confirm, false);

        // 判断是不是未知来源的应用,若是开启容许安装未知来源选项则直接初始化安装
        checkIfAllowedAndInitiateInstall();                    // 💥 💥 💥 重点方法 2 💥 💥 💥 
    }
    ... ...
}

processPackageUri

咱们首先来看看 processPackageUri 所作的工做:

/**
     * Parse the Uri and set up the installer for this package.
     * @param packageUri The URI to parse
     * @return {@code true} iff the installer could be set up
     */
    private boolean processPackageUri(final Uri packageUri) {
        mPackageURI = packageUri;

        // 获得 packageUri 的 Scheme 协议
        final String scheme = packageUri.getScheme();

        // 根据这个 Scheme 协议分别对 package 协议和 file 协议进行处理
        switch (scheme) {
            // 处理 scheme 为 package 的状况
            case SCHEME_PACKAGE: {
                try {
                    /**
                      *     PackageInfo mPkgInfo;
                      *
                      * 获取 package 对应的 Android 应用信息 PackageInfo 如:应用名称,权限列表等...
                      */
                    mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
                            PackageManager.GET_PERMISSIONS | PackageManager.MATCH_UNINSTALLED_PACKAGES);
                } catch (NameNotFoundException e) {
                }
                
                // 若是没法获取 PackageInfo ,弹出一个错误的对话框,而后直接退出安装
                if (mPkgInfo == null) {
                    Log.w(TAG, "Requested package " + packageUri.getScheme()
                            + " not available. Discontinuing installation");
                    showDialogInner(DLG_PACKAGE_ERROR);
                    setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                    return false;
                }
                // 建立 AppSnipet 对象,该对象封装了待安装 Android 应用的标题和图标
                mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo), 
                                  mPm.getApplicationIcon(mPkgInfo.applicationInfo));
            } break;

            // 处理 scheme 为 file 的状况
            case ContentResolver.SCHEME_FILE: {
                // 根据 packageUri 建立一个新的 File
                File sourceFile = new File(packageUri.getPath());
                // 建立 APK 文件的分析器 parsed
                // 💥 💥 💥 重点方法 💥 💥 💥 
                PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);      

                // 说明解析错误,则弹出对话框,并退出安装
                if (parsed == null) {
                    Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
                    showDialogInner(DLG_PACKAGE_ERROR);
                    setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                    return false;
                }
                
                /**
                 * 走到这边,说明解析成功
                 * 对 parsed 进行进一步处理获得包信息 PackageInfo,获取权限部分
                 * 这里面包含APK文件的相关信息
                 *
                 *     PackageInfo mPkgInfo;
                 */
                mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
                        PackageManager.GET_PERMISSIONS, 0, 0, null, new PackageUserState());
                        
                /*
                 *     private PackageUtil.AppSnippet mAppSnippet;
                 *
                 * 获取 PackageUtil.AppSnippet,AppSnippet 是 PackageUtil 的静态内部类
                 * 内部封装了 icon 和 label ;
                 * AppSnippet 中只有两个属性:lable(应用名称)、icon(应用图标)
                 *
                 *     // getAppSnippet 返回的是 label 和 icon
                 *     return new PackageUtil.AppSnippet(label, icon);  
                 */
                mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
            } break;

            // 若是不是这两个协议就会抛出异常
            default: {
                throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
            }
        }

        return true;
    }

PackageUtil.getPackageInfo

/**
     * Utility method to get package information for a given {@link File}
     */
    public static PackageParser.Package getPackageInfo(Context context, File sourceFile) {
        // new 了一个 PackageParser 对象
        final PackageParser parser = new PackageParser();
        parser.setCallback(new PackageParser.CallbackImpl(context.getPackageManager()));
        try {
            return parser.parsePackage(sourceFile, 0);
        } catch (PackageParserException e) {
            return null;
        }
    }

checkIfAllowedAndInitiateInstall

接下来咱们看看 checkIfAllowedAndInitiateInstall 作所工做:

private void checkIfAllowedAndInitiateInstall() {
        final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
                UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
        if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
            showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
            return;
        } else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
            startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
            finish();
            return;
        }

        /**
          * isInstallRequestFromUnknownSource    // 安装请求是否来自一个未知的源
          *
          * 判断若是容许安装未知来源或者根据 Intent 判断得出该 APK 不是未知来源
          */
        if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
            // 初始化安装
            initiateInstall();      // 💥 💥 💥 重点方法 1 💥 💥 💥 
        } else {
            final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
                    UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
            // 若是管理员限制来自未知源的安装, 就弹出提示 Dialog
            if ((unknownSourcesRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
                showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
            } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
                startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
                finish();
            } else {
                handleUnknownSources();      // 💥 💥 💥 重点方法 2 💥 💥 💥 
            }
        }
    }

initiateInstall

// 判断若是容许安装未知来源或者根据Intent判断得出该APK不是未知来源
    private void initiateInstall() {
        // 获得包名
        String pkgName = mPkgInfo.packageName;

        // 是否有同名应用已经安装上去了,在此安装则被认为是替换安装
        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
        // 检查这个包是否真的被安装,若是要替换,则显示替换对话框
        try {
            // 获取设备上的残存数据,而且标记为 “installed” 的,实际上已经被卸载的应用
            mAppInfo = mPm.getApplicationInfo(pkgName,
                    PackageManager.MATCH_UNINSTALLED_PACKAGES);
            if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
                // 若是应用是被卸载的,可是又是被标识成安装过的,则认为是新安装
                mAppInfo = null;
            }
        } catch (NameNotFoundException e) {
            mAppInfo = null;
        }

        // 列出权限列表,等待用户确认安装
        startInstallConfirm();
    }

咱们能够看到, initiateInstall 主要作了三件事

        ✨ 一、检查设备是不是同名安装,若是是则后续是替换安装。

        ✨ 二、检查设备上是否已经安装了这个安装包,若是是,后面是替换安装。

        ✨ 三、调用 startInstallConfirm() 这个方法是安装的核心代码。

startInstallConfirm

下面咱们就来看下 startInstallConfirm() 方法里面的具体实现:

private void startInstallConfirm() {
        // We might need to show permissions, load layout with permissions
        if (mAppInfo != null) {
            bindUi(R.layout.install_confirm_perm_update, true);
        } else {
            bindUi(R.layout.install_confirm_perm, true);
        }

        ((TextView) findViewById(R.id.install_confirm_question))
                .setText(R.string.install_confirm_question);
        TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost);
        tabHost.setup();
        ViewPager viewPager = (ViewPager)findViewById(R.id.pager);
        TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);

        // If the app supports runtime permissions the new permissions will
        // be requested at runtime, hence we do not show them at install.
        // 根据 sdk 版原本判断 app 是否支持运行时权限
        // 若是app支持运行时权限,这里会显示新的运行时权限
        boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion
                >= Build.VERSION_CODES.M;
                
        // 显示权限列表的变量,true:显示权限列表,false:未显示权限列表
        boolean permVisible = false;
        mScrollView = null;
        mOkCanInstall = false;
        int msg = 0;

        // perms 这个对象包括了该应用的用户的 uid 以及相应的一些权限,以及权限组信息
        AppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo);
        
        // 获取隐私相关权限的数量
        final int N = perms.getPermissionCount(AppSecurityPermissions.WHICH_ALL);

        // 判断是否为已经安装过的应用
        if (mAppInfo != null) {
            // 若是已经安装过则继续判断是否为系统应用
            msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
                    ? R.string.install_confirm_question_update_system
                    : R.string.install_confirm_question_update;
            // 用来显示权限列表的 scrollview
            mScrollView = new CaffeinatedScrollView(this);
            // 若是显示的内容超过了 mScrollView ,则就会折叠能够滚动
            mScrollView.setFillViewport(true);
            boolean newPermissionsFound = false;
            if (!supportsRuntimePermissions) {
                // 针对更新应用程序相对于旧版本而判断是否加入新的权限
                newPermissionsFound =
                        (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0);
                if (newPermissionsFound) {
                    // 将新的权限列表视频添加到滚动视图中
                    permVisible = true;
                    // 调用 AppSecurityPermissions 的 getPermissionsView方法来获取
                    // PermissionItemView,并将 PermissionItemView
                    // 添加到 CaffeinatedScrollView 中,
                    // 这样安装该 APK 须要访问的系统权限就能够所有的展现出来了
                    mScrollView.addView(perms.getPermissionsView(
                            AppSecurityPermissions.WHICH_NEW));
                }
            }
            if (!supportsRuntimePermissions && !newPermissionsFound) {
                // 若是既不支持可运行权限项也没有新权限发现,
                // 则提示没有新权限(没有设置任何权限,只显示应用程序名称和图标)
                LayoutInflater inflater = (LayoutInflater)getSystemService(
                        Context.LAYOUT_INFLATER_SERVICE);
                TextView label = (TextView)inflater.inflate(R.layout.label, null);
                label.setText(R.string.no_new_perms);
                mScrollView.addView(label);
            }
            adapter.addTab(tabHost.newTabSpec(TAB_ID_NEW).setIndicator(
                    getText(R.string.newPerms)), mScrollView);
        }
        // 若是至少设置了一个权限
        if (!supportsRuntimePermissions && N > 0) {
            permVisible = true;
            LayoutInflater inflater = (LayoutInflater)getSystemService(
                    Context.LAYOUT_INFLATER_SERVICE);
            // 解析权限列表的视图
            View root = inflater.inflate(R.layout.permissions_list, null);
            if (mScrollView == null) {
                mScrollView = (CaffeinatedScrollView)root.findViewById(R.id.scrollview);
            }
            // 添加到权限列表的视图
            ((ViewGroup)root.findViewById(R.id.permission_list)).addView(
                        perms.getPermissionsView(AppSecurityPermissions.WHICH_ALL));
            adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator(
                    getText(R.string.allPerms)), root);
        }
        if (!permVisible) {
            // 若是不须要任何权限
            if (mAppInfo != null) {
                // 若是是更新安装包,而且没有任何权限要求
                // 判断是不是系统应用来设置布局文件
                msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
                        ? R.string.install_confirm_question_update_system_no_perms
                        : R.string.install_confirm_question_update_no_perms;
            } else {
                // 是新安装的 app 而且没有权限列表
                msg = R.string.install_confirm_question_no_perms;
            }

            // We do not need to show any permissions, load layout without permissions
            // 设置相应的 UI
            bindUi(R.layout.install_confirm, true);
            mScrollView = null;
        }
        if (msg != 0) {
            ((TextView)findViewById(R.id.install_confirm_question)).setText(msg);
        }
        if (mScrollView == null) {
            // There is nothing to scroll view, so the ok button is immediately
            // set to install.
            mOk.setText(R.string.install);
            mOkCanInstall = true;
        } else {
            mScrollView.setFullScrollAction(new Runnable() {
                @Override
                public void run() {
                    mOk.setText(R.string.install);
                    mOkCanInstall = true;
                }
            });
        }
    }

这个方法其实主要是根据不一样的状况来设置相应的 UI,主要是将安装包分为新安装和更新安装,在更新安装里面又分为系统应用和非系统应用,而后根据不一样的状况来显示不一样的 UI,UI这块主要是经过 getPermissionsView 方法来获取不一样的权限 View。

总结

PackageInstaller 初始化的过程:

        ✨ 一、根据 Uri 的 Scheme 协议不一样,跳转到不一样的界面,content 协议跳转到 InstallStart,其余的跳转到 PackageInstallerActivity。本文应用场景中,若是是 Android7.0 以及更高版本会跳转到 InstallStart。
        ✨ 二、InstallStart 将 content 协议的 Uri 转换为 File 协议,而后跳转到 PackageInstallerActivity。
        ✨ 三、PackageInstallerActivity 会分别对 package 协议和 file 协议的 Uri 进行处理,若是是 file 协议会解析 APK 文件获得包信息 PackageInfo。
        ✨ 四、PackageInstallerActivity 中会对未知来源进行处理,若是容许安装未知来源或者根据 Intent 判断得出该 APK 不是未知来源,就会初始化安装确认界面,若是管理员限制来自未知源的安装, 就弹出提示 Dialog 或者跳转到设置界面。

后做

Android 9.0 源码_核心篇 -- 深刻研究 PMS 系列(5)之 APK 安装流程(PI)

参考

 01. http://liuwangshu.cn/framewor...
 02. https://www.cnblogs.com/ouyan...

相关文章
相关标签/搜索