Android源码解析(十三)-- apk安装流程

作者: android01 发布时间: 2019-08-29 浏览: 3173 次 编辑

上一篇文章中给大家分析了一下android系统启动之后调用PackageManagerService服务并解析系统特定目录,解析apk文件并安装的过程,这个安装过期实际上是没有图形界面的,底层调用的是我们平时比较熟悉的adb命令,那么我们平时安装apk文件的时候大部分是都过图形界面安装的,那么这种方式安装apk具体的流程是怎样的呢?

下面我们就来具体看一下apk的具体安装过程,相信大家都知道如果我们想在代码里执行apk的安装,那么一般都是这样:

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.parse("file://" + path),"application/vnd.android.package-archive");
context.startActivity(intent);

这样,我们就会打开安装apk文件的程序并执行安装逻辑了,那么这段代码具体是打开那个activity呢?好吧,从这个问题开始,我们来解析apk的安装流程...

这里跟大姐简单介绍一下android的源码,平时我们使用的android.jar里面的java源码只是android系统源码的一部分,还有好多源码并没有打入到android.jar中,这里为大家推荐一个android源码的地址:https://github.com/android
里面根据android系统的不同模块包含了许多android模块的源码。
这里写图片描述

这里我们找到platform_packages_apps_packageinstaller库,这里面就是android系统安装程序的源码了。
这里写图片描述

这里我们找到其androidManifest.xml,然后我们来看一下其具体的定义:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.android.packageinstaller" coreApp="true">
 
    <original-package android:name="com.android.packageinstaller" />
 
    ...
 
    <application android:label="@string/app_name"
            android:allowBackup="false"
            android:theme="@style/Theme.DialogWhenLarge"
            android:supportsRtl="true">
 
        <activity android:name=".PackageInstallerActivity"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:excludeFromRecents="true">
            <intent-filter>
                <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:mimeType="application/vnd.android.package-archive" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.INSTALL_PACKAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="file" />
                <data android:scheme="package" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.content.pm.action.CONFIRM_PERMISSIONS" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
 
        <activity android:name=".InstallAppProgress"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:exported="false" />
 
        <activity android:name=".UninstallerActivity"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:excludeFromRecents="true"
                android:theme="@style/Theme.AlertDialogActivity">
            <intent-filter android:priority="1">
                <action android:name="android.intent.action.DELETE" />
                <action android:name="android.intent.action.UNINSTALL_PACKAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="package" />
            </intent-filter>
        </activity>
 
        <activity android:name=".UninstallAppProgress"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:exported="false" />
 
        <activity android:name=".permission.ui.GrantPermissionsActivity"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:excludeFromRecents="true"
                android:theme="@style/GrantPermissions">
            <intent-filter>
                <action android:name="android.content.pm.action.REQUEST_PERMISSIONS" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
 
        <activity android:name=".permission.ui.ManagePermissionsActivity"
                  android:configChanges="orientation|keyboardHidden|screenSize"
                  android:excludeFromRecents="true"
                  android:label="@string/app_permissions"
                  android:theme="@style/Settings"
                  android:permission="android.permission.GRANT_RUNTIME_PERMISSIONS">
            <intent-filter>
                <action android:name="android.intent.action.MANAGE_PERMISSIONS" />
                <action android:name="android.intent.action.MANAGE_APP_PERMISSIONS" />
                <action android:name="android.intent.action.MANAGE_PERMISSION_APPS" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
 
        <activity android:name=".permission.ui.OverlayWarningDialog"
                android:excludeFromRecents="true"
                android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar" />
 
        <provider android:name=".wear.WearPackageIconProvider"
                  android:authorities="com.google.android.packageinstaller.wear.provider"
                  android:grantUriPermissions="true"
                  android:exported="true" />
 
        <activity android:name=".permission.ui.wear.WarningConfirmationActivity"
                  android:permission="android.permission.GRANT_RUNTIME_PERMISSIONS"
                  android:theme="@style/Settings"/>
    </application>
 
</manifest>

好吧,这里我们大概看一下Activity的定义,这里我们重点看一下PackageInstallerActivity的定义:

<activity android:name=".PackageInstallerActivity"
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:excludeFromRecents="true">
    <intent-filter>
        <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:mimeType="application/vnd.android.package-archive" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.INSTALL_PACKAGE" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="file" />
        <data android:scheme="package" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.content.pm.action.CONFIRM_PERMISSIONS" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

