实战-Android 预置第三方应用

需求:将第三方应用预置到系统,用户可以卸载,恢复出厂设置后,应用可以恢复

实现方案
将第三方应用预置到一个目录,比如/vendor/preinstall、系统第一次起来时,将这个目录下的所有apk文件拷贝到data/app下或者通过pm脚本安装目录下的所有apk。

将第三方应用预置到系统目录

有两种方式实现

1.通过编写Android.mk 将apk 文件拷贝到指定系统编译目录
2.直接将目录下的所有apk 文件拷贝到系统编译目录

方式1:属于常规操作,缺点是每新增一个第三方apk文件,都需要重新修改Android.mk文件

LOCAL_PATH := $(call my-dir)
#########################
include $(CLEAR_VARS)
LOCAL_MODULE := preinstallApk
LOCAL_MODULE_TAGS := optional
LOCAL_DEX_PREOPT :=false
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_MODULE_PATH := $(TARGET_OUT)/vendor/preinstall
LOCAL_MODULE_CLASS := APPS
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
include $(BUILD_PREBUILT) 

方式2:这种方式简单快捷,后续新增apk 也很方便
新增一个目录preintall/prethirdapk、并将apk文件全部放置其中、然后加入编译选项

PRODUCT_COPY_FILES += \
    $(call find-copy-subdir-files,*,device/amlogic/$(PRODUCT_DIR)/preinstall/prethirdapk/,/$(TARGET_COPY_OUT_VENDOR)/preinstall) \

注意:直接拷贝apk文件,系统编译时会报错

Prebuilt apk found in PRODUCT_COPY_FILES: preinstallApk, use BUILD_PREBUILT instead!

vim build/make/core/Makefile +28

@@ -28,7 +28,6 @@ product_copy_files_ignored :=
 $(foreach cf,$(unique_product_copy_files_pairs), \
     $(eval _src := $(call word-colon,1,$(cf))) \
     $(eval _dest := $(call word-colon,2,$(cf))) \
-    $(call check-product-copy-files,$(cf),$(_dest)) \

屏蔽check-product-copy-files即可

第三方应用预装到系统
1.系统第一次起来时,将这个目录下的所有apk文件拷贝到data/app下,系统自动解析
2.系统第一次起来时,通过pm脚本安装目录下的所有apk

两种方式大同小异,略有点不同


1.将目录下的apk 直接拷贝到/data/apk,Android 9.0会报错

"Application package " + pkg.packageName
                                + " not found; ignoring."

修改如下(解析data app时,去掉SCAN_REQUIRE_KNOWN 标记):

--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2853,7 +2853,7 @@ public class PackageManagerService extends IPackageManager.Stub
             if (!mOnlyCore) {
                 EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                         SystemClock.uptimeMillis());
-                scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
+                scanDirTracedLI(sAppInstallDir, 0, scanFlags, 0);

2.上面的修改略微觉得有点欠妥,修改的系统代码,破坏了原有逻辑,推荐第二种办法:

增加pm preinstall 命令
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -26,6 +26,7 @@ import android.accounts.IAccountManager;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.Application;
+import android.app.PackageInstallObserver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IIntentReceiver;
@@ -156,6 +157,8 @@ class PackageManagerShellCommand extends ShellCommand {
                     return runQueryIntentReceivers();
                 case "install":
                     return runInstall();
+                case "preinstall":
+                    return preInstall();
                 case "install-abandon":
                 case "install-destroy":
                     return runInstallAbandon();
@@ -898,11 +901,60 @@ class PackageManagerShellCommand extends ShellCommand {
         return 0;
     }

+    private int preInstall()  throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        String path = getNextArg();
+        pw.println("preInstall path: " + path);
+        if (path == null) {
+            pw.println("Error: no package specified");
+            return 1;
+        }
+
+        File[] files = new File(path).listFiles();
+        for(File apkFilePath : files) {
+            final String inPath = apkFilePath.getPath();
+            SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+            InstallParams params = new InstallParams();
+            params.sessionParams = sessionParams;
+            pw.println("preInstall pkg: " + inPath);
+            setParamsSize(params, inPath);
+            final int sessionId = doCreateSession(params.sessionParams,
+                    params.installerPackageName, params.userId);
+            boolean abandonSession = true;
+            try {
+                if (inPath == null && params.sessionParams.sizeBytes == -1) {
+                    pw.println("Error: must either specify a package size or an APK file : "+inPath);
+                    continue;
+                }
+                if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
+                        false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+                    pw.println("doWriteSplit Error :"+inPath);
+                    continue;
+                }
+                if (doCommitSession(sessionId, false /*logSuccess*/)
+                        != PackageInstaller.STATUS_SUCCESS) {
+                    pw.println("doCommitSession Error :"+inPath);
+                    continue;
+                }
+                abandonSession = false;
+                pw.println("preInstall : " + inPath+ " Success!");
+            } finally {
+                if (abandonSession) {
+                    try {
+                        doAbandonSession(sessionId, false /*logSuccess*/);
+                    } catch (Exception ignore) {
+                    }
+                }
+            }
+        }
+        pw.println("preInstall path: " + path + " ok");
+        return 0;
+    }
增加启动脚本,执行pm preinstall

