레이블이 native인 게시물을 표시합니다. 모든 게시물 표시
레이블이 native인 게시물을 표시합니다. 모든 게시물 표시

2015년 5월 13일 수요일

Tizen WebView 사용 방법 정리, 설정, page 관련 signal, navigation policy 관리

Tizen Native app 개발 중 WebView를 사용하다 답답해서 정리함.

Tizen에서 제공하는 webview는 ewebkit2 기반이라고 한다.
자세한 내용과 간단한 사용 방법은 아래 링크 참고

Hello ewebkit?
: http://bunhere.tistory.com/m/post/417

Tizen WebView tutorial
https://developer.tizen.org/documentation/tutorials/native-application/web

Tizen WebView API reference
https://developer.tizen.org/dev-guide/2.3.0/org.tizen.native.mobile.apireference/group__WEBVIEW.html


Tizen document를 봐도 간단히 page만 loading하는 수준으로만 설명 되어 있고
그리고 몇가지 생각 나는 것은

- Tizen 2.2 platform API 대비 API 수준이 퇴화 한 것 같다.
 : 하나의 예로 WebView -> Native Interface가 없어졌음.

- WebView 내 page의 디버깅할 방법이 없다.
 : console log를 확인할 방법이 없고 안드로이드 처럼 크롬 개발자 도구 연동도 못한다.

- Documentation이 부실하다.
 : 예로 API reference상의evas_object_smart_callback_add()를 사용해서 처리하는 signal에 대해서는  signal definition과 argument type만 나오고 어떻게 사용하는지는 알 수 없다.


암튼... 삽질 하면서 몇가지 알게 된 것과
일반적인 Tizen Webview 사용 방법을 정리함.


[User agent 설정, Javascritp enable, cookie 사용 설정]

이부분은 API reference에 나와 있어서 쉽게 사용할 수 있는 부분임.

// user agent 설정
ewk_view_user_agent_set(ewk_view, "사용하고 싶은 USER AGENT");
Ewk_Settings* settings = ewk_view_settings_get(ewk_view);
// javascritp 사용 설정
ewk_settings_javascript_enabled_set(settings, true);

// 모든 cookie 사용 설정
  if(NULL != ewk_view_context_get(ewk_view) &&
NULL != ewk_context_cookie_manager_get(ewk_view_context_get(ewk_view)))
{
ewk_cookie_manager_accept_policy_set(ewk_context_cookie_manager_get(ewk_view_context_get(ewk_view)), EWK_COOKIE_ACCEPT_POLICY_ALWAYS);
}

근데.. webview 내에서 cookie를 설정할 때
webview 내의 web page에서 javascript로 cookie를 설정할 경우는 생성이 되지만
native에서 webview로 javascript execute로 cookie를 설정할 경우는 잘 안된다.

그리고 안드로이드는 platform 차원에서 cookie를 관리하는 cookie manager가 있지만
Tizen은 그런 것 없다. 만약 일반 브라우저에서 로그인 후 생성된 cookie를 동기화 해야할 경우가 있다면.... 방법을 모르겠다.


[JavaScript 실행]

Native에서 WebView 내 API를 호출할 수 있고 이는 Native에서 WebView로 전달할 것이 있을 때 유용함.

ewk_view_script_execute(ewk_view,"자바스크립트 코드", 
            "결과 전달 callback", "callback으로 전달할 user_data");

typedef void(*Ewk_View_Script_Execute_Cb )(Evas_Object *o, const char *result_value, void *user_data)

근데 재미 있는 것은 callback의 result_value가 항상 null로 넘어온다. 언젠가 수정 될듯.
(다른분께서 알려주신건데 result_value가 script에서 마지막의 변수나 return값을 가지는 함수의 결과값이 전달된다고 하니 참고..)


[Page 관련 signal]

page loading 관련, URL 변경 관련 처리를 할 수 있는 signal들을 아래와 같이 등록 가능 함.

