Apk安装之——安装确认页面的初始化
如何给手机安装APK应用 #生活技巧# #数码产品使用技巧# #手机技巧#
APK的安装场景主要有以下几种:
通过adb命令安装:adb 命令包括adb push/install,没有安装界面用户下载的Apk,通过系统安装器packageinstaller安装该Apk。packageinstaller是系统内置的应用程序,用于安装和卸载应用程序,有安装界面。系统开机时安装系统应用,没有安装界面。电脑或者手机上的应用商店自动安装,没有安装界面。apk的四种安装方式,最终都会通过PMS的sanPackageTracedLI(),scanPackageLI(),scanPackageDirtyLI()方法来完成。
作为开发者,我们平时安装apk都是点击AS上的按钮进行安装的,当然也有通过adb install/push 命令行进行安装的。下面以点击"RUN"安装的方式进行安装为例来阐述,apk安装的过程。一般点击"RUN"按钮,会对项目进行编译,打包会生成一个apk文件,在推送到手机上。下图就是点击AS的"RUN"按钮后的as执行的安装命令
通过命令行的命令可以看出,显示通过adb push 命令将pc端的apk推到手机上,然后通过pm工具进行安装。
通过指定路径安装apk到手机中(与adb install不同的是adb install安装的.apk是在你的电脑上,而pm install安装的apk是存储在你的手机中)
当执行完pm install命令后,就会在手机上显示安装确认页面,如下图:
这个过程是通过Android系统的packageinstaller应用程序进行安装。packageinstaller这个系统内置的应用的入口在7.0之前是PackageInstallerActivity,在8.0及以后的入口是InstallStart。下面是按照在9.0系统上安装apk,所以,packageinstaller这个应用程序的入口是InstallStart这个Activity,下面看看这个Activity的onCreate方法:
http://androidxref.com/9.0.0_r3/xref/packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mIPackageManager = AppGlobals.getPackageManager(); Intent intent = getIntent(); String callingPackage = getCallingPackage(); //... Intent nextActivity = new Intent(intent); nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); // ... if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) { nextActivity.setClass(this, PackageInstallerActivity.class); } else { Uri packageUri = intent.getData(); 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 //关键代码1 nextActivity.setClass(this, InstallStaging.class); } else if (packageUri != null && packageUri.getScheme().equals( PackageInstallerActivity.SCHEME_PACKAGE)) { 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; } } if (nextActivity != null) {//关键代码2 startActivity(nextActivity); } finish(); }
123456789101112131415161718192021222324252627282930313233343536373839404142434445由于本案例是在9.0系统上安装apk,8.0及以后的系统,在StrictMode下,api是禁止应用程序将file://uri暴露给另外一个应用程序,否则就会报FileUriException异常,所以需要使用FileContentProvider来将file://uri替换成content://uri,所以,packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))这个判断是成立的,这样就走关键代码1处的逻辑。在关键代码1处,nextActivity设置的Class是InstallStaging.class,
在关键代码2处,启动InstallStaging这个Activity,这时,代码跳转到InstallStaging类中,下面看看InstallStaging的onResume方法:
http://androidxref.com/9.0.0_r3/xref/packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java @Override protected void onResume() { super.onResume(); // This is the first onResume in a single life of the activity if (mStagingTask == null) { // File does not exist, or became invalid if (mStagedFile == null) { // Create file delayed to be able to show error try { mStagedFile = TemporaryFileManager.getStagedFile(this); } catch (IOException e) { showError(); return; } }//关键代码1 mStagingTask = new StagingAsyncTask(); mStagingTask.execute(getIntent().getData()); } } private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> { @Override protected Boolean doInBackground(Uri... params) { if (params == null || params.length <= 0) { return false; } Uri packageUri = params[0]; try (InputStream in = getContentResolver().openInputStream(packageUri)) { // Despite the comments in ContentResolver#openInputStream the returned stream can // be null. if (in == null) { return false; } try (OutputStream out = new FileOutputStream(mStagedFile)) { byte[] buffer = new byte[1024 * 1024]; int bytesRead; while ((bytesRead = in.read(buffer)) >= 0) { // Be nice and respond to a cancellation if (isCancelled()) { return false; } out.write(buffer, 0, bytesRead); } } } catch (IOException | SecurityException | IllegalStateException e) { Log.w(LOG_TAG, "Error staging apk from content URI", e); return false; } return true; } @Override protected void onPostExecute(Boolean success) { if (success) { // Now start the installation again from a file Intent installIntent = new Intent(getIntent());//关键代码2 installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class); installIntent.setData(Uri.fromFile(mStagedFile)); if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); } installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); startActivity(installIntent); InstallStaging.this.finish(); } else { showError(); } } }
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879在InstallStaging的onResume方法中,会创建一个StagingAsyncTask,并执行其execute方法,这个StagingAsyncTask内部,将apk文件临时存储起来,然后跳转到DeleteStagedFileOnResult这个Activity,并结束当前页面,下面看DeleteStagedFileOnResult的onCreatea方法
http://androidxref.com/9.0.0_r3/xref/packages/apps/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java public class DeleteStagedFileOnResult extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { Intent installIntent = new Intent(getIntent()); installIntent.setClass(this, PackageInstallerActivity.class); installIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); startActivityForResult(installIntent, 0); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { File sourceFile = new File(getIntent().getData().getPath()); sourceFile.delete(); setResult(resultCode, data); finish(); } }
123456789101112131415161718192021222324这个方法内部,跳转到PackageInstallerActivity,并等待PackageInstallerActivity的返回结果。在onActivityResult方法中,根据不论返回结果是成功还是失败,都删除InstallStaging这个Activity中保存的临时apk文件。这个过程,其实就是将Uri协议转换成File协议,这样后续的流程就和8.0之前的一致了,下面看PackageInstallerActivity类的onCreate方法
http://androidxref.com/9.0.0_r3/xref/packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @Override protected void onCreate(Bundle icicle) { super.onCreate(null); if (icicle != null) { mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY); } ... //关键代码1 boolean wasSetUp = processPackageUri(packageUri); if (!wasSetUp) { return; } // load dummy layout with OK button disabled until we override this layout in // startInstallConfirm bindUi(R.layout.install_confirm, false); //关键代码2 checkIfAllowedAndInitiateInstall(); }
1234567891011121314151617181920212223先看关键代码1处的processPackageUri方法,这个方法的具体实现是:
http://androidxref.com/9.0.0_r3/xref/packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java private boolean processPackageUri(final Uri packageUri) { mPackageURI = packageUri; final String scheme = packageUri.getScheme(); switch (scheme) { case SCHEME_PACKAGE: { ... } break; case ContentResolver.SCHEME_FILE: { //关键代码 File sourceFile = new File(packageUri.getPath()); PackageParser.Package parsed = PackageUtil.getPackageInfo(this, 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); return false; } mPkgInfo = PackageParser.generatePackageInfo(parsed, null, PackageManager.GET_PERMISSIONS, 0, 0, null, new PackageUserState()); mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile); } break; default: { throw new IllegalArgumentException("Unexpected URI scheme " + packageUri); } } return true; }
12345678910111213141516171819202122232425262728293031323334353637这个方法内部,会先获取scheme,由于之前在InstallStaging这个Activity中,已经将content://uri协议转成了file://uri协议,所以,这里的scheme就是ContentResolver.SCHEME_FILE,在关键代码处这个case中,主要是根据apk文件创建一个PackageParser.Package对象,在根据这个PackageParser.Package对象创建一个PackageInfo对象。到此,PackageInstallerActivity类的processPackageUri()方法分析完毕,接着看PackageInstallerActivity类的onCreate方法的关键代码2处
http://androidxref.com/9.0.0_r3/xref/packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @Override protected void onCreate(Bundle icicle) { super.onCreate(null); if (icicle != null) { mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY); } ... //关键代码1 boolean wasSetUp = processPackageUri(packageUri); if (!wasSetUp) { return; } // load dummy layout with OK button disabled until we override this layout in // startInstallConfirm bindUi(R.layout.install_confirm, false); //关键代码2 checkIfAllowedAndInitiateInstall(); } private void checkIfAllowedAndInitiateInstall() { // Check for install apps user restriction first. ... // 关键代码1 if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) { initiateInstall(); } else { // Check for unknown sources restriction final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource( UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle()); if ((unknownSourcesRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) { showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER); } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) { startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS)); finish(); } else { handleUnknownSources(); } } }
1234567891011121314151617181920212223242526272829303132333435363738394041424344在checkIfAllowedAndInitiateInstall方法内部,会先做个判断,如果是允许未知来源的apk或者apk是非未知来源的,就直接调用initiateInstall()方法进行初始化安装。否则,就执行else分支,else分支中,会判断,如果是管理员限制来自未知来源的安装,则弹出提示框或者调整到设置页面,否则就执行handleUnknownSources()方法安装未知来源的apk。下面先看initiateInstall()方法:
http://androidxref.com/9.0.0_r3/xref/packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java 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.MATCH_UNINSTALLED_PACKAGES); if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { mAppInfo = null; } } catch (NameNotFoundException e) { mAppInfo = null; } startInstallConfirm(); } private void startInstallConfirm() { // We might need to show permissions, load layout with permissions if (mAppInfo != null) { bindUi(R.layout.install_confirm_perm_update, true); } else { bindUi(R.layout.install_confirm_perm, true); } ... AppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo); final int N = perms.getPermissionCount(AppSecurityPermissions.WHICH_ALL); if (mAppInfo != null) { msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ? R.string.install_confirm_question_update_system : R.string.install_confirm_question_update; mScrollView = new CaffeinatedScrollView(this); mScrollView.setFillViewport(true); boolean newPermissionsFound = false; if (!supportsRuntimePermissions) { newPermissionsFound = (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0); if (newPermissionsFound) { permVisible = true; mScrollView.addView(perms.getPermissionsView( AppSecurityPermissions.WHICH_NEW)); } } if (!supportsRuntimePermissions && !newPermissionsFound) { LayoutInflater inflater = (LayoutInflater)getSystemService( Context.LAYOUT_INFLATER_SERVICE); TextView label = (TextView)inflater.inflate(R.layout.label, null); label.setText(R.string.no_new_perms); mScrollView.addView(label); } adapter.addTab(tabHost.newTabSpec(TAB_ID_NEW).setIndicator( getText(R.string.newPerms)), mScrollView); } if (!supportsRuntimePermissions && N > 0) { permVisible = true; LayoutInflater inflater = (LayoutInflater)getSystemService( Context.LAYOUT_INFLATER_SERVICE); View root = inflater.inflate(R.layout.permissions_list, null); if (mScrollView == null) { mScrollView = (CaffeinatedScrollView)root.findViewById(R.id.scrollview); } ((ViewGroup)root.findViewById(R.id.permission_list)).addView( perms.getPermissionsView(AppSecurityPermissions.WHICH_ALL)); adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator( getText(R.string.allPerms)), root); } if (!permVisible) { if (mAppInfo != null) { // This is an update to an application, but there are no // permissions at all. msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ? R.string.install_confirm_question_update_system_no_perms : R.string.install_confirm_question_update_no_perms; } else { // This is a new application with no permissions. msg = R.string.install_confirm_question_no_perms; } // We do not need to show any permissions, load layout without permissions bindUi(R.layout.install_confirm, true); mScrollView = null; }... }
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899initiateInstall方法内部,给PackageInfo的packageName和PackageInfo.ApplicationInfo的packageName赋值,并调用startInstallConfirm方法,初始化安装确认页面,并将应用需要的权限都展示出来,并显示确定和取消按钮。分析完允许安装未知来源或者应用是非未知来源的apk后,接着分析安装未知来源的apk安装未知来源的apk是在PackageInstallerActivity类的handleUnknownSources()方法中完成的
http://androidxref.com/9.0.0_r3/xref/packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java private void handleUnknownSources() { if (mOriginatingPackage == null) { Log.i(TAG, "No source found for package " + mPkgInfo.packageName); showDialogInner(DLG_ANONYMOUS_SOURCE); return; } // Shouldn't use static constant directly, see b/65534401. final int appOpCode = AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES); final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpCode, mOriginatingUid, mOriginatingPackage); switch (appOpMode) { case AppOpsManager.MODE_DEFAULT: try { int result = mIpm.checkUidPermission( Manifest.permission.REQUEST_INSTALL_PACKAGES, mOriginatingUid); if (result == PackageManager.PERMISSION_GRANTED) { initiateInstall(); break; } } catch (RemoteException exc) { Log.e(TAG, "Unable to talk to package manager"); } mAppOpsManager.setMode(appOpCode, mOriginatingUid, mOriginatingPackage, AppOpsManager.MODE_ERRORED); // fall through case AppOpsManager.MODE_ERRORED: showDialogInner(DLG_EXTERNAL_SOURCE_BLOCKED); break; case AppOpsManager.MODE_ALLOWED: initiateInstall(); break; default: Log.e(TAG, "Invalid app op mode " + appOpMode + " for OP_REQUEST_INSTALL_PACKAGES found for uid " + mOriginatingUid); finish(); break; } }
1234567891011121314151617181920212223242526272829303132333435363738394041由于是第一次安装,并且是未知来源的,如果要安装的应用未声明REQUEST_INSTALL_PACKAGES权限,则
会走AppOpsManager.MODE_ALLOWED这个case,弹出对话框,提示用户如果要安装的应用声明了REQUEST_INSTALL_PACKAGES权限,会走AppOpsManager.MODE_DEFAULT 这个case分支,接着会检测用户是否授予了REQUEST_INSTALL_PACKAGES权限,如果授予了,则走initiateInstall()方法,初始化安装确认页面,还有一个case,是升级安装未知来源的apk时,就直接执行initiateInstall()方法,显示升级安装确认页面。
以上便是,点击AS的"RUN"按钮显示安装确认页面的过程分析。
总结:packageinstaller在安装应用时,如果系统版本是8.0及以上,会启动InstallStart这个Activity作为packageinstaller安装程序的入口,如果是8.0以下版本,则会将PackageInstallerActivity作为packageinstaller安装程序的入口。根据scheme协议的不同,如果是content协议,则跳转到InstallStaging这个Activity中,复制文件,将content协议转换成file协议,然后在次跳转到DeleteStagedFileOnResult这个Activity中,DeleteStagedFileOnResult这个Activity中,其实直接跳转到PackageInstallerActivity,并等待安装结果,不论安装成功还是失败,都将在InstallStaging这个Activity中复制文件删除。后续的安装逻辑其实还是8.0以前的一致。都是通过判断要安装的apk是否允许安装未知来源的apk,或者安装的apk是非未知来源的,如果满足这个条件,就直接启动安装确认页面,并初始化这个页面,将需要的权限信息,确认,取消按钮,app的icon,app名称等信息显示出来。如果是管理员限制了未知来源的apk,则会弹出提示框,或者跳转到设置页面。如果管理员未限制未知来源的apk安装,则就安装未知来源的apk,安装过程中,也会检测这个未知来源的apk中是否声明了REQUEST_INSTALL_PACKAGES权限,如果未声明,则弹出提示框。如果声明了REQUEST_INSTALL_PACKAGES权限,则检查用户是否授予了这个权限,如果未授予,则安装失败,如果授予了,则和安装非未知来源的逻辑一致,显示安装确认页面。还有一种情况是,是升级安装未知来源的apk时,就直接执行initiateInstall()方法,显示升级安装确认页面。
网址:Apk安装之——安装确认页面的初始化 https://www.yuejiaxmz.com/news/view/415593
相关内容
Nico,从零开始干掉 Appium,移动端自动化测试框架实现(一) · 测试之家Android网络设置APK:告别繁琐,轻松优化手机网络体验
Flipclock翻页时钟电脑版
王国传承Mac电脑版下载安装教程 Mac电脑怎么玩王国传承攻略
【Docker与Termux】闲置旧安卓手机上的NAS无缝部署方案
翻页时钟电脑版
RFS的web自动化验收测试——安装篇
【教程】用安卓厨房制作你自己的卡刷包!你也可以是Romer !
百味食光电脑版PC端下载安装教程 电脑版怎么玩百味食光攻略
雪豹速清官网下载安装