热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:Android中应用安装分析

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Android中应用安装分析相关的知识,希望对你有一定的参考价值。#1安装方式

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Android中应用安装分析相关的知识,希望对你有一定的参考价值。



#1 安装方式



  • 1 安装系统APK和预制APK时,通过PMS的构造函数中安装,即第一次开机时安装应用,没有安装界面。

  • 2 网络下载安装,通过应用商店等,即调用PackageManager.installPackages(),有安装界面。

  • 3 通过adb工具安装,没有安装界面,它通过启动pm脚本的形式,然后调用com.android.commands.pm.Pm类,之后调用到PMS.installStage()完成安装。

  • 4 安装本地apk,有安装界面,由PackageInstaller系统应用安装。
    上述几种方式均通过PackageInstallObserver来监听安装是否成功。


#2 安装流程分析


2.1 首次安装

首次安装即系统第一次开机时安装应用,包括系统应用和预制应用,其最主要过程在PMS构造函数中,

整个过程关键步骤大致为上述15步,与应用安装相关实际上就是扫描和安装两步。方法调用时序图如图1所示。
[图1 PMS安装应用时序图]



  • 1 向动态设置中添加系统默认的共享ID(system、phone、log、nfc、bluetooth、shell、se等)。

  • 2 初始化成员变量,如Installer、PackageDexOptimizer、DexManager、ArtManagerService、MoveCallbacks、OnPermissionChangeListeners等,并获取系统配置。

  • 3 启动一个服务类线程。

  • 4 初始化用户管理服务

  • 5 将权限配置传入包管理器

  • 6 清除代码路径不存在的孤立包

  • 7 将系统应用权限从安装态升级为运行时

  • 8 在扫描应用前,手机供应商的覆盖安装包(/overlay)

  • 9 扫描应用目录,依次为特权系统目录 priv-app、普通目录 app、供应商系统目录 vendor/app等

  • 10 解析存储管理器

  • 11 如果是第一次开机,需要初始化用户的默认偏好应用

  • 12 在启动时,为用户准备好存储空间,因为SystemUI等启动不能等待用户

  • 13 安装应用,完成后检查webview,默认浏览器等。

  • 14 启动PackageInstallerService

  • 15 向系统组件暴露私有服务
    下面我们结合代码做详细分析



  1. 判断应用包是否已安装,如果包名存在于uninstalled_deapp.xml中或者已安装,则直接返回null。



2.2 下载安装

下载安装可分为两部分:拷贝应用和安装应用。拷贝过程的函数调用时序图如图2所示。
【图2 下载安装应用程序时序图】

frameworks层的入口函数为PackageManager.installPackage,由应用市场APP调用,然后调用PMS.installPackageAsUser,然后发送消息INIT_COPY、MCS_BOUND开始复制,调用HandlerParams.startCopy。这个方法主要分两部分,一部分是拷贝应用的执行程序,另一部分是创建应用的数据目录,拷贝部分由handleStartCopy完成。之后调用handlerReturnCode来处理创建数据目录。拷贝部分会调用DefaultContainerService来完成,该服务为那些可能位于可删除空间上的文件提供检查和拷贝功能。当底层设置被移除时,这样设计可以防止系统进程保留打开的文件时,不被内核杀死。

handleStartcopy实现在PMS内部类InstallParams中,它的功能是调用远程方法获取包信息和安装位置,如有必要则给与默认车辆覆盖安装位置,然后基于安装位置创建安装参数。下面我们结合关键代码做进一步分析。

首先是拷贝应用过程



  • 1 PMS.installPackageAsUser的功能主要是:根据uid确定installFlags,并校验权限,并构造InstallParam,然后发送INIT_COPY消息。

@Override
public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, int userId) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);
final int callingUid = Binder.getCallingUid();
enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, true /* checkShell */, "installPackageAsUser");
if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
try {
if (observer != null) {
observer.onPackageInstalled("", INSTALL_FAILED_USER_RESTRICTED, null, null);
}
} catch (RemoteException re) {
}
return;
}
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
installFlags |= PackageManager.INSTALL_FROM_ADB;
} else {
// Caller holds INSTALL_PACKAGES permission, so we\'re less strict
// about installerPackageName.
installFlags &= ~PackageManager.INSTALL_FROM_ADB;
installFlags &= ~PackageManager.INSTALL_ALL_USERS;
}
UserHandle user;
if ((installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
user = UserHandle.ALL;
} else {
user = new UserHandle(userId);
}
// Only system components can circumvent runtime permissions when installing.
if ((installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
&& mContext.checkCallingOrSelfPermission(Manifest.permission
.INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
throw new SecurityException("You need the "
+ "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
+ "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
}
if ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0
|| (installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
throw new IllegalArgumentException(
"New installs into ASEC containers no longer supported");
}
final File originFile = new File(originPath);
final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile);
final Message msg = mHandler.obtainMessage(INIT_COPY);
final VerificationInfo verificatiOnInfo= new VerificationInfo(
null /*originatingUri*/, null /*referrer*/, -1 /*originatingUid*/, callingUid);
final InstallParams params = new InstallParams(origin, null /*moveInfo*/, observer,
installFlags, installerPackageName, null /*volumeUuid*/, verificationInfo, user,
null /*packageAbiOverride*/, null /*grantedPermissions*/,
null /*certificates*/, PackageManager.INSTALL_REASON_UNKNOWN);
params.setTraceMethod("installAsUser").setTraceCOOKIE(System.identityHashCode(params));
msg.obj = params;
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "installAsUser",
System.identityHashCode(msg.obj));
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
System.identityHashCode(msg.obj));
mHandler.sendMessage(msg);
}


  • 2 之后根据Handler.doHandleMessage调用到InstallParams.handleStartCopy方法,首先检查文件和cid是否已生成,如生成则设置installFlags。

