预置APK文件到sdcard下

为了满足客户需求,将APK文件预置到Android设备的/data/media/0/System/APK目录下,以便在系统启动后由预装应用决定是否安装。在恢复出厂设置后,仍需保持预置文件。通过在Android.mk文件中利用PRODUCT_COPY_FILES,将文件复制到系统和SD卡指定位置。在恢复出厂设置过程中,当/data和/cache分区清理后,通过复制/system/pre-install/目录下的文件到/data/media/0/System/APK以保留预置APK。修改bootable/recovery/recovery.cpp和Android.mk,以及installd.c中的相关代码来实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

客户需求,要预置APK文件到data/media/0/System/APK文件下,也就是说客户要求预置资源文件到sdcard,系统第一次启动的时候,加载完谷歌向导之后,会启动客户的另外一个应用来选择是否安装预置到SD卡的应用文件,此应用可在文件浏览器中找到并且可安装卸载,恢复出厂设置之后,预置的文件APK文件保留。

如果只是预置可卸载的文件,恢复出厂设置不可恢复,很简单,只需要在Android.mk文件中加上:LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)就可以了。

不过这次需求是预置APK文件(非安装好的),在第一次启动之后加载完谷歌向导后,会启动客户另外一个已经预安装好的应用来选择是否要安装此应用,并且此应用可卸载,恢复出厂设置之后还要在SD卡中,预置文件到SD卡上比较简单,主要问题是恢复出厂设置还要在预置的APK文件还要在SD卡中。

下面直接给出解决方案:

1.预置到sdcard: 在项目的配置文件中,利用 PRODUCT_COPY_FILES 分别拷贝文件到system目录下与/data/media/0/System/APK,如下:

PRODUCT_COPY_FILES += \
 tyconfig/tyOverSeaCommon/packages/apps/3rd_Oversea/archos/preset_apps/100Games_Archos_50_Neon_100_AB46.apk:data/media/System/APK/100Games_Archos_50_Neon_100_AB46.apk \
tyconfig/tyOverSeaCommon/packages/apps/3rd_Oversea/archos/preset_apps/100Games_Archos_50_Neon_100_AB46.apk:system/pre-install/100Games_Archos_50_Neon_100_AB46.apk 
这样保证系统第一次启动的时候可以满足需求,之所以会要在system/pre-install/目录下拷贝一份,是为了备份一下这个文件,在恢复出厂设置的时候要把/system/pre-install目录下的文件拷贝到/data/media/文件下。

2.恢复出厂设置,文件可以恢复。

在恢复出厂设置这一块代码下手,恢复出厂设置会把/data分区跟/cache全部清除,data分区清除意味着预置的APK文件也将会被清除掉。所以,在/data,以及/cache清除完毕后,可以做一个操作,复制/system/pre-install/目录下的文件到/data/media/0/System/APK目录,这样就能够保证在恢复出厂设置之后,预置的APK文件还在sdcard中。

bootable/recovery/recovery.cpp

//拷贝方法,需要把 build/libs/host/include/host/CopyFile.h 与 /build/libs/host/CopyFile.c文件拷贝至recovery.cpp同级目录
#include "CopyFile.h"
static int
apply_pre_installed_apps() {
	if (ensure_path_mounted("/data")) {
		ui_print("copy_app_data: mount /data failed!");
		return -1;
	}
	if (ensure_path_mounted("/system/")) {
		ui_print("copy_app_data: mount /system failed!");
		return -1;
	}

        //保证对应目录存在
        if(mkdir("/data/media/",00777) != 0)
        {  
            LOGE("create /media failed\n"); 
        }
        if(mkdir("/data/media/System/",00777) != 0)
        {  
            LOGE("create /System failed\n"); 
        }
        if(mkdir("/data/media/System/APK/",00777) != 0)
        {  
            LOGE("create /APK failed\n"); 
        }

	//ui_print("copy property app \n");
        //开始拷贝
	copyFile("/system/pre-install/", "/data/media/System/APK/", COPY_RECURSIVE);
	//copyFile("/system/etc/permissions", "/data/mojl/", COPY_RECURSIVE);
      chmod("/data/",0777);
        chmod("/data/media/",0777);
        chmod("/data/media/System/",0777);
        chmod("/data/media/System/APK/",0777);

	 if(ensure_path_unmounted("/data") != 0 ){
       ui->Print("\n-- system unmounted fail.\n");
    }
    if(ensure_path_unmounted("/system/") != 0){
       ui->Print("\n-- data unmounted fail.\n");
    }
	sync();  // For good measure.
	return 0;

}