恩?这里不就是我们刚刚定义的启动安装Apk activity的intent filter?好吧,所以说一开始我们调用的startActivity其实启动的就是PackageInstallerActivity,那么下面我们就看一下PackageInstellerActivity的具体实现:

@Override
protected void onCreate(Bundle icicle) {
    super.onCreate(icicle);

    mPm = getPackageManager();
    mInstaller = mPm.getPackageInstaller();
    mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);

    final Intent intent = getIntent();
    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;
        mPackageURI = Uri.fromFile(new File(info.resolvedBaseCodePath));
        mOriginatingURI = null;
        mReferrerURI = null;
    } else {
        mSessionId = -1;
        mPackageURI = intent.getData();
        mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
        mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
    }

    final boolean unknownSourcesAllowedByAdmin = isUnknownSourcesAllowedByAdmin();
    final boolean unknownSourcesAllowedByUser = isUnknownSourcesEnabled();

    boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent);
    mInstallFlowAnalytics = new InstallFlowAnalytics();
    mInstallFlowAnalytics.setContext(this);
    mInstallFlowAnalytics.setStartTimestampMillis(SystemClock.elapsedRealtime());
    mInstallFlowAnalytics.setInstallsFromUnknownSourcesPermitted(unknownSourcesAllowedByAdmin
            && unknownSourcesAllowedByUser);
    mInstallFlowAnalytics.setInstallRequestFromUnknownSource(requestFromUnknownSource);
    mInstallFlowAnalytics.setVerifyAppsEnabled(isVerifyAppsEnabled());
    mInstallFlowAnalytics.setAppVerifierInstalled(isAppVerifierInstalled());
    mInstallFlowAnalytics.setPackageUri(mPackageURI.toString());

    if (DeviceUtils.isWear(this)) {
        showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
        mInstallFlowAnalytics.setFlowFinished(
                InstallFlowAnalytics.RESULT_NOT_ALLOWED_ON_WEAR);
        return;
    }

    final String scheme = mPackageURI.getScheme();
    if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
        Log.w(TAG, "Unsupported scheme " + scheme);
        setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
        mInstallFlowAnalytics.setFlowFinished(
                InstallFlowAnalytics.RESULT_FAILED_UNSUPPORTED_SCHEME);
        finish();
        return;
    }

    final PackageUtil.AppSnippet as;
    if ("package".equals(mPackageURI.getScheme())) {
        mInstallFlowAnalytics.setFileUri(false);
        try {
            mPkgInfo = mPm.getPackageInfo(mPackageURI.getSchemeSpecificPart(),
                    PackageManager.GET_PERMISSIONS | PackageManager.GET_UNINSTALLED_PACKAGES);
        } catch (NameNotFoundException e) {
        }
        if (mPkgInfo == null) {
            Log.w(TAG, "Requested package " + mPackageURI.getScheme()
                    + " not available. Discontinuing installation");
            showDialogInner(DLG_PACKAGE_ERROR);
            setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
            mInstallFlowAnalytics.setPackageInfoObtained();
            mInstallFlowAnalytics.setFlowFinished(
                    InstallFlowAnalytics.RESULT_FAILED_PACKAGE_MISSING);
            return;
        }
        as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
                mPm.getApplicationIcon(mPkgInfo.applicationInfo));
    } else {
        mInstallFlowAnalytics.setFileUri(true);
        final File sourceFile = new File(mPackageURI.getPath());
        PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);

        // Check for parse errors
        if (parsed == null) {
            Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
            showDialogInner(DLG_PACKAGE_ERROR);
            setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
            mInstallFlowAnalytics.setPackageInfoObtained();
            mInstallFlowAnalytics.setFlowFinished(
                    InstallFlowAnalytics.RESULT_FAILED_TO_GET_PACKAGE_INFO);
            return;
        }
        mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
                PackageManager.GET_PERMISSIONS, 0, 0, null,
                new PackageUserState());
        mPkgDigest = parsed.manifestDigest;
        as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
    }
    mInstallFlowAnalytics.setPackageInfoObtained();

    //set view
    setContentView(R.layout.install_start);
    mInstallConfirm = findViewById(R.id.install_confirm_panel);
    mInstallConfirm.setVisibility(View.INVISIBLE);
    PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);

    mOriginatingUid = getOriginatingUid(intent);

    // Block the install attempt on the Unknown Sources setting if necessary.
    if (!requestFromUnknownSource) {
        initiateInstall();
        return;
    }

    // If the admin prohibits it, or we're running in a managed profile, just show error
    // and exit. Otherwise show an option to take the user to Settings to change the setting.
    final boolean isManagedProfile = mUserManager.isManagedProfile();
    if (!unknownSourcesAllowedByAdmin
            || (!unknownSourcesAllowedByUser && isManagedProfile)) {
        showDialogInner(DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES);
        mInstallFlowAnalytics.setFlowFinished(
                InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
    } else if (!unknownSourcesAllowedByUser) {
        // Ask user to enable setting first
        showDialogInner(DLG_UNKNOWN_SOURCES);
        mInstallFlowAnalytics.setFlowFinished(
                InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
    } else {
        initiateInstall();
    }
}

这里我们主要先看一下PackageInstallerActivity的onCreate方法:

    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        mPm = getPackageManager();
        mInstaller = mPm.getPackageInstaller();
        mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);

        final Intent intent = getIntent();
        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;
            mPackageURI = Uri.fromFile(new File(info.resolvedBaseCodePath));
            mOriginatingURI = null;
            mReferrerURI = null;
        } else {
            mSessionId = -1;
            mPackageURI = intent.getData();
            mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
            mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
        }

        final boolean unknownSourcesAllowedByAdmin = isUnknownSourcesAllowedByAdmin();
        final boolean unknownSourcesAllowedByUser = isUnknownSourcesEnabled();

        boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent);
        mInstallFlowAnalytics = new InstallFlowAnalytics();
        mInstallFlowAnalytics.setContext(this);
        mInstallFlowAnalytics.setStartTimestampMillis(SystemClock.elapsedRealtime());
        mInstallFlowAnalytics.setInstallsFromUnknownSourcesPermitted(unknownSourcesAllowedByAdmin
                && unknownSourcesAllowedByUser);
        mInstallFlowAnalytics.setInstallRequestFromUnknownSource(requestFromUnknownSource);
        mInstallFlowAnalytics.setVerifyAppsEnabled(isVerifyAppsEnabled());
        mInstallFlowAnalytics.setAppVerifierInstalled(isAppVerifierInstalled());
        mInstallFlowAnalytics.setPackageUri(mPackageURI.toString());

        if (DeviceUtils.isWear(this)) {
            showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
            mInstallFlowAnalytics.setFlowFinished(
                    InstallFlowAnalytics.RESULT_NOT_ALLOWED_ON_WEAR);
            return;
        }

        final String scheme = mPackageURI.getScheme();
        if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
            Log.w(TAG, "Unsupported scheme " + scheme);
            setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
            mInstallFlowAnalytics.setFlowFinished(
                    InstallFlowAnalytics.RESULT_FAILED_UNSUPPORTED_SCHEME);
            finish();
            return;
        }

        final PackageUtil.AppSnippet as;
        if ("package".equals(mPackageURI.getScheme())) {
            mInstallFlowAnalytics.setFileUri(false);
            try {
                mPkgInfo = mPm.getPackageInfo(mPackageURI.getSchemeSpecificPart(),
                        PackageManager.GET_PERMISSIONS | PackageManager.GET_UNINSTALLED_PACKAGES);
            } catch (NameNotFoundException e) {
            }
            if (mPkgInfo == null) {
                Log.w(TAG, "Requested package " + mPackageURI.getScheme()
                        + " not available. Discontinuing installation");
                showDialogInner(DLG_PACKAGE_ERROR);
                setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                mInstallFlowAnalytics.setPackageInfoObtained();
                mInstallFlowAnalytics.setFlowFinished(
                        InstallFlowAnalytics.RESULT_FAILED_PACKAGE_MISSING);
                return;
            }
            as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
                    mPm.getApplicationIcon(mPkgInfo.applicationInfo));
        } else {
            mInstallFlowAnalytics.setFileUri(true);
            final File sourceFile = new File(mPackageURI.getPath());
            PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);

            // Check for parse errors
            if (parsed == null) {
                Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
                showDialogInner(DLG_PACKAGE_ERROR);
                setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                mInstallFlowAnalytics.setPackageInfoObtained();
                mInstallFlowAnalytics.setFlowFinished(
                        InstallFlowAnalytics.RESULT_FAILED_TO_GET_PACKAGE_INFO);
                return;
            }
            mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
                    PackageManager.GET_PERMISSIONS, 0, 0, null,
                    new PackageUserState());
            mPkgDigest = parsed.manifestDigest;
            as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
        }
        mInstallFlowAnalytics.setPackageInfoObtained();

        //set view
        setContentView(R.layout.install_start);
        mInstallConfirm = findViewById(R.id.install_confirm_panel);
        mInstallConfirm.setVisibility(View.INVISIBLE);
        PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);

        mOriginatingUid = getOriginatingUid(intent);

        // Block the install attempt on the Unknown Sources setting if necessary.
        if (!requestFromUnknownSource) {
            initiateInstall();
            return;
        }

        // If the admin prohibits it, or we're running in a managed profile, just show error
        // and exit. Otherwise show an option to take the user to Settings to change the setting.
        final boolean isManagedProfile = mUserManager.isManagedProfile();
        if (!unknownSourcesAllowedByAdmin
                || (!unknownSourcesAllowedByUser && isManagedProfile)) {
            showDialogInner(DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES);
            mInstallFlowAnalytics.setFlowFinished(
                    InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
        } else if (!unknownSourcesAllowedByUser) {
            // Ask user to enable setting first
            showDialogInner(DLG_UNKNOWN_SOURCES);
            mInstallFlowAnalytics.setFlowFinished(
                    InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
        } else {
            initiateInstall();
        }
    }

可以发现,在onCreate方法中,首先执行一些初始化操作,获取PackageManager和Installer、UserManager等对象,然后会根据当前Intent的信息最一些逻辑判断并弹出消息弹窗,我们可以看一下具体的消息弹窗类型:

private static final int DLG_BASE = 0;
    private static final int DLG_UNKNOWN_SOURCES = DLG_BASE + 1;
    private static final int DLG_PACKAGE_ERROR = DLG_BASE + 2;
    private static final int DLG_OUT_OF_SPACE = DLG_BASE + 3;
    private static final int DLG_INSTALL_ERROR = DLG_BASE + 4;
    private static final int DLG_ALLOW_SOURCE = DLG_BASE + 5;
    private static final int DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES = DLG_BASE + 6;
    private static final int DLG_NOT_SUPPORTED_ON_WEAR = DLG_BASE + 7;

可以发现当分析Intent对象的时候,如果可以得到这样几种结果:不知道apk的来源,package信息错误,存储空间不够,安装时报,来源正确,允许未知来源的apk文件,在wear上不支持等,这样根据不同的消息类型会弹出不同的消息弹窗:

    @Override
    public Dialog onCreateDialog(int id, Bundle bundle) {
        switch (id) {
        case DLG_UNKNOWN_SOURCES:
            return new AlertDialog.Builder(this)
                    .setTitle(R.string.unknown_apps_dlg_title)
                    .setMessage(R.string.unknown_apps_dlg_text)
                    .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            Log.i(TAG, "Finishing off activity so that user can navigate to settings manually");
                            finish();
                        }})
                    .setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            Log.i(TAG, "Launching settings");
                            launchSecuritySettings();
                        }
                    })
                    .setOnCancelListener(this)
                    .create();
        case DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES:
            return new AlertDialog.Builder(this)
                    .setTitle(R.string.unknown_apps_dlg_title)
                    .setMessage(R.string.unknown_apps_admin_dlg_text)
                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            finish();
                        }
                    })
                    .setOnCancelListener(this)
                    .create();
        case DLG_PACKAGE_ERROR :
            return new AlertDialog.Builder(this)
                    .setTitle(R.string.Parse_error_dlg_title)
                    .setMessage(R.string.Parse_error_dlg_text)
                    .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            finish();
                        }
                    })
                    .setOnCancelListener(this)
                    .create();
        case DLG_OUT_OF_SPACE:
            // Guaranteed not to be null. will default to package name if not set by app
            CharSequence appTitle = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
            String dlgText = getString(R.string.out_of_space_dlg_text,
                    appTitle.toString());
            return new AlertDialog.Builder(this)
                    .setTitle(R.string.out_of_space_dlg_title)
                    .setMessage(dlgText)
                    .setPositiveButton(R.string.manage_applications, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            //launch manage applications
                            Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
                            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            startActivity(intent);
                            finish();
                        }
                    })
                    .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            Log.i(TAG, "Canceling installation");
                            finish();
                        }
                })
                  .setOnCancelListener(this)
                  .create();
        case DLG_INSTALL_ERROR :
            // Guaranteed not to be null. will default to package name if not set by app
            CharSequence appTitle1 = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
            String dlgText1 = getString(R.string.install_failed_msg,
                    appTitle1.toString());
            return new AlertDialog.Builder(this)
                    .setTitle(R.string.install_failed)
                    .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            finish();
                        }
                    })
                    .setMessage(dlgText1)
                    .setOnCancelListener(this)
                    .create();
        case DLG_ALLOW_SOURCE:
            CharSequence appTitle2 = mPm.getApplicationLabel(mSourceInfo);
            String dlgText2 = getString(R.string.allow_source_dlg_text,
                    appTitle2.toString());
            return new AlertDialog.Builder(this)
                    .setTitle(R.string.allow_source_dlg_title)
                    .setMessage(dlgText2)
                    .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            setResult(RESULT_CANCELED);
                            finish();
                        }})
                    .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            SharedPreferences prefs = getSharedPreferences(PREFS_ALLOWED_SOURCES,
                                    Context.MODE_PRIVATE);
                            prefs.edit().putBoolean(mSourceInfo.packageName, true).apply();
                            startInstallConfirm();
                        }
                    })
                    .setOnCancelListener(this)
                    .create();
        case DLG_NOT_SUPPORTED_ON_WEAR:
            return new AlertDialog.Builder(this)
                    .setTitle(R.string.wear_not_allowed_dlg_title)
                    .setMessage(R.string.wear_not_allowed_dlg_text)
                    .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            setResult(RESULT_OK);
                            finish();
                        }
                    })
                    .setOnCancelListener(this)
                    .create();
       }
       return null;
   }

