问题背景
在某些时候我们希望对于预置的app可卸载,但是恢复出厂不能恢复。比如设备上的一些生产工具之类的软件,生产验证结束之后人工卸载,而在用户手里不能恢复出来。
预置能够减少生产流程中的安装环节。
实现方法
原生实现
有一种原生的实现方式是将app打到data分区,这样相当于在编译的时候就把app装上,最终打入的img在userdata.img里。下面是Android.mk的写法:
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
但是有些工程组织是默认不打userdata分区的,还有没有其他的实现方式呢?
另一种实现
其实想做到恢复出厂不能恢复,无非是要能记录到该app曾经被卸载过的状态,与app本身放在系统的哪个目录下无关。下面我讲一下实现的关键路径。
- 在cache目录下新建文件deleteApkFile.dat(文件名随意啦,但是在cache下面就行)
- 将app打入vendor/preinstall_gone 目录(这个取名也随意吧)
- 在PackageManagerService的构造函数中添加对于该目录的扫描,就是上一步你放该app的目录
// Collect ordinary system packages.
final File systemAppDir = new File(Environment.getRootDirectory(), "app");
scanDirTracedLI(systemAppDir, mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
// Collect all vendor packages.
File vendorAppDir = new File("/vendor/app");
try {
vendorAppDir = vendorAppDir.getCanonicalFile();
} catch (IOException e) {
// failed to look up canonical path, continue with original one
}
scanDirTracedLI(vendorAppDir, mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
// Collect all OEM packages.
final File oemAppDir = new File(Environment.getOemDirectory(), "app");
scanDirTracedLI(oemAppDir, mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
// Collect bundled app packages which can be uninstalled
scanDirTracedLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
...
//假设该app放在getPrebundledUninstallGoneDirectory这个目录下
scanDirTracedLI(Environment.getPrebundledUninstallGoneDirectory(),
mDefParseFlags | PackageParser.PARSE_IS_PREBUNDLED_DIR,
scanFlags,0);
- scanDirTracedLI最终使用的是scanDirLI方法,去里面改一下增加对于deleteApkFile.dat的判断即可
if (dir.getAbsolutePath().contains(VENDOR_BUNDLED_UNINSTALL_GONE_DIR)) {
if (!readDeleteFile(list)) {
Log.e(TAG,"read data failed");
return;
}
}
for (File file : files) {
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
if (!isPackage) {
// Ignore entries which are not packages
continue;
}
if (file.getAbsolutePath().contains(VENDOR_BUNDLED_UNINSTALL_GONE_DIR)) {
if (list != null && list.size() > 0) {
final boolean isdeleteApk = isDeleteApk(file,parseFlags,list);
if (isdeleteApk) {
// Ignore deleted bundled apps
continue;
}
}
}
try {
scanPackageTracedLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
scanFlags, currentTime, null);
if (isPrebundled) {
final PackageParser.Package pkg;
try {
pkg = new PackageParser().parsePackage(file, parseFlags);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
}
synchronized (mPackages) {
mSettings.markPrebundledPackageInstalledLPr(pkg.packageName);
}
}
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());
// Delete invalid userdata apps
if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
logCriticalInfo(Log.WARN, "Deleting invalid package at " + file);
removeCodePathLI(file);
}
}
}
下面给出读取记录删除app文件的方法示例
public static boolean readDeleteFile(ArrayList<String> list) {
File deleteApkFile = new File(DELETE_APK_FILE);
if (!deleteApkFile.exists()) {
Slog.w(TAG,"deliteApkFile not exist");
return true;
}
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(deleteApkFile));
String name = null;
while(null != (name = br.readLine())) {
list.add(name);
}
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
br = null;
}
}
}
- 最后就是用户删除app的时候去记录一下包名到该文件内就ok啦,这个就自己去实现吧