[RK3568][Android12.0]--- 系统自带预置第三方APK方法

简介: [RK3568][Android12.0]--- 系统自带预置第三方APK方法

Platform: RK3568

OS: Android 12.0

Kernel: 4.19

Rockchip默认提供了机制来预置第三方APK, 方法很简单:

1. 在device/rockchip/rk3568创建preinstall目录(如果要可卸载,那就创建preinstall_del目录)

2. 将你要预安装的APK放进此目录即可

preinstall 不可卸载

preinstall_del 可卸载,回复出厂可恢复

preinstall_del_forever 可卸载 恢复出厂不可恢复

下面看下实现原理过程:


device/rockchip/common/device.mk中有:

# Prebuild apps
$(call inherit-product, device/rockchip/common/modules/preinstall.mk)

device\rockchip\common\modules\preinstall.mk

# Include this makefile to support prebuild apps
ifneq ($(strip $(TARGET_PRODUCT)), )
    $(shell python device/rockchip/common/auto_generator.py $(TARGET_DEVICE_DIR) preinstall bundled_persist-app $(TARGET_ARCH))
    $(shell python device/rockchip/common/auto_generator.py $(TARGET_DEVICE_DIR) preinstall_del bundled_uninstall_back-app $(TARGET_ARCH))
    $(shell python device/rockchip/common/auto_generator.py $(TARGET_DEVICE_DIR) preinstall_del_forever bundled_uninstall_gone-app $(TARGET_ARCH))
    -include $(TARGET_DEVICE_DIR)/preinstall/preinstall.mk
    -include $(TARGET_DEVICE_DIR)/preinstall_del/preinstall.mk
    -include $(TARGET_DEVICE_DIR)/preinstall_del_forever/preinstall.mk
endif

auto_generator.py是个python脚本,用于生成Android.mk和preinstall.mk文件,

def main(argv):

   preinstall_dir = os.path.join(argv[1] + '/' + argv[2])

   if os.path.exists(preinstall_dir):

       #Use to define modules for install

       makefile_path = preinstall_dir + '/Android.mk'

       #Use to include modules

       include_path = preinstall_dir + '/preinstall.mk'

       if os.path.exists(makefile_path):

           os.remove(makefile_path)

       if os.path.exists(include_path):

           os.remove(include_path)

       makefile = file(makefile_path, 'w')

       includefile = file(include_path, 'w')

       makefile.write("LOCAL_PATH := $(my-dir)\n\n")

       for root, dirs, files in os.walk(preinstall_dir):

           for file_name in files:

               p = re.compile(r'\S*(?=.apk\b)')

               found = p.search(file_name)

               if found:

                   makefile.write(templet %(found.group(), argv[2]))

                   includefile.write('PRODUCT_PACKAGES += %s\n' %found.group())

       makefile.close()

       includefile.close()

 

Android.mk用于制定编译规则,如我在preinstall目录下放了个AVSourceTester.apk,那么生成的文件内容是

LOCAL_PATH := $(my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := AVSourceTester

LOCAL_MODULE_CLASS := APPS

LOCAL_MODULE_PATH := $(TARGET_OUT)/preinstall

LOCAL_SRC_FILES := $(LOCAL_MODULE)$(COMMON_ANDROID_PACKAGE_SUFFIX)

LOCAL_CERTIFICATE := PRESIGNED

LOCAL_DEX_PREOPT := false

LOCAL_MODULE_TAGS := optional

LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)

include $(BUILD_PREBUILT)

preinstall.mk内容如下:

PRODUCT_PACKAGES += AVSourceTester

编译系统之后,生成路径是

out/target/product/rk3568/system/preinstall/AVSourceTester/AVSourceTester.apk

系统开机之后会调用

frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java

preinstallThirdPartyAPK(packageParser,executorService,scanFlags);
private void preinstallThirdPartyAPK(PackageParser2 packageParser, ExecutorService executorService,int scanFlags){
        preinstallPrebundledpersist(packageParser,executorService,scanFlags);
        preinstallPrebundledUninstallBack(packageParser,executorService,scanFlags);
        preinstallPrebundledUninstallGone(packageParser,executorService,scanFlags);
    }
