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方法时会抛出异常。
接下里就分析具体是如何获取权限列表的,这个在放在下一篇来说。