한밤 중 1시 51분,

잘까 말까 잠시 망설이다가,

DB에 대한 포스팅을 마무리하기 위해 세수를 하고 왔습니다.


이 포스팅에는 대단한 스키마가 나오는 것도 아니고,

어마무시한 쿼리문도 나오지 않습니다.


그저 비몽사몽 간에,

앱단에서 사용할만한 함수 몇가지를 공유하고자 합니다.


타이젠에서 공식적으로 지원하는 DB는 SQLite3입니다.

(참고, "Tizen 플랫폼 DB 엿보기-", http://storycompiler.tistory.com/25)

오래전부터 SQLite였고 그 기조는 당분간 바뀌지 않을겁니다.

따라서 SQLite를 다른 DB로 포팅할 일도 없겠군요.


그렇지만, 순전히 앱사이드에서 sqlite3를 덕지덕지 소스에 붙여 놓고 싶지도 않습니다.

sqlite3_open() / sqlite3_close()를 매번 수행하며,

그 때마다 온갖 예외처리를 하다보면,

분명 비슷한 루틴이 반복될 것이고,

그러는 와중에 버그라는 친숙한 녀석을 만나게 될겁니다.


따라서 sqlite3를 앱 내부 함수에 캡슐로 감춰서

조금이라도 노출을 막고,

조금이라도 쉽게 사용하고자 합니다.


static struct {
    sqlite3 *db;
} db_info = {
    .db = NULL,
};


우선, sqlite3_open()으로 나오는 db 핸들은 static 전역 변수로 선언해볼까요?

전역 변수는 매우 위험하여 가급적이면 사용하진 않지만,

- 개발하고자 하는 앱은 오직 한 프로세스에서만 사용하고,

- 단일스레드로 동작하며,

- reentrant 따위는 일어날리도 없기에,

단일 파일에서만 접근할 수 있는 static 전역 변수로 선언하곤 합니다.


이로써 모든 함수의 첫번째 인자(sqlite3 *db)는 생략할 수 있게 된 셈이지요.

덕분에 API 사용이 간단해졌습니다.

하지만, 이러한 제한적인 상황 외에 전역변수를 남용한다면,

숨겨진 버그를 중요한 순간마다 튀어나와 정신건강에 치명적인 독이 되고 맙니다. :)

특히, 라이브러리에서 전역변수는 고민에 고민을 거듭하여 제거하는게 좋겠지요.


db_open()은 이전 포스팅에서 설명한 바 있습니다.

Tizen 플랫폼에서 앱의 데이터 저장공간을 app_get_data_path()로 얻어와 DB 파일을 저장합니다.

(참고, "Tizen 앱 DB는 언제 어디에 초기화할까", http://storycompiler.tistory.com/29)


HAPI appl_error_e db_open(void)
{
    char *path = NULL;
    char db_file[FILE_LEN] = {0, };
    int ret = SQLITE_OK;

    path = app_get_data_path();
    retv_if(!path, APPL_ERROR_FAIL);
   
    snprintf(db_file, sizeof(db_file), "%s/%s", path, APP_DB_FILE);

    ret = sqlite3_open(db_file, &db_info.db);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        free(path);
        return APPL_ERROR_FAIL;
    }

    free(path);
    return APPL_ERROR_NONE;
}

HAPI void db_close(void)
{
    if (!db_info.db) {
        _D("DB is already NULL");
        return;
    }

    sqlite3_close(db_info.db);
    db_info.db = NULL;
}


db_open()과 db_close()는 db 핸들을 전역으로 뽑아놨기 때문에 함수 패러미터도 없습니다.

따라서 원하는 시점에 인자에 대한 고민없이 open / close 하면 됩니다.

편하군요~


HAPI sqlite3_stmt *db_prepare(const char *query)
{
    sqlite3_stmt *stmt = NULL;
    int ret = SQLITE_OK;

    retv_if(!query, NULL);

    ret = sqlite3_prepare_v2(db_info.db, query, strlen(query), &stmt, NULL);
    if (SQLITE_OK != ret) {
        _E("%s, %s", query, sqlite3_errmsg(db_info.db));
        return NULL;
    }

    return stmt;
}
HAPI appl_error_e db_next(sqlite3_stmt *stmt)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_FAIL);

    ret = sqlite3_step(stmt);
    switch (ret) {
    case SQLITE_ROW:
        return APPL_ERROR_NONE;
    case SQLITE_DONE:
        return APPL_ERROR_NO_DATA;
    default:
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}
HAPI appl_error_e db_reset(sqlite3_stmt *stmt)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_INVALID_PARAMETER);

    ret = sqlite3_reset(stmt);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    sqlite3_clear_bindings(stmt);

    return APPL_ERROR_NONE;
}


db_prepare(), db_next(), db_reset()은 sqlite3_prepare_v2(), sqlite3_next(), sqlite3_reset() 기능을 수행하기 위한 함수입니다.

우선, db_prepare()에서 패러미터로 전달받은 쿼리문을 파싱하여 sqlite3 *stmt를 리턴하죠.

stmt는 db_next()에 인자로 들어가서 실행됩니다.

db_reset()에서는 stmt를 재사용하기 위해 bind된 인자가 있으면 clear 합니다.

반드시 clear를 하고 다시 bind를 해야합니다.

어렴풋 reset을 제대로 하지 않아서 삽질을 했던 기억이 떠오르네요.

여기서 bind는 이어서 설명하는 db_bind_xxxx()함수와 연결됩니다.


HAPI appl_error_e db_bind_bool(sqlite3_stmt *stmt, int idx, bool value)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_FAIL);

    ret = sqlite3_bind_int(stmt, idx, (int) value);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}
HAPI appl_error_e db_bind_int(sqlite3_stmt *stmt, int idx, int value) {
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_FAIL);

    ret = sqlite3_bind_int(stmt, idx, value);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}
HAPI appl_error_e db_bind_double(sqlite3_stmt *stmt, int idx, double value)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_FAIL);

    ret = sqlite3_bind_double(stmt, idx, value);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}
HAPI appl_error_e db_bind_str(sqlite3_stmt *stmt, int idx, const char *str)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_FAIL);
    retv_if(!str, APPL_ERROR_FAIL);

    ret = sqlite3_bind_text(stmt, idx, str, strlen(str), SQLITE_TRANSIENT);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}


바인드는 db_prepare()에서 파싱한 쿼리문 중에 '?'로 처리한 부분에 치환되어 들어갑니다.

문자열 쿼리문을 더 빨리 처리하기 위해,

확정된 부분은 db_prepare()로 먼저 파싱하고,

확정할 수 없는 부분은 sqlite3_bind_xxxx()로 차후에 입력하게 됩니다.


예를 들어,

query = "select sequence from bookmarks where parent=? order by sequence desc";

(참고, git://review.tizen.org/apps/web/browser, src/database/browser-bookmark-db.cpp)

위와 같은 쿼리문의 where 절에 '?'로 처리된 부분은,

sqlite3_bind_int로 차후에 채워넣게 됩니다.


단, 항상 헛갈리는 부분인데 bind에 들어가는 index는 0이 아닌 1로 시작합니다. 응?

위의 예에서 parent의 '?'에 바인드하기 위해서는 '0'이 아니라 '1'을 index로 넣어야 합니다.

오래 전에 SQLite 서적을 보며 1부터 시작하는 납득할 만한 이유를 찾았던 것으로 기억하는데,

더 이상 기억이 나지 않는 것으로 보아 충분히 납득하지 못했었나 봅니다.


HAPI bool db_get_bool(sqlite3_stmt *stmt, int index)
{
    retv_if(!stmt, false);
    return (bool) sqlite3_column_int(stmt, index);
}
HAPI int db_get_int(sqlite3_stmt *stmt, int index)
{
    retv_if(!stmt, 0);
    return sqlite3_column_int(stmt, index);
}
HAPI int db_get_double(sqlite3_stmt *stmt, int index)
{
    retv_if(!stmt, 0);
    return sqlite3_column_double(stmt, index);
}
HAPI const char *db_get_str(sqlite3_stmt *stmt, int index)
{
    retv_if(!stmt, NULL);
    return (const char *) sqlite3_column_text(stmt, index);
}


위의 함수군은 select 쿼리의 결과로 나온 값들을 얻기 위해 사용합니다.

index는 0부터 차례대로 카운팅됩니다.

bind처럼 1부터 시작하지 않고 0부터 시작하니 헛갈리지 마세요.


HAPI appl_error_e db_finalize(sqlite3_stmt *stmt)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_INVALID_PARAMETER);

    ret = sqlite3_finalize(stmt);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}


stmt를 더 이상 사용할 필요가 없다면 db_finalize를 해야합니다.

이로써 db_prepare()에서 생성된 stmt의 라이프사이클이 드디어 종료됩니다.

db_prepare()로 핸들을 생성하였으면,

반드시 db_finalize()까지 진행해주세요.


HAPI appl_error_e db_exec(const char *query)
{
    sqlite3_stmt *stmt = NULL;

    retv_if(!query, APPL_ERROR_INVALID_PARAMETER);

    stmt = db_prepare(query);
    retv_if(!stmt, APPL_ERROR_FAIL);

    goto_if(APPL_ERROR_FAIL == db_next(stmt), ERROR);
    goto_if(APPL_ERROR_FAIL == db_finalize(stmt), ERROR);

    return APPL_ERROR_NONE;

ERROR:
    if (stmt) db_finalize(stmt);
    return APPL_ERROR_FAIL;
}


db_exec()는 create / drop / insert / update / delete 처럼,

유의미한 실행결과를 얻을 필요가 없는 쿼리문에 사용합니다.


쿼리문과 db_exec()만 사용하면 얼마든지 원하는 쿼리문을 수행할 수 있습니다.

하지만, 너무나 쉽게 사용할 수 있다보니 해킹의 위험에 노출될 수도 있죠.

개발자가 최초에 쿼리문을 하나 만들어 sqlite3_exec()에 넣었다고 해보죠.

해커가 쿼리문의 "%s" 같은 부분을 조작하여 다중 쿼리문으로 바꿔치기하면,

력된 다중 쿼리문이 sqlite3_exec()에 의해 모두 실행됩니다.

난리나게 되는 거죠.


따라서 언제나 오직 하나의 쿼리문만 실행할 수 있도록,

db_exec() 내부에서 prepare, next, finalize를 수행하도록 변경하였습니다.

next는 하나의 쿼리문만 수행하도록 설계가 되어 있답니다.