// [InstallParams.handleStartCopy]
if (origin.staged) {
if (origin.file != null) {
installFlags |= PackageManager.INSTALL_INTERNAL;
installFlags &= ~PackageManager.INSTALL_EXTERNAL;
} else if (origin.cid != null) {
installFlags |= PackageManager.INSTALL_EXTERNAL;
installFlags &= ~PackageManager.INSTALL_INTERNAL;
} else {
throw new IllegalStateException("Invalid stage location");
}
}


  • 3 然后检查空间大小,如果空间不够则释放无用空间。

// [InstallParams.handleStartCopy]
if (!origin.staged && pkgLite.recommendedInstallLocation
== PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
// TODO: focus freeing disk space on the target device
final StorageManager storage = StorageManager.from(mContext);
final long lowThreshold = storage.getStorageLowBytes(
Environment.getDataDirectory());
final long sizeBytes = mContainerService.calculateInstalledSize(
origin.resolvedPath, isForwardLocked(), packageAbiOverride);
try {
mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0);
pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath,
installFlags, packageAbiOverride);
} catch (InstallerException e) {
Slog.w(TAG, "Failed to free cache", e);
}
if (pkgLite.recommendedInstallLocation
== PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
pkgLite.recommendedInstallLocation
= PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
}
}


  • 4 覆盖原有安装位置的文件,并根据返回结果来确定函数的返回值,并设置installFlags。

// [InstallParams.handleStartCopy]
// Override with defaults if needed.
loc = installLocationPolicy(pkgLite);
if (loc == PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE) {
ret = PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
} else if (!onSd && !onInt) {
// Override install location with flags
if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
// Set the flag to install on external media.
installFlags |= PackageManager.INSTALL_EXTERNAL;
installFlags &= ~PackageManager.INSTALL_INTERNAL;
} else if (loc == PackageHelper.RECOMMEND_INSTALL_EPHEMERAL) {
if (DEBUG_EPHEMERAL) {
Slog.v(TAG, "...setting INSTALL_EPHEMERAL install flag");
}
installFlags |= PackageManager.INSTALL_INSTANT_APP;
installFlags &= ~(PackageManager.INSTALL_EXTERNAL
|PackageManager.INSTALL_INTERNAL);
} else {
// Make sure the flag for installing on external
// media is unset
installFlags |= PackageManager.INSTALL_INTERNAL;
installFlags &= ~PackageManager.INSTALL_EXTERNAL;
}
}


  • 5 确定是否有任何已安装的包验证器,如有,则延迟检测。主要分三步:首先新建一个验证Intent,然后设置相关的信息,之后获取验证器列表,最后向每个验证器发送验证Intent。