evas_object_smart_callback_add(ewk_view, "url,changed", __on_url_changed, user_data);
evas_object_smart_callback_add(ewk_view, "load,started", __on_load_started, user_data);
evas_object_smart_callback_add(ewk_view, "load,finished", __on_load_finished, user_data);
evas_object_smart_callback_add(ewk_view, "load,error", __on_load_error, user_data);

아래와 같이 page 시작 시 signal을 등록하여 처리할 수 있음.
다만 WebView engine에서 loading은 이미 시작된 후 efl port에서 IPC를 전달 받아
callback이 실행되므로 시점 차이가 있을 수 있다.

void __on_load_started(void *user_data, Evas_Object *webview, void *event_info)
{
const char* url = ewk_view_url_get(webview);
DLOG("__on_load_started, URL = %s", url);

}

URL 변경 시 변경된 URL에 따라서 처리할 수 있음.
Hash값이 변경 되었을 경우에도 이 callback이 불리어 hash에 따라서
native에서 처리할 수 있다.

void __on_url_changed(void *user_data, Evas_Object *webview, void *event_info)
{
appdata_s *ad = (appdata_s *)user_data;
const char* url = ewk_view_url_get(webview);

DLOG("__on_url_changed, URL = %s", url);

// Do something for the changed URL.
}

이것을 활용하면 JavaScript -> Native 코드 호출이 가능하다.
JavsScript에서 hash 값을 변경하여 url 뒤에 필요한 명령을 붙이고
Native에서 __on_url_changed event callback 내에서 url의 hash값을 보고
native 코드를 처리하면 된다.


[WebView page navigation policy]

특정 domain내의 page만 webview를 통해서 보여주고 싶을 경우
아래와 같이 signal을 등록해서 처리해야 한다.

evas_object_smart_callback_add(ewk_view, "policy,navigation,decide", __on_policy_navigation, ad);

callback 내에서 이동하려는 url을 ewk_policy_decison_url_get()으로 확인한 뒤
허용되는 domain 내의 page이면 ewk_policy_decision_use()로 이동 허용
아니면 ewk_policy_decision_ignore()로 이동 취소를 하면 됨.


void __on_policy_navigation(void *user_data, Evas_Object *webview, void *event_info)
{
Ewk_Policy_Decision* decision = (Ewk_Policy_Decision*)event_info;

string _loading_url((char*)ewk_policy_decision_url_get(decision));
string _current_url(ewk_view_url_get(webview));

// 허용되는 SITE_DOMAIN 인지 확인
if(0  == _loading_url.compare(0, strlen(SITE_DOMAIN), SITE_DOMAIN) )
{
ewk_policy_decision_use(decision);
return;
}

// 허용되지 않는 URL
ewk_policy_decision_ignore(decision);
}


2014년 5월 14일 수요일

Tizen native app 개발 시 nine patched png 처리 이슈

아래 내용은 Tizen 2.2 기반이라 상위 버전에서는 별 의미 없는 내용입니다.
--------------------------------------------------------------------------- nine patch 적용된 png 파일을 사용하다보니
bitmap을 읽어오는 API에 따라서 nine patch image로 인식이 되지 않을 수 있어서 정리함.
(내가 방법을 모를지도..)

아래는 tizen 내부 저장소에서 그림을 읽어오는 sample code

result
MainForm::OnDraw(void)
{

// App resource(res/screen-density-xhigh)에서 나인패치 이미지 가져오는 예
// 하지만 이렇게 가져온 bitmap은 nine patch 로 인식 안됨 .
// false == pBitmap->IsNinePatchedBitmap()
// canvas->DrawNinePatchedBitmap() 시 에러메세지 발생

Bitmap* pBitmap = pAppResource->GetBitmapN(L"ninepatch.9.png");

// 아래는 App 영역내 data 폴더에 저장된 나인패치 이미지를 가져오는 예
// 이때는 정상적으로 인식됨.

Image image;
Bitmap* pBitmap = null;

image.Construct();
String filePath = App::GetInstance()->GetAppRootPath() + L"data/ninepatch.9.png";
pBitmap = image.DecodeN(filePath, BITMAP_PIXEL_FORMAT_ARGB8888, 100, 100);


Canvas* canvas = this->GetCanvasN(rect);
if (canvas != null)
{

r = canvas->DrawNinePatchedBitmap(Rectangle(0,0,100,100), *pBitmap);
AppLog("%d", r);

delete canvas;
}

Form::OnDraw();
}