private void preinstallPrebundledpersist(PackageParser2 packageParser, ExecutorService executorService,int scanFlags){
        scanDirTracedLI(new File(BUNDLED_PERSIST_DIR),
                    mDefParseFlags | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR
                    | ParsingPackageUtils.PARSE_IS_PREINSTALL,
                    scanFlags | SCAN_AS_PREINSTALL
                    | SCAN_AS_SYSTEM,
                    0,packageParser, executorService);
    }
 
    private void preinstallPrebundledUninstallBack(PackageParser2 packageParser, ExecutorService executorService,int scanFlags){
        scanDirTracedLI(Environment.getPrebundledUninstallBackDirectory(),
                    mDefParseFlags | ParsingPackageUtils.PARSE_IS_PREBUNDLED_DIR,
                    scanFlags | SCAN_AS_PREBUNDLED_DIR,
                    0,packageParser, executorService);
    }
 
    private void preinstallPrebundledUninstallGone(PackageParser2 packageParser, ExecutorService executorService,int scanFlags){
        scanDirTracedLI(Environment.getPrebundledUninstallGoneDirectory(),
                    mDefParseFlags | ParsingPackageUtils.PARSE_IS_PREBUNDLED_DIR,
                    scanFlags | SCAN_AS_PREBUNDLED_DIR,
                    0,packageParser, executorService);
    }
 private static final String BUNDLED_PERSIST_DIR = "/odm/bundled_persist-app";
 
    private static final String BUNDLED_UNINSTALL_GONE_DIR = "/odm/bundled_uninstall_gone-app";
    private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags,
            long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
        try {
            scanDirLI(scanDir, parseFlags, scanFlags, currentTime, packageParser, executorService);
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }
 
    private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime,
            PackageParser2 packageParser, ExecutorService executorService) {
        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));
        }
 
        ArrayList<String> list = new ArrayList<String>();
        boolean isPrebundled = (parseFlags & ParsingPackageUtils.PARSE_IS_PREBUNDLED_DIR) != 0;
        if (isPrebundled) {
            synchronized (mPackages) {
                mSettings.readPrebundledPackagesLPr();
            }
        }
 
        if (scanDir.getAbsolutePath().contains(BUNDLED_UNINSTALL_GONE_DIR)) {
            if (!readDeleteFile(list)) {
                Log.e(TAG, "read data failed");
                return;
            }
        }
 
        ParallelPackageParser parallelPackageParser =
                new ParallelPackageParser(packageParser, executorService);
 
        // Submit files for parsing in parallel
        int fileCount = 0;
        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(BUNDLED_UNINSTALL_GONE_DIR)) {
                if (list != null && list.size() > 0) {
                    final boolean isdeleteApk = isDeleteApk(file,parseFlags,list);
                    if (isdeleteApk) {
                        // Ignore deleted bundled apps
                        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;
            String errorMsg = null;
 
            if (throwable == null) {
                // TODO(toddke): move lower in the scan chain
                // Static shared libraries have synthetic package names
                if (parseResult.parsedPackage.isStaticSharedLibrary()) {
                    renameStaticSharedLibraryPackage(parseResult.parsedPackage);
                }
                try {
                    addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
                            currentTime, null);
                    if (isPrebundled) {
                        final PackageParser.Package pkg;
                        try {
                            pkg = new PackageParser().parsePackage(parseResult.scanFile, parseFlags);
                        } catch (PackageParserException e) {
                            throw PackageManagerException.from(e);
                        }
                        synchronized (mPackages) {
                            mSettings.markPrebundledPackageInstalledLPr(pkg.packageName);
                        }
                    }
                } catch (PackageManagerException e) {
                    errorCode = e.error;
                    errorMsg = "Failed to scan " + parseResult.scanFile + ": " + e.getMessage();
                    Slog.w(TAG, errorMsg);
                }
            } else if (throwable instanceof PackageParserException) {
                PackageParserException e = (PackageParserException)
                        throwable;
                errorCode = e.error;
                errorMsg = "Failed to parse " + parseResult.scanFile + ": " + e.getMessage();
                Slog.w(TAG, errorMsg);
            } else {
                throw new IllegalStateException("Unexpected exception occurred while parsing "
                        + parseResult.scanFile, throwable);
            }
 
            if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0 && errorCode != INSTALL_SUCCEEDED) {
                mApexManager.reportErrorWithApkInApex(scanDir.getAbsolutePath(), errorMsg);
            }
 
            // 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);
            }
        }
 
        if (isPrebundled) {
            synchronized (mPackages) {
                mSettings.writePrebundledPackagesLPr();
            }
        }
    }

 

关键函数是copyPackagesToAppInstallDir(),它会把preinstall目录下的安装文件copy到安装目录。

这样安装就成功了。


安装preinstall和preinstall_del的区别在于后者在安装完之后会删除系统目录下的apk,因此要是做了恢复出厂设置或者卸载动作,那就不能恢复了。


删除函数是deletePreinstallDir(),通过init中的ctl命令实现。

   private void deletePreinstallDir(File dir) {

       String[] files = dir.list();

       if (files != null) {

           Slog.d(TAG, "Ready to cleanup preinstall");

           SystemProperties.set("ctl.start", "preinst_clr");

       }

   }

不过在source code中并没有找到preinst_clr这个service,可以在init.rc中自己添加下,

参考的是 Nu3001/device_rockchip_rksdk

service preinst_clr /system/bin/preinstall_cleanup.sh

   disabled

   oneshot

preinstall_cleanup.sh这个文件默认是有的,本质是直接删除apk。

