主頁 > 移動端開發 > 安卓11 init初始化以及init.rc的決議執行程序詳解

安卓11 init初始化以及init.rc的決議執行程序詳解

2021-01-09 11:27:14 移動端開發

最近做了一個高通平臺安卓的需求,功能使得data磁區在第一次啟動時,自動適配emmc/ufs的實際大小,在此程序中對init的執行以及.rc檔案的決議流程有了一些理解,但是對于一些細節的東西還不清楚,在這里提出幾個自己疑惑的關鍵問題,趁熱打鐵!!梳理并尋找答案!!!

這里以高通平臺為例,基于最新的安卓11,init這塊的代碼mtk與高通基本是一模一樣的(差異很小),都是中間層的東西;

1,init行程在第二初始階段如何加載init.rc,在整個工程中有幾個地方存放rc檔案,存放有什么規律??

2,根據目前的理解,rc檔案中的cmd最終映射到了對應的函式執行,如下圖,他們是什么時候被呼叫的?? 例如:rc檔案中cmd mount_all 最侄訓映射執行do_mount_all()函式;

3,*.rc檔案加載的時候就伴隨執行嗎,還是是分開的??

4,目前看磁區并不是一次性掛載的,磁區分了幾次掛載,這幾次區分有什么規律??

對于init在整個系統中(宏觀)的執行流程想必大家都很清楚了,init行程是linux內核啟動后創建的第一個行程,地位非常重要,init行程在初始化程序中會啟動很多重要的守護行程,因此理解init行程的啟動程序可以使我們更好的理解安卓系統,init本身也是一個守護行程,linux內核加載完畢后,會首先啟動init行程,啟動程序中會決議linux配置腳本init.rc檔案,根據init.rc檔案的內容,init行程會裝載Android的檔案系統,創建系統目錄,初始化屬性系統,啟動android系統重要的守護行程, 這些行程包括USB守護行程,adb守護行程,vold守護行程,rild守護行程等;

最后init行程也會作為守護行程來執行修改屬性請求,重啟崩潰的行程操作;

init行程初始化程序

init行程的原始碼位于目錄system/core/init 下,程式的入口函式main()位于檔案main.cpp中;

main函式的流程;

int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
    __asan_set_error_report_callback(AsanReportCallback);
#endif

    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);//uevent的初始化
    }

    if (argc > 1) {
        if (!strcmp(argv[1], "subcontext")) {
            android::base::Initialling(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();//命令映射表
            
            return SubcontextMain(argc, argv, &function_map);
        }

        if (!strcmp(argv[1], "selinux_setup")) {//selinux的初始化設定
            return SetupSelinux(argv);
        }

        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv); //init的第二階段初始化
        }
    }

    return FirstStageMain(argc, argv); //init的第一階段初始化
}
system/core/init/main.cpp

main函式一開始,首先初始化了守護行程uevent,然后緊接著的就是init的初始化程序;

init引導序列分為三個主要階段:1 first_stage_init;

2 selinux_setup;(可選)

3 second_stage_init;

其中 first_stage_init負責設定最低限度的基本需求用以加載系統其余部分,具體來說,包括“/dev”,“/proc”的掛載,掛載“early mount”磁區(這包括所有包含系統代碼的磁區,例如system和vendor)對于有ramdisk的設備,將system.img掛載到“/”;

一旦first_stage_init完成接著執行 execs /system/bin/init 并以“selinux_setup”作為引數,在這個階段,SELinux可選地編譯并加載到系統中,這個階段主要是加載初始化selinux相關的東西,關于此更多的細節在Selinux.cpp中;

最后,一旦該階段結束,它將再次使用"second_stage"引數執行/system/bin/init,此時,init的主要階段將運行并通過init繼續引導init.rc腳本

由此我們可以知道rc檔案的決議執行均在init的第二階段,因此我們需要重點關注init初始化的第二階段

second_stage_init;

...

初始化屬性系統;

初始化信號;

LoadBootScripts(am, sm); //加載*.rc檔案

進入while(1)回圈,監聽處理到達的事件;

init.rc語言

這部分將介紹init.rc檔案的格式這是理解決議程序的根本;

Android Init語言由5大類陳述句組成:
Actions, Commands, Services, Options, 和 Imports.

以下是對各個陳述句的簡單解釋

actions