2014년 5월 1일 목요일

Regarding Tizen native REST client app development (HTTP, JSON parser, Event-driven thread)

아래 내용은 Tizen 2.2 기반이라 상위 버전에서는 별 의미 없는 내용입니다.
--------------------------------------------------------------------------- Tizen native app으로 REST client app을 개발하고자
관련된 tutorial 링크 및 참고 사항들을 대충 정리 함...

- HTTP request/response 처리
- JSON parsing
- HTTP response 처리를 위한 Event-driven thread 사용

[HTTP transaction concept]

https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.appprogramming/html/guide/net/http_transaction.htm

REST client app을 만드니... 기본적인 HTTP transaction에 대해서는 이해가 필요하겠지..

 . HTTP transaction 에 대한 내용들
http://www.pearsonhighered.com/assets/hip/us/hip_us_pearsonhighered/samplechapter/0672324547.pdf



http://www.w3.org/2002/Talks/www2002-p3p/all.htm

 . Chunked transfer
  : 간단히 말하면 일반적인 http streaming transfer는 content를 response로 한번에 보내지만 chunked transfer는 content를 임의의 크기로 쪼개서 보냄. 이런 경우는 server측의 performance의 제약이나 전달되는 content가 실시간 생산되는 content의 경우(크기를 예측할 수 없음) 이런 방법을 사용.
  : Tizen HTTP transfer default mode는 Non chunked transfer mode 이지만 일반적인 server에서는 chunked transfer 지원.
  : http://en.wikipedia.org/wiki/Chunked_transfer_encoding

[HTTP request/response]


[HttpClient Sample Overview]
: https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.appprogramming/html/sample_descriptions/httpclient.htm



HTTP request를 전송하고 response의 body bytes 수를 표시하는 sample app.
tizen.org 를 대상으로 HTTPS request를 실행하므로 외부 network으로 연결에 문제가 없는 지 확인 필요. (proxy 같은)

[HTTP Client]
https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.appprogramming/html/tutorials/net_tutorial/task_httpclient.htm

* HTTP request 시 http transaction을 asynchronous 하게 처리하기 위해 아래 IHttpTransactionEventListener 등록이 필요

Tizen::Net::Http::IHttpTransactionEventListener
: https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.apireference/classTizen_1_1Net_1_1Http_1_1IHttpTransactionEventListener.html

  • OnTransactionAborted
  • OnTransactionCertVerificationRequestedN
  • OnTransactionCertVerificationRequiredN
  • OnTransactionCompleted
  • OnTransactionHeaderCompleted
  • OnTransactionReadyToRead
  • OnTransactionReadyToWrite


HTTP-GET 시나리오에서
no chunked transfer에서는 response header 전달 이후 전체 body가 전송되므로 다음과 같은 이벤트가 발생할 것이고

Client   event                                                                         Server
                                                 GET request >
 HeaderCompleted event        < response header
 Completed event                   < response body

chunked transfer에서는 response header 전달 이후 body가 나눠서 오므로 다음과 같은 이벤트가 발생할 것이다.

Client    event                                                                        Server
                                                 GET request >
 HeaderCompleted event        < response header
 ReadyToRead event               < response body
 ReadyToRead event               < response body
 ReadyToRead event               < response body
 Completed event                   < response body

HTTP-POST 시나리오는 아마도 이렇지 않을까? (해보지는 않음.)