// [InstallParams.handleStartCopy]
final Intent verification = new Intent( //构造验证Intent
Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
// ......
final PackageVerificationState verificatiOnState= new PackageVerificationState(
requiredUid, args);
mPendingVerification.append(verificationId, verificationState);
// 获取验证器列表
final List sufficientVerifiers = matchVerifiers(pkgLite,
receivers, verificationState);
DeviceIdleController.LocalService idleCOntroller= getDeviceIdleController();
final long idleDuration = getVerificationTimeout();
/*
* If any sufficient verifiers were listed in the package
* manifest, attempt to ask them.
*/
if (sufficientVerifiers != null) {
final int N = sufficientVerifiers.size();
if (N == 0) {
Slog.i(TAG, "Additional verifiers required, but none installed.");
ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
} else {
for (int i = 0; i final ComponentName verifierCompOnent= sufficientVerifiers.get(i);
idleController.addPowerSaveTempWhitelistApp(Process.myUid(),
verifierComponent.getPackageName(), idleDuration,
verifierUser.getIdentifier(), false, "package verifier");
// 向每个验证器发送验证Intent
final Intent sufficientIntent = new Intent(verification);
sufficientIntent.setComponent(verifierComponent);
mContext.sendBroadcastAsUser(sufficientIntent, verifierUser);
}
}
}


  • 6 向验证器客户端发送intent,只有当验证成功之后才会开启copy工作。如果没有任何验证器则直接拷贝。

下面为安装过程入口是PMS.processPendingInstall方法,调用时序图如图3
【图3 下载安装-安装过程图】



  • 1 首先启动一个新线程,然后设置安装信息,处理安装参数,开始安装,并发送关于安装状态的广播,然后处理安装完的事情,比如打印错误信息,清除临时文件等。

private void processPendingInstall(final InstallArgs args, final int currentStatus) {
// Queue up an async operation since the package installation may take a little while.
mHandler.post(new Runnable() {
public void run() {
mHandler.removeCallbacks(this);
// Result object to be returned
PackageInstalledInfo res = new PackageInstalledInfo();
res.setReturnCode(currentStatus);
res.uid = -1;
res.pkg = null;
res.removedInfo = null;
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
args.doPreInstall(res.returnCode);
synchronized (mInstallLock) {
installPackageTracedLI(args, res);
}
args.doPostInstall(res.returnCode, res.uid);
//......
}


  • 2 installPackageTracedLI是安装过程的核心方法,然后调用installPackageLI.首先检查安装包的完整性并解析安装包。

//[PMS.installPackageLI]
// 完整性校验
if (instantApp && (forwardLocked || onExternal)) {
Slog.i(TAG, "Incompatible ephemeral install; fwdLocked=" + forwardLocked
+ " external=" + onExternal);
res.setReturnCode(PackageManager.INSTALL_FAILED_INSTANT_APP_INVALID);
return;
}
// 检索包设置,并解析应用
final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
| PackageParser.PARSE_ENFORCE_CODE
| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
| (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0)
| (instantApp ? PackageParser.PARSE_IS_EPHEMERAL : 0)
| (forceSdk ? PackageParser.PARSE_FORCE_SDK : 0);
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setDisplayMetrics(mMetrics);
pp.setCallback(mPackageParserCallback);
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
final PackageParser.Package pkg;
try {
//解析安装包
pkg = pp.parsePackage(tmpPackageFile, parseFlags);
DexMetadataHelper.validatePackageDexMetadata(pkg);
} catch (PackageParserException e) {
res.setError("Failed parse during installPackageLI", e);
return;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}


  • 3 检查SDK版本和沙箱版本,同时检查是否有静态共享库,如有则需要放在内部存储中。

//[PMS.installPackageLI]
//检查SDK版本和沙箱版本
if (instantApp && pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
Slog.w(TAG, "Instant app package " + pkg.packageName + " does not target O");
res.setError(INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE,
"Instant app package must target O");
return;
}
if (instantApp && pkg.applicationInfo.targetSandboxVersion != 2) {
Slog.w(TAG, "Instant app package " + pkg.packageName
+ " does not target targetSandboxVersion 2");
res.setError(INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE,
"Instant app package must use targetSanboxVersion 2");
return;
}
//检查是否有静态共享库
if (pkg.applicationInfo.isStaticSharedLibrary()) {
// Static shared libraries have synthetic package names
renameStaticSharedLibraryPackage(pkg);
// No static shared libs on external storage
if (onExternal) {
Slog.i(TAG, "Static shared libs can only be installed on internal storage.");
res.setError(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
"Packages declaring static-shared libs cannot be updated");
return;
}
}


  • 4 检查是否有子安装包,如有则子安装包也需要检测。

//[PMS.installPackageLI]
// If we are installing a clustered package add results for the children
if (pkg.childPackages != null) {
synchronized (mPackages) {
final int childCount = pkg.childPackages.size();
for (int i = 0; i PackageParser.Package childPkg = pkg.childPackages.get(i);
PackageInstalledInfo childRes = new PackageInstalledInfo();
childRes.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
childRes.pkg = childPkg;
childRes.name = childPkg.packageName;
PackageSetting childPs = mSettings.getPackageLPr(childPkg.packageName);
if (childPs != null) {
childRes.origUsers = childPs.queryInstalledUsers(
sUserManager.getUserIds(), true);
}
if ((mPackages.containsKey(childPkg.packageName))) {
childRes.removedInfo = new PackageRemovedInfo(this);
childRes.removedInfo.removedPackage = childPkg.packageName;
childRes.removedInfo.installerPackageName = childPs.installerPackageName;
}
if (res.addedChildPackages == null) {
res.addedChildPackages = new ArrayMap<>();
}
res.addedChildPackages.put(childPkg.packageName, childRes);
}
}
}


  • 5 检查安装包是否已存在,如已存在则需要检查旧的父包、沙箱、sdk等是否已为空,否则会报错。

  • 6 校验安装包签名

//[PMS.installPackageLI]
PackageSetting signatureCheckPs = ps;
if (pkg.applicationInfo.isStaticSharedLibrary()) {
SharedLibraryEntry libraryEntry = getLatestSharedLibraVersionLPr(pkg);
if (libraryEntry != null) {
signatureCheckPs = mSettings.getPackageLPr(libraryEntry.apk);
}
}
// Quick sanity check that we\'re signed correctly if updating;
// we\'ll check this again later when scanning, but we want to
// bail early here before tripping over redefined permissions.
if (shouldCheckUpgradeKeySetLP(signatureCheckPs, scanFlags)) {
if (!checkUpgradeKeySetLP(signatureCheckPs, pkg)) {
res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
+ pkg.packageName + " upgrade keys do not match the "
+ "previously installed version");
return;
}
} else {
try {
verifySignaturesLP(signatureCheckPs, pkg);
} catch (PackageManagerException e) {
res.setError(e.error, e.getMessage());
return;
}
}


  • 7 设置相关的全向,包括生成权限、移植权限等

  • 8 如果这是一个系统应用,则检查是否在外部存储上或是是否被其他应用替换等

//[PMS.installPackageLI]
if (systemApp) {
if (onExternal) {
// Abort update; system app can\'t be replaced with app on sdcard
res.setError(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
"Cannot install updates to system apps on sdcard");
return;
} else if (instantApp) {
// Abort update; system app can\'t be replaced with an instant app
res.setError(INSTALL_FAILED_INSTANT_APP_INVALID,
"Cannot update a system app with an instant app");
return;
}
}


  • 9 生成安装包Abi(Application binary interface,应用二进制接口,描述应用程序和操作系统之间或其他应用程序的低级接口)

//[PMS.installPackageLI]
try {
String abiOverride = (TextUtils.isEmpty(pkg.cpuAbiOverride) ?
args.abiOverride : pkg.cpuAbiOverride);
final boolean extractNativeLibs = !pkg.isLibrary();
derivePackageAbi(pkg, new File(pkg.codePath), abiOverride,
extractNativeLibs, mAppLib32InstallDir);
} catch (PackageManagerException pme) {
Slog.e(TAG, "Error deriving application ABI", pme);
res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Error deriving application ABI");
return;
}


  • 10更新共享库

//[PMS.installPackageLI]
synchronized (mPackages) {
try {
updateSharedLibrariesLPr(pkg, null);
} catch (PackageManagerException e) {
Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
}
}


  • 11如有必要,优化dex文件

//[PMS.installPackageLI]
final boolean performDexopt = (res.returnCode == PackageManager.INSTALL_SUCCEEDED)
&& !forwardLocked
&& !pkg.applicationInfo.isExternalAsec()
&& (!instantApp || Global.getInt(mContext.getContentResolver(),
Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
&& ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0);
if (performDexopt) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
// Do not run PackageDexOptimizer through the local performDexOpt
// method because `pkg` may not be in `mPackages` yet.
//
// Also, don\'t fail application installs if the dexopt step fails.
DexoptOptions dexoptOptiOns= new DexoptOptions(pkg.packageName,
REASON_INSTALL,
DexoptOptions.DEXOPT_BOOT_COMPLETE |
DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE);
mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
null /* instructionSets */,
getOrCreateCompilerPackageStats(pkg),
mDexManager.getPackageUseInfoOrDefault(pkg.packageName),
dexoptOptions);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}


  • 12替换安装,则直接安装新包,这里应用时生成应用数据目录。ps:替换安装:其主要过程为更新设置,清除原有的某些APP数据,重新生成相关的app数据目录等步骤,同事要区分系统应用替换和非系统应用替换。而安装新包:则直接更新设置,生成APP数据即可。

try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags,
"installPackageLI")) {
if (replace) {
if (pkg.applicationInfo.isStaticSharedLibrary()) {
// Static libs have a synthetic package name containing the version
// and cannot be updated as an update would get a new package name,
// unless this is the exact same version code which is useful for
// development.
PackageParser.Package existingPkg = mPackages.get(pkg.packageName);
if (existingPkg != null && existingPkg.mVersionCode != pkg.mVersionCode) {
res.setError(INSTALL_FAILED_DUPLICATE_PACKAGE, "Packages declaring "
+ "static-shared libs cannot be updated");
return;
}
}
replacePackageLIF(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
installerPackageName, res, args.installReason);
} else {
installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
args.user, installerPackageName, volumeUuid, res, args.installReason);
}
}


  • 13 如果是安装一个不存在的包,则调用PMS.installNewPackageLIF方法。首先会检查是否有重复的包名,并更新设置,然后根据安装的结果,如果安装失败则删除安装过程中产生的文件。