消息弹窗的主要作用,用于提示用户当前安装apk文件的特性。都知道android系统在android apk文件之前会解析器manifest文件,这个操作也是早onCreate方法中执行的:

PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);

我们具体看一下getPackageInfo方法的实现:

public static PackageParser.Package getPackageInfo(File sourceFile) {
    final PackageParser parser = new PackageParser();
    try {
        PackageParser.Package pkg = parser.parseMonolithicPackage(sourceFile, 0);
        parser.collectManifestDigest(pkg);
        return pkg;
    } catch (PackageParserException e) {
        return null;
    }
}

好吧,到了这里是不是代码变得很熟悉了?parseMonolithicPackage就是我们上一节分析的android系统解析manifest文件的过程,具体的可参考:http://blog.csdn.net/qq_23547831/article/details/51203482

而collectManifestDigest方法,我们这里简单的介绍一下,其主要是要争apk的签名是否正确。好吧通过这两部我们就把apk文件的manifest和签名信息都解析完成并保存在了Package中。

接着往下走,在所有的解析完成之后我们会在onCreate方法中执行initiateInstall();方法,刚方法的主要作用是初始化安装。

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
    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.GET_UNINSTALLED_PACKAGES);
        if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
            mAppInfo = null;
        }
    } catch (NameNotFoundException e) {
        mAppInfo = null;
    }

    mInstallFlowAnalytics.setReplace(mAppInfo != null);
    mInstallFlowAnalytics.setSystemApp(
            (mAppInfo != null) && ((mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0));

    // If we have a session id, we're invoked to verify the permissions for the given
    // package. Otherwise, we start the install process.
    if (mSessionId != -1) {
        startInstallConfirm();
    } else {
        startInstall();
    }
}