actions其實就是以序列的commands的集合,每個actions都有一個trigger,它用于決定action的執行時機,當一個符合action觸發條件的事件發生了,此action會加入到執行佇列的末尾,除非它已經在佇列里;

每一個action都將以此從佇列中取出,此action的每個command都將依次執行,在這些命令執行時init還同時處理這其他活動(設備節點的創建和銷毀,設定屬性,重啟行程);

services:

services是一個后臺程式,在init中啟動,如果退出了可以由系統重啟(可選);

options

options是services的修飾項,它們決定一個services何時以及如何運行;

triggers

Triggers是一個用于匹配某種事件型別的字串,它將使對應的actions執行;

觸發器分為事件觸發器和屬性觸發器,

事件觸發器
是由'trigger'命令或init可執行檔案中的QueueEventTrigger()函式觸發的字串,它們采用簡單字串的形式,如'boot'或'late-init',

屬性觸發器
是指指定屬性將值更改為給定的新值或指定屬性將值更改為任何新值時觸發的字串,它們分別采用'property:='和'property:=\*'的形式,屬性觸發器將在init的初始引導階段額外計算并相應地觸發,

一個操作可以有多個屬性觸發器,但可能只有一個事件觸發器,

commands

command是actions的命令串列中的命令,或者是service的選項引數命令;

import

一般用作 “import <path>”,擴展當前配置,如果path是一個目錄,該目錄中的每個檔案都被決議為一個組態檔,它不是遞回的,嵌套的目錄將不會被決議,

import關鍵字不是命令,而是它自己的部分,這意味著它不是作為action的一部分發生的,而是在決議檔案時處理匯入;

第一級安裝設備的實際順序是:
1. 決議/init.rc,然后遞回地決議它的每個匯入(此處遞回是import的遞回,不是檔案夾的遞回,檔案夾不支持遞回);
2. /system/etc/init/的內容按字母順序排列并按順序決議,在每個檔案決議后遞回地進行匯入;
3. 步驟2重復/vendor/etc/init,然后是/odm/etc/init;

-----------------

/init.rc是主要的.rc檔案,由init可執行檔案在開始執行時加載,它負責系統的初始設定,

在加載主目錄/init.rc后,init立即加載包含在/{system,vendor,odm}/etc/init/目錄中的所有檔案,

rc檔案的存放目錄以及目的:
1 /system/etc/init/ 用于核心系統項,例如 SurfaceFlinger, MediaService和logd,
2 /vendor/etc/init/ 是針對SoC供應商的專案,如SoC核心功能所需的actions或守護行程,
3 /odm/etc/init/ 用于設備制造商的專案,如actions或運動傳感器或其他外圍功能所需的守護行程,

以下是個人認為理解init.rc腳本重要的幾點內容:

1,init.rc檔案是以section為單位組織的,一個section可以包含多行,section可以分為兩大類,一類是action,另一類是service;action以關鍵字on開始,表示一組命令的集合,service以關鍵字service開始,表示啟動某個行程的方式和引數;

2,section以on或service開始,直到下一個on或者service結束,中間的所有行都屬于這一個section(空行或者注釋不具有分割作用),截取init.rc部分如下

on property:apexd.status=ready && property:ro.product.cpu.abilist32=*
    exec_start boringssl_self_test_apex32
on property:apexd.status=ready && property:ro.product.cpu.abilist64=*
    exec_start boringssl_self_test_apex64

service boringssl_self_test32 /system/bin/boringssl_self_test32
    setenv BORINGSSL_SELF_TEST_CREATE_FLAG true # Any nonempty value counts as true
    reboot_on_failure reboot,boringssl-self-check-failed
    stdio_to_kmsg

根據1,2可以得出,上圖有3個section,其中兩個是action,一個是service;

3,無論是action還是service,并不是按照檔案的編排順序執行的,他們只是一份定義,至于執行與否以及什么時候執行取決于init在運行時的操作.

腳本檔案的決議程序:

init行程啟動時最重要的作業就是決議并執行啟動檔案init.rc,本節介紹init行程決議腳本檔案的流程;

這里我們主要以決議action為例講解,service的決議程序同理,首先講解基本流程,然后列舉實體進行決議分析;

init.rc檔案以及*.rc檔案加載是從下面這個函式開始的:

static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser = CreateParser(action_manager, service_list);

    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
        parser.ParseConfig("/system/etc/init/hw/init.rc");
        if (!parser.ParseConfig("/system/etc/init")) {
            late_import_paths.emplace_back("/system/etc/init");
        }
        // late_import is available only in Q and earlier release. As we don't
        // have system_ext in those versions, skip late_import for system_ext.
        parser.ParseConfig("/system_ext/etc/init");
        if (!parser.ParseConfig("/product/etc/init")) {
            late_import_paths.emplace_back("/product/etc/init");
        }
        if (!parser.ParseConfig("/odm/etc/init")) {
            late_import_paths.emplace_back("/odm/etc/init");
        }
        if (!parser.ParseConfig("/vendor/etc/init")) {
            late_import_paths.emplace_back("/vendor/etc/init");
        }
    } else {
        parser.ParseConfig(bootscript);
    }
}

以下是主要的決議流程:

ParseConfig 函式傳入需要決議的rc檔案的路徑,如果是目錄,則遍歷該目錄取出所有的 rc 檔案并呼叫 ParseConfigFile 函式進行決議,如果是檔案路徑,則直接呼叫 ParseConfigFile 函式進行決議,

從代碼中可以看出,init 決議 rc 檔案的程序中,首先呼叫 ReadFile 函式將 rc 檔案的內容全部保存為字串,存在 data 中,然后呼叫 ParseData 進行決議,ParseData 函式會根據關鍵字決議出service和action,最終掛在到 service_list 與 action_manager 的向量(vector)上,

下面分析一下 ParseData 函式,根據關鍵字的不同會呼叫不同的 parser 去決議(多型),action 使用 ActionParser,而 service 使用 ServiceParser 決議,該部分定義在LoadBootScrip()函式的第一行,parser.AddSectionParser()方法為parser的map成員section_parsers_創建了三個SectionParser,分別用來決議service,on,import的section;

下面重點分析ParseData函式:

next_token(&parse_state)處理資料型別使用parse_state的構體回傳:

struct parse_state
{
    char *ptr;      // 要決議的字串
    char *text;     // 決議到的字串,可以理解為回傳一行的資料
    int line;       // 決議到第行數
    int nexttoken;  // 決議狀態,有 T_EOF T_NEWLINE T_TEXT
};//其中 T_EOF 表示字串決議結束,T_NEWLINE 表示決議完一行的資料,T_TEXT 表示決議到一個單詞

其中 T_EOF 表示字串決議結束,T_NEWLINE 表示決議完一行的資料,T_TEXT 表示決議到一個單詞,