private void installNewPackageLIF(PackageParser.Package pkg, final int policyFlags,
int scanFlags, UserHandle user, String installerPackageName, String volumeUuid,
PackageInstalledInfo res, int installReason) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installNewPackage");
// Remember this for later, in case we need to rollback this install
String pkgName = pkg.packageName;
if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg);
synchronized(mPackages) {
final String renamedPackage = mSettings.getRenamedPackageLPr(pkgName);
if (renamedPackage != null) {
// 如果已有相同包名的应用,则报错
res.setError(INSTALL_FAILED_ALREADY_EXISTS, "Attempt to re-install " + pkgName
+ " without first uninstalling package running as "
+ renamedPackage);
return;
}
if (mPackages.containsKey(pkgName)) {
// Don\'t allow installation over an existing package with the same name.
res.setError(INSTALL_FAILED_ALREADY_EXISTS, "Attempt to re-install " + pkgName
+ " without first uninstalling.");
return;
}
}
try {
PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags, System.currentTimeMillis(), user);
updateSettingsLI(newPackage, installerPackageName, null, res, user, installReason);
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
prepareAppDataAfterInstallLIF(newPackage);
} else {
// Remove package from internal structures, but keep around any
// data that might have already existed
deletePackageLIF(pkgName, UserHandle.ALL, false, null,
PackageManager.DELETE_KEEP_DATA, res.removedInfo, true, null);
}
} catch (PackageManagerException e) {
res.setError("Package couldn\'t be installed in " + pkg.codePath, e);
}
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}


  • 14 然后为已安装的应用准备数据目录,其依次的顺序是

    • PMS.prepareAppDataAfterInstallLIF

    • PMS.prepareAppDataLIF

    • PMS.prepareAppDataLeafLIF

    • Installer.createAppData