好吧,这里面有调用了startInstallConfirm方法,然后我们看一下startInstallConfirm方法的实现:

private void startInstallConfirm() {
    ...
    //初始化安装确认界面
    ...
}

好吧,这个方法的实现比较简单,主要的实现逻辑就是现实该activity的用户界面,平时我们安装某一个应用的时候会弹出一个安装确认页面,还有一个确认和取消按钮,有印象么?其实就是在这里执行的界面初始化操作。

好吧,一般情况下在apk安装确认页面,我们会点击确认按钮执行安装逻辑吧?那么这里我们找一下确认按钮的点击事件:

public void onClick(View v) {
    if (v == mOk) {
        if (mOkCanInstall || mScrollView == null) {
            mInstallFlowAnalytics.setInstallButtonClicked();
            if (mSessionId != -1) {
                mInstaller.setPermissionsResult(mSessionId, true);

                // We're only confirming permissions, so we don't really know how the
                // story ends; assume success.
                mInstallFlowAnalytics.setFlowFinishedWithPackageManagerResult(
                        PackageManager.INSTALL_SUCCEEDED);
                finish();
            } else {
                startInstall();
            }
        } else {
            mScrollView.pageScroll(View.FOCUS_DOWN);
        }
    } else if (v == mCancel) {
        // Cancel and finish
        setResult(RESULT_CANCELED);
        if (mSessionId != -1) {
            mInstaller.setPermissionsResult(mSessionId, false);
        }
        mInstallFlowAnalytics.setFlowFinished(
                InstallFlowAnalytics.RESULT_CANCELLED_BY_USER);
        finish();
    }
}