void Parser::ParseData(const std::string& filename, std::string* data) {
    data->push_back('\n');  // TODO: fix tokenizer
    data->push_back('\0');

    parse_state state;
    state.line = 0;
    state.ptr = data->data();
    state.nexttoken = 0;

    SectionParser* section_parser = nullptr;
    int section_start_line = -1;
    std::vector<std::string> args;

    // If we encounter a bad section start, there is no valid parser object to parse the subsequent
    // sections, so we must suppress errors until the next valid section is found.
    bool bad_section_found = false;

    auto end_section = [&] {//lambda類似于一個函式指標
        bad_section_found = false;
        if (section_parser == nullptr) return;
        //同樣是呼叫相應的section的EndSection()結束該section的決議;
        if (auto result = section_parser->EndSection(); !result.ok()) {
            parse_error_count_++;
            LOG(ERROR) << filename << ": " << section_start_line << ": " << result.error();
        }

        section_parser = nullptr;
        section_start_line = -1;
    };

    for (;;) {
        switch (next_token(&state)) {
            case T_EOF:
                end_section();

                for (const auto& [section_name, section_parser] : section_parsers_) {
                    section_parser->EndFile();//決議到檔案末尾,則結束
                }

                return;
            case T_NEWLINE: { // 開始處理新的一行
                state.line++;
                if (args.empty()) break;
                // If we have a line matching a prefix we recognize, call its callback and unset any
                // current section parsers.  This is meant for /sys/ and /dev/ line entries for
                // uevent.
            /*    auto line_callback = std::find_if(
                    line_callbacks_.begin(), line_callbacks_.end(),
                    [&args](const auto& c) { return android::base::StartsWith(args[0], c.first); });
                if (line_callback != line_callbacks_.end()) {
                    end_section();

                    if (auto result = line_callback->second(std::move(args)); !result.ok()) {
                        parse_error_count_++;
                        LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                    }
            */              //這部分是uevent的暫時跳過
            //section_parsers_ 是一個map,是在LoadBootScrip()第一行創建了三個鍵值對  "on"      ---> ActionParser
            //                                                                 "import"  ---> ImportParser
            //                                                                 "service" ---> ServiceParser
            //args[0]為某一行的第一個單詞,該行由可能為section的開頭,也有可能為command行,count()方法判斷該行的第一個單詞是不是為on/import/serivce,如果是則回傳1
                } else if (section_parsers_.count(args[0])) { 
                    end_section();//在處理新的section前,結束之前的section;
                    section_parser = section_parsers_[args[0]].get(); //依據args[0]是on/import/service取出其對應的決議方法的地址ActionParser/ImportParser/ServiceParser
                    section_start_line = state.line;
                    if (auto result =
                                section_parser->ParseSection(std::move(args), filename, state.line); //呼叫相應的section決議函式(ActionParser/ImportParser/ServiceParser)決議section
                        !result.ok()) {
                        parse_error_count_++;
                        LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                        section_parser = nullptr;
                        bad_section_found = true;
                    }
                } else if (section_parser) {//該行的第一個單詞不是新的section的開頭,則使用上一次的決議方法的函式ActionParser/ImportParser/ServiceParser決議其命令列
                    if (auto result = section_parser->ParseLineSection(std::move(args), state.line);//決議命令
                        !result.ok()) {
                        parse_error_count_++;
                        LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                    }
                } else if (!bad_section_found) {
                    parse_error_count_++;
                    LOG(ERROR) << filename << ": " << state.line
                               << ": Invalid section keyword found";
                }
                args.clear();
                break;
            }
            case T_TEXT: //決議到一個單詞,就把它存入args中
                args.emplace_back(state.text);
                break;
        }
    }
}

next_token()函式的作用就是尋找單詞結束或者行結束標志,如果是單詞結束標志就將單詞push到args中,如果是行結束標志,則根據第一個單詞來判斷是否是一個section,section的標志只有三個"on","service","import",如果是"section",則呼叫相應的ParseSection()來處理一個新的section,否則把這一行繼續作為前“section”所屬的行來處理,

ParseSection()被用來決議一個新的section,ParseLineSection()被用來決議該section下的命令列,依據注釋內容我們可以繪制出決議的基本流程圖如下:

針對action,import,service分別定義了其對應的三個函式(不列舉import了),這三個函式在決議中扮演了重要的角色;

ActionParser::ParseSection() ServiceParser::ParseSection() ....

ActionParser::ParseLineSection() ServiceParser::ParseLineSection()

ActionParser::EndSection() ServiceParser::EndSection()

還是以下面action為例進行說明,

on boot
    ifup io
    start sshd

首先next_token決議到 on boot這一行,根據args[0] 值為“on” 取出ActionParser的指標,首先呼叫ActionParser::ParseSection處理這個新的action;

Result<void> ActionParser::ParseSection(std::vector<std::string>&& args,
                                        const std::string& filename, int line) {
    std::vector<std::string> triggers(args.begin() + 1, args.end());
    if (triggers.size() < 1) {
        return Error() << "Actions must have a trigger";
    }
    Subcontext* action_subcontext = nullptr;
    if (subcontext_ && subcontext_->PathMatchesSubcontext(filename)) {
        action_subcontext = subcontext_;
    }
    std::string event_trigger;
    std::map<std::string, std::string> property_triggers;
    if (auto result =
                ParseTriggers(triggers, action_subcontext, &event_trigger, &property_triggers);
        !result.ok()) {
        return Error() << "ParseTriggers() failed: " << result.error();
    }
#ifdef G1122717
    for (const auto& [property, _] : property_triggers) {
        action_manager_->StartWatchingProperty(property);
    }
#endif
    auto action = std::make_unique<Action>(false, action_subcontext, filename, line, event_trigger,
                                           property_triggers);
    action_ = std::move(action);
    return {};
}