这个方法是PMS与Installer交互的接口函数,这里的数据目录是CE类型。

private void prepareAppDataLeafLIF(PackageParser.Package pkg, int userId, int flags) {
if (DEBUG_APP_DATA) {
Slog.v(TAG, "prepareAppData for " + pkg.packageName + " u" + userId + " 0x"
+ Integer.toHexString(flags));
}
final String volumeUuid = pkg.volumeUuid;
final String packageName = pkg.packageName;
final ApplicationInfo app = pkg.applicationInfo;
final int appId = UserHandle.getAppId(app.uid);
Preconditions.checkNotNull(app.seInfo);
long ceDataInode = -1;
try {
// 调用Installd守护进程的入口
ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,
appId, app.seInfo, app.targetSdkVersion);
} catch (InstallerException e) {
//......
}
// Prepare the application profiles.
mArtManagerService.prepareAppProfiles(pkg, userId);
if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
// TODO: mark this structure as dirty so we persist it!
synchronized (mPackages) {
final PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps != null) {
ps.setCeDataInode(ceDataInode, userId);
}
}
}
prepareAppDataContentsLeafLIF(pkg, userId, flags);
}


  • 15 如果是替换应用,一般情况是应用更新,或者是重新安装。它的主要过程包括:验证签名,如是系统更新则还需要校验hash值,检查共享ID的更改情况,不允许完整更新,更新已被删除数据,最后根据应用是否是系统应用来判断接下去的操作。

private void replacePackageLIF(PackageParser.Package pkg, final int policyFlags, int scanFlags,
UserHandle user, String installerPackageName, PackageInstalledInfo res,
int installReason) {
final boolean isInstantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
final PackageParser.Package oldPackage;
final PackageSetting ps;
final String pkgName = pkg.packageName;
final int[] allUsers;
final int[] installedUsers;
// ......
boolean sysPkg = (isSystemApp(oldPackage));
if (sysPkg) {
// Set the system/privileged flags as needed
final boolean privileged =
(oldPackage.applicationInfo.privateFlags
& ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
final int systemPolicyFlags = policyFlags
| PackageParser.PARSE_IS_SYSTEM
| (privileged ? PackageParser.PARSE_IS_PRIVILEGED : 0);
replaceSystemPackageLIF(oldPackage, pkg, systemPolicyFlags, scanFlags,
user, allUsers, installerPackageName, res, installReason);
} else {
replaceNonSystemPackageLIF(oldPackage, pkg, policyFlags, scanFlags,
user, allUsers, installerPackageName, res, installReason);
}
}


  • 16 最后这两个方法均会调用到PMS.prepareAppDataLeafLIF。

  • 17 安装完成后,更新设置,更新安装锁等。


2.3 adb安装

关于adb安装,其copy过程与下载安装不同,但安装过程却与下载过程是相同的,这里不做重复分析,需要注意的是adb安装是不能替换安装的,具体原因?

拷贝过程
其调用时序图如图4 所示。
【图4 adb安装-copy过程时序图】



  • 1 adb的入口在com.android.commands.pm.Pm类,那么这是如何调用到这个类的呢,这是adb命令通过adbd守护进程调用到/system/bin/pm这个脚本,其脚本源码如下:

base=/system
export CLASSPATh-$base/framework/pm.jar
exec app_process $base/bin.com.android.commands.pm.Pm "$@"


  • 2 Pm类通过脚本启动,执行顺序是main->run->runInstall,然后提交session。

public static void main(String[] args) {
int exitCode = 1;
try {
exitCode = new Pm().run(args);
} catch (Exception e) {
Log.e(TAG, "Error", e);
System.err.println("Error: " + e);
if (e instanceof RemoteException) {
System.err.println(PM_NOT_RUNNING_ERR);
}
}
System.exit(exitCode);
}
public int run(String[] args) throws RemoteException {
boolean validCommand = false;
if (args.length <1) {
return showUsage();
}
mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE));
mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE));
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
if (mPm == null) {
System.err.println(PM_NOT_RUNNING_ERR);
return 1;
}
mInstaller = mPm.getPackageInstaller();
mArgs = args;
String op = args[0];
mNextArg = 1;
//......
if ("install".equals(op)) {
return runInstall();
}
//......
}


  • 3 Pm.runInstall中首先是创建session,然后提交session,代码如下。