很明显了,这里当我们点击确认按钮的时候会执行startInstall方法,也就是开始执行安装逻辑:

private void startInstall() {
    // Start subactivity to actually install the application
    Intent newIntent = new Intent();
    newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
            mPkgInfo.applicationInfo);
    newIntent.setData(mPackageURI);
    newIntent.setClass(this, InstallAppProgress.class);
    newIntent.putExtra(InstallAppProgress.EXTRA_MANIFEST_DIGEST, mPkgDigest);
    newIntent.putExtra(
            InstallAppProgress.EXTRA_INSTALL_FLOW_ANALYTICS, mInstallFlowAnalytics);
    String installerPackageName = getIntent().getStringExtra(
            Intent.EXTRA_INSTALLER_PACKAGE_NAME);
    if (mOriginatingURI != null) {
        newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
    }
    if (mReferrerURI != null) {
        newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
    }
    if (mOriginatingUid != VerificationParams.NO_UID) {
        newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
    }
    if (installerPackageName != null) {
        newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
                installerPackageName);
    }
    if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
        newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
        newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    }
    if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
    startActivity(newIntent);
    finish();
}

可以发现,点击确认按钮之后我们调用启用了一个新的Activity-->InstallAppProgress,这个Activity主要用于执行apk的安装逻辑了。

