18.Android Recovery 源碼解析和界面定制

本文我們主要了解學(xué)習(xí)Android系統(tǒng)中recovery系統(tǒng)的功能知識(shí)與系統(tǒng)實(shí)現(xiàn)源碼。

目錄

1. Recovery主要功能
2. 源碼路徑和主要原文件
3. recovery.cpp命令行參數(shù)
4. main 函數(shù)
5. 界面定制實(shí)現(xiàn)Recovery UI
6. 實(shí)現(xiàn)頭部顯示和列表項(xiàng)
7. 實(shí)現(xiàn)ScreenRecoveryUI
8. 實(shí)現(xiàn)設(shè)備類
9. 添加編譯實(shí)現(xiàn)

1.Recovery主要功能

深入了解recovery源碼前,先瀏覽下recovery能夠給我們提供哪些功能;

  • 首先是我們熟悉的恢復(fù)工廠設(shè)置 –> wipe_data wipe_cache
  • 刷升級(jí)包,可以通過sdcard升級(jí),通常說的卡刷,有些還提供ADB sideload升級(jí);
  • 可以進(jìn)行系統(tǒng)的系統(tǒng)的OTA升級(jí),本質(zhì)上同手動(dòng)刷包一樣;
2.源碼路徑和主要原文件

在Android源碼環(huán)境中,recovery的源碼主要在bootable/recovery文件下,另外再device目錄下,會(huì)根據(jù)各個(gè)設(shè)備定制自己的接口以及UI界面,也就是文章后半部分分析的界面定制的內(nèi)容;

bootable/recovery目錄下,主要的源文件有:

LOCAL_SRC_FILES := \     
   adb_install.cpp \     
   asn1_decoder.cpp \     
   bootloader.cpp \     
   device.cpp \     
   fuse_sdcard_provider.c \    
   install.cpp \     
   recovery.cpp \     
   roots.cpp \     
   screen_ui.cpp \     
   ui.cpp \     
   verifier.cpp \

該部分代碼在編譯后,會(huì)統(tǒng)一輸出到 out/target/product/arbutus/recovery/root/目錄;

3.recovery.cpp命令行參數(shù)

recovery最后是編譯成一個(gè)可執(zhí)行的命令,放在recovery文件系統(tǒng)中的/sbin/recovery;所以我們可以在終端中直接運(yùn)行該命令,具體的參數(shù)如下:

--send_intent=anystring   - 傳遞給recovery的信息 
--adbd   - adb sideload升級(jí) 
--update_package=path   - 指定OTA升級(jí)包 
--wipe_data   - 清楚用戶數(shù)據(jù)并重啟 
--wipe_cache   - 清楚緩存并重啟 
--set_encrypted_filesystem=on|off   - 使能或者關(guān)閉文件系統(tǒng)加密 
--just_exit   - 退出并重啟
4.main 函數(shù)

從main入口函數(shù)分析recovery的主要源碼:
輸出重定向

redirect_stdio(TEMPORARY_LOG_FILE);
    //redirect log to serial output
#ifdef LogToSerial
    freopen("/dev/ttyFIQ0", "a", stdout); setbuf(stdout, NULL);
    freopen("/dev/ttyFIQ0", "a", stderr); setbuf(stderr, NULL);
#endif

這部分代碼很容易理解,主要作用是輸出log到/tem/recovery.log文件中
執(zhí)行adb sideload分支

if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
      adb_main(0, DEFAULT_ADB_PORT);
      return 0;
}

判斷命令行參數(shù)是否為–adbd,并執(zhí)行adb_main函數(shù),這部分代碼在后續(xù)adb_install.cpp中分析;
填充fstab結(jié)構(gòu)體
在main函數(shù)中調(diào)用 load_volume_table(),讀取/etc/recovery.emmc.fstab文件內(nèi)容,并填充fstab結(jié)構(gòu)體,但是并沒有執(zhí)行掛載操作;
load_volume_table函數(shù)在roots.cpp文件中,也是很容易理解:

void load_volume_table()
{
    ...
    int emmcState = getEmmcState();//判斷是否為emmc設(shè)備
    if(emmcState) {
        fstab = fs_mgr_read_fstab("/etc/recovery.emmc.fstab");
    }else {
        fstab = fs_mgr_read_fstab("/etc/recovery.fstab");
    }
    ...
    //讀取文件中每個(gè)條目?jī)?nèi)容,填充fstab結(jié)構(gòu)體
    ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
    ...
    //日志打印fstable信息
    printf("recovery filesystem table\n");
    printf("=========================\n");
    for (i = 0; i < fstab->num_entries; ++i) {
        Volume* v = &fstab->recs[i];
        printf(" %d %s %s %s %lld\n", i, v->mount_point, v->fs_type,
               v->blk_device, v->length);
    }
    printf("\n");
}

