Apk的安装过程探究
有了APK的构建过程和APK的组成结构两篇铺垫,以及PackageManagerService基础。终于到了最后阶段:Apk的安装过程。这块内容比较多,抽空断断续续的看了一个星期摸清楚。通常Apk的安装方式有三种:
①手机上点击Apk文件,打开安装界面进行安装
②通过代码的方式静默安装,比如手机自带的应用商店安装应用。
③开发者使用adb命令安装apk文件
挨个探究以上三个方式,每种方式Apk的安装流程实现。本文所有流程图使用迅捷流程图完成,在线查看地址:Apk安装流程图。
1、PackageInstallerActivity界面安装Apk
先从第一种Apk安装方式讲起,这是用户经常使用的Apk安装方式。点击下载的Apk文件,会启动系统安装应用界面,执行Apk安装过程。
启动安装界面的常规代码如下:
File apkFile = new File(apkPath); //构造Apk安装Intent, Intent.ACTION_INSTALL_PACKAGE已经废弃 Intent intent = new Intent(Intent.ACTION_VIEW); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); /**Android 7.0以上使用FileProvider, * Android禁止将file://Uri暴露给其它应用, * 否则会抛出FileUriExposedException异常 */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // 7.0+以上版本:content://Uri Uri apkUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", apkFile); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); } else { // file://Uri intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive"); } //启动系统安装界面 context.startActivity(intent);
1.1、启动安装界面
打开的安装界面是哪个应用的呢?很简单,用下面的命令就可以查看当前手机的应用包名和启动的Activity:
adb shell dumpsys window | findStr mCurrentFocus
当前手机屏幕显示的应用是com.android.packageinstaller,Activity界面是com.android.packageinstaller.PackageInstallerActivity。
mCurrentFocus=null mCurrentFocus=Window{331c409 u0 com.android.packageinstaller/com.android.packageinstaller.PackageInstallerActivity}
紧接着用后面两个命令将系统应用的程序包导出来,反编译Apk也是用这种方式将三方Apk文件导出,详见APK反编译。
adb shell pm path com.android.packageinstaller adb pull system/priv-app/PackageInstaller/PackageInstaller.apk
查看APK中的AndroidManifest文件,可以找到能够处理"application/vnd.android.package-archive"的intent-filter对应的Activity是InstallStart,没错这就是安装界面的入口。
<activity android:name=".InstallStart"> <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="content" /> <data android:mimeType="application/vnd.android.package-archive" /> </intent-filter> ... </activity>
已经从AOSP中拿出packageinstaller相关的源码,单独上传到GitHub/packageinstaller方便查看。不同的手机厂商会基于AOSP开源的代码,定制各自ROM的应用安装界面。大体流程如下:
从InstallStart的onCreate()方法开始,启动PackageInstallerActivity后关闭自身。
protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... Intent nextActivity = new Intent(intent); nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); // 作为nextActivity的安装源认为此活动是源,因此显式设置原始UID和sourceInfo nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage); nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo); nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid); if (isSessionInstall) { nextActivity.setClass(this, PackageInstallerActivity.class); } else { Uri packageUri = intent.getData(); if (packageUri != null && packageUri.getScheme().equals( ContentResolver.SCHEME_CONTENT)) { // [重要说明]此条路径已经废弃,但仍然可以使用。 仅可以添加必要的功能。 // 复制文件以防止在此过程中更改文件 nextActivity.setClass(this, InstallStaging.class); } else if (packageUri != null && packageUri.getScheme().equals( PackageInstallerActivity.SCHEME_PACKAGE)) { nextActivity.setClass(this, PackageInstallerActivity.class); } else { ... nextActivity = null; } } if (nextActivity != null) { startActivity(nextActivity); } finish(); }
PackageInstallerActivity就是安装Apk确认界面。举个例子,长这样:
onCreate()时通过bindUi()方法初始化界面。
mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install), (ignored, ignored2) -> { if (mOk.isEnabled()) { if (mSessionId != -1) { mInstaller.setPermissionsResult(mSessionId, true); finish(); } else { startInstall(); } } }, null); ... mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
点击安装按钮mOk开始安装,执行startInstall()方法,启动InstallInstalling正在安装界面并关闭自身。
private void startInstall() { // Start subactivity to actually install the application Intent newIntent = new Intent(); newIntent.setClass(this, InstallInstalling.class); ... startActivity(newIntent); finish(); }
进入InstallInstalling,在onCreate()时先创建安装会话Session
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); mSessionId = getPackageManager().getPackageInstaller().createSession(params); ... } }
随后在onResume()方法中,开启安装任务。
@Override protected void onResume() { super.onResume(); ... mInstallingTask = new InstallingAsyncTask(); mInstallingTask.execute(); ... }
Apk安装任务被封装在内部类InstallingAsyncTask中,继承自AsyncTask。
/** * Send the package to the package installer and then register a event result observer that * will call {@link #launchFinishBasedOnResult(int, int, String)} */ private final class InstallingAsyncTask extends AsyncTask<Void, Void, PackageInstaller.Session> { volatile boolean isDone; protected PackageInstaller.Session doInBackground(Void... params) { PackageInstaller.Session session; ... session = getPackageManager().getPackageInstaller().openSession(mSessionId); session.setStagingProgress(0); ... try (InputStream in = new FileInputStream(file)) { long sizeBytes = file.length(); try (OutputStream out = session .openWrite("PackageInstaller", 0, sizeBytes)) { byte[] buffer = new byte[1024 * 1024]; while (true) { ... session.fsync(out); } } } return session; } protected void onPostExecute(PackageInstaller.Session session) { ... session.commit(pendingIntent.getIntentSender()); ... } }
会后面的安装结果显示不同的界面,安装成功就显示com.android.packageinstaller.InstallSuccess,否则就实现com.android.packageinstaller.InstallFailed。
1.2、安装会话(Session)
最终通过Session将APK提交给PMS处理。
从PackageInstaller.Session的commit(IntentSender statusReceiver)方法继续往下看:
public void commit(@NonNull IntentSender statusReceiver) { mSession.commit(statusReceiver, false); }
成员mSession为IPackageInstallerSession远程会话接口,PackageInstallerSession类实现了IPackageInstallerSession接口。先不管mSession是如何创建的,2.1节会讲到。
@Override public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) { ... mHandler.obtainMessage(MSG_COMMIT).sendToTarget(); }
向Handler发送消息处理MSG_COMMIT类型的消息,内部Handler构造时使用了自定义的Handler.Callback,只会执行Callback的handleMessage(Message msg)方法,关于Handler.Callback详见Handler中Callback的作用。
private final Handler.Callback mHandlerCallback = new Handler.Callback() { public boolean handleMessage(Message msg) { switch (msg.what) { case MSG_COMMIT: handleCommit(); break; ... } return true; } };
通过Handler中执行handleCommit()方法:
private void handleCommit() { ... try { synchronized (mLock) { commitNonStagedLocked(childSessions); } } ... }
内部再次调用commitNonStagedLocked(List<PackageInstallerSession> )方法,最终将任务交给mPm执行,也就是PMS。
@GuardedBy("mLock") private void commitNonStagedLocked(List<PackageInstallerSession> childSessions){ ... if (isMultiPackage()) { ... mPm.installStage(activeChildSessions); } else { mPm.installStage(committingSession); } }
1.3、PMS执行安装
以下面执行的installStage(ActiveInstallSession )方法为例,了解PMS处理Apk安装的流程。
进入PMS的installStage(ActiveInstallSession )方法开始执行:
void installStage(ActiveInstallSession activeInstallSession) { final Message msg = mHandler.obtainMessage(INIT_COPY); final InstallParams params = new InstallParams(activeInstallSession); msg.obj = params; mHandler.sendMessage(msg); }
PackageManagerService类内部定义了PackageHandler用来处理各种Apk的处理请求。
class PackageHandler extends Handler { PackageHandler(Looper looper) { super(looper); } public void handleMessage(Message msg) { doHandleMessage(msg); } void doHandleMessage(Message msg) { switch (msg.what) { case INIT_COPY: { HandlerParams params = (HandlerParams) msg.obj; if (params != null) { params.startCopy(); } break; } ... } } }
执行关键代码params.startCopy(),这里的params是从Message携带过来的,刚刚在installStage(ActiveInstallSession )方法中创建的InstallParams实例。
final void startCopy() { handleStartCopy(); handleReturnCode(); } abstract void handleStartCopy(); abstract void handleReturnCode();
InstallParams继承自内部抽象类HandlerParams,实现了handleStartCopy()和handleReturnCode()两个抽象方法,完成Apk的拷贝,解析等操作。代码比较复杂,流程图进行了简化。
2、PackageInstaller安装Apk流程
第二种方式就是代码的方式安装,这种方式现在仅限于系统应用,普通应用不能再进行任何后台静默安装操作。比如手机应用商店,下载应用后台静默安装,大都使用这种方式。
//Apk文件 File apkFile = new File(apkPath); //获取PackageManager和PackageInstaller PackageManager pm = getPackageManager(); PackageInstaller pi = pm.getPackageInstaller(); //创建SessionParams PackageInstaller.SessionParams sp = new PackageInstaller .SessionParams(MODE_FULL_INSTALL); try { //开启安装会话 int sessionID = pi.createSession(sp); PackageInstaller.Session se = pi.openSession(sessionID); //文件输入流 InputStream in; in = new FileInputStream("apk_path"); //输出流 OutputStream out; out = se.openWrite("apk_install_session", 0, apkFile.length()); int total = 0; byte[] buffer = new byte[65536]; int len; while ((len = in.read(buffer)) != -1) { total += len; out.write(buffer, 0, len); } se.fsync(out); in.close(); out.close(); //创建PendingIntent PendingIntent broadCastTest = PendingIntent.getBroadcast( this, sessionID, new Intent("ACTION_INSTALL_COMPLETE"), PendingIntent.FLAG_UPDATE_CURRENT); //提交之前必须关闭所有流 se.commit(broadCastTest.getIntentSender()); se.close(); } catch (Exception ignore) { }
整体流程图简化如下:
2.1、开启会话
通过PackageManager获取PackageInstaller实例,实际实现方法在ApplicationPackageManager类中。
@Override public PackageInstaller getPackageInstaller() { if (mInstaller == null) { mInstaller = new PackageInstaller(mPM.getPackageInstaller(), mContext.getPackageName(), getUserId()); return mInstaller; }
其次,构造“会话”参数PackageInstaller中的SessionParams实例,只需要一个mode参数,有两种,一般使用MODE_FULL_INSTALL。
/** * Construct parameters for a new package install session. * * @param mode one of {@link #MODE_FULL_INSTALL} or * {@link #MODE_INHERIT_EXISTING} describing how the session * should interact with an existing app. */ public SessionParams(int mode) { this.mode = mode; }
通过PackageInstaller开启会话,并获取会话id。
public int createSession(@NonNull SessionParams params) throws IOException { ... return mInstaller.createSession(params, installerPackage, mUserId); }
用sessionID打开“会话”
public @NonNull Session openSession(int sessionId) throws IOException { ... return new Session(mInstaller.openSession(sessionId)); }
2.2、写入Apk并提交
文件流的写入细节就不看了,实现非常复杂。跳到写完文件流提交的地方commit()方法:
public void commit(@NonNull IntentSender statusReceiver) { ... mSession.commit(statusReceiver, false); }
执行IPackageInstallerSession的commit()方法,IPackageInstallerSession接口的实现类是PackageInstallerSession。
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) { ... mHandler.obtainMessage(MSG_COMMIT).sendToTarget(); }
通过Handler执行handleCommit()方法,又调用commitNonStagedLocked(List<PackageInstallerSession> )方法
@GuardedBy("mLock") private void commitNonStagedLocked(List<PackageInstallerSession> childSessions) throws PackageManagerException { ... if (isMultiPackage()) { mPm.installStage(activeChildSessions); } else { mPm.installStage(committingSession); } }
最后又到了PMS的installStage(...)中执行,详见1.3节。
3、adb命令安装Apk
最后要探索的这种安装方式,开发者接触的较多。使用adb命令安装Apk文件,关于adb命令的用法详见adb常用命令一文。
adb install file.apk
这种方式安装Apk,分为两个阶段:(1)adb命令传递处理阶段,(2)PMS执行命令阶段。
3.1、adb命令传递阶段
这部分adb相关的源码在/system/core/adb目录下,主体流程如下:
从adb的主函数入口开始探索:main.cpp。
int main(int argc, char* argv[], char* envp[]) { ... return adb_commandline(argc - 1, const_cast<const char**>(argv + 1)); }
执行commandline.cpp中的adb_commandline(...)方法,在该方法中,解析输入的install参数,并执行adb_install.cpp中的install_app(argc, argv)方法:
int install_app(int argc, const char** argv) { ... switch (installMode) { case INSTALL_PUSH: return install_app_legacy(passthrough_argv.size(), passthrough_argv.data(), use_fastdeploy, use_localagent); case INSTALL_STREAM: return install_app_streamed(passthrough_argv.size(), passthrough_argv.data(), use_fastdeploy, use_localagent); case INSTALL_DEFAULT: default: return 1; } }
以其中执行的install_app_legacy()方法为例,先将apk push到/data/local/tmp目录。
static int install_app_legacy(int argc, const char** argv, bool use_fastdeploy, bool use_localagent) { ... std::vector<const char*> apk_file = {argv[last_apk]}; std::string apk_dest = "/data/local/tmp/" + android::base::Basename(argv[last_apk]); ... if (!do_sync_push(apk_file, apk_dest.c_str(), false)) goto cleanup_apk; result = pm_command(argc, argv); ... }
再执行pm_command(argc, argv)方法,向命令中拼接一个pm命令参数:
static int pm_command(int argc, const char** argv) { std::string cmd = "pm"; while (argc-- > 0) { cmd += " " + escape_arg(*argv++); } return send_shell_command(cmd); }
最后调用commandline.cpp中的send_shell_command(cmd)方法发送命令给adbd,后面的逻辑太复杂,简单概括步骤如下:
①命令通过socket发送给adbd
②adbd调用pm处理目录
③pm转交给cmd处理
④cmd解析package命令:cmdMain()方法中
⑤通过Binder调用package:调用IBinder::shellCommand()
⑥执行PMS.onShellCommand()
3.2、PMS执行命令
命令从adb紧接着进入PMS执行,执行主流程如下:
PackageManagerService中的onShellCommand(...)方法执行命令:
@Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { (new PackageManagerShellCommand(this, mPermissionManagerService)).exec( this, in, out, err, args, callback, resultReceiver); }
构造PackageManagerShellCommand实例并执行,PackageManagerShellCommand继承自ShellCommand。
public int exec(Binder target, ...) { ... final int result = super.exec(target, in, out, err, args); return result; }
ShellCommand又继承自BasicShellCommandHandler,执行它的exec(...)方法:
public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args) { String cmd; ... res = onCommand(mCmd); ... return res; }
PackageManagerShellCommand实现了BasicShellCommandHandler中的onCommand(String cmd)抽象方法。
@Override public int onCommand(String cmd) { if (cmd == null) { return handleDefaultCommands(cmd); } try { switch (cmd) { case "path": return runPath(); ... case "install": return runInstall(); case "install-streaming": return runStreamingInstall(); ... } } return -1; }
解析命令行中的install参数,执行对应的runInstall()方法
private int runInstall() throws RemoteException { return doRunInstall(makeInstallParams()); }
调用doRunInstall(InstallParams params)执行最终的安装,分为三步:添加文件、写入文件、提交。
private int doRunInstall(final InstallParams params) throws RemoteException { ... doAddFiles(sessionId, args, params.sessionParams.sizeBytes, isApex) ... doWriteSplits(sessionId, args, params.sessionParams.sizeBytes, isApex) ... doCommitSession(sessionId, false /*logSuccess*/) ... }
细节和前面2.2节提到的Session会话安装一样。
流程附件:
APK安装会话:PackageInstallerSession
-
Quibbler 2021-5-272楼
Android的应用安装涉及到的几个目录
/data/app:存放用户安装apk的目录,安装时,把apk拷贝到这里
/data/app/~~-zcyVWv4fXg8poNL8-7Bsg==
/data/app/~~-zcyVWv4fXg8poNL8-7Bsg==/package-iIktCcF4ZfEkdjrHwRoPYg==/lib
/data/app/~~-zcyVWv4fXg8poNL8-7Bsg==/package-iIktCcF4ZfEkdjrHwRoPYg==/lib/arm
/data/app/~~-zcyVWv4fXg8poNL8-7Bsg==/package-iIktCcF4ZfEkdjrHwRoPYg==/oat
/data/app/~~-zcyVWv4fXg8poNL8-7Bsg==/package-iIktCcF4ZfEkdjrHwRoPYg==/oat/arm
system/app:系统自带app,访问需要有root权限
/system/app
/system/app/packagename/lib
/system/app/packagename/oat
vender/app:设备厂商提供的app
/vendor/app/
/vendor/app/
/data/data:应用安装完成后,在/data/data目录下自动生成和APK包名相同的文件夹,用户存放应用程序的数据
/data/data/packagename
/data/data/android.a.b/cache
/data/data/android.a.b/code_cache
/data/data/android.a.b/database
/data/data/android.a.b/shared_prefs
/data/data/android.a.b/lib
/data/dalivk-cache:存放apk的dex、odex、vdex、art相关文件,便于应用启动时直接执行
odex、vdex、art是优化的可执行二进制文件,加快启动速度
/data/dalvik-cache/arm
/data/dalvik-cache/arm64