static void
wipe_data(int confirm, Device* device) {
    if (confirm) {
        static const char** title_headers = NULL;

        if (title_headers == NULL) {
            const char* headers[] = { "Confirm wipe of all user data?",
                                      "  THIS CAN NOT BE UNDONE.",
                                      "",
                                      NULL };
            title_headers = prepend_title((const char**)headers);
        }

        const char* items[] = { " No",
                                " No",
                                " No",
                                " No",
                                " No",
                                " No",
                                " No",
                                " Yes -- delete all user data",   // [7]
                                " No",
                                " No",
                                " No",
                                NULL };

        int chosen_item = get_menu_selection(title_headers, items, 1, 0, device);
        if (chosen_item != 7) {
            return;
        }
    }

    ui->Print("\n-- Wiping data...\n");
    device->WipeData();
    erase_volume("/data");
    erase_volume("/cache");
	/* 在清除完data与cache之后开始拷贝 */
    apply_pre_installed_apps();
    erase_persistent_partition();
    ui->Print("Data wipe complete.\n");
}


int
main(int argc, char **argv) {
    time_t start = time(NULL);

    redirect_stdio(TEMPORARY_LOG_FILE);
……
    int status = INSTALL_SUCCESS;

    if (update_package != NULL) {
        ……
    } else if (wipe_data) {
        if (device->WipeData()) status = INSTALL_ERROR;
        if (erase_volume("/data")) status = INSTALL_ERROR;
        if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
        if (erase_persistent_partition() == -1 ) status = INSTALL_ERROR;
ui->Print("status  == "+status+" . \n");
        if (status != INSTALL_SUCCESS) ui->Print("Data wipe failed.\n");
		/* 在清除失败后也要做拷贝动作 */
	    apply_pre_installed_apps();
    } else if (wipe_cache) {
        if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
        if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n");
    } else if (!just_exit) {
        status = INSTALL_NONE;  // No command specified
        ui->SetBackground(RecoveryUI::NO_COMMAND);
    }
……


还需要在recovery目录下对应的Android.mk文件中增加对CopyFile.c文件的引用,LOCAL_SRC_FILES += CopyFile.c


按上述操作,问题基本上算是解决了。

还有一个问题,通过adb shell进入到data/media/目录下,可以看到有一个0目录,这里面就是手机内存里面的文件,其实,系统第一次启动时installd进程会自动将/data/media/里面的东西移动到/data/media/0/目录,代码如下

/framework/native/cmds/installd/Installd.c

int initialize_directories() {
    int res = -1;

    // Read current filesystem layout version to handle upgrade paths
    char version_path[PATH_MAX];
    snprintf(version_path, PATH_MAX, "%s.layout_version", android_data_dir.path);

    int oldVersion;
    if (fs_read_atomic_int(version_path, &oldVersion) == -1) {
        oldVersion = 0;
    }
    int version = oldVersion;

    // /data/user
    char *user_data_dir = build_string2(android_data_dir.path, SECONDARY_USER_PREFIX);
    // /data/data
    char *legacy_data_dir = build_string2(android_data_dir.path, PRIMARY_USER_PREFIX);
    // /data/user/0
    char *primary_data_dir = build_string3(android_data_dir.path, SECONDARY_USER_PREFIX, "0");
    if (!user_data_dir || !legacy_data_dir || !primary_data_dir) {
        goto fail;
    }

    // Make the /data/user directory if necessary
    if (access(user_data_dir, R_OK) < 0) {
        if (mkdir(user_data_dir, 0711) < 0) {
            goto fail;
        }
        if (chown(user_data_dir, AID_SYSTEM, AID_SYSTEM) < 0) {
            goto fail;
        }
        if (chmod(user_data_dir, 0711) < 0) {
            goto fail;
        }
    }
    // Make the /data/user/0 symlink to /data/data if necessary
    if (access(primary_data_dir, R_OK) < 0) {
        if (symlink(legacy_data_dir, primary_data_dir)) {
            goto fail;
        }
    }

    if (version == 0) {
        // Introducing multi-user, so migrate /data/media contents into /data/media/0
        ALOGD("Upgrading /data/media for multi-user");

        // Ensure /data/media
        if (fs_prepare_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
            goto fail;
        }

        // /data/media.tmp
        char media_tmp_dir[PATH_MAX];
        snprintf(media_tmp_dir, PATH_MAX, "%smedia.tmp", android_data_dir.path);

        // Only copy when upgrade not already in progress
        if (access(media_tmp_dir, F_OK) == -1) {
            if (rename(android_media_dir.path, media_tmp_dir) == -1) {//将/data/media/目录切换成/data/media.tmp
                ALOGE("Failed to move legacy media path: %s", strerror(errno));
                goto fail;
            }
        }

        // Create /data/media again
        if (fs_prepare_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {//重新创建/data/meida目录
            goto fail;
        }

        if (selinux_android_restorecon(android_media_dir.path, 0)) {
            goto fail;
        }

        // /data/media/0
        char owner_media_dir[PATH_MAX];
        snprintf(owner_media_dir, PATH_MAX, "%s0", android_media_dir.path);

        // Move any owner data into place
        if (access(media_tmp_dir, F_OK) == 0) {
            if (rename(media_tmp_dir, owner_media_dir) == -1) {//将/data/media.tmp/切换成/data/meida/0目录
                ALOGE("Failed to move owner media path: %s", strerror(errno));
                goto fail;
            }
        }

    ……

    // Persist layout version if changed
    if (version != oldVersion) {
        if (fs_write_atomic_int(version_path, version) == -1) {//将version写入到.layout_version中,做为renam成功标识。
            ALOGE("Failed to save version to %s: %s", version_path, strerror(errno));
            goto fail;
        }
    }

    // Success!
    res = 0;
}

通过上面的方法,确实可以实现需求。但是试用过程中,会有几率出现问题的,预置的文件有可能会被/data/media/0/0目录中,相当于多了一级0目录,这样就会导致谷歌向导结束后,客户应用扫描不到对应的APK文件导致无法安装。翻阅大量资料,有人认为是因为上面代码的稳定性依赖于函数fs_write_atomic_int()的原子操作性,并给出解决方案要在此函数调用后加入sync(),经过大量试验,此方法并没有生效。


有一个方法就是去判断,如果目录中多了一级0目录,那就去把这个目录删除,代码如下

/framework/native/cmds/installd/Installd.c

int initialize_directories() {
    int res = -1;

    // Read current filesystem layout version to handle upgrade paths
    char version_path[PATH_MAX];
    snprintf(version_path, PATH_MAX, "%s.layout_version", android_data_dir.path);

    ……

        // /data/media/0
        char owner_media_dir[PATH_MAX];
        snprintf(owner_media_dir, PATH_MAX, "%s0", android_media_dir.path);

        // Move any owner data into place
        if (access(media_tmp_dir, F_OK) == 0) {
            if (rename(media_tmp_dir, owner_media_dir) == -1) {
                ALOGE("Failed to move owner media path: %s", strerror(errno));
                goto fail;
            }
        }

/* 判断如果/data/meida/0/0目录存在*/
	if (access("/data/media/0/0", F_OK) == 0) {
	    ALOGE("/data/media/0/0 is access now :");
	    int result = rename("/data/media/0/0/System", "/data/media/0/System");//rename目录
	    ALOGE("result ==== %i ",result);
	    if (result == -1) {//rename失败
		ALOGE("Failed to raname /data/media/0/0 : %s", strerror(errno));
            }else{
        	ALOGE("success to raname /data/media/0/0");
		if(rmdir("/data/media/0/0") == -1){//rename成功后需要删除/data/media/0/0目录
		     ALOGE("Failed to rmdir /data/media/0/0:  %s", strerror(errno));		
		}
	    }
	}else{
		ALOGE("/data/media/0/0 is not access .");
	}
/* yueshuai 20150916 modify for Pre-install end*/

        // Ensure media directories for any existing users
        DIR *dir;
        struct dirent *dirent;
        char user_media_dir[PATH_MAX];
……
经过上述操作,所有问题都已经解决。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值