IT/Tizen2015.05.27 02:26

어느 플랫폼에서건 앱을 작성하게 되면,

앱의 라이프사이클에 대해 고민을 하게 됩니다.


앱 프로세스가 생성(fork & exec)된 직후에 수행해야하는 루틴,

종료 직전에 처리해야하는 루틴,

앱이 일시정지할 때 혹은 다시 시작할 때 필요한 루틴 등은,

앱 라이프 사이클을 구상할때 필히 고려해야하는 요소입니다.


안녕하세요, Tizen 개발자 윤진입니다.


타이젠 2.3 버전 기준으로 앱  라이프사이클은 "application"이라는 framework에서 관리합니다.

"application"의 소스를 살펴보려면,

아래처럼 command를 입력하여 소스를 clone 받을 수 있습니다.

간단하게 웹으로 보고 싶다면 여기를 누르면 됩니다.



앱은 main()에서 앱 라이프사이클을 관리해주는 ui_app_main()을 불러줘야 합니다.

ui_app_main()은 아래와 같이 선언되어있습니다.


/**
 * @brief Runs the application's main loop until ui_app_exit() is called.
 *
 * @details This function is the main entry point of the Tizen application.
 *          The app_create_cb() callback function is called to initialize the application before the main loop of application starts up.
 *          After the app_create_cb() callback function returns true, the main loop starts up and the app_control_cb() callback function is subsequently called.
 *          If the app_create_cb() callback function returns false, the main loop doesn't start up and app_terminate_cb() callback function is called.
 *          This main loop supports event handling for the Ecore Main Loop.
 *
 * @since_tizen 2.3
 * @param[in] argc The argument count
 * @param[in] argv The argument vector
 * @param[in] callback The set of callback functions to handle application lifecycle events
 * @param[in] user_data The user data to be passed to the callback functions
 *
 * @return 0 on success, otherwise a negative error value
 * @retval #APP_ERROR_NONE Successful
 * @retval #APP_ERROR_INVALID_PARAMETER Invalid parameter
 * @retval #APP_ERROR_INVALID_CONTEXT The application is illegally launched, not launched by the launch system
 * @retval #APP_ERROR_ALREADY_RUNNING The main loop already starts
 *
 * @see app_create_cb()
 * @see app_terminate_cb()
 * @see app_pause_cb()
 * @see app_resume_cb()
 * @see app_control_cb()
 * @see ui_app_exit()
 * @see #ui_app_lifecycle_callback_s
 */
int ui_app_main(int argc, char **argv, ui_app_lifecycle_callback_s *callback, void *user_data);

ui_app_main()에 들어가는 세 번째 인자 ui_app_lifecycle_callback_s는 라이프사이클을 위한 콜백을 등록해주는 부분입니다.

ui_app_lifecycle_callback_s는 아래와 같은 구조체입니다.


typedef struct
{
    app_create_cb create; /**< This callback function is called at the start of the application. */
    app_terminate_cb terminate; /**< This callback function is called once after the main loop of the application exits. */
    app_pause_cb pause; /**< This callback function is called each time the application is completely obscured by another application and becomes invisible to the user. */
    app_resume_cb resume; /**< This callback function is called each time the application becomes visible to the user. */
    app_control_cb app_control; /**< This callback function is called when another application sends the launch request to the application. */
} ui_app_lifecycle_callback_s;


위의 구조체를 하나 만들어준 후,

create와 terminate,

pause와 resume,

그리고 app_control 콜백을 만들어 구조체에 채워넣어주어야 합니다.


typedef bool (*app_create_cb) (void *user_data);
typedef void (*app_terminate_cb) (void *user_data);
typedef void (*app_pause_cb) (void *user_data);
typedef void (*app_resume_cb) (void *user_data);
typedef void (*app_control_cb) (app_control_h app_control, void *user_data);

app_create_cb은 앱이 fork-exec된 이후 mainloop에 진입하기 직전에 불립니다.

이후 앱이 terminate되어 메모리에서 제거된 후 다시 런칭되기 전까지 불리지 않습니다.

앱이 런칭된 직후 프로세스를 초기화하는 루틴은 이곳에서 수행됩니다.


static int __before_loop(struct ui_priv *ui, int *argc, char ***argv)
{
    int r;

    if (argc == NULL || argv == NULL) {
        _ERR("argc/argv is NULL");
        errno = EINVAL;
        return -1;
    }

    g_type_init();
    elm_init(*argc, *argv);

    r = appcore_init(ui->name, &efl_ops, *argc, *argv);
    _retv_if(r == -1, -1);

    LOG(LOG_DEBUG, "LAUNCH", "[%s:Platform:appcore_init:done]", ui->name);
    if (ui->ops && ui->ops->create) {
        r = ui->ops->create(ui->ops->data);
        if (r == -1) {
            _ERR("create() return error");
            appcore_exit();
            errno = ECANCELED;
            return -1; 
        }
        LOG(LOG_DEBUG, "LAUNCH", "[%s:Application:create:done]",
            ui->name);
    }   
    ui->state = AS_CREATED;

    __add_climsg_cb(ui);

    return 0;
}