讀取控制參數(shù)
recovery 和 bootloader 必須通過內(nèi)存的一個(gè)特定分區(qū),才能進(jìn)行相互的通信,這個(gè)分區(qū)一般是/misc;
對(duì)應(yīng)的信息數(shù)據(jù)結(jié)構(gòu)體為bootloader_message
參照源碼中bootloader_message的注釋

struct bootloader_message {
    char command[32];//bootloader 啟動(dòng)時(shí)讀取改數(shù)據(jù),決定是否進(jìn)入recovery模式
    char status[32];//由bootloader進(jìn)行更新,標(biāo)識(shí)升級(jí)的結(jié)果;
    char recovery[768];//由Android系統(tǒng)進(jìn)行寫入,recovery從中讀取信息;
    char stage[32];
    char reserved[224];
};

recovery 根據(jù)命令行參數(shù),再?gòu)?code>/misc分區(qū)中解析出對(duì)應(yīng)的參數(shù),進(jìn)行后續(xù)的操作,具體的調(diào)用函數(shù)為get_args(&argc, &argv)

static void
get_args(int *argc, char ***argv) {
    struct bootloader_message boot;//參數(shù)結(jié)構(gòu)體
    memset(&boot, 0, sizeof(boot));
    get_bootloader_message(&boot);  // 具體的讀取信息的函數(shù),可能為空的情況
    stage = strndup(boot.stage, sizeof(boot.stage));
    ...

     // 如果上述情況為空,則從/cache/recovery/command獲取參數(shù),其中COMMAND_FILE=/cache/recovery/command
    if (*argc <= 1) {
        FILE *fp = fopen_path(COMMAND_FILE, "r");
        if (fp != NULL) {
            char *token;
            char *argv0 = (*argv)[0];
            *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
            (*argv)[0] = argv0;  // use the same program name

            char buf[MAX_ARG_LENGTH];
            for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
                if (!fgets(buf, sizeof(buf), fp)) break;
                token = strtok(buf, "\r\n");
                if (token != NULL) {
                    (*argv)[*argc] = strdup(token);  // Strip newline.
                } else {
                    --*argc;
                }
            }

            check_and_fclose(fp, COMMAND_FILE);
            LOGI("Got arguments from %s\n", COMMAND_FILE);
        }
    }

    //把從/cache/recovery/command獲取參數(shù)重新寫回到/misc分區(qū)
    // --> write the arguments we have back into the bootloader control block
    // always boot into recovery after this (until finish_recovery() is called)
    strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
    strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
    int i;
    for (i = 1; i < *argc; ++i) {
        strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery));
        strlcat(boot.recovery, "\n", sizeof(boot.recovery));
    }
    set_bootloader_message(&boot);
}

解析命令行參數(shù)

while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
        switch (arg) {
        case 'f': factory_mode = optarg; bFactoryMode = true; break;
        case 'i': send_intent = optarg; break;
        case 'u': update_package = optarg; break;
        case 'w': should_wipe_data = true; break;
        case 'k':  update_rkimage = optarg;break;
        case 'c': should_wipe_cache = true; break;
        case 't': show_text = true; break;
        case 's': sideload = true; break;
        case 'a': sideload = true; sideload_auto_reboot = true; break;
        case 'x': just_exit = true; break;
        case 'l': locale = optarg; break;
        case 'g': {
            if (stage == NULL || *stage == '\0') {
                char buffer[20] = "1/";
                strncat(buffer, optarg, sizeof(buffer)-3);
                stage = strdup(buffer);
            }
            break;
        }
        case 'f'+'w': //fw_update
            if((optarg)&&(!sdboot_update_package)){
            sdboot_update_package = strdup(optarg);
            }
            break;
        case 'd': //demo_copy
            if((optarg)&&(! demo_copy_path)){
                demo_copy_path = strdup(optarg);
            }
            break;
        case 'p': shutdown_after = true; break;
        case 'r': reason = optarg; break;
        case 'w'+'a': { should_wipe_all = should_wipe_data = should_wipe_cache = true;show_text = true;} break;
        case '?':
            LOGE("Invalid command argument\n");
            continue;
        }
    }