HAPI appl_error_e db_begin_transaction(void)
{
    int ret = SQLITE_BUSY;

    while (1) {
        ret = sqlite3_exec(db_info.db, "BEGIN IMMEDIATE TRANSACTION", NULL, NULL, NULL);
        if (SQLITE_BUSY != ret) {
            break;
        }
        /* FIXME : we have to fix this sleep */
        sleep(1);
    }

    if (SQLITE_OK != ret) {
        _E("sqlite3_exec() Failed(%d)", ret);
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}



HAPI appl_error_e db_end_transaction(void)
{
    int ret = SQLITE_OK;

    while (1) {
        ret = sqlite3_exec(db_info.db, "COMMIT TRANSACTION", NULL, NULL, NULL);
        if (SQLITE_BUSY != ret) {
            break;
        }
        /* FIXME : we have to fix this sleep */
        sleep(1);
    }

    if (SQLITE_OK != ret) {
        _E("sqlite3_exec() Failed(%d)", ret);
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}


트랜잭션이 필요할 수도 있겠네요.

BEGIN TRANSACTION - COMMIT TRANSACTION 사이에 벌어지는 디비 루틴은 한 번에 처리됩니다.


다만, 상기 함수에서 트랜잭션 쿼리를 수행할 때,

SQLITE_BUSY가 나는 경우 sleep()을 걸어놓았습니다.

sleep()은 프로세스 전체를 멈추게 할 수도 있으니 신중하게 사용해야 합니다.

프로세스의 디자인에 따라 이 부분은 각자의 mainloop()에 사용할 수 있는 timer 등으로 치환할 수도 있습니다.


이제는 정말 취침에 들어가야겠네요.

집중력이 바닥났습니다.


끝_


* 2015. 6. 2 정신차리고 일어나서 한밤중에 멋대로 휘갈긴 문장을 수정.


  1. developer 2016.06.29 16:12

    애정이 느껴지네요 잘보고 갑니다 ㅎㅎ

    • 안녕하세요, 개발자님. 애정은 넘치죠. 하하하. 제가 지금은 러시아에 나와 있어서 잠시 타이젠과 멀어져 있긴 한데... 언제나 애정만큼은 충만해있어요.

일요일 석양무렵,

관리비 용지 위에다가 앱을 위한 디비 스키마를 그렸습니다.


공동항목과 세대항목으로 나뉘어진 관리비 용지 가운데,

가장 큰 부분을 차지하는 주차비충당금을 보며 한숨을 쉬다가도,

디비에 새겨놓아야하는 필드가 떠오를때마다 다시 펜을 부여잡고 관리비 용지 귀퉁이에 스키마를 그렸죠.


테이블 여덟개를 끄적이고 나니

코드로 옮겨 확인을 해봐야겠다는 생각이 들었습니다.


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


앱을 위한 DB는 어느 시점에 초기화되어야할까요?

DB 초기화 시점은 아래 중에 하나일 것입니다.

- 앱이 설치되는 시점

- 앱이 처음 런칭되는 시점

- 앱에서 실질적으로 DB에 접근하는 시점


1. 앱이 설치되는 시점

앱이 설치되는 시점에 DB를 만들면,

앱을 런칭한 이후에 DB를 만들 필요가 없으므로,

런칭 혹은 런타임 중에 DB 초기화로 시간을 보내지 않아도 됩니다.


다만 멀티유저 시스템에서 하나의 앱을 다수의 사용자가 공유할 때,

각 사용자마다 DB 파일을 따로 관리하는 경우,

DB 파일을 런타임 중에 추가로 초기화해야합니다.


앱을 최초로 설치한 사용자는 앱을 설치하며 DB를 초기화하고,

앱을 최초로 설치하지 않은 사용자는 설치하며 DB를 초기화하지 못했기 때문에, 런타임 중에 초기화를 해야합니다.

이러한 루틴이 적합한 곳도 있을 수도 있지만,

일관성 있는 룰을 원하는 개발자는 다른 방식으로 DB를 초기화하고 싶겠죠?


2. 앱이 처음 런칭되는 시점

앱이 처음 런칭되는 시점에,

DB파일에 접근하여 DB파일이 준비되어 있지 않다면,

DB를 초기화하는 루틴을 추가할 수도 있습니다.


이 경우는 첫 런칭에서 DB를 초기화해야하기 때문에

첫 런칭 속도에 영향을 줄 수 있습니다.

사용자가 홈/메뉴에서 앱을 선택하였지만,

DB 초기화로 시간을 허비하다보면 최적화가 되어 있지 않은 앱으로 의심 받을 수 있죠. :)


하지만, 멀티유저 환경에서 유연하게 대응할 수 있습니다.

앱을 설치한 유저 외에 다른 유저가 앱을 런칭할 때,

홈 디렉토리에서 DB 파일을 찾아보고 DB 파일이 없으면 바로 생성하면 됩니다.

앱을 설치한 유저나 사용하는 유저나 모두 일관되게 앱을 런칭하며 DB를 만들게 되죠.


3. 앱에서 실질적으로 DB에 접근하는 시점

DB 초기화 루틴을 최대한 뒤로 미룬다.

DB에서 관리하는 데이터가 런칭과 무관하다면,

어렵지 않게 런칭 루틴에서 떼어내어 실제 DB가 사용되는 시점으로 초기화를 늦출 수 있습니다.

이 경우 런칭 성능에는 DB가 전혀 영향을 미치지 않는다는 장점이 있습니다.


다만 DB에 접근하는 루틴과 사용자 동작과 겹치게 된다면,

사용자는 앱이 순간적으로 멈춘다는 인상을 받을 수도 있겠죠.


위의 세 가지 모두 장단점이 있습니다.

모두의 장점을 취할 수는 없기 때문에,

여기서는 2번, 앱이 런칭되며 DB를 초기화하는 루틴을 선택하기로 하죠.

첫 런칭에서 DB 초기화로 약간의 시간을 소요하고 난 뒤,

앱에 진입하고 나서부터는 적어도 DB 때문에 사용자가 기다리지 않도록 하고 싶습니다.


그렇다면, DB 파일은 어디에 생성해야하는 것일까요?


Tizen 2.4에서 제공하는 API는,

앱의 데이터 영역을 확정하여 알려줍니다.



위의 함수에서 데이터를 저장할 디렉토리를 얻어온 후,

런칭 직후 위의 디렉토리에 DB가 없으면 바로 초기화 루틴으로 진입하면 됩니다.


그렇다면, 위의 함수는 멀티유저에 대비가 되어 있을까요?

현재 소스를 따라가 보면,

앱의 pkgid로 data 디렉토리를 한정짓고 있네요.


static int __get_root_path(char *root_path, int root_path_len, bool external)
{
    static char pkgid[_MAX_PACKAGE_ID_LEN] = {0,};
    const char *specific_path = external ? _EXTERNAL_APP_SPECIFIC_PATH : _APP_SPECIFIC_PATH;

    if (pkgid[0] == '\0')
    {  
        int err = __get_pkgid(pkgid, _MAX_PACKAGE_ID_LEN - 1);
        if (err != AUL_R_OK)
        {
            return err;
        }
    }  
    {  
        int specific_path_len = strlen(specific_path);
        int pkgid_len = strlen(pkgid);
        int total_len = specific_path_len + pkgid_len + 1;

        if (total_len > root_path_len)
        {
            _E("Assert: path length %d is too long", total_len);
            assert(false);
        }

        strncat(root_path, specific_path, specific_path_len);
        strncat(root_path + specific_path_len, pkgid, pkgid_len);
        root_path[specific_path_len + pkgid_len] = '/';
    }  

    return AUL_R_OK;
}


위의 함수를 사용하여,

아래와 같이 db_open()을 구현할 수 있습니다.


HAPI appl_error_e db_open(void)
{
    char *path = NULL;
    char db_file[FILE_LEN] = {0, };
    int ret = SQLITE_OK;

    path = app_get_data_path();
    retv_if(!path, APPL_ERROR_FAIL);
   
    snprintf(db_file, sizeof(db_file), "%s/%s", path, APP_DB_FILE);

    ret = sqlite3_open(db_file, &db_info.db);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        free(path);
        return APPL_ERROR_FAIL;
    }  

    free(path);
    return APPL_ERROR_NONE;
}


위처럼 app_get_data_path()로 data를 저장할 디렉토리를 얻어온 후,

DB 명을 대입하여 sqlite3_open()을 수행하면 되죠.


이제 막 DB 초기화와 관련된 걸음마를 떼었습니다.

앞으로 갈 길이 삼천리인데 또 눈꺼풀이 잠겨오네요.


끝_

  1. initor 2019.05.17 20:43 신고

    타이젠 블로그에서 많은 도움을 얻고 있습니다. 위 코드에서 HAPI appl_error_e 리턴형이나 APPL_ERROR_FAIL 등의 매크로는 어디에 정의된 것인지 모르겠습니다. __get_pkgid 등의 함수에 대해서도 알 수가 없습니다.

개발자로서 살아온 기간내내

함수 안팎으로 로그를 '신나게' 심어왔습니다.


코드에 로그가 없는 경우는,

- 아직 로그를 심기 직전이거나

- 동작을 확인하고 로그를 지운 뒤겠죠.


앱이 물고 있는 프레임워크의 동작이 언제나 완벽하다면,

앱에서 로그를 출력할 일은 크게 줄어들 것입니다.


하지만, 언제나 프레임워크는 개발막바지가 되어야 비로소 쓸만해지죠. :)

모든 함수에 에러체크는 필수이고,

에러로그는 가장 쉬우면서 확실한 디버깅 방법입니다.


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


타이젠은 플랫폼 차원에서 로그를 남길 수 있는 방법을 제공합니다.

printf(), fprintf()도 사용할 수 있지만,

- SDK에 로그를 노출하거나,

- 시스템 로그들과 함께 사용하기 위해서는,

타이젠에서 제공하는 dlog를 사용해야 합니다.