private int runInstall() throws RemoteException {
long startedTime = SystemClock.elapsedRealtime();
final InstallParams params = makeInstallParams();
final String inPath = nextArg();
if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
File file = new File(inPath);
if (file.isFile()) {
try {
ApkLite baseApk = PackageParser.parseApkLite(file, 0);
PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
null, null);
params.sessionParams.setSize(
PackageHelper.calculateInstalledSize(pkgLite, false,
params.sessionParams.abiOverride));
} catch (PackageParserException | IOException e) {
System.err.println("Error: Failed to parse APK file: " + e);
return 1;
}
} else {
System.err.println("Error: Can\'t open non-file: " + inPath);
return 1;
}
}
final int sessiOnId= doCreateSession(params.sessionParams,
params.installerPackageName, params.userId);
try {
if (inPath == null && params.sessionParams.sizeBytes == -1) {
System.err.println("Error: must either specify a package size or an APK file");
return 1;
}
if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
return 1;
}
Pair status = doCommitSession(sessionId, false /*logSuccess*/);
if (status.second != PackageInstaller.STATUS_SUCCESS) {
return 1;
}
Log.i(TAG, "Package " + status.first + " installed in " + (SystemClock.elapsedRealtime()
- startedTime) + " ms");
System.out.println("Success");
return 0;
} finally {
try {
mInstaller.abandonSession(sessionId);
} catch (Exception ignore) {
}
}
}


  • 4 这里Pm相当于客户端,接受session的服务端在PackageInstallerSession中,这里利用AIDL来完成传输,其调用过程为:

    • Pm.doCommitSession

    • PackageInstaller.Session.commit

    • IPackageInstallerSession.commit

    • PackageInstallerSession.commit

    • Handler.Callback.handleMessage

    • PackageInstallerSession.commitLock

    • PMS.installStage



以上关于session传递过程暂不分析,下面我们来详细看下installStage方法。



  • 5 installStage方法主要功能就是构造InstallParam对象,并发送INIT_COPY。

void installStage(String packageName, File stagedDir, String stagedCid,
IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
String installerPackageName, int installerUid, UserHandle user,
Certificate[][] certificates) {
if (DEBUG_EPHEMERAL) {
if ((sessionParams.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
Slog.d(TAG, "Ephemeral install of " + packageName);
}
}
final VerificationInfo verificatiOnInfo= new VerificationInfo(
sessionParams.originatingUri, sessionParams.referrerUri,
sessionParams.originatingUid, installerUid);
final OriginInfo origin;
if (stagedDir != null) {
origin = OriginInfo.fromStagedFile(stagedDir);
} else {
origin = OriginInfo.fromStagedContainer(stagedCid);
}
final Message msg = mHandler.obtainMessage(INIT_COPY);
final int installReason = fixUpInstallReason(installerPackageName, installerUid,
sessionParams.installReason);
final InstallParams params = new InstallParams(origin, null, observer,
sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
verificationInfo, user, sessionParams.abiOverride,
sessionParams.grantedRuntimePermissions, certificates, installReason);
params.setTraceMethod("installStage").setTraceCOOKIE(System.identityHashCode(params));
msg.obj = params;
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "installStage",
System.identityHashCode(msg.obj));
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
System.identityHashCode(msg.obj));
mHandler.sendMessage(msg);
}


  • 6 发送完Handler消息后就与下载安装过程相同了。


2.4 本地安装

本地安装参与对象包括PackageInstaller应用,PMS两部分。下面我们就来分析下PackageInstaller是如何调用到PMS中的。函数调用时序图如图5所示。
【图5 本地安装前提调用时序图】



  • 1 点击文件管理器中的apk文件时,会调用到FolderFragment类的openFile方法,然后调用startActivitySafety方法启动PackageInstallerActivity。