#!/system/bin/sh

log -t PackageManager "Start to clean up /system/preinstall_del/"

mount -o rw,remount -t ext4 /system

rm system/preinstall_del/*.*

mount -o ro,remount -t ext4 /system

 


目录
相关文章
|
1天前
|
Java Linux Android开发
Android面试题之说说系统的启动流程(总结)
这篇文章概述了Android系统的启动流程,从Boot Rom到Zygote进程和SystemServer的启动。init进程作为用户级别的第一个进程,负责创建文件目录、初始化服务并启动Zygote。Zygote通过预加载资源和创建Socket服务,使用fork函数生成SystemServer进程。fork过程中,子进程继承父进程大部分信息但具有独立的进程ID。Zygote预加载资源以减少后续进程的启动时间,而SystemServer启动众多服务并最终开启Launcher应用。文中还讨论了为何从Zygote而非init或SystemServer fork新进程的原因。
7 2
|
7天前
|
前端开发 Java API
Android系统中读写和显示图片
Android系统中读写和显示图片
7 0
|
8天前
|
JavaScript Java 测试技术
基于ssm+vue.js+uniapp小程序的安卓的微博客系统附带文章和源代码部署视频讲解等
基于ssm+vue.js+uniapp小程序的安卓的微博客系统附带文章和源代码部署视频讲解等
20 2
|
11天前
|
Java 机器人 Linux
01. 【Android教程】系统背景及结构概述
01. 【Android教程】系统背景及结构概述
8 0
|
Java Android开发
Android NDK开发系列教程3:基本方法调用及传参(续)
终于建了一个自己个人小站:https://huangtianyu.gitee.io,以后优先更新小站博客,欢迎进站,O(∩_∩)O~~ 上一节主要讲解Java向native传参,下面主要讲解从native传相应的数据到java层。
1221 0
|
2天前
|
存储 Android开发 Kotlin
Kotlin开发安卓app,在使用 MediaPlayer 播放 res/raw 中的音乐时遇到突然中断的问题,而 onErrorListener 没有接收到任何报错
在使用 Android MediaPlayer 播放 res/raw 中的音乐时遇到中断问题,可能的原因包括资源问题、媒体文件编码格式、生命周期管理和设备资源配置。要排查问题,检查音频文件是否正确包含,格式编码是否支持,MediaPlayer 是否正确管理及释放,以及设备是否有足够存储和配置。通过设置 onErrorListener 日志和确保在 onDestroy 中释放资源来调试。如果文件过大,考虑使用 AssetManager。遵循这些步骤可帮助诊断并解决播放中断的问题。
|
2天前
|
Android开发 Kotlin
kotlin开发安卓应用 如何修改app安装后的名称
在 Android 应用中,要修改安装后的显示名称,需更新 AndroidManifest.xml 文件中 application 标签的 android:label 属性。可直接在该属性内设置新名称,或在 res/values/strings.xml 文件中修改 app_name 并在 manifest 中引用。推荐使用 strings.xml 方式,以便支持多语言和集中管理。
|
2天前
|
缓存 Android开发 Kotlin
【安卓app开发】kotlin Jetpack Compose框架 | 先用OKhttp下载远程音频文件再使用ExoPlayer播放
使用 Kotlin 的 Jetpack Compose 开发安卓应用时,可以结合 OkHttp 下载远程音频文件和 ExoPlayer 进行播放。在 `build.gradle` 添加相关依赖后,示例代码展示了如何下载音频并用 ExoPlayer 播放。代码包括添加依赖、下载文件、播放文件及简单的 Compose UI。注意,示例未包含完整错误处理和资源释放,实际应用需补充这些内容。
|
2天前
|
移动开发 安全 Android开发
探索安卓应用开发的新趋势:Kotlin与Jetpack Compose的融合
在移动开发领域,Android系统持续创新,为开发者提供更高效的工具和框架。近年来,Kotlin语言因其简洁性和现代化特性成为Android开发的首选语言。与此同时,Jetpack Compose作为一种新的UI工具集,正改变着Android界面的开发方式。本文将深入探讨Kotlin与Jetpack Compose的结合使用,分析它们如何共同推动Android应用开发进入一个更加高效、可维护和响应式的新时代。
|
2天前
|
存储 Android开发 Kotlin
开发安卓app OKhttp下载后使用MediaPlayer播放
在Android Jetpack Compose应用程序中,要使用OkHttp下载远程音频文件并在本地播放,你需要完成以下几个步骤: 1. **添加依赖**:确保`build.gradle`文件包含OkHttp和Jetpack Compose的相关依赖。 2. **下载逻辑**:创建一个`suspend`函数,使用OkHttp发起网络请求下载音频文件到本地。 3. **播放逻辑**:利用`MediaPlayer`管理音频播放状态。 4. **Compose UI**:构建用户界面,包含下载和播放音频的按钮。