ActionParser::ParseSection函式首先決議trigger,以trigger作為引數(triggle分為事件觸發和屬性觸發,都會被賦值給action物件的相應事件成員)構造一個結構action 并賦值給ActionParser的成員函式action_,之后使用ActionParser::ParseLineSection處理下一行命令,需要處理該on boot下的行命令ifup io,呼叫ActionParser::ParseLineSection對命令列進行處理,這個函式首先會根據ifup這個命令查詢BuiltinFunctionMap表找到ifup對應的處理函式,

Result<void> Action::AddCommand(std::vector<std::string>&& args, int line) {
    if (!function_map_) {
        return Error() << "no function map available";
    }
    auto map_result = function_map_->Find(args);
    if (!map_result.ok()) {
        return Error() << map_result.error();
    }
    commands_.emplace_back(map_result->function, map_result->run_in_subcontext, std::move(args),
                           line);
    return {};
}

如上圖ifup對應的處理函式為do_ifup,找到該函式后,根據該函式以及其引數 “io” 構造出一個command實體,并把它push到第一步構造出的action_的成員commands_中,接著繼續呼叫ActionParser::ParseLineSection()處理下一行命令start sshd同樣最后把其push到commands_中;

Result<void> ActionParser::EndSection() {
    if (action_ && action_->NumCommands() > 0) {
        action_manager_->AddAction(std::move(action_));
    }
    return {};
}

此時,ActionParser的成員函式action_ 就代表這個決議出來的action的整體,(這里可以看出action類就是對一個整個action的抽象)最后呼叫ActionParser::EndSection()把這個決議出來的整體的action_添加到ActionParser的單例成員(ActionManager型別)action_manager_中,ActionManager單例成員action_manager_中包含了所有決議出來的action,相當于之前使用的鏈表,這就是一個action的決議程序;

執行action

action和service決議完成之后,所有的action和service都被掛在ActionParser->action_manager_->actions_ServiceParser->service_list_->services_ 這兩個向量里;向量中的每一個action和service都完整的描述了一個action和service的section,在決議完成之后SecondStageMain()函式會呼叫如下代碼:

    am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
    am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
    am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
    am.QueueEventTrigger("early-init");

    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
    am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    // ... so that we can start queuing up actions that require stuff from /dev.
    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
    am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
    Keychords keychords;
    am.QueueBuiltinAction(
            [&epoll, &keychords](const BuiltinArguments& args) -> Result<void> {
                for (const auto& svc : ServiceList::GetInstance()) {
                    keychords.Register(svc->keycodes());
                }
                keychords.Start(&epoll, HandleKeychord);
                return {};
            },
            "KeychordInit");

    // Trigger all the boot actions to get us started.
    am.QueueEventTrigger("init");

am.QueueEventTrigger("early-init");意為early-init時間已經到來,可以執行triggle只為early-init的action了,QueueEventTrigger()函式只把triggle加入到ActionManager的event_queue_中,對于 am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups") 該函式在創建action的同時,把該事件觸發添加到ActionManager的事件佇列中,后續會遍歷ActionManager的event_queue_找到的triggle對應的action的command會被依次執行,程序如下:

while (true) {
        // By default, sleep until something happens.
        // 決定timeout的時間
        auto epoll_timeout = std::optional<std::chrono::milliseconds>{};

        auto shutdown_command = shutdown_state.CheckShutdown();
        // 判斷是否執行了關機
        if (shutdown_command) {
            HandlePowerctlMessage(*shutdown_command);
        }
        //判斷是否有事件需要處理
        if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
            am.ExecuteOneCommand();
        }
        if (!IsShuttingDown()) {
            auto next_process_action_time = HandleProcessActions();

            // If there's a process that needs restarting, wake up in time for that.
            if (next_process_action_time) {
                epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
                        *next_process_action_time - boot_clock::now());
                if (*epoll_timeout < 0ms) epoll_timeout = 0ms;
            }
        }

        if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
            // If there's more work to do, wake up again immediately.
            if (am.HasMoreCommands()) epoll_timeout = 0ms;
        }

        auto pending_functions = epoll.Wait(epoll_timeout);
        if (!pending_functions.ok()) {
            LOG(ERROR) << pending_functions.error();
        } else if (!pending_functions->empty()) {
            // We always reap children before responding to the other pending functions. This is to
            // prevent a race where other daemons see that a service has exited and ask init to
            // start it again via ctl.start before init has reaped it.
            ReapAnyOutstandingChildren();
            for (const auto& function : *pending_functions) {
                (*function)();
            }
        }
        if (!IsShuttingDown()) {
            HandleControlMessages();
            SetUsbController();
        }
    }

    return 0;
}

