• Android7.x 通过Recovery保留特定文件实现恢复出厂设置后保留系统语言设置


    Android7.x 通过Recovery保留特定文件实现恢复出厂设置后保留系统语言设置

    最近有一个需求,要求在恢复出厂设置之后不还原语言设置,由于我们知道语言设置可以在Properties System中保存,所以首先想到的就是能不能将某个property保存下来。

    恢复出厂设置不影响的文件持久化保存方法

    经过研究发现,有几种方法可以实现,可以做一个新的分区,并且在恢复出厂时不擦除。
    但后来发现Recovery在恢复出厂的时候会保留/cache/recovery目录中的部分文件,因为我们的需求只是保留个别配置,感觉这个方式更合适。
    因此参照这个过程照葫芦画瓢搞一个,我们先看关键代码:

    // bootable\recovery.cpp
     
    // 清理的入口函数为:
    static bool wipe_data(int should_confirm, Device* device);
    {
        //.....
        //往里追踪可以看到关键语句,实际上是跳进了erase_volume方法
        ui->Print("\n-- Wiping data...\n");
        bool success =
            device->PreWipeData() &&
            erase_volume("/data") &&
            (has_cache ? erase_volume("/cache") : true) &&
            device->PostWipeData();
        ui->Print("Data wipe %s.\n", success ? "complete" : "failed");
        return success;
    }
     
    //再来看这个操作
    bool erase_volume(const char* volume) {
        bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
        bool is_data = (strcmp(volume, DATA_ROOT) == 0);
        ui->SetBackground(RecoveryUI::ERASING);
        ui->SetProgressType(RecoveryUI::INDETERMINATE);
     
        saved_log_file* head = NULL;
     
        if (is_cache) {
            // If we're reformatting /cache, we load any past logs
            // (i.e. "/cache/recovery/last_*") and the current log
            // ("/cache/recovery/log") into memory, so we can restore them after
            // the reformat.
            // 可以看到这里其实已经写明了,会将既往logs先存到内存里,然后再格式化完成后在进行
            //  为此,要先确保待擦除的该cache分区已经挂载了
            ensure_path_mounted(volume);
            
            DIR* d;
            struct dirent* de;
            d = opendir(CACHE_LOG_DIR);
            if (d) {
                char path[PATH_MAX];
                strcpy(path, CACHE_LOG_DIR);
                strcat(path, "/");
                int path_len = strlen(path);
                while ((de = readdir(d)) != NULL) {
                    //然后扫描所有符合条件的文件名,并分配结构体所需的内存。
                    if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0 || strncmp(de->d_name, "Recovery_", 9) == 0) {
                        saved_log_file* p = (saved_log_file*) malloc(sizeof(saved_log_file));
                        strcpy(path+path_len, de->d_name);
                        p->name = strdup(path);
                        if (stat(path, &(p->st)) == 0) {
                            // truncate files to 512kb
                            if (p->st.st_size > (1 << 19)) {
                                p->st.st_size = 1 << 19;
                            }
                            //分配文件本身内容所需的内存,并将其读出,至此完成将内容保存至内存中
                            p->data = (unsigned char*) malloc(p->st.st_size);
                            FILE* f = fopen(path, "rb");
                            fread(p->data, 1, p->st.st_size, f);
                            fclose(f);
                            //通过一个链表保存所有的文件
                            p->next = head;
                            head = p;
                        } else {
                            free(p);
                        }
                    }
                }
                closedir(d);
            } else {
                if (errno != ENOENT) {
                    printf("opendir failed: %s\n", strerror(errno));
                }
            }
        }
        ui->Print("Formatting %s...\n", volume);
        ensure_path_unmounted(volume);
        //....还有很多代码省略了,后面的流程就是接触挂载,调用系统api format_volume格式化分区,然后重新挂载并将内存中的文件写入到分区里。
        
        if (is_cache) {
            while (head) {
                FILE* f = fopen_path(head->name, "wb");
                if (f) {
                    fwrite(head->data, 1, head->st.st_size, f);
                    fclose(f);
                    chmod(head->name, head->st.st_mode);
                    chown(head->name, head->st.st_uid, head->st.st_gid);
                }
                free(head->name);
                free(head->data);
                saved_log_file* temp = head->next;
                free(head);
                head = temp;
                //上面这块是回写到文件的步骤,同时也包括了设置文件权限的操作。
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95

    上面就是recovery里保存其自身log文件的方法,既然知道原理,而我们目的只是保存一个文件,那么就不需要搞的那么复杂,实现一个自己的操作:

    //定义文件最大大小
    #define LOCAL_FILE_LEN 1024
    
    //指针,用来开辟一块内存,存放文件内容
    static char *p_locale_buffer = NULL;
    //记录从文件中读出的字节数
    static int locale_file_copy_cnt = 0;
    //路径
    static const char *LOCALE_FILE_PATH = "/cache/persist.prop";
    
    //将文件读出到内存里,可以同样放在erase_volume中,也可以放到wipe_data里,我这里是放在wipe_data的erase_volumn调用之前
        {
            ui->Print("\nStorage persist prop...\n");
            p_locale_buffer = (char*)malloc(LOCAL_FILE_LEN);
            int res;
            if(NULL == p_locale_buffer){
                printf("malloc locale failed!\n");
            }else{
                memset(p_locale_buffer, 0, LOCAL_FILE_LEN);
                if( copyFileToMem(LOCALE_FILE_PATH, p_locale_buffer, &locale_file_copy_cnt, LOCAL_FILE_LEN)  < 0 ){
                    ui->Print("copyFileToMem failed: %s\n", LOCALE_FILE_PATH);
                }else{
                    ui->Print("Persist prop file copied, total: %d byte.\n", locale_file_copy_cnt);
                }
            }
        }
        
    //待erase_volumn返回后,再重新回写
        {
            if(p_locale_buffer != NULL){
                if(copyFileFromMem(LOCALE_FILE_PATH, p_locale_buffer, locale_file_copy_cnt) < 0){
                    ui->Print("copyFileFromMem failed: %s\n", LOCALE_FILE_PATH);
                }else{
                    ui->Print("Persist prop file restored.\n");
                }
                free(p_locale_buffer);
                p_locale_buffer = NULL;
            }
        }
    
    //上面所使用的copyFileToMem/FromMem如下,基本文件IO,没什么好说的:
    static int copyFileToMem(const char *path,char *p,int *count,int size) {
        char *tmp = p;
        chmod(path,0770);
        chown(path,1000,1000);
        FILE* fd = fopen_path(path,"r");
    
        if(NULL == fd) {
            printf("open %s failed %d(%s)\n",path,errno,strerror(errno));
            return -1;
        }
    
        int res = 0;
        char buf[50] = {0};
        while((res = fread(buf,1,sizeof(buf),fd)) > 0) {
            *count += res;
            if (*count <= size) {
                memcpy(tmp,buf,res);
                tmp += res;
            } else {
                *count -= res;
                printf("size overflow");
                break;
            }
        }
        fclose(fd);
        return 0;
    }
    
    
    static int copyFileFromMem(const char *path,char *p,int count) {
        FILE* fd = fopen_path(path,"w+");
        //设置下文件权限,这里的644是必要的,否则就需要改init中的util.cpp,不然init会因为权限问题认为该文件是insecure的,拒绝读取
        chmod(path,0644);
        chown(path,1000,1000);
        if(NULL == fd) {
            printf("open %s failed %d(%s)\n",path,errno,strerror(errno));
            return -1;
        }
        int res = 0;
        if ((res = fwrite(p,1,count,fd)) > 0) {
            printf("write done\n");
        }
        fclose(fd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    这样一来就实现了文件的持久化保存,但这只是第一步,下面要将其作用在保存系统语言。

    将配置写入持久化保存的文件

    因为android的系统语言都是可以在property中定义的,所以首先想到的就是在底层的property_set的入口里加一个处理函数,根据property key来决定是不是写入到持久化的文件里,后来查找一下发现这个函数比较深,而且目前的需求只是写一个参数,所以想最小化它对系统代码的影响,所以直接移到设置入口里写。

    Android 7.x中,语言设置在Settings的入口是这样的,实现了一个List的Drag&Drop交互,因为允许多个语言按优先级来显示,所以看起来有点复杂:
    但是通过一顿搜索,发现最终实际都通过系统内部的一个接口类LocalePicker.updateLocales方法来更新系统语言,直接一顿查找,发现下面这个入口

    //com.android.settings.localepicker 是的,同名
    //import com.android.internal.app.LocalePicker; 上面说的内部接口类是这个
        public void updateLocalesWhenAnimationStops(final LocaleList localeList) {
            if (localeList.equals(mLocalesToSetNext)) {
                return;
            }
            // This will only update the Settings application to make things feel more responsive,
            // the system will be updated later, when animation stopped.
            LocaleList.setDefault(localeList);
            mLocalesToSetNext = localeList;
            final RecyclerView.ItemAnimator itemAnimator = mParentView.getItemAnimator();
            itemAnimator.isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
                @Override
                public void onAnimationsFinished() {
                    if (mLocalesToSetNext == null || mLocalesToSetNext.equals(mLocalesSetLast)) {
                        // All animations finished, but the locale list did not change
                        return;
                    }
                    //调用内部接口,更新系统语言,这里的mLocaleToSetNext是一个LocaleList,也是由内部提供的,由于系统支持按照语言优先级显示,实际上是对Locale的一个便利化封装,并且实现了序列化接口
                    LocalePicker.updateLocales(mLocalesToSetNext);
                    mLocalesSetLast = mLocalesToSetNext;
                    mLocalesToSetNext = null;
                    mNumberFormatter = NumberFormat.getNumberInstance(Locale.getDefault());
                }
            });
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    发现入口之后直接在这里做手脚, 写一个工具方法将某个系统语言保存到持久化文件中,调用的方法直接插在LocalePicker.updateLocales调用的后面,只需要获取mLocalesToSetNext的第一位:

         public void saveLocaleToPersistProp(Locale locale){
            try {
                File output = new File("/cache/persist.prop");
                if (!output.exists()) output.createNewFile();
                FileOutputStream fos = new FileOutputStream(output);
                //因为最终是按照prop来读的,这里直接写进去文件里
                fos.write(("persist.sys.locale="+locale.toLanguageTag()).getBytes());
                Log.e("GCAT DBG", "Write to persist prop.");
                fos.flush();
                fos.close();
            } catch (Exception ex) {
                Log.e("GCAT DBG", "Error when writing file.");
                ex.printStackTrace();
            }
         }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    顺带一提,实际负责property写入的其实是在ActivityManager里

    //frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java
    private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
                boolean initLocale, boolean persistent, int userId, boolean deferResume) {
            int changes = 0;
    
            if (mWindowManager != null) {
                mWindowManager.deferSurfaceLayout();
            }
            if (values != null) {
                Configuration newConfig = new Configuration(mConfiguration);
                changes = newConfig.updateFrom(values);
                if (changes != 0) {
                    if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.i(TAG_CONFIGURATION,
                            "Updating configuration to: " + values);
    
                    EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);
    
                    if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) {
                        final LocaleList locales = values.getLocales();
                        int bestLocaleIndex = 0;
                        if (locales.size() > 1) {
                            if (mSupportedSystemLocales == null) {
                                mSupportedSystemLocales =
                                        Resources.getSystem().getAssets().getLocales();
                            }
                            bestLocaleIndex = Math.max(0,
                                    locales.getFirstMatchIndex(mSupportedSystemLocales));
                        }
                        SystemProperties.set("persist.sys.locale",
                                locales.get(bestLocaleIndex).toLanguageTag());
                        LocaleList.setDefault(locales, bestLocaleIndex);
                        mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG,
                                locales.get(bestLocaleIndex)));
                    }
    
                    mConfigurationSeq++;
                    if (mConfigurationSeq <= 0) {
                        mConfigurationSeq = 1;
                    }
                    newConfig.seq = mConfigurationSeq;
                    mConfiguration = newConfig;
                    Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
                    mUsageStatsService.reportConfigurationChange(newConfig,
                            mUserController.getCurrentUserIdLocked());
                    //mUsageStatsService.noteStartConfig(newConfig);
    
                    final Configuration configCopy = new Configuration(mConfiguration);
    
                    // TODO: If our config changes, should we auto dismiss any currently
                    // showing dialogs?
                    mShowDialogs = shouldShowDialogs(newConfig, mInVrMode);
    
                    AttributeCache ac = AttributeCache.instance();
                    if (ac != null) {
                        ac.updateConfiguration(configCopy);
                    }
    
                    // Make sure all resources in our process are updated
                    // right now, so that anyone who is going to retrieve
                    // resource values after we return will be sure to get
                    // the new ones.  This is especially important during
                    // boot, where the first config change needs to guarantee
                    // all resources have that config before following boot
                    // code is executed.
                    mSystemThread.applyConfigurationToResources(configCopy);
    
                    if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
                        Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
                        msg.obj = new Configuration(configCopy);
                        msg.arg1 = userId;
                        mHandler.sendMessage(msg);
                    }
    
                    final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
                    if (isDensityChange) {
                        // Reset the unsupported display size dialog.
                        mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG);
    
                        killAllBackgroundProcessesExcept(Build.VERSION_CODES.N,
                                ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
                    }
    
                    for (int i=mLruProcesses.size()-1; i>=0; i--) {
                        ProcessRecord app = mLruProcesses.get(i);
                        try {
                            if (app.thread != null) {
                                if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
                                        + app.processName + " new config " + mConfiguration);
                                app.thread.scheduleConfigurationChanged(configCopy);
                            }
                        } catch (Exception e) {
                        }
                    }
                    Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);
                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                            | Intent.FLAG_RECEIVER_REPLACE_PENDING
                            | Intent.FLAG_RECEIVER_FOREGROUND);
                    broadcastIntentLocked(null, null, intent, null, null, 0, null, null,
                            null, AppOpsManager.OP_NONE, null, false, false,
                            MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
                    if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) {
                        intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
                        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
    	            if (initLocale || !mProcessesReady) {
                            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
                        }
                        broadcastIntentLocked(null, null, intent,
                                null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                                null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
                    }
                }
                // Update the configuration with WM first and check if any of the stacks need to be
                // resized due to the configuration change. If so, resize the stacks now and do any
                // relaunches if necessary. This way we don't need to relaunch again below in
                // ensureActivityConfigurationLocked().
                if (mWindowManager != null) {
                    final int[] resizedStacks = mWindowManager.setNewConfiguration(mConfiguration);
                    if (resizedStacks != null) {
                        for (int stackId : resizedStacks) {
                            final Rect newBounds = mWindowManager.getBoundsForNewConfiguration(stackId);
                            mStackSupervisor.resizeStackLocked(
                                    stackId, newBounds, null, null, false, false, deferResume);
                        }
                    }
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126

    让系统启动时读取我们自己持久化的property

    上面完成了文件持久化和设置语言时的保存,最后一步就是使系统加载时读取:
    这点可以直接修改init对应的property_service来实现

    //system\core\init\property_service.cpp
    //这个文件实际负责系统启动时的property初始化,其入口
    void load_system_props(){
        load_properties_from_file(PROP_PATH_SYSTEM_BUILD, NULL);
        load_properties_from_file(PROP_PATH_VENDOR_BUILD, NULL);
        
        //加入自定义配置的读取,这里已经有现成的方法直接从文件中读取并解析到property system里了
        ERROR("Loading CACHE Prop....\n");
        load_properties_from_file("/cache/persist.prop", NULL);
    
        load_properties_from_file(PROP_PATH_FACTORY, "ro.*");
        load_recovery_id_prop();
    }
    
    
    //这个方法会被->init\builtins.cpp注册到一个map中,并且由builtins.h提供一个类。
    //最终在主入口init.cpp的main函数里Action::set_function_map(&function_map);中注册
    //然后通过响应请求load_system_props来调用
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
  • 相关阅读:
    LeetCode刷题---19. 删除链表的倒数第 N 个结点(双指针-快慢指针)
    Oracle中数据迁移的工具
    Android NDK开发之震动服务客户端编写程序(C++)
    125道Python面试题总结
    Java代码中如何计算HashMap对象中元素个数呢?
    webpack、vite--获取环境变量的区别(vue2/vue3)
    【前端面试必知】虚拟DOM与diff算法详解
    cs231n_2022的assignment-1实现(KNN部分)
    蓄电池电压检测单元 电池监控模块 24路电池电压采样模块电源检测
    六款Linux常用远程连接工具介绍,看看哪一款最适合你
  • 原文地址:https://blog.csdn.net/GarfieldGCat/article/details/126844770