這部分代碼很簡(jiǎn)單,就是通過getopt_long進(jìn)行命令行參數(shù)的解析并賦值;
顯示界面和功能選項(xiàng)
接下來(lái)就是創(chuàng)建device,顯示對(duì)應(yīng)UI界面和功能選項(xiàng);

    Device* device = make_device();//可以自己實(shí)現(xiàn)一個(gè)設(shè)備
    ui = device->GetUI();
    gCurrentUI = ui;//賦值ui界面

    ui->SetLocale(locale);//獲取歸屬地信息
    ui->Init();//初始化,可以重載,在init中實(shí)現(xiàn)相應(yīng)功能
    ui->SetStage(st_cur, st_max);
    ui->SetBackground(RecoveryUI::NONE);

進(jìn)行分區(qū)掛載操作
ensure_path_mounted

int ensure_path_mounted(const char* path) {
    ...
    Volume* v = volume_for_path(path);//根據(jù)路徑名獲取分區(qū)信息
    ...
    int result;
    result = scan_mounted_volumes();

    const MountedVolume* mv =
        find_mounted_volume_by_mount_point(v->mount_point);//根據(jù)掛載點(diǎn),獲取已掛載分區(qū)的信息,如果不為空,說明已經(jīng)成功掛載
    if (mv) {
        // volume is already mounted
        return 0;
    }

    result = mkdir(v->mount_point, 0755);  // 創(chuàng)建對(duì)應(yīng)目錄,確保目錄存在,也有可能目錄已經(jīng)存在
    if (result!=0)
    {
        printf("failed to create %s dir,err=%s!\n",v->mount_point,strerror(errno));
    }

    // 根據(jù)文件系統(tǒng)類型,執(zhí)行mount操作
    if (strcmp(v->fs_type, "yaffs2") == 0) {
        // mount an MTD partition as a YAFFS2 filesystem.
        mtd_scan_partitions();
        const MtdPartition* partition;
        partition = mtd_find_partition_by_name(v->blk_device);
        if (partition == NULL) {
            LOGE("failed to find \"%s\" partition to mount at \"%s\"\n",
                 v->blk_device, v->mount_point);
            return -1;
        }
        return mtd_mount_partition(partition, v->mount_point, v->fs_type, 0);
    } else if (strcmp(v->fs_type, "ext4") == 0 ||
               strcmp(v->fs_type, "ext3") == 0) {
        result = mount(v->blk_device, v->mount_point, v->fs_type,
                       MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
        if (result == 0) return 0;

        LOGE("failed to mount %s %s (%s)\n", v->mount_point, v->blk_device, strerror(errno));
        return -1;
    } else if (strcmp(v->fs_type, "vfat") == 0) {
        result = mount(v->blk_device, v->mount_point, v->fs_type,
                       MS_NOATIME | MS_NODEV | MS_NODIRATIME, "shortname=mixed,utf8");
        if (result == 0) return 0;

        LOGW("trying mount %s to ntfs\n", v->blk_device);
        result = mount(v->blk_device, v->mount_point, "ntfs",
                           MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
        if (result == 0) return 0;

        char *sec_dev = v->fs_options;
        if(sec_dev != NULL) {
            char *temp = strchr(sec_dev, ',');
            if(temp) {
                temp[0] = '\0';
            }

            result = mount(sec_dev, v->mount_point, v->fs_type,
                                   MS_NOATIME | MS_NODEV | MS_NODIRATIME, "shortname=mixed,utf8");
            if (result == 0) return 0;

            LOGW("trying mount %s to ntfs\n", sec_dev);
            result = mount(sec_dev, v->mount_point, "ntfs",
                               MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
            if (result == 0) return 0;
        }

        LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno));
        return -1;
    }else if (strcmp(v->fs_type, "ntfs") == 0) {
        LOGW("trying mount %s to ntfs\n", v->blk_device);
        result = mount(v->blk_device, v->mount_point, "ntfs",
                           MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
        if (result == 0) return 0;

        LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno));
        return -1;
    }

    LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, v->mount_point);
    return -1;
}
5.界面定制實(shí)現(xiàn)Recovery UI

在自己的設(shè)備目錄下:device/vendor/recovery/recovery_ui.cpp

#include <linux/input.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>