private void openFile(File f) {
final Uri fileUri = Uri.fromFile(f);
final Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_TITLE, f.getName());
intent.putExtra(EXTRA_ALL_VIDEO_FOLDER, true);
Uri cOntentUri= null;
String type = getMIMEType(f);
//......
if (contentUri != null) {
intent.setDataAndType(contentUri, type);
} else {
intent.setDataAndType(fileUri, type);
}
try {
startActivitySafely(intent);
}
//......
}


  • 2 如下为PackageInstallerActivity.onCreate方法源码,其主要过程初始化各个服务的成员变量如PMS,校验session,并加载UI界面,然用户确定是否安装。

//[PackageInstallerActivity.java]
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
if (icicle != null) {
mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY);
}
//初始化各个关键参数
mPm = getPackageManager();
mIpm = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
mInstaller = mPm.getPackageInstaller();
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
final Intent intent = getIntent();
mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
PackageInstaller.SessionParams.UID_UNKNOWN);
mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
? getPackageNameForUid(mOriginatingUid) : null;
final Uri packageUri;
//校验session
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
final int sessiOnId= intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
finish();
return;
}
mSessiOnId= sessionId;
packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
mOriginatingURI = null;
mReferrerURI = null;
} else {
mSessiOnId= -1;
packageUri = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
}
// if there\'s nothing to do, quietly slip into the ether
if (packageUri == null) {
Log.w(TAG, "Unspecified source");
setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
finish();
return;
}
if (DeviceUtils.isWear(this)) {
showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
return;
}
boolean wasSetUp = processPackageUri(packageUri);
if (!wasSetUp) {
return;
}
// 加载UI界面
bindUi(R.layout.install_confirm, false);
checkIfAllowedAndInitiateInstall();
}


  • 3 当用户点击安装按钮时,响应函数为PackageInstallerActivity.onClick方法,

//[PackageInstallerActivity.java]
public void onClick(View v) {
if (v == mOk) {
if (mOk.isEnabled()) {
if (mOkCanInstall || mScrollView == null) {
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
finish();
} else {
startInstall();
}
} else {
mScrollView.pageScroll(View.FOCUS_DOWN);
}
}
} else if (v == mCancel) {
// Cancel and finish
setResult(RESULT_CANCELED);
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
finish();
}
}


  • 4 之后调用 PackageInstallerActivity.startInstall方法,构造Intent,然后启动InstallInstalling,并销毁PackageInstallerActivity。

private void startInstall() {
// Start subactivity to actually install the application
Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallInstalling.class);
String installerPackageName = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (mOriginatingURI != null) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
}
if (mReferrerURI != null) {
newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
}
if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
}
if (installerPackageName != null) {
newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
installerPackageName);
}
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
}
if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
startActivity(newIntent);
finish();
}


  • 5 之后启动InstallInstalling,因为Activity中的默认成员方法的执行顺序是onCreate->onStart->onResume...其中onCreate的方法中主要过程包括:

    • 1 获取待安装应用信息

    • 2 根据应用安装与否决定如何调用方法

    • 3 如果已存在,则直接调用PackageManager.installExistingPackage

    • 4 如果不存在则构造session

    • 5 之后则为安装事件广播添加一个监测