위의 코드는 __before_loop() 함수에서 사용자가 등록한 create()를 불러주는 부분입니다.

(git://review.tizen.org/framework/appfw/app-core)

함수명에서 알 수 있듯,

mainloop에 진입하기 전에 create()을 불러주는 것을 확인할 수 있습니다.

create()를 실행한 직후 바로 mainloop에 진입합니다.

mainloop에서는 앱이 create()에서 수행한 루틴을 해석하여 첫번째 프레임을 렌더링하겠죠.


app_terminate_cb은 app_create_cb의 반대개념으로,

앱이 종료될 때 한 번 불립니다.

이 콜백에서 메모리를 정리하고,

현 프로세스에서 설정한 값들이나 사용자 데이터를 저장합니다.


app_pause_cb은 앱이 더 이상 화면에서 보이지 않게 된 순간 호출됩니다.

pause에서는 앱이 점유하고 있던 메모리를 내려놓거나,

pause되기 전까지 사용자가 수행한 결과물을 정리할 수 있습니다.


app_resume_cb은 화면에서 사라진 앱이 다시 화면에 보이게 되면 불립니다.

홈/메뉴를 통해 앱 A를 런칭했다가,

홈키나 파워키로 앱 B가 앱 A 위로 올라간 후,

다시 앱 A에 진입하게 되면 앱 A의 resume 콜백이 불리게 됩니다.

resume에서는 앱이 pause때 일시정지해두었던 기능을 다시 수행할 수 있도록 설정하면 됩니다.


app_control_cb은 위에서 설명한 create/terminate/pause/resume과는 달리 app_control_h을 인자로 받습니다.

app_control_cb은 create나 resume와는 다르게,

앱을 런칭하는 쪽에서 특정 정보를 app_control_h에 채워서 줄 수 있습니다.

때때로 앱은 런칭상황에 따라 다른 뷰를 보여주거나 다른 동작을 해야할 필요가 있겠죠.

예를 들어, 홈/메뉴를 통해 앱을 실행할 때와 스토어에서 앱을 실행할 때 서로 다른 뷰를 보여줄 필요가 있을 수도 있습니다.

이때 홈/메뉴는 홈/메뉴에서 앱을 런칭한다는 정보를 실어보내고,

스토어는 스토어에서 앱을 런칭한다는 정보를 실어보낸다면,

앱은 각각의 상황에 맞춰 뷰를 구성할 수 있습니다.


샘플앱을 런칭시켜 어떤 콜백이 불리는지 살펴보면 흥미로운 사실을 알 수 있습니다.

홈/메뉴를 통해 앱을 런칭시키면,

단지 app_create_cb만 불리지 않습니다.

총 3개의 콜백이 차례대로 불립니다.



app_create_cb은 애초에 설명한대로 앱을 런칭하여 mainloop에 진입하기 전에 불리게 됩니다.

하지만, app_control_cb은 왜 불리는 것일까요?

app_control은 런칭을 요청한 caller 앱에서 callee 앱으로 요청합니다.

callee 앱은 mainloop에 진입한 직후에 app_control_cb()을 처리하게 되죠.


static int __app_launch_local(bundle *b) 
{
    if (!aul_is_initialized())
        return AUL_R_ENOINIT;

    if (b == NULL) {
        _E("bundle for APP_START is NULL");
    }   
    if (g_idle_add(__app_start_internal, b) > 0)
        return AUL_R_OK;
    else
        return AUL_R_ERROR;
}

위의 코드는 caller 앱에서 g_idle_add()를 등록하여 callee 앱에서 등록한 app_control_cb을 불러주도록 설정하는 부분입니다.

저 코드로 앱이 idle 상태에 진입한 직후에 app_control_cb의 루틴을 타게 되는 것입니다.

(git://review.tizen.org/framework/appfw/aul-1)


app_control_cb 내에서 윈도우를 elm_win_activate()를 이용하여 최상단으로 올리면,

드디어 앱이 사용자에게 노출이 됩니다.


앱이 일부의 영역이라도 보이게 되면, app_resume_cb()이 불리게 됩니다.

resume()은 앱이 보이게 되면 불리는 콜백이기 때문에,

초기런칭루틴에서도 예외없이 불릴 수밖에 없습니다.

따라서 앱 런칭 시점에는 세 개의 콜백이 약간의 시간차를 두고 연달아 불리게 되죠.


Application Life Cycle Management


이상 타이젠의 앱 라이프사이클을 간단하게 설명하였습니다.

앱 라이프사이클을 잘 이용하면,

런칭속도 개선에 힘을 보탤 수 있습니다.


오늘은 여기서 마무리할까 합니다.

30분이면 포스팅할 수 있을거란 애초의 기대에서 벗어나

3시간째 글을 쓰고 있네요...

아무래도 내일 제 시간이 일어날 수 있을지 걱정이 됩니다.


끝_

Posted by 타이젠 개발자, 윤진