init最侄訓進入了無限回圈的監聽狀態,可以看到這里面一個核心函式就是 am.ExecuteOneCommand();該函式具體如下:

void ActionManager::ExecuteOneCommand() {
    {
        auto lock = std::lock_guard{event_queue_lock_};
        // 編譯event_queue_ 佇列直到有事件處理
        while (current_executing_actions_.empty() && !event_queue_.empty()) {
            //遍歷action的向量(鏈表),包括所有決議出來的action,每一個action都包含了完整的資訊(command,triggle等)
            for (const auto& action : actions_) {
            // 一個action是否要執行,事件trigger和屬性trigger都必須要滿足,這里檢查event_queue_的第一個元素的屬性事件是不是滿足,會從屬性map表里查找其值,如果滿足才會執行下一步
                if (std::visit([&action](const auto& event) { return action->CheckEvent(event); },
                               event_queue_.front())) {
                    //如果滿足這證明該action需要執行,把action壓入current_executing_actions_當前執行佇列中;
                    current_executing_actions_.emplace(action.get());
                }
            }
            event_queue_.pop();
        }
    }

    if (current_executing_actions_.empty()) {
        return;
    }
    // 從當前需要執行的action佇列中取出第一個要執行的action
    auto action = current_executing_actions_.front();

    if (current_command_ == 0) {
        std::string trigger_name = action->BuildTriggersString();
        LOG(INFO) << "processing action (" << trigger_name << ") from (" << action->filename()
                  << ":" << action->line() << ")";
    }
    // 開始執行action
    action->ExecuteOneCommand(current_command_);

    // 如果這是當前需要執行的action的最后一個命令,則從current_executing_actions_佇列中移除該action
    // 如果這個action只執行依次,則從actions_向量中移除它
    ++current_command_;
    if (current_command_ == action->NumCommands()) {
        current_executing_actions_.pop();
        current_command_ = 0;
        if (action->oneshot()) {
            auto eraser = [&action](std::unique_ptr<Action>& a) { return a.get() == action; };
            actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser),
                           actions_.end());
        }
    }
}

在上一步中,QueueEventTrigger("early-init")把early-init加入到event_queue_的佇列中,ExecuteOneCommand()一開始就遍歷之前決議的action向量表,使用每一個action自己的eventtrigger和event_queue_佇列中的第一個trigger對比,如果一樣,則繼續判斷該action中的PropertyTriggers,遍歷PropertyTriggers的map表查找當前action的PropertyTriggers值是否滿足條件,如果在eventtrigger相同且PropertyTriggers滿足條件的情況下,就把當前action push到current_executing_actions_佇列中;

接下來,從current_executing_actions_佇列中取出第一個要執行的action,呼叫action->ExecuteOneCommand(current_command_); 執行該命令,如下代碼

Result<void> Command::InvokeFunc(Subcontext* subcontext) const {
    if (subcontext) {
        if (execute_in_subcontext_) {
            return subcontext->Execute(args_);
        }

        auto expanded_args = subcontext->ExpandArgs(args_);
        if (!expanded_args.ok()) {
            return expanded_args.error();
        }
        return RunBuiltinFunction(func_, *expanded_args, subcontext->context());
    }

    return RunBuiltinFunction(func_, args_, kInitContext);
}

該action下的所有命令被執行完成,;

當一個 action 物件所有的 command 均執行完畢后,再執行下一個action,

當一個 trigger 觸發對應的所有 action 物件均執行完畢后(一個action有且僅有一個eventtrigger,但是一個eventtrigger可以對應多個action,他們可以由不同的屬性),再執行下一個 trigger 對應 action,

到此就是一個action被決議以及到被執行的整個程序!!因為service和action存在自有的特性,因此決議執行稍有不同,需要理解的可以自行分析;

文章一開始的四個問題,前三個都在決議分析的程序中解開了;

關于磁區掛載的后續碰到再另行補充吧!

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/246586.html

標籤:其他

上一篇:安卓基礎學習 Day01 |第一個安卓應用程式:Hello Word!

下一篇:安卓呼叫系統相機拍照并回傳,實作圖片預覽

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more