apk的安装过程同样离不开Binder机制和PMS。当我们选中安装Apk,一般分为两种安装方式:
- 有界面安装
- 无界面安装
一、有界面安装方式
比如有些版本的手机会弹出是否确定安装的界面,点击确定以后才会继续安装。对于这种安装方式会首先通过跨进程的方式调用系统的启动安装界面。流程图如下:
1、不同android版本手动安装的apk代码不一样
在7.0版本之前我们可能使用以下代码方式进行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);
但是如果在之后版本使用这种方式,就会抛出异常:FileUriExposeException,产生异常的原因是因为StrictMode API政策禁止应用程序将file:// Uri暴露给另一个应用程序。所以7.x之后我们使用如下方式进行apk安装:
Uri apkUri = FileProvider.getUriForFile(context,"包名.fileprovider", apkFile);
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
install.setDataAndType(apkUri, "application/vnd.android.package-archive");
context.startActivity(install)
在此之前,你需要在res/xml/file_paths.xml配置如下内容:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path
path=""
name="Download"/>
</paths>
</resources>
在AndroidManifest.xml配置如下内容:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="包名.provider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
2、不同android版本启动方式不一样
为什么会出现上面的问题呢?这是因为7.x版本之后,系统的启动界面入口发生了变化。从之前的PackageInstallerActivity.java变成了InstallStart.java.不过最后你会发现,还是会走到PackageInstallerActivity这个类。
(1) InstallStart.java启动界面
进入onCreate()方法,代码如下:
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
//--------1、判断是否有安装权限 ,没有的话直接结束界面
if (mAbortInstall) {
setResult(RESULT_CANCELED);
finish();
return;
}
//---------2、跳转参数设置
Intent nextActivity = new Intent(intent);
nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
//---------3、当前action是否为PackageInstaller.ACTION_CONFIRM_PERMISSIONS 显示这里不是,我们是Intent.ACTION_VIEW
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
//-------4、判断packageUri是否为空不成立,判断Uri的Scheme协议是否是content,对于7.x系列条件是成立的
if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE)
|| packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) {
// Copy file to prevent it from being changed underneath this process
nextActivity.setClass(this, InstallStaging.class);
} else if (packageUri != null && packageUri.getScheme().equals(
PackageInstallerActivity.SCHEME_PACKAGE)) {
//---------5、不是content协议跳转到 PackageInstallerActivity界面
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;
}
}
//----------6、跳转并关闭当前界面
if (nextActivity != null) {
startActivity(nextActivity);
}
finish();
}
(2) InstallStaging.java界面
它的作用就是将传入的content协议转为file协议。关键代码如下:
private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {
@Override
protected Boolean doInBackground(Uri... params) {
...
return true;
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
Intent installIntent = new Intent(getIntent());
//设置跳转界面 8.0代码
//9.0会跳转到DeleteStagedFileOnResult 并在进入PackageInstallerActivity之后通知它将file协议删除
installIntent.setClass(InstallStaging.this, PackageInstallerActivity.class);
//将content协议转换成file协议
installIntent.setData(Uri.fromFile(mStagedFile));
installIntent.setFlags(installIntent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
//跳转到PackageInstallerActivity界面
startActivityForResult(installIntent, 0);
} else {
showError();
}
}
}
(3) PackageInstallerActivity.java界面
该界面才是真正安装的界面,在onCreate()方法会初始化安装需要的对象信息、通过Uri创建一个apk文件、并解析apk文件得到包信息,主要代码如下:
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
//------------1、初始化安装需要的对象信息
mPm = getPackageManager();
mIpm = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
mInstaller = mPm.getPackageInstaller();
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
//------------2、根据Uri的Scheme进行预处理
boolean wasSetUp = processPackageUri(packageUri);
if (!wasSetUp) {
return;
}
//-------------3、判断是否是未知来源的应用
checkIfAllowedAndInitiateInstall();
}
进入注释2处方法,主要代码如下:
private boolean processPackageUri(final Uri packageUri) {
mPackageURI = packageUri;
final String scheme = packageUri.getScheme();
switch (scheme) {
...
case SCHEME_FILE: {
//-------1、根据apk路径创建apk文件
File sourceFile = new File(packageUri.getPath());
//-------2、解析得到apk包信息
PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);
...
//-------3、通过PackageParser类重新生成一个对象PackageInfo,保存包信息
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
PackageManager.GET_PERMISSIONS, 0, 0, null,
new PackageUserState());
mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
} break;
...
}
return true;
}
进入注释3处方法,主要代码如下:
private void checkIfAllowedAndInitiateInstall() {
//-----------1、允许未知应用安装或者根据Intent判断得出该APK不是未知来源,进入初始化安装方法
if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
initiateInstall();
return;
}
// ----------2、是否跳转到设置界面手动开启权限
if (isUnknownSourcesDisallowed()) {
if ((mUserManager.getUserRestrictionSource(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
Process.myUserHandle()) & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
return;
} else {
startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
finish();
}
} else {
handleUnknownSources();
}
}
initiateInstall()方法,最终会弹出安装确定的dialog,当用户点击确定后进入正在安装界面InstallInstalling.java
(4) InstallInstalling.java界面
主要处理安装的代码在onResume()方法中会开启异步任务,代码如下:
private final class InstallingAsyncTask extends AsyncTask<Void, Void,
PackageInstaller.Session> {
volatile boolean isDone;
@Override
protected PackageInstaller.Session doInBackground(Void... params) {
PackageInstaller.Session session;
...
//------------1、将APK的信息通过IO流的形式写入到PackageInstaller.Session中
...
return session;
}
@Override
protected void onPostExecute(PackageInstaller.Session session) {
...
//-----------2、调用PackageInstaller.Session的commit方法,将APK的信息交由PMS处理
session.commit(pendingIntent.getIntentSender());
...
}
}
安装成功后会进入回调,最终进入launchSuccess()方法,启动安装成功界面
(4) InstallSuccess.class界面
这样就完成了有界面的apk安装
二、无界面安装方式
当我们输入"adb -s 设备地址 install apk全路径"执行命令以后,同样可以进行apk的安装。它的安装流程流程图如下:
c层代码不做太多分析,调动的文件源码路径为"system/core/adb/commandline.cpp",c层最终通过shell脚本指向pm.java类。
1、pm.java
pm.java的入口方法main中会调用run方法,主要代码如下:
(1)run()方法
public int run(String[] args) throws IOException, RemoteException {
...
//--------1、拿到PMS的代理对象
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
if (mPm == null) {
System.err.println(PM_NOT_RUNNING_ERR);
return 1;
}
mInstaller = mPm.getPackageInstaller();
....
//--------2、安装方法
if ("install".equals(op)) {
return runInstall();
}
//--------3、卸载方法
if ("uninstall".equals(op)) {
return runUninstall();
}
...
}
该方法会拿到PMS的代理对象,然后执行runInstall()方法,主要代码如下:
(2)runInstall()方法
private int runInstall() {
...
try {
VerificationParams verificationParams = new VerificationParams(verificationURI,
originatingURI, referrerURI, VerificationParams.NO_UID, null);
mPm.installPackageAsUser(apkFilePath, obs.getBinder(), installFlags,
installerPackageName, verificationParams, abi, userId);
...
}
} catch (RemoteException e) {
...
}
}
最终调用PMS远程服务的installPackageAsUser()方法。
2、PackageManagerService.java
(1)installPackageAsUser()方法
该方法最后会发送一条Message消息,交给内部的handler处理。handler处理消息
(1)doHandleMessage()方法
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
//----------1、取出安装信息InstallParams
HandlerParams params = (HandlerParams) msg.obj;
//----------2、将新的安装请求放入到mPendingIntalls中,等待处理 idx为当前安装apk个数
PendingInstalls.add(idx, params);
//---------3、发送消息
mHandler.sendEmptyMessage(MCS_BOUND);
...
break;
}
case MCS_BOUND: {
...
//---------4、调用startCopy()方法执行安装
if (params.startCopy()) {
...
}
...
break;
}
}
}
最后第4步调用startCopy方法。
(2)startCopy()方法
内部很简单,会做另外一个方法handleStartCopy()调用,代码不贴出来了
(3)handleStartCopy()方法
public void handleStartCopy() throws RemoteException {
...
//---------1、判断是否安装在D卡
final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
//判断是否安装内部空间
final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
...
//---------2、创建安装参数对象
final InstallArgs args = createInstallArgs(this);
mArgs = args;
...
//---------3、校验安装包是否需要校验
if(false){
//---------4、如果不需要校验,调用InstallArgs的copyApk函数
ret = args.copyApk(mContainerService, true);
...
}
判断安装路径,校验安装包,最后执行copyApk()方法。
(4)copyApk()方法
内部会调用doCopyApk()方法。
(5)doCopyApk()方法
private int doCopyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
...
//-----------1、创建安装目录
final File tempDir =mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
codeFile = tempDir;
resourceFile = tempDir;
...
//------------2、真正实现apk文件拷贝
ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);
if (ret != PackageManager.INSTALL_SUCCEEDED) {
Slog.e(TAG, "Failed to copy package");
return ret;
}
//-----------3、 获取库的跟目录,进行Native代码,即so的拷贝
final File libraryRoot = new File(codeFile, LIB_DIR_NAME);
NativeLibraryHelper.Handle handle = null;
...
handle = NativeLibraryHelper.Handle.create(codeFile);
ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,abiOverride);
}
以上拷贝完成以后就完成了apk的安装。
总结: