android 系统中使一个系统应用(system/app下的应用)开机过程不再安装,但是依然存在在system/app下,等到某种条件下需要安装时,任然可以安装。
需求:
在android 系统中开发的所有的应用,不全都是给用户使用的,有的应用在前期,例如在卖场中展示使用,但是到用户手上后,又不需要它了。
这里有两种方案,一是将应用安装到data 底下。出厂后卸载。二是依然将应用以系统应用的方式,打包在system/app下,出厂后,使其不再安装,而到想使用的时候,再通过某种开关打开一个flag,然后重启,这个应用就可以再次被安装了。这里主要来说第二种方案.
ps:当然还有一种方案,就是可以动态的通过某个条件去隐藏disable 这个apk。具体可以参考我这边文章
具体做法
这里我们介绍下在开机过程中动态的去选择安装或者不安装它。
应用安装的过程是在PMS 中来做的。熟悉PMS的应该知道,android 系统在开机的时候会走到PMS这一流程中来,然后会在PackageManagerService.java 中去遍历安装系统预制的apk。当然这也就是为什么我们adb push 一个apk到system下,然后重启后,这个apk会在开机后自动安装好的原因了。
frameworks / base/services/core/java/com/android/server/pm/PackageManagerService.java
private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
final File[] files = scanDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
Log.d(TAG, "No files in app dir " + scanDir);
return;
}
if (DEBUG_PACKAGE_SCANNING) {
Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags
+ " flags=0x" + Integer.toHexString(parseFlags));
}
try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
mParallelPackageParserCallback)) {
// Submit files for parsing in parallel
int fileCount = 0;
String isNetflix = Utils.getConfigDataIsSupportNetflix();
for (File file : files) {
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
if(file.toString().equals("/system/app/QQ")) { // 1
if(动态安装与否的某个条件) // 2
{
// Ignore install Netflix packages
continue;
}
}
if (!isPackage) {
// Ignore entries which are not packages
continue;
}
parallelPackageParser.submit(file, parseFlags);
fileCount++;
}
// Process results one by one
for (; fileCount > 0; fileCount--) {
ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
Throwable throwable = parseResult.throwable;
int errorCode = PackageManager.INSTALL_SUCCEEDED;
if (throwable == null) {
// TODO(toddke): move lower in the scan chain
// Static shared libraries have synthetic package names
if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {
renameStaticSharedLibraryPackage(parseResult.pkg);
}
try {
if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags,
currentTime, null);
}
} catch (PackageManagerException e) {
errorCode = e.error;
Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage());
}
} else if (throwable instanceof PackageParser.PackageParserException) {
PackageParser.PackageParserException e = (PackageParser.PackageParserException)
throwable;
errorCode = e.error;
Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage());
} else {
throw new IllegalStateException("Unexpected exception occurred while parsing "
+ parseResult.scanFile, throwable);
}
// Delete invalid userdata apps
if ((scanFlags & SCAN_AS_SYSTEM) == 0 &&
errorCode != PackageManager.INSTALL_SUCCEEDED) {
logCriticalInfo(Log.WARN,
"Deleting invalid package at " + parseResult.scanFile);
removeCodePathLI(parseResult.scanFile);
}
}
}
}
在以上的文件中,scanDirLI 方法主要是遍历系统中的apk的,我们可以在它遍历的过程中,把它过滤掉,这样这个apk就不安装了。在1处,我们以QQ.apk 为例。假如有这个apk,并且符合我们不安装的条件,如2处。我们就continue 掉它。
到此。这个apk在开机后就不会被安装了。