타이젠 개발자 사이트(http://developer.tizen.org)에 들어가서,

하단의 API References 메뉴에 들어갑니다.



왼편에 있는 프레임에서,

API References > Native Application > Native API Reference > System > dlog

위의 항목을 선택합니다.



플랫폼에서 지원하는 로그를 위한 함수는 2가지가 있습니다.

dlog_print()와 dlog_vprint()입니다.

위의 두 함수는 공통적으로 priority와 tag를 입력해주어야 합니다.


priority는 출력할 로그의 종류에 따라 선택하면 되죠.

개발자의 필요에 따라 사용하는 로그이므로,

priority 사용에 제한을 두진 않습니다.

(소스 출처 : git://review.tizen.org/framework/system/dlog, tizen_2.3)


 * @brief log priority values, in ascending priority order.
 * @since_tizen 2.3
 */
typedef enum {
    DLOG_UNKNOWN = 0, /**< Keep this always at the start */
    DLOG_DEFAULT, /**< Default */
    DLOG_VERBOSE, /**< Verbose */
    DLOG_DEBUG, /**< Debug */
    DLOG_INFO, /**< Info */
    DLOG_WARN, /**< Warning */
    DLOG_ERROR, /**< Error */
    DLOG_FATAL, /**< Fatal */
    DLOG_SILENT, /**< Silent */
    DLOG_PRIO_MAX   /**< Keep this always at the end. */
} log_priority;


tag는 일반적으로 자신의 모듈명을 입력합니다.

플랫폼에서 사용하는 tag는 대부분 대문자와 '_'(underscore)를 사용하고 있네요.


#define LOG_TAG "BUNDLE"

#define LOG_TAG "PKGMGR"

#define LOG_TAG "CAPI_APPFW_APPLICATION"

#define LOG_TAG "VOLUME"


하지만, 소문자만으로 태그이름을 정한 모듈도 있긴 합니다.

이를 보건대 태그이름에 대한 정책적인 제약은 없는 것으로 보이네요.

모듈로그는 태그명으로 검색할 수 있으므로 차후를 위해 잘 기억해두세요.


/**
 * @addtogroup CAPI_SYSTEM_DLOG
 * @{
 */
/**
 * @brief     Send log with priority and tag.
 * @details   for application
 * @since_tizen 2.3
 * @param[in] prio log_priority
 * @param[in] tag tag
 * @param[in] fmt format string
 * @return On success, the function returns the number of bytes written.
 *         On error, a negative errno-style error code
 * @retval #DLOG_ERROR_INVALID_PARAMETER Invalid parameter
 * @retval #DLOG_ERROR_NOT_PERMITTED Operation not permitted
 * @pre       none
 * @post      none
 * @see       dlog_vprint
 * @code
#include<dlog.h>
int main(void)
{
    int integer = 21;
    char string[] = "test dlog";

    dlog_print(DLOG_INFO, "USR_TAG", "test dlog");
    dlog_print(DLOG_INFO, "USR_TAG", "%s, %d", string, integer);
    return 0;
}
 * @endcode
 */
int dlog_print(log_priority prio, const char *tag, const char *fmt, ...);
/**
 * @brief     Send log with priority, tag and va_list.
 * @details   for application
 * @since_tizen 2.3
 * @param[in] prio log_priority
 * @param[in] tag tag
 * @param[in] fmt format string
 * @param[in] ap va_list
 * @return On success, the function returns the number of bytes written.
 *         On error, a negative errno-style error code
 * @retval #DLOG_ERROR_INVALID_PARAMETER Invalid parameter
 * @retval #DLOG_ERROR_NOT_PERMITTED Operation not permitted
 * @pre       none
 * @post      none
 * @see       dlog_print
 * @code
#include<dlog.h>
void my_debug_print(char *format, ...)
{
    va_list ap;

    va_start(ap, format);
    dlog_vprint(DLOG_INFO, "USR_TAG", format, ap);
    va_end(ap);
}

int main(void)
{
    my_debug_print("%s", "test dlog");
    my_debug_print("%s, %d", "test dlog", 21);
    return 0;
}
 * @endcode
 */
int dlog_vprint(log_priority prio, const char *tag, const char *fmt, va_list ap);


함수 정의를 살펴보면,

priority와 tag를 지정한 뒤,

로그로 출력하고자 하는 포맷을 넣어주면 됩니다.

두 함수 모두 내부적으로 동일한 루틴을 따르네요.


static int __write_to_log_sd_journal(log_id_t log_id, log_priority prio, const char *tag, const char *msg)
{
    pid_t tid = (pid_t)syscall(SYS_gettid);
    /* XXX: sd_journal_sendv() with manually filed iov-s might be faster */
    return sd_journal_send("MESSAGE=%s", msg,
                   "PRIORITY=%i", dlog_pri_to_journal_pri(prio),
                   "LOG_TAG=%s", tag,
                   "LOG_ID=%s", dlog_id_to_string(log_id),
                   "TID=%d", tid,
                   NULL);
}


dlog는 내부적으로 systemd의 journal을 사용합니다.

sd-journal(systemd's journal)은 systemd에서 관리하는 로그를 위한 서비스입니다.

dlog는 sd_journal_send()를 사용하여 sd-journal에 로그를 입력하고 있습니다.

sd-journal에 대한 이야기는 기회가 있을때 다시 하기로 하죠.


앱에서는 로그를 수도 없이 사용하므로,

간단히 사용할 수 있는 매크로 함수를 사용하기로 합니다.

실제 타이젠의 시스템 앱에서 사용하고 있는 매크로 함수가 있습니다.

시스템 앱에서 사용하고 있는 매크로 함수를,

SDK로 개발하는 앱에서 사용할 수 있도록 수정하였습니다.

(소스출처 : http://www.freedesktop.org/software/systemd/man/sd-journal.html, master)


#ifndef __YOUR_APPLICATION_LOG_H__
#define __YOUR_APPLICATION_LOG_H__

#include <dlog.h>

#undef LOG_TAG
#define LOG_TAG "YOUR_APPLICATION"

#define COLOR_RED "\033[0;40;31m"
#define COLOR_LIGHTBLUE "\033[1;40;34m"
#define COLOR_YELLOW "\033[1;40;33m"
#define COLOR_END "\033[0;m"
#define COLOR_GRAY "\033[1;40;30m"
#define COLOR_MAGENTA "\033[1;35m"

#if !defined(_D)
#define _D(fmt, arg...) dlog_print(DLOG_DEBUG, LOG_TAG, COLOR_YELLOW fmt COLOR_END"\n", ##arg)
#endif

#if !defined(_W)
#define _W(fmt, arg...) dlog_print(DLOG_WARN, LOG_TAG, COLOR_MAGENTA fmt COLOR_END"\n", ##arg)
#endif

#if !defined(_E)
#define _E(fmt, arg...) dlog_print(DLOG_ERROR, LOG_TAG, COLOR_RED fmt COLOR_END"\n", ##arg)
#endif

#define retv_if(expr, val) do { \
    if(expr) { \
        _E("(%s) -> %s() return", #expr, __FUNCTION__); \
        return (val); \
    } \
} while (0)

#define ret_if(expr) do { \
    if(expr) { \
        _E("(%s) -> %s() return", #expr, __FUNCTION__); \
        return; \
    } \
} while (0)

#define goto_if(expr, val) do { \
    if(expr) { \
        _E("(%s) -> goto", #expr); \
        goto val; \
    } \
} while (0)

#define break_if(expr) { \
    if(expr) { \
        _E("(%s) -> break", #expr); \
        break; \
    } \
}

#define continue_if(expr) { \
    if(expr) { \
        _E("(%s) -> continue", #expr); \
        continue; \
    } \
}

#endif                /* __YOUR_APPLICATION_LOG_H__ */


헤더를 사용하려면 아래 로그헤더를 다운로드 받으시면 됩니다.

log.h

헤더를 사용하시려면,

- LOG_TAG에 자신의 모듈이름을 적어주세요.

- 일반적인 디버그 상황에서 사용하는 디버그 로그는 _D 매크로 함수를 사용하고,

    _D("%s's GEOMETRY : [%d, %d, %d, %d]", (const char *) data, x, y, w, h);

- 에러는 아니지만, 일반적인 디버그 로그보다 위험한 경우는 워닝 로그 _W 매크로 함수를 사용하고,

    _W("cannot find index.");

- 에러 상황에서는 _E 매크로 함수를 사용하시면 됩니다.

    _E("Failed to remove vconf %s", "memory/menuscreen/mapbuf");


로그를 위한 장치가 준비되었으니,

이제 본격적으로 코딩을 하며 '신나게' 로그를 삽입하면 됩니다.

SDK > 하단 프레임 > Log

위의 메뉴와 친해지도록 하세요. :)


끝_

간단한 앱을 하나 짜더라도

사용자로그를 관리하기 위해서는 DB가 필요하죠.


하지만, 플랫폼마다 지원하는 DBMS가 달라서,

낯선 DBMS 환경에 적응해야하는 경우도 있습니다.


다행스럽게도 타이젠에서는 모바일이나 웨어러블 같은 임베디드 환경에서,

가장 널리 사용되고 있는 SQLite를 지원하고 있습니다.


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


타이젠 2.3에서 지원하고 있는 API 레퍼런스를 살펴보면,

API Reference > Native Application > Native API Reference > Base > sqlite

위와 같이 SQLite 항목을 찾을 수 있습니다.



SQLite 항목을 살펴보면,

SQLite는 가벼운 sql 데이터베이스로 라이브러리 형태로 제공된다고 언급되어 있습니다.

플랫폼에 탑재된 버전은 3.7.13(2015. 5. 28 현재 3. 8. 10. 2가 최신버전)이고,

Documentation은 SQLite 공식사이트(http://www.sqlite.org/docs.html)에 방문하여 보아야 합니다.

SQLite는 시중에 좋은 책도 많이 나와있고,

구글링으로도 깊이 있는 정보를 찾을 수 있기 때문에 API 사용법에 대한 언급은 하지 않겠습니다.


타이젠 플랫폼 소스를 살펴보면 몇가지 흥미로운 사실을 발견할 수 있습니다.


- sqlite3_open()을 직접 호출해주는 라이브러리가 몇개 있습니다.

  libslp-memo, libslp-alaram는 라이브러리 이름으로 파악해보건대,

  위의 라이브러리를 사용하는 앱에서만 DB 정보를 독점하는 것으로 보입니다.

  다른 프로세스에 앱 DB에 대한 정보를 건네줄 필요가 없기 때문에,

  앱에서 사용하는 라이브러리내에 직접 sqlite3를 사용했겠지요.


- 앱단에서는 sqlite3_open() 대신 db_util_open()이라는 유틸리티 함수를 사용하고 있네요.

  db_util_open() 함수 내부에서 sqlite3_open()을 하고, busy handler와 journal mode를 설정하고 있습니다.


/**
 * @brief invoke sqlite3_open with platform common configuration
 * @details register busy handler, create localized collation
 * @param [in] database file name (UTF-8)
 * @param [out] SQLite database handle
 * @param [in] option value
 * @return sqlite3 function return value will be returned
 * @see db_util_open_with_options()
 * @see db_util_close()
 *
 */
EXPORT_API int db_util_open(const char *pszFilePath, sqlite3 **ppDB,
                        int nOption);

위의 함수는 "git://review.tizen.org/framework/appfw/libslp-db-util"의 "include/util-func.h"에서 볼 수 있습니다.

첫번째 인자로 DB 파일이름을 넣어주면,

두번째 인자로 sqlite handle을 넘겨줍니다.


static int __db_util_open(sqlite3 *ppDB)
{
   /* ...생략... */
    /* Register Busy handler */
    rc = sqlite3_busy_handler(ppDB, __db_util_busyhandler, NULL);
    if (SQLITE_OK != rc) {
        DB_UTIL_TRACE_WARNING("Fail to register busy handler\n");
        sqlite3_close(ppDB);
        return rc;
    }

    /* Code to change default journal mode of sqlite3 is enabled so this option is disabled */
    /* Enable persist journal mode */
    rc = sqlite3_exec(ppDB, "PRAGMA journal_mode = PERSIST",
            NULL, NULL, &pszErrorMsg);
    if (SQLITE_OK != rc) {
        DB_UTIL_TRACE_WARNING("Fail to change journal mode: %d, %d, %s, %s\n",
                                sqlite3_errcode(ppDB),
                                sqlite3_extended_errcode(ppDB),
                                pszErrorMsg,
                                sqlite3_errmsg(ppDB));
        sqlite3_free(pszErrorMsg);
        sqlite3_close(ppDB);
   /* ...생략... */ }


위의 코드에서 busy handler를 등록하여,

DB가 EXCLUSIVE 등의 이유로 busy인 경우 retry하도록 설정합니다.


그리고 저널모드로 Persist를 지정해줍니다.

Persist는 롤백 루틴에 사용하는 파일을 일정한 크기로 유지하여,

파일 create & destroy에 따른 비용을 줄이고,

read & write를 최소한의 비용으로 할 수 있게,

디스크 공간은 버리고 성능을 택하는 저널방법입니다.

적어도 이 API만 보면, 타이젠은 성능 최우선의 정책을 취하는 것으로 보입니다.


busy handler에서는 usleep()을 직접 썼습니다.

usleep()은 프로세스 자체를 멈춰버리므로,

busy 상황에서 db_util_open()을 사용하여 강제로 앱프로세스를 멈추게 합니다.


static int __db_util_busyhandler(void *pData, int count)
{
    if(5 - count > 0) {
        DB_UTIL_TRACE_DEBUG("Busy Handler Called! : PID(%d) / CNT(%d)\n", getpid(), count+1);
        usleep((count+1)*100000);
        return 1;
    } else {
        DB_UTIL_TRACE_DEBUG("Busy Handler will be returned SQLITE_BUSY error : PID(%d) \n", getpid());
        return 0;
    }
}


db_util_open()은 framework이나 app단 모두에서 고루 사용하고 있습니다.

framework/api/favorites

framework/pim/calendar-service

framework/pim/contact-service

framework/messaging/email-service

framework/appfw/ail

framework/appfw/slp-pkgmgr

framework/appfw/alarm-manager

apps/home/menu-screen

apps/home/notification

apps/web/browser

등 상당수의 framework와 app에서 사용하고 있습니다.


DB를 open하는 방식으로도 타이젠 플랫폼의 정책을 엿볼 수 있습니다.

플랫폼 버전이 업그레이드될 때마다,

세부구현방식은 바뀔 수 있지만,

플랫폼 정책은 바꾸기 힘들겠죠.

경량화와 빠른 속도를 특장점으로 내세우는 타이젠은,

어쩌면, ivi나 IoT에 적합한 플랫폼일지도 모른다는 생각이 듭니다.


이제 너무 졸려 더 이상 단어를 쓸 여력이 없는 새벽 1시 54분에 마칩니다.


끝_




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

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


앱 프로세스가 생성(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시간째 글을 쓰고 있네요...

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


끝_

혼자 개발하지 않는 이상,

팀원과 개발 중인 소스를 시시각각 공유해야할 필요가 있습니다.

타인과 데이터를 공유하는 수많은 방법이 있겠지만,

2015년 현재 가장 대중적으로 이용되는 git을 이용하여 소스를 공유하고자 합니다.


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


Tizen SDK 내에 git repository를 clone하거나 pull 받을 수 있는 기능은 없습니다.

Tizen SDK와 git은 서로 별개의 독립적인 모듈입니다.


따라서,

Tizen SDK에서 작업한 후,

git에서 수정사항을 push 하는 과정을 거쳐줘야 합니다.

vim에서 에디팅을 하고,

커맨드라인에서 git 명령어로 push 하는 것과 마찬가지의 절차입니다.


* 준비물

1. GIT Repository

2. Tizen SDK Project 

   (http://storycompiler.tistory.com/20 샘플앱 생성 참고)


위의 준비물은 미리 준비되어 있다고 가정하겠습니다.

Tizen SDK를 실행하여 git으로 공유하고자 하는 프로젝트를 엽니다.

그리고 File > Export 메뉴를 선택합니다.



Export 팝업창이 아래와 같이 뜹니다.

General > File System 메뉴를 선택합니다.



Next 버튼을 누르면,

SDK에 등록된 모든 프로젝트 리스트가 왼편에 나옵니다.

git repo.로 옮기고자하는 프로젝트를 선택합니다.

오른편 박스에는 해당 프로젝트에서 Export 하고자 하는 메타파일들이 나옵니다.

해당 파일들은 프로젝트를 위한 메타파일들이므로 모두 선택해야합니다.



To directory는 옮기고자 하는 git repo.의 루트 디렉토리 혹은 부모 디렉토리를 선택합니다.

- 루트 디렉토리 : git repo. 밑에 프로젝트명으로 디렉토리가 생성됩니다.

  git repo.에 다수의 프로젝트를 관리하고자 할때 효과적입니다.

  프로젝트 디렉토리 밑에 inc, src, res 와 같은 각종 디렉토리가 생성됩니다.

- 부모 디렉토리 : 프로젝트명과 git repo.명이 동일하게 설정된 경우,

  부모 디렉토리를 선택하면 git repo. 루트디렉토리에 바로 inc, src, res와 같은 디렉토리가 생성됩니다.

  한 git repo.에 하나의 프로젝트를 관리하고자 할때 사용하면 됩니다.


git repo.의 루트 디렉토리를 선택하여,

루트 디렉토리 밑에 프로젝트 디렉토리를 생성하였지만,

프로젝트 디렉토리만 없애고 루트디렉토리를 사용하길 원할 수도 있겠죠?

그럴 경우 프로젝트 디렉토리 안에 있는 모든 파일(숨김 파일 포함)을 루트 디렉토리로 복사하세요.


여기까지 진행하였다면,

git add / commit / push를 하세요.

팀원과 소스를 공유하기 위한 모든 준비가 끝났습니다.


팀원은 위에서 push한 커밋을 pull 받은 후,

Tizen SDK에서 File > Import 메뉴를 선택합니다.



Import 타입을 선택할 수 있는 팝업메뉴가 나타납니다.

General > Existing Projects into Workspace

위의 메뉴에 진입합니다.



Next 버튼을 눌러 다음으로 진행합니다.

Select root directory 라디오 버튼을 선택합니다.

그리고, Import 하고자 하는 git repo.의 루트 디렉토리를 기입하세요.



Finish 버튼을 누르면,

SDK 좌측에 좀 전에 Import한 프로젝트가 있는 것을 확인할 수 있습니다.


이제 SDK에서 코드를 마음껏 수정하고,

git 명령어를 통해 수정사항을 관리하면 됩니다.


팀원들과 합심하여 앱개발을 시작해보세요.


끝_

애초에 타이젠 앱을 짜려고 마음을 먹은 것은,

5월 5일 어린이날,

초딩러시를 피하기 위해 두문불출할 때였습니다.

(http://storycompiler.tistory.com/14)


그날 이후,

SDK를 설치하고(http://storycompiler.tistory.com/15),

타이젠 오픈소스를 다운로드 받고(http://storycompiler.tistory.com/16),

Z1을 구해 개발자+시료인증(http://storycompiler.tistory.com/20)까지 하니,

5월 25일 부처님 오신 날이 되었네요.


20일이 지나는 동안,

본격적인 코딩은 커녕 앱 아키텍쳐를 고민해보지도 않았다.

갈 길이 멀어요.


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


그렇다고 오늘 아키텍쳐를 고민하거나 코딩을 하겠다는 것은 아닙니다.

오늘은 템플릿 소스를 분석하여 타이젠 앱 디렉토리 구조를 살펴보겠습니다.


SDK를 런칭하여 템플릿 프로젝트를 만들어보죠.

File > New > Tizen Native Project

위의 메뉴로 진입하여 Native Project를 생성해보겠습니다.



Template > MOBILE-2.3 > UI Application > Basic UI Application (EDC)

차례대로 선택하여 기본앱을 위한 템플릿을 선택합니다.

하단의 Project name은 원하는 이름으로 지정합니다.

Package name은 Project name에 "org.tizen." 접두사가 붙습니다.

하지만, 원하는 대로 수정할 수 있습니다.

Finish를 눌러 프로젝트를 생성해보세요.



SDK 상에는 위의 디렉토리와 파일이 자동으로 생성됩니다.

앱의 root 디렉토리에 5개의 서브디렉토리와 1개의 메타파일이 생겼습니다.

위에 누락된 data 디렉토리를 추가하여 간단하게 설명하면 아래와 같습니다.



폴더명은 축약어와 풀네임을 혼용하였습니다.

inc, res, src, lib은 축약어로,

data와 shared는 풀네임으로 디렉토리 이름을 정했습니다.

최근 네이밍 트랜드로는 풀네임이 대세이지만,

inc, res, src, lib은 축약어로 오랜 기간 사랑을 받았기 때문에,

(오랜 고민과 토의 끝에) 축약어와 풀네임을 혼용하기로 결정된 것으로 보이네요.


- inc에는 앱 내부에서 정의하고 사용할 함수의 선언이 담긴 헤더파일을 넣습니다.

  대규모 프로젝트인 경우 inc 내에 디렉토리가 추가될 수 있습니다.

  디렉토리 내에 헤더파일이 각각의 의미에 맞게 배치되겠죠.


- src에는 SDK를 통하여 빌드될 소스파일들이 채워집니다.

  소스 디렉토리 내부에도 트리구조의 디렉토리를 추가할 수 있습니다.

  소스파일은 각각의 의미에 맞게 디렉토리에 배치합니다.


- res에는 앱 내에서만 사용할 리소스파일, edc 혹은 이미지 파일들을 넣습니다.

  edc 파일은 타이젠의 네이티브 UIFW인 EFL에서 사용하는 레이아웃을 위한 소스파일입니다.

  edc 파일은 빌드타입에 파싱되어 이미지파일까지 머금은 edj 파일로 변환이 됩니다.

  edj 파일은 c 파일 내부에서 edje_object prefix의 함수군으로 통제할 수 있습니다.

  따라서 기본적으로 생성되는 디렉토리 이름이 res/edje 입니다.


- lib은 빌드가 완료된 라이브러리 파일 자체가 놓이는 곳입니다.

  SDK로 빌드할 수 없어 외부에서 빌드했거나,

  소스없이 라이브러리만 사용할 수 있는 경우 lib 디렉토리에 라이브러리 파일을 위치시키죠.


- data는 앱내에서 읽고 쓰기가 가능한 xml이나 db파일을 저장하는 곳입니다.

  앱에서 사용하는 데이터 파일들을 초기화하기 위한 목적으로 사용합니다.


- shared는 아이콘처럼 다른 앱에서 접근할 수 있는 공간입니다.

  타이젠의 앱디렉토리 정책상 다른 앱의 디렉토리에는 앱권한의 프로세스가 접근할 수 없습니다.

  다만 역시 앱권한의 홈/메뉴/작업관리자 등의 앱에서 앱아이콘을 보여주어야 하기 때문에,

  앱 내의 shared 디렉토리에 위치한 아이콘에는 어느 앱이나 접근할 수 있게 허용하였습니다.


앱 소스 디렉토리 구조에는 군더더기가 없습니다.

SDK로 빌드를 진행하면,

패키지도 별도의 디렉토리 구조를 갖게 됩니다.

패키지 디렉토리 구조도 차후에 다시 설명할 기회가 있을 것입니다.


2015년에도 '월'요일에 공휴일을 만들어주신 부처님의 한 수에 감사드리며 포스팅을 끝마칩니다.


끝_


* References

https://developer.tizen.org/dev-guide/2.2.1/org.tizen.native.appprogramming/html/app_dev_process/project_files_and_folders.htm


지난 5월 21일,

Tizen Seller Office에서 메일이 하나 왔습니다.



인도, 방글라데시에 이어 스리랑카에서도 유료앱을 판매할 수 있다는 공지였어요.

인도나 방글라데시의 경우,

Z1이 이미 판매된 국가이기에 유료앱 판매 대상이 되는 것은 당연한 일이었죠.


하지만, 'Z1이 공식 판매되지 않은' 스리랑카가 여기에 추가가 되었습니다.

- 스리랑카가 인도생활권 내에 편입되어 있어 Z1을 스리랑카에서도 구할 수 있거나,

- 스리랑카에도 공식적인 발표없이 판매가 되고 있기 때문 아닐까요?


182개국 중 3개국에서 유료앱 판매가 가능하게 되었으니,

이제 남은 179개국까지 유료앱 판매가 확대되길 기대해봅니다.



+ 번외편

인도는 힌두교,

방글라데시는 이슬람교,

스리랑카는 불교이니,

다음 유료앱 판매 대상국가의 주종교는 별도의 관전포인트가 되겠네요. :)


끝_

Tizen SDK를 실행시키고,

Tizen Z1을 USB로 데스크탑과 연결하였습니다.

하지만, SDK에서 기본 제공하는 샘플앱을 Z1에 설치할 수 없었습니다.


Tizen SDK에서 제공하는 샘플앱을 정상적인 방법으로 Build하여,

Tizen Z1에 정상적인 방법으로 Install하고자 하는데 에러가 나네요.


무슨 일인걸까요?


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


인스톨 에러는 결코 SDK의 문제가 아닙니다.

문제라면 Z1의 문제이죠.

Z1은 판매용 상품이니 만큼,

소비자가 크래킹의 위협에서 벗어날 수 있도록 보안을 견고하게 다져놓았습니다.

플랫폼 입장에서는 사용자가 인증받지 않은 앱을 설치하는 것만큼 위험한 것은 없습니다.


타이젠 스토어에는,

- 등록된 개발자만이 업로드를 할 수 있고,

- 업로드한 앱은 검증을 통과해야,

비로소 스토어에 노출됩니다.

따라서 타이젠 스토어를 통해 내려받은 앱은 안심하고 사용할 수 있겠죠.


하지만, 사용자가 타이젠 스토어가 아닌 다른 곳에서 불량앱을 다운로드 받을 수도 있으니,

Z1 입장에서는 다시 한번 인증하는 절차를 통해 불량앱을 적발하고자 합니다.


따라서 타이젠 SDK에서 만든 샘플앱도 적절한 인증절차를 거치지 않으면,

Z1 입장에서는 그저 불량앱에 지나지 않습니다.


타이젠 SDK를 실행시킨 상태에서,

Z1을 연결하면 아래 이미지처럼 왼편 하단 'Connection Explorer'에 SM-Z130H가 나타납니다.



샘플앱을 Z1에 올려보죠.

아래 이미지처럼 File > New > Tizen Native Project 에 진입하여 샘플앱을 생성합니다.



타이젠 네이티브 앱 개발을 위한 프로젝트를 생성합니다.

아래 이미지처럼 Sample 탭을 눌러,

Mobile-2.3 > Native UI App > Calculator UI sample application 을 차례대로 선택합니다.



Project name은 마음대로 지정합니다.

Project name에 따라 Package name은 자동으로 정해집니다.

자동으로 정해진 Package name은 org.tizen이 prefix로 붙습니다.

다른 이름을 지정하고 싶다면 바로 바꿀 수 있습니다.



프로젝트를 생성하면,

왼편 Project explorer에 생성한 프로젝트가 나열됩니다.

Project > Build Project를 눌러 샘플앱을 빌드합니다.

그리고 Run > Run 을 실행하여 타겟으로 샘플앱을 로딩하고자 하면...

아래처럼 타겟에 install 할 수 없다는 메시지가 나타납니다.



인증된 개발자가 만든 앱이 아니므로 Z1 시료에는 설치조차 할 수 없습니다.

앱을 설치하여 런칭까지 진행하기 위해서는 인증을 먼저 받아야 합니다.

인증을 받기 위해 삼성 Z1 개발자 사이트(http://developer.samsung.com/samsung-z)에 접속합니다.



Z1에 대한 특징과 스펙에 대한 정보가 채워져 있습니다.

사이트 하단으로 스크롤해보면 Certificate을 다운로드 받을 수 있는 버튼을 찾을 수 있습니다.



위의 이미지를 보면,

인증서를 등록하기 위해 다운로드하라고 되어 있습니다.

다운로드 버튼을 누르면 SDK 라이센스 합의서가 나옵니다.



여러가지 문구가 나와있지만, 결국 하고 싶었던 말은 이 부분이 아닌가 싶습니다.


  1. 4.1

    Your Applications must not (i) breach any applicable laws, regulations or generally accepted practices or guidelines in the applicable jurisdictions; (ii) contain any material, component or code which could damage, destroy, unduly burden or unreasonably affect software, firmware, hardware, data, systems, services, or networks; or (iii) disable, hack or otherwise interfere with any authentication, content protection, digital signing, digital rights management, security or verification mechanisms implemented in or by the Tizen Certified platform.


한 마디로 나쁜 짓을 하라 말아달라는 요청입니다.

위의 이미지에 왼쪽 하단에 있는, "I agree to this SDK License Agreement"에 체크를 하면 Download 버튼이 생깁니다.

"tizen2.3-certificate-extension.zip"을 다운로드합니다.


이제 Tizen SDK를 설치할 때 사용할 적이 있는,

Tizen SDK Install Manager를 실행합니다.

(Tizen SDK 설치는 여기를 참고해주세요.)



위의 메뉴에서, Update를 누릅니다.



위의 이미지 왼편에 있는,

Extra repository 버튼을 누릅니다.



자동으로 Certificate을 다운로드 받은 디렉토리가 추가되어있습니다.

만약 추가가 되어 있지 않다면,

Name은 적당한 이름으로 짓고,

Repository는 꼭 다음 syntax로 추가해야 합니다.


"file:///" + [절대경로 혹은 상대경로] + [확장자가 빠진 파일명]

디렉토리를 '/'나 '\' 어느 것으로 사용해도 검색이 됩니다.

여기서는 아래와 같은 주소를 입력하였습니다.

"file:///C:\Users\Administrator\Downloads\tizen2.3-certificate-extension"

(차기 버전에서는 Certificate repository를 입력하기 위한 Search 버튼을 추가해주세요~ :))

주소를 올바르게 입력하였다면,

위처럼 리스트에 Certificate 이름이 나올 겁니다.

이름 옆에 체크박스를 체크한 후 OK 버튼을 누릅니다.



위처럼 Extra 항목에 "Certificate Extension"이라는 항목이 추가되었습니다.

Install 버튼은 눌러 인스톨을 진행합니다.

인스톨이 완료되면 SDK를 통해 Certificate을 요청할 수 있습니다.


Certificate을 요청하기 위해서는,

내 신분이 기록된 CSR 파일을 생성해야 합니다.

우선 아래 이미지의 Certificate 버튼을 누릅니다.


버튼을 누르면 아래 이미지와 같은 다이얼로그가 나옵니다.



개발자 인증을 받기 위해서는,

Author 패널 > Generate a certificate signing request(CSR file)

위의 버튼을 눌러 개발자 인증을 받기 위한 파일을 생성해야 합니다.


개발자 인증서를 처음 발급받는 것이니,

Generate a new certificate signing request

위의 라디오 버튼을 선택한 후 Next를 누릅니다.



사용자 이름과 비밀번호 그리고 기타정보를 입력한 후 Finish 버튼을 누릅니다.

여기서 입력한 비밀번호는 차후 Certificate과 함께 입력해야하므로 잘 기억해두세요.



C:\tizen-sdk-data\keystore\author.csr

위의 위치에 csr 파일이 생성되었습니다.


이제 다시 다이얼로그 Author 패널에서,

"Request the certificate" 버튼을 누릅니다.



팝업이 뜨며,

다음 단계에서 삼성계정에 로그인해야한다고 알려줍니다.

확인 버튼을 눌러 다음 단계로 진행해보죠.


삼성 개발자 사이트 로그인 화면창이 왼편에 나오고 연결된 디바이스 정보가 오른편에 함께 나타납니다.

이미 삼성 개발자 사이트에 아이디가 있으면 Sign in을 눌러 접속을 하고,

아이디가 없으면 중간에 "Create a Samsung accrount"를 눌러 아이디를 만드세요.



로그인을 완료하면,

그러면 Request Developer Certificate을 위한 창이 새로 생깁니다.



좀 전에 생성한 csr 파일을 선택하여 Submit 버튼을 누릅니다.



메일로 개발자를 위한 인증서를 받을 수 있습니다.

메일을 열어보면 첨부파일로 author.crt 파일이 있는 것을 확인할 수 있습니다.

위의 파일을 Author 패널의 Certificate file 항목에 넣어줍니다.

Password에는 csr 파일을 생성할 때 넣어준 암호를 넣어주면 됩니다.


이제 디바이스를 인증받을 차례입니다.

Z1 디바이스를 USB로 연결한 후,

Device Profile 패널 > Request a device profile을 선택합니다.

등록하고자 하는 Mobile 기기를 등록하기 위한 페이지가 나옵니다.



Privilege Level은 "Public", "Partner" 혹은 "Platform"을 선택할 수 있지만 일반 개발자가 선택할 수 있는 것은 "Public"입니다.

Developer Type은 "Individual"(개인)이나 "Company"(회사) 중에 선택합니다.

Device ID는 오른편에 연결된 기기에서 Copy 버튼을 눌러 갖다 붙입니다.

그리고 하단의 Submit 버튼을 누릅니다.


본인 계정으로 Profile에 대한 정보를 보내는데 성공하였다면,

위와 같은 화면이 나옵니다.

이제 본인의 계정으로 등록한 1) device-profile.xml 파일과 2) 디바이스에 대한 인증패스워드가 날라옵니다.



첨부파일은 Device Profile > Certificate file에 붙입니다.

Password는 메일로 함께 날라온 암호를 입력합니다.

그리고 OK를 누르면 드디어 인증이 완료됩니다.



인증이 완료되었다면,

Window > Preferences > Tizen SDK > Security Profiles

위의 위치에서 등록된 인증서를 확인할 수 있습니다.


자, 이제 마지막 단계입니다.

SDK에 등록된 인증서를 타겟에 옮기세요.

SDK 왼편에 있는 Connection Explorer에 있는 타겟을 선택하고 마우스 오른버튼을 누릅니다.



위의 팝업메뉴에서 가장 아래에 있는,

Permit to install applications

버튼을 눌러 인증서를 타겟에 복사합니다.


여기까지 왔다면, SDK에서 만든 앱을 Z1에서 실행할 수 있습니다.


만약 앱을 실행하려 했으나 다음과 같은 에러가 떠서 실행이 안된다면,

인증 과정에서 오류가 발생하여 제대로 인증이 안되었을 가능성이 높습니다.


- "Cannot create package"

- " java.io.IOException: Invaild password  If you want to know more information, please check your 'Error Log' view"

- "Cannot launch ..."


차분히 다시 인증과정을 진행해보길 권장합니다.

그래도 안되면 무한도전 등을 보며 심신의 안정을 취한 후,

다시 처음부터 차분히 진행보세요. :)


끝_




* Reference

http://developer.samsung.com/samsung-z#certification_guide

http://developer.samsung.com/technical-doc/view.do?v=T000000198




Tizen SDK로 개발한 앱을 Tizen Z1에 옮기려면,

Z1의 개발자 USB 디버깅 모드를 활성화해야 합니다.


디버깅 모드가 활성화되어야 비로소

SDK에서 Z1을 인식하여 Z1에 대한 정보를 자동으로 읽습니다.

디버깅 모드가 활성화되어 있지 않다면,

당연히, SDK에서 Z1을 인식조차 하지 못합니다.


따라서 원활한 개발을 위해서는,

USB 디버깅 모드를 활성화해야 합니다.


하지만, 어디에서도 USB 디버깅 모드에 대한 메뉴를 찾을 수가 없습니다.


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


디버깅 모드를 찾기 위해 설정앱을 두어번 꼼꼼하게 뒤져보았습니다.

하위메뉴에 숨겨져 있나...?

하나하나 메뉴를 다시 열어보았습니다.

하지만, 디버깅 모드를 찾아볼 수 없었습니다.


원래 타이젠 플랫폼은 USB 디버깅 모드가 없는 것일까요?

Tizen 오픈소스를 뒤져보았습니다.

설정앱에 USB 디버깅 메뉴에 대한 흔적이 남아있었습니다.


- settings/setting-connectivity/src/setting-connectivity-usb.c

    ad->debug_mode =
setting_create_Gendial_field_def(scroller, &itc_1text_1icon,
setting_connectivity_usb_mouse_up_Gendial_list_cb,
ad, SWALLOW_Type_1ICON_1RADIO, NULL,
NULL, toggle_dbg, "IDS_ST_BODY_USB_DEBUGGING",
NULL, setting_connectivity_main_chk_usb_debug_cb);


위의 코드는 USB Debugging 여부를 설정할 수 있는 리스트 아이템을 만드는 부분입니다.

위의 코드는 결국 "setting-developeroption-efl"라는 이름의 Library가 됩니다.

라이브러리로 만들어진다는 것은 다른 앱 혹은 데몬에서 사용한다는 것을 의미합니다.


그렇다면 누가 위의 라이브러리를 사용할까요?

그것이 앱이라면 해당 앱을 런칭시켜 진입경로를 찾을 수 있을 것입니다.


가장 확률이 높은 것은 위의 라이브러리를 공급하는 설정앱입니다.

설정앱 내에서 라이브러리를 만들고 다시 재사용할 수 있습니다.

굳이 라이브러리로 만든 이유는,

설정앱 뿐만 아니라 다른 앱에서도 호출할 수 있기 때문입니다.


하지만 현재 오픈된 코드에서는 호출해주는 쪽이 감춰져있습니다(혹은 나타나지 않습니다).

Z1이 오픈소스를 그대로 썼을리는 없으므로,

오픈소스와는 다르게 위의 메뉴를 활성화시키는 주체가 있겠죠?


이렇게 메뉴를 만들어 놓고 감춰놨다는 것은,

개발자 모드를 감춰야할 정책적인 고려가 있었기 때문으로 추정됩니다.

그러한 고려에서 어떤 의지가 느껴지네요.


이 포스팅을 쓰려고 마음 먹은 2주 전부터 진지하게 고민을 하였습니다.

숨은 의도가 무엇일까요?

하지만, 2주가 지나 포스팅을 쓰는 이 시점에도 "모르겠습니다".

(혹시 아시는 분이 있으시면, 댓글 부탁드립니다.)


그래서 도대체 Developer option은 어떻게 활성화시킬 수 있을까요?

해답은 간단한 곳에 있습니다.

전화앱에 다이얼러에서 특정 번호를 입력하면,

Developer option을 활성화시킬 수 있는 메뉴가 나옵니다.


다이얼러에 있는 영문을 보고 "*#TIZEN#"을 누르세요.

위의 TIZEN은 숫자로 "84936"입니다.

의미없는 숫자가 아닙니다. :)



위의 숫자를 치면,

Developer option을 활성화할 수 있는 on <-> off 토글 버튼이 나옵니다.

0은 off, 1은 on입니다.

현재의 화면은 off 상태이죠.

토글을 한 번 하여 on으로 만드세요.


그리고 설정앱에 다시 가보세요.

하지만 안타깝게 설정앱에 아무런 변화가 없습니다.


왜냐하면, Developer option을 활성화한다고 바로 설정앱이 메뉴를 생성하지 않기 때문입니다.

설정앱은 동적인 메뉴재편성에 대한 필요가 없기 때문에,

developer option 활성화/비활성화에 따라 메뉴재편성을 하지 않습니다.


홈키 롱프레스를 눌러 설정앱을 종료하거나 재부팅을 합니다.

그리고 새로운 프로세스로 설정앱을 띄웁니다.



그러면 위와 같이 "개발자 옵션" 메뉴가 추가되어 있는 것을 볼 수 있습니다.

개발자 옵션 메뉴를 눌러 하위 메뉴에 진입합니다.



다양한 메뉴가 나타나지만,

우리가 관심있는 부분은 USB 디버깅입니다.

USB 디버깅의 토글버튼을 눌러 '1'로 활성화시키세요.


이 상태에서 SDK에 연결하면,

SDK는 드디어 Z1을 인식합니다.


일단 인식은 시켰고,

SDK에서 개발한 앱을 Z1으로 옮기기 위해서는 추가적인 절차가 필요합니다.


그 부분은 주말 중에 공유하도록 하겠습니다.


끝_

  1. kjun.kr 2017.06.21 16:24 신고

    늦은감이 있지만 타이젠 개발에 관심이 있어 돌아보다가 들립니다. Xamarin 으로 개발해 보려는데 확실히 UI 디자인이 이쁘긴한것 같습니다.

    • 전혀 늦지 않았습니다 :) Tizen with Xamarin이 오픈된지 얼마 안되어 무언가 새로운 것을 해볼 여지는 많이 있습니다.

타이젠 플랫폼을 탑재한 최초의 모바일 기기, "Z1"

몇 년간 출시설만 수차례 돌더니 드.디.어. 저가모델로 출시가 되었습니다.

전 세계 중 '인도'와 '방글라데시' 2개 국가에서만 판매하기에,

'한국'에서는 해외직구가 아니고서는 구하기가 힘들어요.


이런 열악한 상황에서,

굳이 타이젠 앱을 개발하기로 마음 먹었기에,

어렵게 "Z1"을 공수해왔습니다.

(참고 : "Tizen Store에 앱을 팔아보겠다는 의지" Tizen Store 계정 만들기)


4개월 전 제품이라 새로운 사실을 남들과 공유하는 즐거움은 없겠지만,

첫번째 타이젠 모바일을 위한 약간의 주관적인 의견을 남기는 것에 의의를 둡니다.


박스는 삼성의 컬러이자

타이젠의 컬러이기도 한 파란색입니다다.

타이젠의 컬러 Identity가 공표되거나 확정된 것은 아니겠지만,

바람개비 모양의 로고의 색배합이 무채색과 파란색이기에 자연스럽게 타이젠에 파란 색감이 입혀졌습니다.





상단 이미지에는 가격이 나와있습니다.

5,990루피.

오늘자 (2015. 5. 20) 환율로 한화 10만 3천원 정도입니다.

스마트폰이 10만원이면 세컨폰으로는 손색이 없는 가격이죠.




하드웨어적인 사양은 이미 여러 매체를 통해 널리 알려져 있으니,

GSM Arena 기사를 참고하길 바랍니다.

위의 사진에서 언급한 Z1의 특장점은,

(10만원 치고) 놀라운 디스플레이, 강력한 WiFi&블루투스, 빠른 카메라 속도, 쉽고 멋진 UI,

그리고 울트라 파워 세이빙 모드입니다.

온갖 저울질 끝에 하드웨어 장점 2가지와 소프트웨어 장점 2가지를 선택했겠죠?




인도에 특화된 서비스가 제공됩니다.

낯선 로고와 이름으로 가득한 것으로 보아 모두 인도 내수용 서비스로 보입니다.

더불어 14가지 인도 언어도 지원합니다.

거기에 위에는 언급되어있지 않지만, 영어와 한국어도 지원합니다.



박스를 열어보니 박스에서 언급한 장점이 다시 한 번 언급되어 있습니다.

다만 쉽고 멋진 UI 부분만 삭제된 것으로 보아,

"쉽고 멋진 UI"는 다른 장점보다 우선순위가 떨어진다고 추측할 수 있겠죠.

과연, 실제로 그러할지는 사용자가 판단할 것입니다.



구성물은 배터리 1개, 사용설명서, 이어폰, 충전용 어댑터입니다.

여느 스마트폰 구성물과 대동소이한 물품이 제공됩니다.




본체는 한 손에 쏙 들어올 정도의 크기입니다.

10만원이라는 가격을 생각하면,

확실히 무난하고 군더더기 없는 디자인으로 보입니다.



심은 2개가 들어갑니다.

2개 심에 각각 이름을 부여하여 동시에 사용할 수 있습니다.

과거 러시아에서 심 2개가 탑재되는 삼성 '핸드폰'이 인기라는 얘기를 들었는데

당시 재미 좀 본 기술이 Z1으로 이식되었습니다.



부팅을 하니 큼지막하게 삼성 브랜드 네임이 크게 나오고,

하단에 상대적으로 조그맣게 타이젠 로고가 등장합니다.



부팅 직후에 등장한 셋업 위자드 화면입니다.

언어와 날짜를 선택할 수 있습니다.



날짜는 심카드가 인식되거나 WiFi가 활성화되면 자동으로 변경됩니다.




박스에 있는 14종의 인도언어를 선택할 수 있습니다.

인도 언어 밑에 자리잡고 있는 한국어를 선택하였습니다.

이후 셋업 위자드 화면은 모두 한국어로 진행되네요.


이미 사용하고 있는 심카드가 있으면 바로 붙여서 사용할 수 있습니다.

다만, 여기서는 따로 심카드를 장착하지 않은 상태입니다.



심카드 설정 다음으로 와이파이를 설정 화면이 나옵니다.

이제 와이파이를 검색하여 인터넷에 접근할 수 있습니다.

모바일의 핵심 가치가 '연결'이니,

셋업 위자드는 심카드와 WiFi 연결을 우선적으로 확인하는 것입니다.



라이센스에 동의하지 않으면,

더 이상 진행할 수가 없습니다. :)

반면 진단은 설정하지 않아도 다음으로 넘어갈 수 있습니다.



모바일 기기의 위치 정보에 대한 사용자 동의를 얻는 화면입니다.

위치 정보를 굉장히 개인적인 정보의 범주로 볼 수 있기 때문에

라이센스 동의와 함께 위치 정보에 대한 동의를 연달아 진행합니다.



위의 앱들이 위치정보에 접근할 수 있습니다.

인터넷, 날씨, 지도, 페이스북, 위젯, 카메라



타이젠 스토어에 접근하려면,

"삼성" 계정을 권장합니다.

(물론 페이스북과 같은 계정으로도 가능해보입니다)

타이젠 스토어에는 "타이젠" 계정이 더 어울려 보이지만,

이미 갤럭시 시리즈에서 사용하고 있는 "삼성" 계정과 동일하게 나가는 방향을 선택하였네요.


2015. 5. 21 추가, 왜 Z1은 타이젠 계정이 아닌 삼성 계정을 택했을까요?

'백업 및 복원' 기능을 위해서는,

사용자 스마트폰에 저장된 데이터를 어딘가의 서버에 백업해두어야 합니다.

서버를 유지보수하고 관리하는 일은 오픈소스 플랫폼인 타이젠으로서는 버거운 선택이었을 것이었을까요?

이러한 정책적 고민에 의해 삼성계정을 택한 것이 아닐까 합니다.


2015. 5. 22 추가

타이젠 운영체제를 삼성 외의 업체에 이식하려면,

타이젠 계정에 대한 고민 - 계정을 관리하는 주체가 누구인가 - 이 좀 더 필요해보입니다.

예를 들어, 러시아 모바일 업체가 타이젠을 탑재하고자 하는데 삼성계정을 활용할 수는 없을 것입니다.

그렇다면 결국 타이젠에서 관리하는 계정을 이용하거나,

자체적인 계정시스템을 구축해야하죠.

타이젠에서 관리하는 계정이 없다면... 자신만의 시스템을 갖출 수밖에 없을 겁니다.



삼성 계정으로 백업 & 복구를 할 수 있습니다.

더불어 하단 그림처럼,

WiFi가 연결되어 있는 경우에만 백업하도록 설정할 수 있습니다.



뒤이어 드랍박스 계정에 50기가 무료제공 혜택을 받을 수 있습니다.

무료로 준다는데 마다할 이유는 없죠. :)

1년이 지나면 유료계정으로 전환하거나 혜택을 반환하면 됩니다.




드랍박스에 카메라에서 찍은 사진이 자동으로 업로드되도록 설정할 수 있습니다.

이러한 기능은 이미 갤럭시나 아이폰에서도 볼 수 있는 기능입니다.

타이젠은 타 플랫폼에서 제공하는 기능을 모두 제공합니다.




그리고 마지막으로 프로필사진 / 사용자이름 / 디바이스이름을 등록할 수 있습니다.

셀프 샷 촬영 버튼을 눌러,

바로 셀피를 찍어 등록할 수도 있네요.


자, 여기까지 완료하였다면,

드디어 홈화면에 진입할 수 있습니다.



날씨 위젯은 도시를 선택하면,

바로 도시의 날씨로 업데이트 됩니다.



다음 페이지에는 인도에서 인기있는 서비스 모음 위젯이 있습니다.

한국 사용자에게는 필요없는 기능도 있으니 적당히 편집하거나 삭제하면 됩니다.


홈화면의 하단 두줄은 Favorite 영역이기도 하고,

메뉴화면으로 진입하기 위한 핸들러이기도 하죠.

Favorite 영역을 상단으로 올리면, 메뉴화면이 아래에서부터 올라옵니다.



위의 메뉴는 두번째 페이지입니다.

상단의 Favorite 영역은 페이지 이동을 하여도 그대로 유지됩니다.

Favorite 영역을 하단으로 내리면 다시 홈화면으로 복귀합니다.



작업관리자는 앱아이콘과 앱이름만으로 간소하게 표시됩니다.

캡쳐 루틴은 저가모델에서는 부담이 될 수 있겠죠.



락화면에서는 바로 카메라 촬영모드로 진입할 수 있습니다.

심을 꽂지 않아서 "긴급전화만 가능"이라는 안내문구가 표시되어 있습니다.



자체 브라우저로 네이버에 접속하여 보았습니다.

화면은 작지만,

나름 선명한 화면으로 브라우징에 어려움은 없었습니다.


Z1을 대강 살펴보았으니,

다음부터는 Z1으로 앱을 개발하는 방법을 공유하도록 하겠습니다.


무려 5개월이나 지난 시점에

제품을 구해 개봉기를 작성하는 이 포스팅이 얼마나 유용할지는 모르겠으나

이왕 시작하였으니 졸음을 참고 마무리를 지어봅니다.


끝_

플랫폼의 특징을 기술하는,

추상화된 '멋진' 단어의 나열만으로는 플랫폼을 이해하기 힘들죠.


타이젠을 심도깊게 분석한 서적이 있으면,

당장 구매하여 보겠지만,

아직까지 그런 책이 나온 적은 없습니다.


그래서 결국 의지할 수 있는 것은 소스코드 뿐입니다.

마침 타이젠은 오픈소스이기 때문에,

공개된 소스를 다운로드 받아 살펴보죠.


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


타이젠의 소스는,

http://source.tizen.org에서 얻을 수 있습니다.


사이트에 가입 혹은 로그인 하지 않아도 소스코드를 볼 수 있습니다.

플랫폼을 개발하려면 막대한 자원이 필요할텐데,

아무런 조건없이 배포하고 있는 중입니다.



위에서,

SOURCE CODE 버튼을 누르세요.


아래처럼 ('15. 5. 17 기준) 3,507개의 git repository가 나옵니다.

갯수가 갯수이니만큼 로딩에 시간이 걸리네요.



특별히 살펴보고 싶은 repository가 있으면,

Ctrl-F로 찾아서 살펴보세요.


Tizen 오픈소스의 repository 이름은 디렉토리 구조를 따르고 있습니다.

예를 들면, adaptation/alsa-scenario-scm-data-o-base나 framework/web/webkit-efl처럼 디렉토리 구조를 가지지요.

따라서, 디렉토리 이름을 보면,

해당 repository의 성격을 어느 정도 유추할 수 있습니다.

3,507개의 repo.에는 총 19개의 카테고리가 있네요.

위의 그림에서 2~20번까지 임의로 나눈 카테고리에 모든 repo.가 분류될 수 있습니다.

2번 meta 카테고리의 경우,

분류가 애매한 repo.들을 몰아넣었습니다.


위의 분류를 보고 관심있는 git repo.를 받아볼 수 있도록 스크립트를 첨부합니다.

1. 첨부된 스크립트(우분투 기준)를 다운로드 받고,

get_tizen.sh

2. 스크립트에 실행권한을 주세요.

     # chmod +x get_tizen.sh

3. 그리고 실행을 합니다.

     # ./get_tizen.sh

그러면 아래와 같은 화면이 나옵니다.

관심가는 카테고리 번호를 입력합니다.

그러면, 현 위치(Working directory)에 "tizen"이라는 하위 디렉토리를 만든 후,

그곳에 카테고리에 해당하는 repo.를 다운로드 받습니다.


만약, 모든 소스에 관심이 있어서,

"1. all"을 선택한다면,

정말...

오랜 시간이 필요합니다.

이 글을 쓰기 시작하기 2시간 전에 이미 clone을 걸어놓고 있지만,

이제 겨우 1000개를 clone 받았습니다.


* 2015. 5.18 추가

"1. all"로 전체를 내려받으니 대략 24시간이 걸렸습니다.

컴퓨터 성능이 상당히 저사양이기도 하고 전송속도도 느렸기에 시간이 꽤나 소요되었죠.

총 3,097,632개 파일, 37.4 GB.


보통 git을 이용하여 clone을 하면,

디렉토리 구조의 이름 중 최하위 디렉토리 이름으로 디렉토리를 생성하여 소스를 다운로드 받습니다.

예를 들어, framework/web/webkit-efl repo.를 clone받으면,

"webkit-efl"이라는 디렉토리가 만들어져 소스가 다운로드됩니다.


하지만, 3,507개의 repo.중에는 최하위 디렉토리의 이름이 동일한 repo.가 많습니다.

그럴 경우, 서로 다른 repo.임에도 같은 디렉토리에 위치하게 되어 제대로 다운로드 받을 수 없습니다.

따라서, 스트립트에서는 디렉토리를 생성할 때, 디렉토리 이름을 통째로 사용하도록 만들었습니다.

다만 '/'를 '.'으로 변경하여 다운로드하게 하였습니다.

곧, framework/web/webkit-efl은 framework.web.webkit-efl 디렉토리에 다운로드됩니다.

이로써, 중복없이 3,507개의 repo.를 모두 다운로드 받을 수 있습니다.


clone을 하는 중에 이미 다운로드받은 repo.가 있으면,

굳이 clone을 다시 하지 않고, pull을 하도록 하였습니다.

다만, 이 경우에는 받고 싶은 코드의 branch를 사용자가 체크아웃 해줘야 제대로 동작합니다.

모든 repo.에 대해 동일한 branch를 보도록 수정하고자 하였으나,

최신 branch가 repo.마다 제각각이라서 이 부분은 스크립트 사용자에게 자유도를 부여합니다. :)


* 추신,

만약 첨부된 스크립트가 제대로 동작하지 않거나,

repo. 갱신으로 인하여 스크립트가 변경되어야 한다면 답글주세요.

가급적이면 빨리 수정하도록 하겠습니다.



  1. developer 2016.09.22 19:01

    멋진 설명 감사합니다.

일개 개발자로서 낯선 플랫폼에 한걸음 다가가기 위해서는,

플랫폼의 철학과 의지를 엿볼 수 있는 SDK를 설치해보아야 합니다.

개발자에게 제공되는 함수를 하나씩 음미하는 것으로,

플랫폼을 이끌어나가는 아키텍트의 판단과 프로그래머의 고민을 엿볼 수 있습니다.

타이젠을 만들고 있는 사람들의 가장 중요한 가치는 무엇일까요.

이 모든 궁금증을 풀기 이전에 오늘은 SDK부터 설치해보고자 합니다.


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


타이젠 SDK를 우분투에 설치하시려는 분은 아래 포스팅을 참고하세요.

[Tizen] 타이젠 SDK 우분투에 설치하기


타이젠 SDK는 공식 홈페이지(https://www.tizen.org)에서 다운로드 받을 수 있습니다.

위에 걸어놓은 링크를 통해 타이젠 홈페이지에 접속해보세요.


공식홈페이지 시작화면 하단을 보면,

"Download the SDK" 버튼을 찾을 수 있습니다.

현재(2015. 5월)까지 공개된 최신버전은 Tizen 2.3입니다.

버튼을 눌러 Tizen SDK 다운로드를 위한 페이지에 들어가보세요.




Tizen SDK로 개발할 수 있는 앱 타입은,

- Web application

- Native application

위의 두 가지라는 사실을 확인할 수 있습니다.

물론, 위의 두 가지 방식이 혼합된 Hybrid type의 앱도 개발할 수 있습니다.


그리고 여느 플랫폼이 제공하는 SDK처럼,

- 코드를 작성하는 Editor

- 컴파일하도록 도와주는 Toolchain

- 개발한 앱을 가상의 환경에서 돌려볼 수 있는 Emulator

- 개발자를 위한 샘플코드와 도큐먼트

위의 컴포넌트들로 구성되어 있습니다.


Tizen SDK는 윈도우, 우분투, 맥OS X에서 사용할 수 있습니다.

여기서는 윈도우 PC에 설치하도록 해보겠습니다.

(우분투나 맥OS X에서도 설치과정은 동일합니다)


Download Tizen 2.3 Rev2 SDK

위의 메뉴에 진입하면 mobile / wearable 앱을 개발할 수 있는 SDK를 설치할 수 있습니다.


Download Tizen SDK for Wearable 1.0.0

위의 버튼을 누르면 wearable 앱을 개발하기 위한 SDK를 설치할 수 있습니다.


여기서는 Tizen 2.3 Revision 2 SDK를 받아,

mobile & wearable 앱을 개발하기 위한 환경을 갖추고자 합니다.



Tizen 2.3 Rev2 SDK 페이지에는,

- Ubuntu 32 bits

- Ubuntu 64 bits

- Windows 7 32 bits

- Windows 7 64 bits

- Mac OS X(Intel)

- Mac OS X(Intel, CLI)

위의 운영체제에서 돌아가는 버전의 SDK를 다운로드 받게 되어있습니다.

자신에게 맞는 운영체제를 선택합니다.


우분투 환경에서는 설치하기 전에 다운로드 받은 파일에 실행권한을 추가해야합니다.

$ chmod +x tizen-sdk_2.3.63_ubuntu-32.bin
$ ./tizen-sdk_2.3.63_ubuntu-32.bin


다운로드 받은 파일을 설치하려고 하면,



위와 같은 팝업이 나타날 수도 있습니다.

위의 팝업은 Java runtime environment(JRE) version 6 이상 버전을 먼저 설치해야 된다는 알림입니다.

JRE를 설치해야 하는 이유는,

Tizen SDK에서 (자바개발자에게 너무나도 친숙한) Eclipse를 사용하기 때문입니다.

Eclipse가 Java로 개발되었기 때문에,

(아래 그림의 Written in Java 참조, wikipedia에서 더 자세한 내용을 볼 수 있습니다.)

Eclipse를 돌리기 위해서는 우선적으로 Java가 돌아갈 수 있는 환경을 구축해주어야 합니다.



이러한 불편함은 Tizen 만의 문제는 아니고,

Eclipse를 설치하고자 하는 모든 플랫폼/환경에서는 항상 발생하는 일입니다.


윈도우에서 JRE를 다운로드 받으려면 오라클 자바 페이지에 들어가야 합니다.

사이트에 접속하여 자신의 환경에 부합하는 Java SE Development Kit을 다운로드 받습니다.

여기서는,

Java SE Development Kit 8u45의 Windows x64(jdk-8u45-windows-x64.exe)를 다운로드 받습니다.

다운로드를 받고 설치를 시작해보세요.


Java SE Development Kit 8 Update 45를 설치한다는 문구를 확인할 수 있습니다.

Next를 눌러 다음으로 넘어갑니다.



그리고 설치를 시작합니다.

설치과정에 어려운 점은 전혀 없습니다.

설치를 완료한 후,

다시 tizen-sdk_2.3.63_windows_64.exe로 설치를 시도합니다.

그러면, 또 이상한 팝업이 하나 나옵니다.


위의 팝업은 자바를 설치한 직후,

자바를 사용하기 위한 환경변수를 설정해주지 않아서 나타났습니다.

환경변수(PATH)를 설정해주는 방법은 여기를 참고하면 됩니다.


윈도우 7의 경우는 아래처럼 하면 됩니다.

  1. 시작 메뉴에서 컴퓨터를 선택합니다.
  2. 상황에 맞는 메뉴에서 시스템 속성을 선택합니다.
  3. 고급 시스템 설정 > 고급 탭을 누릅니다.
  4. 시스템 변수 아래에서 환경 변수를 누른 다음 PATH를 찾아 누릅니다.
  5. 편집 창에서 PATH 값에 클래스 위치를 추가하여 PATH를 수정합니다. PATH 항목이 없으면 새 변수를 추가하고 이름으로 PATH를, 값으로 클래스 위치를 추가하도록 선택할 수도 있습니다.
  6. 명령 프롬프트 창을 다시 열고 Java 코드를 실행합니다

여기서는 PATH에 아래와 같은 경로를 추가해주었습니다.

C:\Program Files (x86)\IDM Computer Solutions\UltraEdit\;C:\Program Files\Java\jdk1.8.0_45\bin


이 단계까지 마쳤으면,

다시 tizen-sdk_2.3.63_windows_64.exe로 설치를 해봅니다.



아직 설치한 SDK가 없으므로 Uninstall 버튼은 비활성화되어있습니다.

Advanced 버튼을 누르면,

패키지를 다운로드 받을 수 있는 서버를 변경하거나 SDK Image로 직접 설치할 수 있는 메뉴가 나옵니다.

여기서는 따로 설정하지 않고 그냥 Install 버튼을 누르도록 합니다.



Mobile profile이나 Wearable profile을 선택할 수 있는 화면이 나옵니다.

여기서는 Custom에 아래처럼 진입하여 Mobile / Wearable / Other tools를 모두 설치합니다.

하지만, Mobile이나 Wearable만 개발하고자 한다면,

하나의 profile만 선택하여 인스톨해도 무방합니다.



Next를 눌러 다음 화면에 진입합니자.

소프트웨어 라이센스에 대한 설명이 나옵니다.



자세히 읽어보고...?

I agree를 눌러 다음으로 진행합니다.

그러면 무려 2.5기가 이상을 다운로드 받고 설치한다는 메시지가 나옵니다.



사이즈가 사이즈이니 만큼,

인스톨하는데 상당한 시간이 소요될 예정입니다.


인스톨 버튼을 눌러보세요.

인텔 CPU를 사용하는 컴퓨터에서는,

Intel Hardware Accelerated Execution Manager(HAXM)를 설치하라는 메시지가 나옵니다.

HAXM을 설치하면, 에뮬레이터를 보다 빠르게 구동할 수 있는 환경이 갖춰집니다.



HAXM 또한 Tizen만의 사항은 아니고,

다른 플랫폼 - 안드로이드 SDK에서도 에뮬레이터 성능 확보를 위해 설치를 제안합니다.


HAXM까지 설치를 마치면,

이제 본격적인 다운로드 & 인스톨의 과정을 거칩니다.



처음에는 멍하니 모니터를 바라보며 %가 올라가는 것을 바라보고 있다가,

그간 밀린 쓰레기 분리수거를 위해 1층에 내려갔다 왔습니다.

그래도 여전히 인스톨 중이네요.


한참을 기다리니 드디어 인스톨이 완료되었습니다.



자, 이제 정말 설치완료.


설치과정은 타 플랫폼과 대동소이해보였고,

특별히 어려운 점은 없었습니다.


자, 이제 SDK를 사용해보겠습니다.

일단 잠 좀 자고,

다음 시간에 계속 해보겠습니다.


끝_


* 타이젠 관련 블로깅

"Tizen Store 182개국 오픈"에 대한 각지 반응

"Tizen Store에 앱을 팔아보겠다는 의지" Tizen Store 계정 만들기

Tizen SDK 설치하기, "생각보다 쉽다"



타이젠 스토어도 182개국에서 오픈되었고,

개장 1년간 수익의 100%를 개발자에게 돌려준다는 정책에 솔깃하여,

(2015. 1. 14 ~ 2016. 1. 31)

Tizen Store에 앱을 개발하여 올려보기로 합니다.


앱을 하루 아침에 개발할 수는 없을테니,

빠르면 3개월-

늦어도 6개월 안쪽으로-

앱개발과 그에 대한 포스팅을 병행하며,

무수한 삽질의 노하우를 공유하고자 합니다.


과연 1달러의 수익이라도 거둬들일 수 있을까요?


타이젠 앱개발의 첫걸음으로,

타이젠 스토어에 Seller 계정을 만드는 것에서부터 시작할까 합니다.


가장 큰 동기는 최종 결과물에 대한 '헛된' 보상을 꿈꾸는 것일테니,

SDK 설치나 API 분석은 후순위로 미루고자 합니다.



Tizen Store Seller Site에 들어가봅니다.

계정이 없으므로,

하단에 위치한 Sign Up Now 버튼을 눌러 신규가입을 합니다.



다음 단계에서는 판매의 주체가 개인인지 회사인지를 선택하라고 합니다.

개인자격으로 판매할 예정이므로 Private seller를 선택합니다.

계정등록 절차상 개인자격은 회사자격과 큰 차이가 없습니다.

회사자격의 경우 사업자 등록증을 추가로 등록하면 됩니다.



타이젠 계정에 대한 약관,

타이젠 셀러 오피스에 대한 약관,

타이젠 개인정보 정책에 대한 약관에 모두 동의하고 다음 단계로 넘어갑니다.



기본 정보를 입력할 차례입니다.

이메일 / 비번 / 이름 / 국가 / 생일 / 주소 / 우편번호 / 도시 / 전화번호가 필수입력사항입니다.

한글로 입력해도 무방합니다.



다음단계는 이전 단계에서 입력한 이메일 주소를 검증하는 단계입니다.

아래 문구를 확인하고 이전 단계에서 등록한 메일함에 타이젠 스토어에서 보낸 메일을 확인합니다.



아래와 같은 메일이 왔을 것입니다.

Verify Now를 누르면 다시 타이젠 스토어 셀러 사이트에 들어옵니다.



이제 기본 정보 입력 단계는 모두 끝났습니다.

무료 앱을 팔 수 있는 권한을 획득하였습니다.

하지만, 유료 앱을 팔고 싶다면?

그렇다면 하단의 Request Commercial Seller Status 버튼을 누릅니다.



유료 앱 판매를 위한 추가적인 정보를 입력합니다.

이름 / 주민등록번호 / 국가 / 주소 / 도시 / 전화번호가 필수정보입니다.

통신판매업신고번호는 필수정보는 아닙니다.



이어서 유료 앱에 대한 판매수익이 입금될 계좌에 대한 정보를 입력합니다.

신분증을 따로 스캔하거나 사진을 찍어 올려야 하네요.



여기까지 무사히 진행하였다면,

아래와 같은 결과창을 볼 수 있습니다.

자신이 입력한 정보가 제대로 되어 있는지 확인해보도록 합니다.



참고로,

회사계정으로 만든 경우,

아래와 같이 사업자 등록증 사본을 업로드해야 합니다.



계정등록에 어려울 것은 없습니다.

5분이면 됩니다.

자, 이제 반년에 걸쳐 앱 하나만 그럴싸하게 만들면 되겠죠?

그 때까지 계정은 휴면상태로 방치하는걸로-



끝_



* 타이젠 관련 블로깅

"Tizen Store 182개국 오픈"에 대한 각지 반응

"Tizen Store에 앱을 팔아보겠다는 의지" Tizen Store 계정 만들기

Tizen SDK 설치하기, "생각보다 쉽다"



  1. 궁금 2019.03.16 09:24

    앱 판매 하실때 따로, 통신판매신고 안하고 (연 1200만원이하 일경우) 판매해도 법적으로 문제없죠??

페이스북에 올라온 기사에는,

Tizen Store를 182개국에 오픈한다는 반가운 소식이 있었습니다.

국내에는 거의 알려지지 않았지만,

그동안 Tizen Store는 2개 국가(인도, 방글라데시)에서만 사용할 수 있었습니다.


기존 2개 국가에 180개국을 더하니,

이젠 상당수 국가에서 Tizen Store에 접근할 수 있게 되었습니다.


그렇다고 갑자기 미지근한 Tizen 생태계가 폭발적으로 팽창하지는 않겠지만,

Z1 이후 '차기 버전' 혹은 '차기 버전에 대한 소식'이 잠잠한 시점에,

서비스 확대는 Tizen에 대한 삼성의 의지를 엿보게 하네요.




Tizen Store에는 182개국 출시에 대한 공지는 없습니다.

의외로 차분한 분위기입니다.

사이트 하단의 언어탭에는,

여전히 기존에 지원하던 2개국인 '인도'와 '방글라데시' 언어만 선택할 수 있습니다.

이 부분은 점차 확대될 것이라 보여집니다.


Tizen Store에서 홍보하는 앱 중에는 확실히 유명앱이 없습니다.

게임카테고리에 있는 '테트리스'가 가장 유명한 앱이랄까요.


앱이 아직 없다는 것은,

Tizen 사용자에게는 치명적인 약점이겠지만,

어떤 개발자에게는,

기회의 땅처럼 여겨질 것입니다. :)



역시, Tizen Experts에서 발빠르게 기사를 실었습니다.

Tizen Experts는 전세계에 출시될 타이젠 스마트폰에 대한 루머를 갖고 있다면서,

플래그쉽 모델에 대한 기대감을 여지없이 표출하였습니다.

댓글을 달 수 있는 시스템이 아니라서 댓글을 구경할 수는 없었습니다.

다만, 기사를 트위터나 페이스북으로 Share할 수 있게 되어 있고,

그 덕에 트위터나 페이스북에서 본 기사를 여러 차례 목격하였습니다.



ZD Net에서는,

모바일 앱생태계에서 Tizen이 한발 늦었다는 점은 이미 삼성도 알고 있다고 언급합니다.

하지만, 향후 다양한 IoT 기기에서 Tizen을 보게 될 것이라 예상하네요.


다른 기사들보다 퀄리티가 좋은 ZD Net의 기사입니다.

댓글이 따로 달려있지 않아서 아쉽네요.



GSM Arena는,

(182개국 오픈으로) 삼성전자가 유명앱들을 Tizen Store에서 보길 희망한다고 언급했습니다.


재미있는 댓글들도 몇개 달려 있네요.

"10점 만점 중 7점을 줄 수 있다. 여기에 몇가지 앱만 추가된다면 10점을 주겠다. 타이젠이 안드로이드보다 낫다."

첫번째로 보이는 댓글의 평이 굉장히 좋아 놀랐습니다.

"타이젠은 굉장한 OS이다. 차기버전은 더 좋은 하드웨어이길 바란다."

"삼성, 우선 미국에 출시하라."

"(삼성이 시작했다가 사실상 망한) 바다가 떠오르는군."

Tizen에 기사에는 어김없이 바다에 대한 댓글이 달렸습니다.

"타이젠은 지구에서 최악의 OS이다. 그래픽 인터페이스는 촌스럽고, 게임은 없고, 앱들도 없고, 개발자 툴도 없고, 아무 것도 없다. 왜 삼성이 삼성의 다른 기기를 방해하면서 까지 타이젠 출시를 고집하는지 모르겠다."

이제 시작하는 타이젠에 다소 격앙된 감정으로 대응하고 있지만, 이게 현실이죠.

"나는 Z1을 사용하고 있는데, 클래시 오브 클랜 같은 유명 게임조차 없어서 매우 따분하다"


긍정적인 평가와 부정적인 평가가 뒤섞여있긴 하지만,

앱 생태계에 대한 아쉬움이 가장 커 보였습니다.

Tizen이 iOS나 Android에 비해 '상당히' 늦게 시작한 만큼,

건실한 앱생태계를 갖추려면 앞으로도 갈 길이 멉니다.

앱 생태계에 대한 부정적인 의견은 앞으로도 계속되리라 충분히 예상할 수 있습니다.



러시아 매체 PDA는,

삼성이 타이젠 OS를 저가 스마트폰 시장에서 키우기 위해 주요 개발자를 유치하려 한다고 언급했습니다.


댓글이 상당히 많이 달려있어서 기대반 의구심반으로 구글 번역기를 돌려봤습니다.

"만세! 타이젠이 드디어 전 세계에 열렸다."

굉장한 반응의 댓글이 처음 달려있습니다.

"나는 아직도 타이젠이 성공적으로 이륙하고 있다고 믿고 싶습니다"

이런 긍정적인 반응이 넘쳐나네요.

"제대로 이륙하려면 안드로이드보다 근본적으로 달라야 한다"

"삼성의 또다른 바다 OS?"

역시 바다에 대한 댓글이 있습니다.

"이미 나는 타이젠을 사용하고 있다. 전력소모가 정말 낮다."

삼성전자는 언제나 전력소모에는 치밀하게 대응합니다.

"별도의 소프트웨어가 없다."

역시 앱생태계에 대한 반응이 있습니다.



Hardware Zone에서는,

Tizen Store 전면에 부각된 4개 카테고리(게임, 플랜, 사진, EA games)를 언급하며,

25개 앱이 런칭되었다고 기사를 썼는데, 이 25개 앱이 이번에 새로 탑재된 앱인지는 불분명합니다.

기사 자체가 쓰여진지 얼마 되지 않아서 따로 댓글이 달려 있진 않네요.



개인미디어 Juu Chini에서의 반응도 살펴보았습니다.

타이젠 스토어 개시일부터 1년간 앱판매 수익 전부(100%)를 개발자에게 준다고 언급하며,

2015년 2월 현재 가장 인기있는 앱 20개를 나열하였습니다.

20개의 앱을 보니 인도에 특화된 앱도 있지만,

iOS나 안드로이드에서 눈에 익은 앱들도 보입니다.



타이젠을 보고 있노라면,

거대 공룡의 틈에서 꾸역꾸역 버티고 있는게 힘겨워보입니다.

하지만, 과연 내일은 어떤 모습일까요?

안드로이드가 iOS에 비해 앱이 적어 경쟁이 되지 않는다던 옛 기사가 떠오릅니다.



끝_



* 타이젠 관련 블로깅

"Tizen Store 182개국 오픈"에 대한 각지 반응

"Tizen Store에 앱을 팔아보겠다는 의지" Tizen Store 계정 만들기

Tizen SDK 설치하기, "생각보다 쉽다"

+ Recent posts