@Override
public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    Intent intent = getIntent();
    mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
    mInstallFlowAnalytics = intent.getParcelableExtra(EXTRA_INSTALL_FLOW_ANALYTICS);
    mInstallFlowAnalytics.setContext(this);
    mPackageURI = intent.getData();

    final String scheme = mPackageURI.getScheme();
    if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
        mInstallFlowAnalytics.setFlowFinished(
                InstallFlowAnalytics.RESULT_FAILED_UNSUPPORTED_SCHEME);
        throw new IllegalArgumentException("unexpected scheme " + scheme);
    }

    mInstallThread = new HandlerThread("InstallThread");
    mInstallThread.start();
    mInstallHandler = new Handler(mInstallThread.getLooper());

    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(BROADCAST_ACTION);
    registerReceiver(
            mBroadcastReceiver, intentFilter, BROADCAST_SENDER_PERMISSION, null /*scheduler*/);

    initView();
}

可以发现InstallAppProcess这个Activity的onCreate方法中主要初始化了一些成员变量,并调用initView方法,我们在iniTView方法中可以看到:

void initView() {
    ...
    mInstallHandler.post(new Runnable() {
                @Override
                public void run() {
                    doPackageStage(pm, params);
                }
            });
    ...
}

经过一些view的初始化操作之后调用了doPackageStage方法,该方法主要是通过调用PackageInstaller执行apk文件的安装,这里就不在详细的介绍了,在apk文件安装完成之后PackageInstaller会发送一个安装完成的广播,刚刚我们在onCreate方法中注册了一个广播接收器,其可以用来接收apk安装完成的广播:

private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        final int statusCode = intent.getIntExtra(
                PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
        if (statusCode == PackageInstaller.STATUS_PENDING_USER_ACTION) {
            context.startActivity((Intent)intent.getParcelableExtra(Intent.EXTRA_INTENT));
        } else {
            onPackageInstalled(statusCode);
        }
    }
};

这样apk安装完成之后,这里的广播接收器会接收到广播并执行onPackageInstalled方法,执行后续的处理逻辑,那么我们来看一下onPackageInstalled方法的具体实现逻辑:

void onPackageInstalled(int statusCode) {
    Message msg = mHandler.obtainMessage(INSTALL_COMPLETE);
    msg.arg1 = statusCode;
    mHandler.sendMessage(msg);
}