init.amlogic.board.rc

on property:sys.boot_completed=1
    start preinstall
    
service preinstall /vendor/bin/preinstall.sh
    class main
    user root
    group root system
    disabled
    oneshot    

/vendor/bin/preinstall.sh

#!/vendor/bin/sh

if [ ! -e /data/funsys.notfirstrun ]; then
    echo "do preinstall job"
    pm preinstall /vendor/preinstall
    #通过写入文件判断是否第一次开机,恢复出厂会把该文件删掉、用于恢复安装应用
    #也可以通过它判断脚本有没有执行
    touch /data/funsys.notfirstrun 
    echo "preinstall ok"
fi

将preinstall.sh 添加到编译选项 :
/device.mk

PRODUCT_COPY_FILES += device/amlogic/$(PRODUCT_DIR)/preinstall/script/preinstall.sh:$(TARGET_COPY_OUT_VENDOR)/bin/preinstall.sh

编译到系统中后,将镜像重新刷到机器上,启动系统。

系统起来后,通过dmsg 查看系统消息,查看脚本是否有执行:
发现有selinux相关东西,导致脚本没有执行:

[  197.528960] type=1400 audit(1639017983.176:728): avc: denied { write } for pid=5670 comm="preinstall.sh" name="/" dev="mmcblk0p21" ino=2 scontext=u:r:install_recovery:s0 tcontext=u:object_r:system_data_file:s0 tclass=dir permissive=1
[  197.529498] type=1400 audit(1639017983.176:728): avc: denied { write } for pid=5670 comm="preinstall.sh" name="/" dev="mmcblk0p21" ino=2 scontext=u:r:install_recovery:s0 tcontext=u:object_r:system_data_file:s0 tclass=dir permissive=1
[  197.529543] type=1400 audit(1639017983.176:729): avc: denied { add_name } for pid=5670 comm="preinstall.sh" name="hello.txt" scontext=u:r:install_recovery:s0 tcontext=u:object_r:system_data_file:s0 tclass=dir permissive=1
[  197.529678] type=1400 audit(1639017983.176:729): avc: denied { add_name } for pid=5670 comm="preinstall.sh" name="hello.txt" scontext=u:r:install_recovery:s0 tcontext=u:object_r:system_data_file:s0 tclass=dir permissive=1
[  197.529716] type=1400 audit(1639017983.176:730): avc: denied { create } for pid=5670 comm="preinstall.sh" name="hello.txt" scontext=u:r:install_recovery:s0 tcontext=u:object_r:system_data_file:s0 tclass=file permissive=1
[  197.529843] type=1400 audit(1639017983.176:730): avc: denied { create } for pid=5670 comm="preinstall.sh" name="hello.txt" scontext=u:r:install_recovery:s0 tcontext=u:object_r:system_data_file:s0 tclass=file permissive=1
[  197.529947] type=1400 audit(1639017983.180:731): avc: denied { write open } for pid=5670 comm="preinstall.sh" path="/data/hello.txt" dev="mmcblk0p21" ino=14 scontext=u:r:install_recovery:s0 tcontext=u:object_r:system_data_file:s0 tclass=file permissive=1
[  197.594638] audit: audit_lost=563 audit_rate_limit=5 audit_backlog_limit=64
[  197.594644] audit: rate limit exceeded
[  203.093582] type=1400 audit(1639017985.332:753): avc: denied { read } for pid=5691 comm="touch" path="/data/funsys.notfirstrun" dev="mmcblk0p21" ino=15 scontext=u:r:install_recovery:s0 tcontext=u:object_r:system_data_file:s0 tclass=file permissive=1

将以上放到 文件se.txt, 在Linux 终端执行
audit2allow -i se.txt

#============= install_recovery ==============
allow install_recovery system_data_file:dir { write add_name };
allow install_recovery system_data_file:file { write read create open };

selinux 相关就不再此展开了.后续再慢慢介绍。

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值