#include "common.h"
#include "device.h"
#include "screen_ui.h"
6.實(shí)現(xiàn)頭部顯示和列表項(xiàng)
const char* HEADERS[] = { 
    "Volume up/down to move highlight;",
    "power button to select.", 
    "", 
    NULL 
}; 
const char* ITEMS[] ={ 
    "reboot system now", 
  //"apply update from ADB",
    "apply update from external storage", 
    "update rkimage from external storage", 
    "apply update from cache", 
    "wipe data/factory reset", 
    "wipe cache partition", 
    "recovery system from backup", 
    NULL 
};
7.實(shí)現(xiàn)ScreenRecoveryUI
class DeviceUI : public ScreenRecoveryUI {
  public:
    DeviceUI () :
        consecutive_power_keys(0) {
    }

    //實(shí)現(xiàn)自己的識(shí)別key類型的功能,可以為不同的輸入設(shè)備適配recovery功能
    virtual KeyAction CheckKey(int key) {
        if (IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) {
            return TOGGLE;
        }
        if (key == KEY_POWER) {
            ++consecutive_power_keys;
            if (consecutive_power_keys >= 7) {
                return REBOOT;
            }
        } else {
            consecutive_power_keys = 0;
        }
        return ENQUEUE;
    }

  private:
    int consecutive_power_keys;
};
8.實(shí)現(xiàn)設(shè)備類
class MyDevice : public Device {
  public:
    RkDevice() :
        ui(new DeviceUI ) {
    }

    RecoveryUI* GetUI() { return ui; }

    int HandleMenuKey(int key_code, int visible) {
        if (visible) {
            switch (key_code) {
              case KEY_DOWN:
              case KEY_VOLUMEDOWN:
                return kHighlightDown;

              case KEY_UP:
              case KEY_VOLUMEUP:
                return kHighlightUp;

              case KEY_ENTER:
              case KEY_POWER:
                return kInvokeItem;
            }
        }

        return kNoAction;
    }

    BuiltinAction InvokeMenuItem(int menu_position) {
        switch (menu_position) {
          case 0: return REBOOT;
          //case 1: return APPLY_ADB_SIDELOAD;
          case 1: return APPLY_EXT;
          case 2: return APPLY_INT_RKIMG;
          case 3: return APPLY_CACHE;
          case 4: return WIPE_DATA;
          case 5: return WIPE_CACHE;
          case 6: return RECOVER_SYSTEM;
          default: return NO_ACTION;
        }
    }

    const char* const* GetMenuHeaders() { return HEADERS; }
    const char* const* GetMenuItems() { return ITEMS; }

  private:
    RecoveryUI* ui;
};

//創(chuàng)建自己實(shí)現(xiàn)的設(shè)備
Device* make_device() {
    return new MyDevice ;
}
9.添加編譯實(shí)現(xiàn)

主要是覆蓋TARGET_RECOVERY_UI_LIB,輸出到/out/…./recovery/root目錄下:
Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := eng
LOCAL_C_INCLUDES += bootable/recovery
LOCAL_SRC_FILES := recovery_ui.cpp

# should match TARGET_RECOVERY_UI_LIB set in BoardConfig.mk
LOCAL_MODULE := librecovery_ui_$(TARGET_PRODUCT)

include $(BUILD_STATIC_LIBRARY)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,970評(píng)論 25 709
  • 1:InputChannel提供函數(shù)創(chuàng)建底層的Pipe對(duì)象 2: 1)客戶端需要新建窗口 2)new ViewRo...
    自由人是工程師閱讀 5,712評(píng)論 0 18
  • 玩兒過Linux的應(yīng)該都明白R(shí)oot代表了什么,獲取Root權(quán)限你就能控制系統(tǒng)的一切,甚至還可以執(zhí)行rm -rf ...
    freeman521閱讀 5,497評(píng)論 1 10
  • 樹葉莎莎響 陪我去流浪 某一天 我也希望有一個(gè)人陪我去流浪 陪我重游云南 再看小橋流水人家 席地而坐彈奏歌唱 陪我...
    欣兒____閱讀 215評(píng)論 0 3
  • 許多單身的人大概都有過這樣的體驗(yàn),每次逢年過節(jié)回家,除去父母之外,家里的親戚總要打聽打聽你的感情問題,問問你現(xiàn)在是...
    遠(yuǎn)方之遠(yuǎn)閱讀 671評(píng)論 5 2

友情鏈接更多精彩內(nèi)容