Client     event                                                                       Server
                                          POST request header >
ReadyToWrite event                  request data >
ReadyToWrite event                  request data >
ReadyToWrite event                  request data >
ReadyToWrite event                  request data >
 Completed event                   < response body


[주의 및 참고]

* HTTP transfer를 app에서 사용 하려면 http privilege가 필요
  . manifest.xml > privileges > http://tizen.org/privilege/http

* HttpSession의 copy constructor가 private 인 관계로 reference를 사용할 수 없음

* 일반적인 얘기겠지만 HttpSession의 instance를 http transaction 중 유지하지 않으면 listener 호출 중 crash 발생됨.

* Http request 시 특정 사용자 정보를 response callback에서 확인하려면 HttpTransaction의 SetUserObject()를 사용하라 HttpTransaction instance는 request, response시 모두 접근할 수 있음.
 : https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.apireference/classTizen_1_1Net_1_1Http_1_1HttpTransaction.html#a8eff3be989218969838e76310cf99706


[HTTP programming guide]

나머지 항목들은 한번 보시라.

The most common uses of HTTP connectivity are the following:
https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.appprogramming/html/guide/net/http_connectivity.htm


[JSON]


Introduction to JSON on Tizen
https://developer.tizen.org/documentation/articles/introduction-json-on-tizen

JSON Parser App
https://developer.tizen.org/dev-guide/2.2.0/?topic=%2Forg.tizen.native.appprogramming%2Fhtml%2Ftutorials%2Fweb_tutorial%2Ftask_jsonparserapp.htm

이 예제에서는 JSON data를 parsing해서 key, value list를 제공해 주는 예제다.
단지 JSON data인 string을 parsing해서 탐색 가능한 data structure로 제공해 준다는 의미만 있을 뿐.. 그 이상의 편의는 제공하지 않는다.
사용자가 json response를 직접 parsing 해서 원하는 value를 가져오거나 utility 함수를 만들어서 key에 맞는 value를 가져오도록 해야 할듯.

   // Call JSON parser
   IJsonValue* pJson = JsonParser::ParseN(buf);

   __pJsonKeyList->RemoveAll(true);
   __pValueList->RemoveAll(true);
   TraverseFunction(pJson);