好吧,这里是发送Handler异步消息,我们来看一下异步消息的处理逻辑:

private Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case INSTALL_COMPLETE:
                mInstallFlowAnalytics.setFlowFinishedWithPackageManagerResult(msg.arg1);
                if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
                    Intent result = new Intent();
                    result.putExtra(Intent.EXTRA_INSTALL_RESULT, msg.arg1);
                    setResult(msg.arg1 == PackageInstaller.STATUS_SUCCESS
                            ? Activity.RESULT_OK : Activity.RESULT_FIRST_USER,
                                    result);
                    finish();
                    return;
                }
                // Update the status text
                mProgressBar.setVisibility(View.INVISIBLE);
                // Show the ok button
                int centerTextLabel;
                int centerExplanationLabel = -1;
                LevelListDrawable centerTextDrawable =
                        (LevelListDrawable) getDrawable(R.drawable.ic_result_status);
                if (msg.arg1 == PackageInstaller.STATUS_SUCCESS) {
                    mLaunchButton.setVisibility(View.VISIBLE);
                    centerTextDrawable.setLevel(0);
                    centerTextLabel = R.string.install_done;
                    // Enable or disable launch button
                    mLaunchIntent = getPackageManager().getLaunchIntentForPackage(
                            mAppInfo.packageName);
                    boolean enabled = false;
                    if(mLaunchIntent != null) {
                        List<ResolveInfo> list = getPackageManager().
                                queryIntentActivities(mLaunchIntent, 0);
                        if (list != null && list.size() > 0) {
                            enabled = true;
                        }
                    }
                    if (enabled) {
                        mLaunchButton.setOnClickListener(InstallAppProgress.this);
                    } else {
                        mLaunchButton.setEnabled(false);
                    }
                } else if (msg.arg1 == PackageInstaller.STATUS_FAILURE_STORAGE){
                    showDialogInner(DLG_OUT_OF_SPACE);
                    return;
                } else {
                    // Generic error handling for all other error codes.
                    centerTextDrawable.setLevel(1);
                    centerExplanationLabel = getExplanationFromErrorCode(msg.arg1);
                    centerTextLabel = R.string.install_failed;
                    mLaunchButton.setVisibility(View.INVISIBLE);
                }
                if (centerTextDrawable != null) {
                centerTextDrawable.setBounds(0, 0,
                        centerTextDrawable.getIntrinsicWidth(),
                        centerTextDrawable.getIntrinsicHeight());
                    mStatusTextView.setCompoundDrawablesRelative(centerTextDrawable, null,
                            null, null);
                }
                mStatusTextView.setText(centerTextLabel);
                if (centerExplanationLabel != -1) {
                    mExplanationTextView.setText(centerExplanationLabel);
                    mExplanationTextView.setVisibility(View.VISIBLE);
                } else {
                    mExplanationTextView.setVisibility(View.GONE);
                }
                mDoneButton.setOnClickListener(InstallAppProgress.this);
                mOkPanel.setVisibility(View.VISIBLE);
                break;
            default:
                break;
        }
    }
};

可以发现,当apk安装完成之后,我们会更新UI,显示完成和打开按钮,是不是和我们平时安装apk的逻辑对应上了?这时候我们可以看一下这两个按钮的点击事件。

public void onClick(View v) {
    if(v == mDoneButton) {
        if (mAppInfo.packageName != null) {
            Log.i(TAG, "Finished installing "+mAppInfo.packageName);
        }
        finish();
    } else if(v == mLaunchButton) {
        startActivity(mLaunchIntent);
        finish();
    }
}

好吧,比较简单,点击完成按钮,直接finish掉这个activity,点击打开,则直接调用startActivity启动安装的应用,然后直接finish自身。

总结:

  • 代码中执行intent.setDataAndType(Uri.parse("file://" + path),"application/vnd.android.package-archive");可以调起PackageInstallerActivity;

  • PackageInstallerActivity主要用于执行解析apk文件,解析manifest,解析签名等操作;

  • InstallAppProcess主要用于执行安装apk逻辑,用于初始化安装界面,用于初始化用户UI。并调用PackageInstaller执行安装逻辑;

  • InstallAppProcess内注册有广播,当安装完成之后接收广播,更新UI。显示apk安装完成界面;