Android预置可卸载app,恢复出厂不能恢复

问题背景

在某些时候我们希望对于预置的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 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啦,这个就自己去实现吧

你可能感兴趣的:(Android,ROM)