void
JsonForm::TraverseFunction(IJsonValue* pValue)
{   
   switch (pValue->GetType())
   {
      case JSON_TYPE_OBJECT:
      {
         JsonObject* pObject = reinterpret_cast< JsonObject* >(pValue);
         IMapEnumeratorT<const String*, IJsonValue*>* pMapEnum = pObject->GetMapEnumeratorN();

         while (pMapEnum->MoveNext() == E_SUCCESS)
         {

...
 case JSON_TYPE_STRING:
      {
         JsonString* pVal = reinterpret_cast< JsonString* >(pValue);
...
 case JSON_TYPE_ARRAY:
      {
         JsonArray* pJsonArray = static_cast< JsonArray* >(pValue);
         pJsonArray->GetCount();
         IEnumeratorT<IJsonValue*>* pEnum = pJsonArray->GetEnumeratorN();
         while (pEnum->MoveNext() == E_SUCCESS)
...
...

[Alternatives JSON parsers]

JsonCpp
: boost library 기반, library 생성을 위한 컴파일 필요
: http://jsoncpp.sourceforge.net/

cJSON
: simple, MIT license, 2013년이 최신 업데이트
: http://sourceforge.net/projects/cjson/

jsmn
: simple, MIT license, 아직 개발중으로 보임 2014년 최근까지 수정 내역 있음.
: https://bitbucket.org/zserge/jsmn/overview


다른 대안을 찾아 보니 위의 것들이 있었고 http://www.json.org/ 중간에 보면 각 언어에 맞는 json parser library들이 존재하니 알아서 취사 선택하면 될듯함.

간단하게 사용하고자 cJSON을 사용하였고 사실 Tizen에서 제공하는 JSON parser와 기능은 거의 동일하지만 c style로 api 사용 및 array 처리가 간단하다.
JsonCpp도 많이 사용하고 편리한 사용성의 api들을 제공하지만 boost library를 사용하므로 boost library의 cross compile이 필요하고 소스의 양도 좀 되서 그냥 패스함.


cJSON을 통해 parsing하면 cJSON* 이 리턴되고 이를 사용해서 key에 맞는 value들을 뽑아 내면 됨.

typedef struct cJSON {
struct cJSON *next,*prev;     /* next/prev allow you to walk array/object chains.
                                                      Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */

struct cJSON *child;             /* An array or object item will have a child pointer pointing to
                                                        a chain of the items in the array/object. */


int type; /* The type of the item, as above. */

char *valuestring; /* The item's string, if type==cJSON_String */
int valueint; /* The item's number, if type==cJSON_Number */
double valuedouble; /* The item's number, if type==cJSON_Number */

char *string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
} cJSON;


일반적이 primitive value들은 list traversal 방법으로 확인하면 되며 array나 object들은 아래 함수를 사용해서 뽑아내면 된다.

/* Get Array size/item / object item. */
int          cJSON_GetArraySize(cJSON *array)
cJSON *cJSON_GetArrayItem(cJSON *array,int item)
cJSON *cJSON_GetObjectItem(cJSON *object,const char *string)


cJSON* element = null;
element = cJSON_GetObjectItem(item, "peoples");
if(null != element)
{
int size = cJSON_GetArraySize(element);
for(int i = 0 ; i < size ; i++)
{
cJSON* child_element = cJSON_GetArrayItem(element, i);
AppLog("peoples (%d) = %s - %s", i, child_element->string, child_element->valuestring);
...
}
element = null;
}

[Thread]

https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.appprogramming/html/guide/base/thread.htm

Tizen에서는 아래와 같은 두가지(Main thread 제외)의 thread model들이 사용할 수 있고 각각의 특색은 다음과 같음.

[Event-driven Thread]
https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.apireference/classTizen_1_1Base_1_1Runtime_1_1EventDrivenThread.html
: Event-driven threads run based on events and they execute in a loop until they receive an event notification to terminate. Event-driven threads allow using asynchronous calls.

[Worker Thread]
https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.apireference/classTizen_1_1Base_1_1Runtime_1_1Thread.html
: Worker threads run linearly; they only execute the main body of the thread and exit. Asynchronous calls cannot be used, since the thread does not run an event-loop.




주기적인 HTTP request/response를 처리하고자 할 경우 Event-driven thread를 사용해야 함. 단순 http request/reponse를 처리할 때는 상관없으나 loop을 사용할 경우 worker thread에서는 바로 error가 발생하는 것으로 보인다. 실제 tizen platform에서 처리하는 방법은 알 수 없으나 주기적으로 반복해야 하는 상황에서는 event driven thread를 사용하는게 필요 함.

사용상의 별 특이점은 없음. 예제대로 만들면 잘 돌아감.. ㅎ

2014년 2월 14일 금요일

Tizen Native Application Programming 대충 정리

아래 내용은 Tizen 2.2 기반이며... 그 이상의 버전에서는 별 의미없는 내용들입니다.

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

그냥 Tizen SDK document를 보며 정리함.

[Tizen Architecture]
 : https://developer.tizen.org/dev-guide/2.2.1/org.tizen.gettingstarted/html/tizen_overview/tizen_architecture.htm

Tizen architecture

뭐.. Web API, Native API 있다고 보면.. 될듯


[Tizen Native Application Programming]
https://developer.tizen.org/dev-guide/2.2.1/org.tizen.native.appprogramming/html/cover_page.htm

[API reference]
 : https://developer.tizen.org/dev-guide/2.2.1/org.tizen.native.apireference/index.html

[Features]
 : Base
  . database, internatioalization, XML parsing 등
    > Tizen::BaseTizen::Io, and Tizen::Locales namespaces, and in the Libxml2 library.
  . sensors, display, vibrator, power management, device monitoring and event receiving, device analysis
    > Tizen::System

 : Appliation Framework
  . application management (system services, dialer, notification 등 - Tizen::App)

  . Application type
   > 안드로이드와 비슷하게 UI application(UIApp), service applications(ServiceApp) 을 지원함.
  . 소스는 보지 않았지만 아마도 실행 시 life cycle 관리를 위해서 App class에서 자신의 app ID를 넘겨 AppManager에 등록하여 관리하게 하는 것 같음.

app_namespace_classdiagram.png


 : User Interface (UI controls, accessbility- Tizen::Ui Tizen::Uix)

Figure: Tizen native application UI
Tizen native application UI

 : Frame은 Form을 가지고 Form은 full screen container(Ui:Container 상속)로 System UI, 여러 Control들을 포함하고 있음.

 // Creates an instance of Form
    Form* pForm = new Form();
    pForm->Construct(FORM_STYLE_NORMAL| FORM_STYLE_HEADER| FORM_STYLE_FOOTER);

    // Gets a pointer of the frame
    Frame *pFrame = UiApp::GetInstance()->GetAppFrame()->GetFrame();
    pFrame->AddControl(pForm);
    pFrame->SetCurrentForm(pForm);

    // Implements MyActionEventListener
    IActionEventListener* pListener = new MyActionEventListener();

    // Adds a header
    Header * pHeader = GetHeader();
    pHeader->SetTitleText(L"FormSample");

    // Adds a footer
    Footer * pFooter = GetFooter();
    pFooter->SetStyle(FOOTER_STYLE_TAB);
    pFooter->AddActionEventListener(*this);

    // Calls Invalidate() to display the form
    pForm->Invalidate(true)

  . Scene Management
   > 너무 많다.. 모르겠다..
   > https://developer.tizen.org/dev-guide/2.2.1/org.tizen.native.appprogramming/html/guide/ui/scene_management.htm

ui_scenes_namespace_classdiagram.png

 : Communication (3G, Wi-Fi, BT, HTTP, NFC -  Tizen::Net, SMS, MMS, email -  Tizen::Messaging ,  Tizen::Telephony)
 : Web  Tizen::Web
 : Service features (PIMS- Tizen::Social , content management-Tizen::Content)
 : Security Tizen::Security 
 : Graphics  Tizen::Graphics
 > 나머지들은 일반적인 거라....

[Two-phase Construction]
 : Tizen이 C++에서 제공하는 exception mechanism을 제공하지 않고 생성자에서 exception을 발생시킬 수 없으므로 class 생성 시 제대로 생성되었는지 확인하기 위한 approach
 : 초기화를 한뒤 Construct()를 호출하여 결과값을 확인

// Example 3:  Create CallManager
CallType callType = TYPE_UNDEFINED_CALL;
CallStatus callStatus = CALL_STATUS_UNDEFINED;

CallManager* pCallManager = new CallManager();

// Second-phase of construction must be called once right after instantiation
result r = pCallManager->Construct(*this); 

if (IsFailed(r))
{
   delete pCallManager;

   return r;
}

[Application life-cycle]
Figure: Application life-cycle
Application life-cycle


 : App class의 life-cycle 관련 event들을 override하면 상황에 따라서 app resource들을 처리할 수 있음.
virtual bool OnAppInitialized (void)
virtual bool OnAppInitializing (AppRegistry &appRegistry)=0
virtual bool OnAppTerminating (AppRegistry &appRegistry, bool urgentTermination=false)=0
virtual bool OnAppWillTerminate (void)

 : OnAppInitializing()에서 Frame을 추가, OnAppTerminating()에서 Frame 제거
  . 근데 Tizen SDK의 UI application에서는 OnAppInitialized()에서 Frame을 등록하고 있음..
 : Application 실행은 Process Manager가 필요한 library와 app executable을 메모리에 등록하고 OspMain()을 호출한다.

Launching UI applications

[App Control]
 : 다른 Application에서 특정 operation을 요청하는 방법, android intent의 목적과 유사.
    using namespace Tizen::App;

    void
    MyAppClass::AppControlDialSample(void)
    {

        String telUri = L"tel:12345678900";

        AppControl* pAc = AppManager::FindAppControlN(L"tizen.phone", L"http://tizen.org/appcontrol/operation/dial");
        if(pAc)
        {
            pAc->Start(&telUri, null, null, null);
            delete pAc;
        }
    }
 : App Control을 사용하기 위해서는 target application의 Application ID와 operation ID가 필요
 : app control 시작과 결과 전달은 IAppControlResponseListener interface 사용

Launching an application with an AppControl

 : Tizen paltform application의 app control
  > https://developer.tizen.org/dev-guide/2.2.1/org.tizen.native.appprogramming/html/guide/app/platform_appcontrols.htm

[UI application's frame]
 : UI application은 top-level main window인 frame이라는 것을 가지고 있음
 : 아래는 frame의 state transition chart.
 : 당연한 거지만 Deactivated라던지 Minimized될 경우는 resource management 필요

Figure: Frame state transition
Frame state transition

[메모리 부족 시]
 : OnAppCheckpointing (AppRegistry &appRegistry) 호출 후 아래 우선 순위로 종료 시킴
  1.    Background UI applications
  2.    Service applications
  3.    Background media players
  4.    Foreground UI application
  5.    Foreground media player
  6.    Application launcher
  7.    Voice or video call, or other system applications

[기타 참고링크]
Tizen App development : Troubleshooting by mygony
http://mygony.com/archives/4276

2013년 4월 2일 화요일

Android상에서 native shared library loading시 dlsym 성공 여부 확인 방법

이전 블로그에서 이전 함 (원본 글 2013/04/02 작성)

이전 포스트와 관련해서 
다시 linux man page를 찾아보니 dlerror() 예제에서 
dlerror() 를 호출하여 error message를 clear 시키고 있었음.

Load the math library, and print the cosine of 2.0:
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

int
main(int argc, char **argv)
{
    void *handle;
    double (*cosine)(double);
    char *error;

   handle = dlopen("libm.so", RTLD_LAZY);
    if (!handle) {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }

   dlerror();    /* Clear any existing error */

   /* Writing: cosine = (double (*)(double)) dlsym(handle, "cos");
       would seem more natural, but the C99 standard leaves
       casting from "void *" to a function pointer undefined.
       The assignment used below is the POSIX.1-2003 (Technical
       Corrigendum 1) workaround; see the Rationale for the
       POSIX specification of dlsym(). */

   *(void **) (&cosine) = dlsym(handle, "cos");

   if ((error = dlerror()) != NULL)  {
        fprintf(stderr, "%s\n", error);
        exit(EXIT_FAILURE);
    }

   printf("%f\n", (*cosine)(2.0));
    dlclose(handle);
    exit(EXIT_SUCCESS);
}

-----------------------------------------------------------------------------------------
이전 포스트 내용
-----------------------------------------------------------------------------------------

오늘 dlsym 관련 황당한 일이 있어 이를 글로 남김

일반적인 Linux에서 shared library를 loading하고자 할 경우 다음의 예제가 일반적이다.

  #include <stdlib.h>
    #include <stdio.h>
    #include <dlfcn.h>

    int main(int argc, char **argv) {
        void *handle;
        double (*cosine)(double);
        char *error;

        handle = dlopen ("/lib/libm.so.6", RTLD_LAZY);
        if (!handle) {
            fputs (dlerror(), stderr);
            exit(1);
        }

        cosine = dlsym(handle, "cos");
        if ((error = dlerror()) != NULL)  {
            fputs(error, stderr);
            exit(1);
        }

        printf ("%f\n", (*cosine)(2.0));
        dlclose(handle);
    }


하지만 위의 예제를 가지고 Android NDK로 빌드하여 Android 상에서 실행하였지만
"Symbol not found: " 라는 메세지와 함께 종종 dlsym이 실패하는 경우를 확인할 수 있었다.
(Android NDK r8d, Android target의 version은 JBP)

생성된 shared library의 symbol을 확인하였을때는 
해당 함수는 naming mangling(http://en.wikipedia.org/wiki/Name_mangling) 문제 없이 export 되어 있어서 library의 open에 성공하여 symbol 찾는데는 문제가 없어 보였다.

이래 저래 검색하다 아래와 같은 Android bug report를 검색하게 되었다. dlsym에서 symbol을 못찾는 문제와 조금 다른 bug report지만 dlerror() 메세지 처리에서 있어 GetLastError()와 같은 thread unsafe 한 상황이 발생할 수 있음을 알 수 있다.

아래의 답변이 가장 정리가 잘된듯 하다.

 Some important rules:
  1. you should use ndk-build instead directly arm-linux-androideabi-gcc
  2. in Android.mk of library type next options: LOCAL_LDFLAGS += -Wl,--export-dynamic
  3. if dlerror() returns error it doesn't mean that dlsym() failed. Check if dlsym() realle returns null. Because it may return a valid address but dlerror() may return error "Symbol not found" at the same time %)) I had this stupid mistake!!! See code.
  4. You do not need call System.LoadLibrary(your_lib.so). Only dlopen(your_lib.so) from native code with RTLD_NOW or RTLD_LAZY no matter!
  5. Be sure that your_lib.so is sutiated in /data/data/app_name/libs
    func = (int (*)(int, int)) dlsym( dl_handle, "calculate" ); error = (char *) dlerror(); if (error != NULL) {//replace it with if ((func == NULL)&& (error != NULL))__android_log_print(ANDROID_LOG_INFO,"nativeCode",error); return -3; }
That's all :)
share|improve this answer

위에서 답변과 같이 아래와 같이 dlsym의 error check 부분을 수정하여 정상 동작 함을 확인.

 if ((error = dlerror()) != NULL) {

if ((error = dlerror()) != NULL) && (cosine == NULL)) {

하지만 Linux man page를 확인하니 다음의 메세지가 있었음.

 dlsym()
The function dlsym() takes a "handle" of a dynamic library returned by dlopen() and the null-terminated symbol name, returning the address where that symbol is loaded into memory. If the symbol is not found, in the specified library or any of the libraries that were automatically loaded by dlopen() when that library was loaded, dlsym() returns NULL. (The search performed by dlsym() is breadth first through the dependency tree of these libraries.) Since the value of the symbol could actually be NULL (so that a NULL return from dlsym() need not indicate an error), the correct way to test for an error is to call dlerror() to clear any old error conditions, then call dlsym(), and then call dlerror() again, saving its return value into a variable, and check whether this saved value is not NULL.


그래서 아래 처럼 수정하는게 맞을 것 같다...

dlerror(); // to clear dlerror message
cosine = dlsym(handle, "cos");
if ((error = dlerror()) != NULL) && (cosine == NULL)) {




[참고]  nm, readelf를 사용하여 shared library의  symbol확인

 The standard tool for listing symbols is nm, you can use it simply like this:
nm -g yourLib.so
If you want to see symbols of a C++ library, add the "-C" option which demangle the symbols (it's far more readable demangled).
nm -gC yourLib.so
If your .so file is in elf format, you will have to use readelf program to extract symbol information from the binary.
readelf -Ws /usr/lib/libexample.so
You only should extract those that are defined in this .so file, not in the libraries referenced by it. Seventh column should contain a number in this case. You can extract the corresponding lines withawk:인인
readelf -Ws /usr/lib/libstdc++.so.6 | awk '{print $8}';
Update: Thanks to Pavel Shved and Gaspin, I've updated the answer
share|improve this answer