积跬步至千里系列之六--安装与卸载应用程序(PackageInstaller)(一)

PackageInstaller属于framework层的一个系统应用,其位置位于:源码目录/package/apps/PackageInstaller.所有的系统预制应用全都放在apps/目录下,比如Setting, Launcher等都在此目录下.
我是第一次分析系统应用,需要按照步骤来进行分析,一般需要注意的几点主要有:1)寻找一个程序的入口,java,C,C#都是从main函数开始执行的,分析源码程序时也要先照一个入口 (2)先弄清楚程序实现的主要功能是什么 (3)关注流程和逻辑,不要过于关注细节。要学会抓大放小。
* 一、寻找PackageInstaller的入口*
至目前我们已经知道了PackageInstaller也是一个apk程序,其主要的功能就是实现应用的安装和卸载功能。自然的,也就是安装应用和卸载应用时会使用到PackageInstaller程序。
在Android中,调用某个组件或者新开一个界面都是通过Intent来实现的,有显示的调用和隐示调用两种情况,一般都是指定其Action,然后执行startActivity开启界面。对于开启packageInstaller的安装程序界面,会用到如下调用:

Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
//指定要安装的apk的文件路径
intent.setData(Uri.parse("file:/sdcard/qq.apk"));
startActivity(intent);

执行如上intent调用后,就会弹出提示安装qq应用的提示窗口.如上执行的intent包含了一个Action为ACTION_INSTALL_PACKAGE的意图调用,接下来,就去PackageInstaller的清单配置文件中搜索包含Action为ACTION_INSTALL_PACKAGE的Activity,然后进行确定具体调用的那个Activity.
通过浏览mainfest.xml文件,可以总结一些信息:
1.共定义了两对四个窗口。其中PackageInstallerActivity和InstallAppProgress用于安装应用程序;UninstallerActivity和UninstallAppProgress用于卸载应用程序.
2.intent-filter包含android.intent.action.MAIN的Activity Action的Activity会在系统程序列表中列出相应的应用图标。PackageInstaller中的Activity并没有注册MAIN的Action,图标不会列在应用程序列表中
3.PackageInstallerActivity包含了两个Intent Filter,也就是支持两种方式开启该Activity。第一种方式为:

Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setDataAndType(Uri.fromFile(new File("/sdcard/qq.apk")),"application/vnd.android.package-archive");
startActivity(intent);

第二种方式:

Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData(Uri.fromFile(new File("/sdcard/qq.apk")));
startActivity(intent);

特别说明的是:如果通过指定Package的方式安装Android应用,需要该Android应用已安装。
3.卸载应用程序:卸载某个应用程序时,也是通过发送Intent的相对应的Action来实现的。
第一种删除调用:

Intent intent = new Intent(Intent.ACTION_DELETE);
intent.setData(Uri.parse("package:com.tencent.mobileqq"));
startActivity(intent);

第二种卸载调用:

Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
intent.setData(Uri.parse("package:com.tencent.mobileqq"));
startActivity(intent);

安装Android应用前的校验
上面我们知道安装应用时会调用PackageInstallerActivity弹出安装界面,在安装时,我们可以留意到,还会将要安装的应用所用到的权限以列表的形式进行展示,应用名称,应用图标等信息也会一一进行展示,所以我们要看看PckageInstallerActivity中是如何获取到这些信息的。在PackageInstallerActivity中主要完成的工作如下:
1).从Intent中获取Package URI,Scheme等信息。
2).对从Intent对象获取的信息进行校验。主要校验Scheme
3).根据Scheme的具体值(file或者package)进行相应的处理
4).获取ApplicationInfo对象,该对象包含与Android应用相关的信息,如应用名称,应用图标,应用权限等
5).初始化用于显示名称和应用图标的控件
6).校验当前Android系统是否允许“未知来源”的应用被安装
7).进行安装前的准备工作,显示校验窗口
PackageInstallerActivity.java中onCreate方法如下:

//创建PackageManager对象
mPm = getPackageManager();
        mInstaller = mPm.getPackageInstaller();
        mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);

        final Intent intent = getIntent();
       //获取待安装Android应用的路径或Package
            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());
        //获取scheme:file或者package
        final String scheme = mPackageURI.getScheme();
        //从此处可以看到,scheme只有两个值:file或package
        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;
        //scheme是package时
        if ("package".equals(mPackageURI.getScheme())) {
            mInstallFlowAnalytics.setFileUri(false);
            try {
            //获取与package对应的Android应用的信息,包含应用名称,权限列表,应用图标等信息
                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;
            }
            //创建AppSnippet对象。该对象封装了用于待安装Android应用的标题和图标
            as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
                    mPm.getApplicationIcon(mPkgInfo.applicationInfo));
        } else {//scheme为file的情况,及从apk文件安装程序
            mInstallFlowAnalytics.setFileUri(true);
            //获取APK文件的绝对路径
            final File sourceFile = new File(mPackageURI.getPath());
            //创建APK文件的分析器
            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();
        }

关于获取ApplicationInfo对象和显示校验窗口的问题,需要一个方法isInstallingUnknownAppsAllowed方法。该方法如下:

private boolean isInstallRequestFromUnknownSource(Intent intent) {
        String callerPackage = getCallingPackage();
        if (callerPackage != null && intent.getBooleanExtra(
                Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) {
            try {
                mSourceInfo = mPm.getApplicationInfo(callerPackage, 0);
                if (mSourceInfo != null) {
                    if ((mSourceInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0) {
                        // Privileged apps are not considered an unknown source.
                        return false;
                    }
                }
            } catch (NameNotFoundException e) {
            }
        }

        return true;
    }

上述方法通过Content Provider获取设置中的“未知来源”是否选中。如果选中返回true,否则返回false。执行isInstallingUnknownAppsAllowed方法并不需要再Manifest.xml文件中设置任何权限,但必须对APK文件进行系统签名。也就是说使用isInstallingUnknownAppsallowed方法的ape程序必须与Android源代码一起编译,或使用mm/mmm命令单独进行编译。在普通的Android应用中尽管可以编译通过,但执行isInstallingUnknownAppsAllowed方法时会抛出异常。
接下里就分析具体是如何获取权限列表的,这个在放在下一篇来说。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值