@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.install_installing);
// 获取待安装应用信息
ApplicationInfo appInfo = getIntent()
.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
mPackageURI = getIntent().getData();
// 如果应用已存在,则使用这条路径安装
if ("package".equals(mPackageURI.getScheme())) {
try {
getPackageManager().installExistingPackage(appInfo.packageName);
launchSuccess();
} catch (PackageManager.NameNotFoundException e) {
launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
} else { //否则使用session提交安装应用
final File sourceFile = new File(mPackageURI.getPath());
PackageUtil.initSnippetForNewApp(this, PackageUtil.getAppSnippet(this, appInfo,
sourceFile), R.id.app_snippet);
// 如果session已存在,则获取sessionId等数据
if (savedInstanceState != null) {
mSessiOnId= savedInstanceState.getInt(SESSION_ID);
mInstallId = savedInstanceState.getInt(INSTALL_ID);
// Reregister for result; might instantly call back if result was delivered while
// activity was destroyed
try {
InstallEventReceiver.addObserver(this, mInstallId,
this::launchFinishBasedOnResult);
} catch (EventResultPersister.OutOfIdsException e) {
// Does not happen
}
} else { // 否则创建session
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
// ......
try {
mInstallId = InstallEventReceiver
.addObserver(this, EventResultPersister.GENERATE_NEW_ID,
this::launchFinishBasedOnResult);
} catch (EventResultPersister.OutOfIdsException e) {
launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
//创建session
try {
mSessiOnId= getPackageManager().getPackageInstaller().createSession(params);
} catch (IOException e) {
launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
}
//......
mSessiOnCallback= new InstallSessionCallback();
}
}


  • 6 InstallInstalling.onStart中注册回调函数,然后onResume中执行AsyncTask。

@Override
protected void onResume() {
super.onResume();
// This is the first onResume in a single life of the activity
if (mInstallingTask == null) {
PackageInstaller installer = getPackageManager().getPackageInstaller();
PackageInstaller.SessionInfo sessiOnInfo= installer.getSessionInfo(mSessionId);
//如果session非空,则执行AsyncTask
if (sessionInfo != null && !sessionInfo.isActive()) {
mInstallingTask = new InstallingAsyncTask();
mInstallingTask.execute();
} else {
// we will receive a broadcast when the install is finished
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
}
}
}


  • 7 AsyncTask是Android提供的一种轻量级的异步类,执行过程可以表示为5个阶段。

    • 1 准备执行,onPreExecute()

    • 2 正在后台执行,doInBackgroud()

    • 3 进度更新,onProcessUpdate()

    • 4 完成后台任务,onPostExecute()

    • 5 取消任务,onCacelled()



此处重写了方法onPostExecute方法,源码如下。

@Override
protected void onPostExecute(PackageInstaller.Session session) {
if (session != null) {
Intent broadcastIntent = new Intent(BROADCAST_ACTION);
broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntent.setPackage(
getPackageManager().getPermissionControllerPackageName());
broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
InstallInstalling.this,
mInstallId,
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
//提交session
session.commit(pendingIntent.getIntentSender());
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
} else {
getPackageManager().getPackageInstaller().abandonSession(mSessionId);
if (!isCancelled()) {
launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
}
}
}


  • 8 session对象传输顺序为:

    • 1 PackageInstaller.Session.commit

    • 2 IPackageInstallerSession.commit

    • 3 PackageInstallerSession.commit

    • 4 Handler.Callback.handleMessage

    • 5 PackageInstallerSession.commitLock

    • 6 PMS.installStage
      这里是不是似曾相识,这一步跟Adb安装的第4步几乎相同,之后就调用installStage方法完成安装。




#3 总结

安装应用的场景就是上述所示的PMS构造函数安装、adb安装、网络下载安装、本地安装。其最终的入口为PMS.prepareAppDataLeafLIF,然后调用Installer类完成安装,这里涉及到System_server到Installd守护进程的转移。



推荐阅读
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 本文讲述了如何通过代码在Android中更改Recycler视图项的背景颜色。通过在onBindViewHolder方法中设置条件判断,可以实现根据条件改变背景颜色的效果。同时,还介绍了如何修改底部边框颜色以及提供了RecyclerView Fragment layout.xml和项目布局文件的示例代码。 ... [详细]
  • Android系统移植与调试之如何修改Android设备状态条上音量加减键在横竖屏切换的时候的显示于隐藏
    本文介绍了如何修改Android设备状态条上音量加减键在横竖屏切换时的显示与隐藏。通过修改系统文件system_bar.xml实现了该功能,并分享了解决思路和经验。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • 本文介绍了使用cacti监控mssql 2005运行资源情况的操作步骤,包括安装必要的工具和驱动,测试mssql的连接,配置监控脚本等。通过php连接mssql来获取SQL 2005性能计算器的值,实现对mssql的监控。详细的操作步骤和代码请参考附件。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • 2021年最详细的Android屏幕适配方案汇总
    1Android屏幕适配的度量单位和相关概念建议在阅读本文章之前,可以先阅读快乐李同学写的文章《Android屏幕适配的度量单位和相关概念》,这篇文章 ... [详细]
  • 这两天用到了ListView,写下遇到的一些问题。首先是ListView本身与子控件的焦点问题,比如我这里子控件用到了Button,在需要ListView中的根布局属性上加上下面的这一个属性:and ... [详细]
  • 四、连接屏幕流各位读者好!我们已经到了应用开发的一个重要阶段——连接屏幕。如您所知,我们在上一章 ... [详细]
  • 安卓中的刮刮卡视图,示例原文:https://www.gee ... [详细]
  • 竟然可以检查微信是否被删了好友?(Android Accessibility 了解一下)
    前言最近在研究Android辅助服务,实现了这个小工具,也算是对最近学习的一个总结。原理通过Android无障碍辅助功能实现模拟点击 ... [详细]
  • python3 nmap函数简介及使用方法
    本文介绍了python3 nmap函数的简介及使用方法,python-nmap是一个使用nmap进行端口扫描的python库,它可以生成nmap扫描报告,并帮助系统管理员进行自动化扫描任务和生成报告。同时,它也支持nmap脚本输出。文章详细介绍了python-nmap的几个py文件的功能和用途,包括__init__.py、nmap.py和test.py。__init__.py主要导入基本信息,nmap.py用于调用nmap的功能进行扫描,test.py用于测试是否可以利用nmap的扫描功能。 ... [详细]
author-avatar
马岱五号_668
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有