Tistory View
우선 Choreographer의 기본 방식을 모르는 독자는 다음의 링크를 보고 오기를 바란다. 기본적인 내용과 Java코드가 있다.
Native에서 Choreographer를 사용하려면 Api Level이 24(Nougat/누가)이상이 되어야 한다. 따라서 아직 누가 미만의 기기들이 많은 관계로 되도록이면 Java코드로 작성할 것은 현시점에서는 권장한다. 또한, Api27(Oreo_MR1/오레오) 이하에서는 구글 개발자의 착오로 문제가 있기에 실제 Api28이상만 쓸 수 있기 때문이다.
그래도 NDK에서 Api24이상를 지원하기 때문에 굳이 굳이 굳이 굳이 굳이~~~ Native로 코드를 작성하고 싶은 독자를 위한 글이다.(솔직히 왜 쓰는 지도 모르겠다.)
최소SdkVersion
choreographer의 헤더 파일인 <android/choreographer.h>를 사용하려면 minSdkVersion이 24가 되어야 한다. 그렇지 않으면 컴파일시 다음과 같은 에러를 보게 될 것이다.
use of undeclared identifier 'AChoreographer_getInstance'
따라서, build.gradle파일내에 다음의 부분을 찾아 minSdkVersion을 24이상으로 해줘야한다.
defaultConfig {
.
.
minSdkVersion 24
targetSdkVersion 29
.
.
versionCode 1
versionName "1.0"
.
.
}
또한 link로 android library 또한 넣어줘야 한다. 안드로이드 프로그래밍을 하는 것이기에 아마 거의 되어 있을 것이다.
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
.
.
-landroid
.
.
#-ljnigraphics # not
${log-lib} )
(-landroid <--를 추가한다. 이 게 없다면 다음과 같은 에러를 보게 될 것이다.
undefined reference to 'AChoreographer_getInstance'
위의 내용을 보면 이제 Nougat미만의 버전은 이 앱을 실행 시킬 수 없게 된다. 그래서 또 말하지만 그냥 Java로 작성하기를 권장한다.
그래도 하고 싶은 욕심쟁이들은 계속 읽어..야지 뭐~
Choreographer api
AChoreographer* AChoreographer_getInstance();
생성하는 것은 참 쉽다. Java와 마찬가지로 Choregrapher는 시스템 전체에서 공유된 1개만 있으면 되기에 free같은 작업을 할 필요가 없다.
Vsync가 일어날 때 호출되도록 하려면 다음의 함수를 쓴다.
void AChoreographer_postFrameCallback(
AChoreographer *choreographer,
AChoreographer_frameCallback callback,
void *data
);
void AChoreographer_postFrameCallback64(
AChoreographer *choreographer,
AChoreographer_frameCallback64 callback,
void *data
);
두개의 함수가 있는 데, AChoreographer_postFrameCallback은 api24에서 부터 지원하는 것이고, AChoreographer_postFrameCallback64는 api29에서 부터 지원한다. 29부터는 함수가 바뀌기 때문에 AChoreographer_postFrameCallback는 자연스럽게 Deprecate되고 말았다.
이 두함수의 콜백함수는 다음과 같이 생겼다.
void(* AChoreographer_frameCallback)(long frameTimeNanos, void *data);
void(* AChoreographer_frameCallback64)(int64_t frameTimeNanos, void *data);
다음과 같이 간단한 테스트 코드를 만들어 보자.
#include <jni.h>
#include <android/choreographer.h>
#define TAG "NdkChoreographerLog.cpp"
AChoreographer* choreographer;
void MyFrameCallback(long frameTimeNanos, void *data) {
LOGD( TAG, "frameTimeNanos=%ld", frameTimeNanos );
AChoreographer_postFrameCallback( choreographer, MyFrameCallback, nullptr );
}
extern "C"
JNIEXPORT void JNICALL
Java_com_tistory_jamssoft_blogcodetester_NdkChoreographerLog_nOnCreate(JNIEnv* env, jobject thiz ) {
choreographer = AChoreographer_getInstance();
AChoreographer_postFrameCallback( choreographer, MyFrameCallback, nullptr );
}
Activity의 onCreate에서 이 네이티브코드를 수행하면 다음과 같은 로그를 볼 수 있다.
2021-04-07 03:35:02.859 1513-1513/com... D/NdkChoreographerLog.cpp: frameTimeNanos=1944427446892
2021-04-07 03:35:02.872 1513-1513/com... D/NdkChoreographerLog.cpp: frameTimeNanos=1944440265346
2021-04-07 03:35:02.893 1513-1513/com... D/NdkChoreographerLog.cpp: frameTimeNanos=1944461009963
2021-04-07 03:35:02.906 1513-1513/com... D/NdkChoreographerLog.cpp: frameTimeNanos=1944474163433
2021-04-07 03:35:02.926 1513-1513/com... D/NdkChoreographerLog.cpp: frameTimeNanos=1944494621822
위와 같이 약 16~17ms(60fps)마다 최근의 Vsync 시간값을 출력해 주는 것을 알 수 있다. 이 결과는 갤럭시 S6(Nougat)에서 돌린 결과다.
다음은 다른 기기에서 돌린 결과다.
2021-04-07 03:50:02.685 11592-11592/c..r D/NdkChoreographerLog.cpp: frameTimeNanos=-65785732
2021-04-07 03:50:02.707 11592-11592/c..r D/NdkChoreographerLog.cpp: frameTimeNanos=-43179913
2021-04-07 03:50:02.718 11592-11592/c..r D/NdkChoreographerLog.cpp: frameTimeNanos=-32451913
2021-04-07 03:50:02.740 11592-11592/c..r D/NdkChoreographerLog.cpp: frameTimeNanos=-9817361
2021-04-07 03:50:02.751 11592-11592/c..r D/NdkChoreographerLog.cpp: frameTimeNanos=881817
2021-04-07 03:50:02.774 11592-11592/c..r D/NdkChoreographerLog.cpp: frameTimeNanos=23471018
2021-04-07 03:50:02.784 11592-11592/c..r D/NdkChoreographerLog.cpp: frameTimeNanos=34215665
2021-04-07 03:50:02.807 11592-11592/c..r D/NdkChoreographerLog.cpp: frameTimeNanos=56808866
2021-04-07 03:50:02.918 11592-11592/c..r D/NdkChoreographerLog.cpp: frameTimeNanos=167551560
이 결과는 G3에 oreo custom rom을 올려서 나오 결과다.
이 둘의 차이는 갤럭시S6는 64로 동작한 것이고, G3의 오레오는 32bit에서 동작한 결과다.
콜백함수 "void (*AChoreographer_frameCallback)(long frameTimeNanos, void *data)"에서 frameTimeNanos가 long형이라 64bit에서는 문제가 없지만 32bit에서 돌릴 경우 한계로 인해 위와 같이 문제가 발생한다.
즉 약 -21억~0~+21억사이에서 계속 빙글빙글 돌게 된다. frameTimeNanos는 nano-second라서 4.2초마 빙글빙글 돌게 된다. 이는 구글의 개발자가 64비트에서만 테스트를 한 것같다. 그러다 2~3년이 지나서야 누군가 리포트를 했든 자기가 갑자기 떠올랐든 치명적인 버그가 되어 버린 것이다. 이 문제가 Api28에 가서 64bit(int64_t)용 함수가 만들어 지면서 고쳐지게 되고 이전버전은 deprecate가 된다.
frameTimeNanos는 기기가 켜진 시점부터의 시간 값이기 때문에 32bit에서는 이 값을 쓸 수가 없게 된다.
그러니 그냥 java를 써라~, 아직도 쓰고 싶어요? 흠..ㅠㅠ
일단 minSdkVersion부터..
아직 이 글을 쓰는 시점에서 대부분의 앱들은 Kitkat이상을 지원한다. 하지만 Choreographer를 사용하기 위해서는 minSdkVersion을 24로 놓아야 하기 때문에, 세상에서 아직 많이 사용되는 기기를 포기 하기는 좀 이르다.
일단 api24미만도 지원을 하도록 호환코드를 만들어 보자. Choreographer는 Api16(JellyBean)이상에서 지원한다.
JellyBean은 대부분 태블릿에서 지원하기 때문에, 실제 최소 지원은 Api19(Kitkat)부터해도 문제가 별로 없다.
gradle.build파일에 minSdkVersion부분을 찾아 19로 변경한다.
Api24이상의 기기에서는 Ndk방식을 적용하는 코드를 만들기위해 Choregrapher함수가 들어있는 libandroid.so파일을 직접 접근하도록 한다. 따라서 <android/choreographer.h>파일을 include하지 않는다.
우선 함수의 원형을 정의한다.
typedef void (*AChoreographer_frameCallback)(long frameTimeNanos, void* data);
typedef void (*AChoreographer_frameCallback64)(int64_t frameTimeNanos, void *data);
typedef void (*AChoreographer_refreshRateCallback)(int64_t vsyncPeriodNanos, void *data);
typedef void (*func_AChoreographer_postFrameCallback)(AChoreographerCompat* choreographer, AChoreographer_frameCallback callback, void* data );
typedef void (*func_AChoreographer_postFrameCallbackDelayed)(AChoreographerCompat *choreographer, AChoreographer_frameCallback callback, void *data, long delayMillis );
// api 29
typedef void (*func_AChoreographer_postFrameCallback64)(AChoreographerCompat *choreographer, AChoreographer_frameCallback64 callback, void *data );
typedef void (*func_AChoreographer_postFrameCallbackDelayed64)(AChoreographerCompat *choreographer, AChoreographer_frameCallback64 callback, void *data, uint32_t delayMillis );
// api 30
typedef void (*func_AChoreographer_registerRefreshRateCallback)(AChoreographerCompat *choreographer, AChoreographer_refreshRateCallback, void *data );
typedef void (*func_AChoreographer_unregisterRefreshRateCallback)(AChoreographerCompat *choreographer, AChoreographer_refreshRateCallback, void *data );
함수의 포인터들을 담을 변수들을 만든다.
// api24
static func_AChoreographer_getInstance s_AChoreographer_getInstance = nullptr;
static func_AChoreographer_postFrameCallback s_AChoreographer_postFrameCallback = nullptr;
static func_AChoreographer_postFrameCallbackDelayed s_AChoreographer_postFrameCallbackDelayed = nullptr;
// api 29
static func_AChoreographer_postFrameCallback64 s_AChoreographer_postFrameCallback64 = nullptr;
static func_AChoreographer_postFrameCallbackDelayed64 s_AChoreographer_postFrameCallbackDelayed64 = nullptr;
// api 30
static func_AChoreographer_registerRefreshRateCallback s_AChoreographer_registerRefreshRateCallback = nullptr;
static func_AChoreographer_unregisterRefreshRateCallback s_AChoreographer_unregisterRefreshRateCallback = nullptr;
api24이상일 경우 libandroid.so파일을 Process에 붙인 다음 관련 함수 포인터를 전부 가지고 온다.
int sdkVersion = android_get_device_api_level();
if( sdkVersion >= 24 ) {
void* handle = nullptr;
func_AChoreographer_getInstance f_AChoreographer_getInstance = nullptr;
func_AChoreographer_postFrameCallback f_AChoreographer_postFrameCallback = nullptr;
func_AChoreographer_postFrameCallbackDelayed f_AChoreographer_postFrameCallbackDelayed = nullptr;
func_AChoreographer_postFrameCallback64 f_AChoreographer_postFrameCallback64 = nullptr;
func_AChoreographer_postFrameCallbackDelayed64 f_AChoreographer_postFrameCallbackDelayed64 = nullptr;
func_AChoreographer_registerRefreshRateCallback f_AChoreographer_registerRefreshRateCallback = nullptr;
func_AChoreographer_unregisterRefreshRateCallback f_AChoreographer_unregisterRefreshRateCallback = nullptr;
handle = dlopen( "libandroid.so", RTLD_NOW );
if( !handle ) {
LOGE( TAG, "dlopen libandroid.so failrue" );
break;
}
f_AChoreographer_getInstance = (func_AChoreographer_getInstance)dlsym( handle, "AChoreographer_getInstance" );
if( !f_AChoreographer_getInstance ) {
LOGE( TAG, "dlsym [AChoreographer_getInstance] failure" );
break;
}
f_AChoreographer_postFrameCallback = (func_AChoreographer_postFrameCallback)dlsym(handle, "AChoreographer_postFrameCallback" );;
if( !f_AChoreographer_postFrameCallback ) {
LOGE( TAG, "dlsym [AChoreographer_postFrameCallback] failure" );
break;
}
f_AChoreographer_postFrameCallbackDelayed = (func_AChoreographer_postFrameCallbackDelayed)dlsym(handle, "AChoreographer_postFrameCallbackDelayed" );
if( !f_AChoreographer_postFrameCallbackDelayed ) {
LOGE( TAG, "dlsym [AChoreographer_postFrameCallbackDelayed] failure" );
break;
}
if( sdkVersion >= 29 ) {
f_AChoreographer_postFrameCallback64 = (func_AChoreographer_postFrameCallback64)dlsym(handle, "AChoreographer_postFrameCallback64" );
if( !f_AChoreographer_postFrameCallback64 ) {
LOGE( TAG, "dlsym [AChoreographer_postFrameCallback64] failure" );
break;
}
f_AChoreographer_postFrameCallbackDelayed64 = (func_AChoreographer_postFrameCallbackDelayed64)dlsym(handle, "AChoreographer_postFrameCallbackDelayed64" );
if( !f_AChoreographer_postFrameCallbackDelayed64 ) {
LOGE( TAG, "dlsym [AChoreographer_postFrameCallbackDelayed64] failure" );
break;
}
}
if( sdkVersion >= 30 ) {
f_AChoreographer_registerRefreshRateCallback = (func_AChoreographer_registerRefreshRateCallback)dlsym(handle, "AChoreographer_registerRefreshRateCallback" );
if( !f_AChoreographer_registerRefreshRateCallback ) {
LOGE( TAG, "dlsym [AChoreographer_registerRefreshRateCallback] failure" );
break;
}
f_AChoreographer_unregisterRefreshRateCallback = (func_AChoreographer_unregisterRefreshRateCallback)dlsym(handle, "AChoreographer_unregisterRefreshRateCallback" );
if( !f_AChoreographer_unregisterRefreshRateCallback ) {
LOGE( TAG, "dlsym [AChoreographer_unregisterRefreshRateCallback] failure" );
break;
}
}
// ok all success
s_AChoreographer_getInstance = f_AChoreographer_getInstance;
s_AChoreographer_postFrameCallback = f_AChoreographer_postFrameCallback;
s_AChoreographer_postFrameCallbackDelayed = f_AChoreographer_postFrameCallbackDelayed;
s_AChoreographer_postFrameCallback64 = f_AChoreographer_postFrameCallback64;
s_AChoreographer_postFrameCallbackDelayed64 = f_AChoreographer_postFrameCallbackDelayed64;
s_AChoreographer_registerRefreshRateCallback = f_AChoreographer_registerRefreshRateCallback;
s_AChoreographer_unregisterRefreshRateCallback = f_AChoreographer_unregisterRefreshRateCallback;
LOGD( TAG, "s_AChoreographer_getInstance=%p", (void*)s_AChoreographer_getInstance );
LOGD( TAG, "s_AChoreographer_postFrameCallback=%p", (void*)s_AChoreographer_postFrameCallback );
LOGD( TAG, "s_AChoreographer_postFrameCallbackDelayed=%p", (void*)s_AChoreographer_postFrameCallbackDelayed );
LOGD( TAG, "s_AChoreographer_postFrameCallback64=%p", (void*)s_AChoreographer_postFrameCallback64 );
LOGD( TAG, "s_AChoreographer_postFrameCallbackDelayed64=%p", (void*)s_AChoreographer_postFrameCallbackDelayed64 );
LOGD( TAG, "s_AChoreographer_registerRefreshRateCallback=%p", (void*)s_AChoreographer_registerRefreshRateCallback );
LOGD( TAG, "s_AChoreographer_unregisterRefreshRateCallback=%p", (void*)s_AChoreographer_unregisterRefreshRateCallback );
}
이제 호환함수를 다음과 같은 식으로 재작성한다.
typedef void AChoreographerCompat;
typedef void (*AChoreographerCompat_frameCallback64)(int64_t frameTimeNanos, void *data);
typedef void (*AChoreographerCompat_refreshRateCallback)(int64_t vsyncPeriodNanos, void *data);
typedef AChoreographerCompat* (*func_AChoreographer_getInstance)();
AChoreographerCompat* AChoreographerCompat_getInstance();
bool AChoreographerCompat_PostFrameCallBack(AChoreographerCompat* choreographer, AChoreographerCompat_frameCallback64 callBack, void* data );
bool AChoreographerCompat_PostFrameCallBackDelayed(AChoreographerCompat* choreographer, AChoreographerCompat_frameCallback64 callBack, void* data, uint32_t delayMillis );
bool AChoreographerCompat_RegisterRefreshRateCallback( AChoreographerCompat *choreographer, AChoreographerCompat_refreshRateCallback callback, void *data );
bool AChoreographerCompat_UnregisterRefreshRateCallback( AChoreographerCompat *choreographer, AChoreographerCompat_refreshRateCallback callback, void *data );
AChoreographerCompat* AChoreographerCompat_getInstance() {
if( s_AChoreographer_getInstance ) {
return s_AChoreographer_getInstance();
}
return nullptr;
}
// AChoreographerCompat_PostFrameCallBack
// AChoreographerCompat_PostFrameCallBackDelayed
.
.
.
예시에서 "AChoreographerCompat* AChoreographerCompat_getInstance()"처럼 if문으로 구분하여, 다른 함수들도 모두 재작성을 한다.
최소 api값이 24미만이더라도 문제 없이 컴파일할 수 있다.
Api29미만의 32bit문제 해결
위에서 찾은 버그는 Api가 24~28이라도 64bit에서는 문제가 되지 않는다. 32bit일 경우만 문제가 되기 때문에 32bit에서만 다음의 코드를 사용하여 문제를 잡아보자.
static void Choreographer_frameCallback(long frameTimeNanos, void* data) {
//LOGD( TAG, "frameTimeNanos=%" PRId64, frameTimeNanos );
//LOGD( TAG, "frameTimeNanos=%ld", frameTimeNanos );
// ULONG_MAX <limits.h>
const int64_t MYULONGMAX = (int64_t)ULONG_MAX;
assert( sizeof(long) == 4 );
CALLBACK_PARAM_BUG2432* a = (CALLBACK_PARAM_BUG2432*)data;
// 음수로 계산하면 머리가 아파 양수로만 계산한다.
int64_t u = *(unsigned long*)&frameTimeNanos;
int64_t delta;
int64_t now = GetNowNanos();
// 최초 실행 시
if( g_isDoFramecalled ) {
// long형 문제
// long type can handle about 4.2seconds
// if time is passed over 4.2seconds, deta value is not reliable,
// check about 2.1seconds or more passed
if( now - g_frameTimeNanos > MYULONGMAX / 2 ) {
LOGD( TAG, "too 2.1second or more time passed reset time" );
// just reset
g_isDoFramecalled = false;
} else {
}
}
if( !g_isDoFramecalled ) {
g_isDoFramecalled = true;
g_frameTimeNanos = now;
g_lastFrameTimeNanos = u;
}
if( u < g_lastFrameTimeNanos ) {
delta = MYULONGMAX - g_lastFrameTimeNanos + u;
} else {
delta = u - g_lastFrameTimeNanos;
}
g_frameTimeNanos += delta;
g_lastFrameTimeNanos = u;
//LOGD( TAG, "%" PRId64 " %" PRIu32, g_frameTimeNanos, u );
// 마지막 부분
a->callback( g_frameTimeNanos, a->data );
}
위의 코드는 api24~28중 32bit일 경우만 호출 되는 코드다.
// 최초 실행 시
Vsync값은 이 CLOCK_MONOTONIC값을 사용하기 때문에, 최초에 이 함수가 실행되면 현재의 시간값을 가지고 와서(CLOCK_MONOTONIC) 이 값을 그냥 다시 사용자 콜백함수로 전달한다.
// long형 문제 : 이 부분이 실제 32bit에서 발생하는 문제를 잡는 부분이다.
위에서 언급했든 4.2초마다 값이 빙글빙글 돌게 된다. 이 함수는 이전의 값과 현재의 값이 차이를 이용하여 g_lastFrameTimeNanos를 계속 업데이트하게 되는데, 이 함수가 4.2초이내에 다시 실행되면 문제가 없지만, 그렇지 않으면 값이 뒤틀어지게 된다. 따라서 4.2초가 넘으면 다시 현재 시간값으로 재조정해주는 코드다. 위의 코드에서는 2.1초를 사용하지만 실제 대략 170ms(10frame on 60fps )에서 4.2sec사이의 값을 사용하기만 하면 된다.
// 마지막 부분은
Callback함수를 이 함수로 지정해 놓고, 이 함수에서 postFrameCallback함수에 지정된 callback함수를 호출한다.
이 함수를 사용하기 위해 postFrameCallback함수는 다음과 같이 작성된다.
bool AChoreographerCompat_PostFrameCallBack(AChoreographerCompat* choreographer, AChoreographerCompat_frameCallback64 callBack, void* data ) {
if( s_AChoreographer_postFrameCallback64 ) {
s_AChoreographer_postFrameCallback64( choreographer, callBack, data );
return true;
}
if( s_AChoreographer_postFrameCallback && sizeof(long) == 8 ) {
s_AChoreographer_postFrameCallback(choreographer, (AChoreographer_frameCallback)callBack, data );
return true;
}
if( s_AChoreographer_postFrameCallback ) {
assert( sizeof(long) == 4);
CALLBACK_PARAM_BUG2432* param = nullptr;
pthread_mutex_lock( g_mutex );
param = g_queue + g_queue_index;
g_queue_index++;
if( g_queue_index >= QUEUE_COUNT_FOR_BUG2432 ) {
g_queue_index = 0;
}
pthread_mutex_unlock( g_mutex );
param->callback = callBack;
param->data = data;
s_AChoreographer_postFrameCallback(choreographer, Choreographer_frameCallback, param);
return true;
}
LOGE( TAG, "maybe device version < 24" );
return false;
}
api가 28이상이거나 64bit일 경우는 그냥 호출한다. 64비트인지 32비트인지는 sizeof(long)을 이용하여 검사하기 때문에 실제 컴파일 단계에서 32/64를 구분되어 컴파일이 되어버린다.(Optimizer가 얼마나 해줄지는 잘... 모르겠다.)
두번 호출해도 한번만 Callback이 실행 된다. (맞나?)
위에 코드에 보면 mutex를 이용하고 있는 데, 이 게 좀 복잡한 문제가 있다.
postFrameCallback을 호출하면 api24/32bit의 경우 미리만들어놓은 callback을 통해 사용자가 지정한 callback을 호출하는 방식을 사용하는데, 사용자의 callback을 어딘가에 저장을 해 둬야 했다. 그래서 메모리를 할당하게 되지만, postFrameCallback을 여러번 호출해도 이는 다음 frame에 callback함수를 호출하라는 뜻이기에, 한번만 호출이 되서 할당한 메모리가 해제 되지 않는 문제가 발생할 수 있다. 그래서 메모리를 할당하지 않고 좀 큰 사이즈의 Queue만들어 사용하기로 결정했다. (다른 쓰레드에서 날리면 두번 호출 되기도 한다. Java편에 언급해 놓았다.)
// 사용자의 callback과 data를 담는 구조체
struct CALLBACK_PARAM_BUG2432 {
AChoreographerCompat_frameCallback64 callback;
void* data;
};
#define QUEUE_COUNT_FOR_BUG2432 16
.
.
static pthread_mutex_t* g_mutex = nullptr;
static CALLBACK_PARAM_BUG2432 g_queue[QUEUE_COUNT_FOR_BUG2432];
static int g_queue_index = 0; // index of g_queue to fetch
위 코드와 같이 아예 할당자체를 하지 않는 방식을 사용 했다.
(그리고 보니,, 사용자Callback도 이 문제가 발생할 수 있다는 생각이 든다... 음.. 몰라..)
mutex는 이 g_queue_index값을 synchronize시키기 위해 만든 것이다.
전체 소스코드는 이 링크를 통해 다운로드 받을 수 있다.
우선 app이 최초에 실행 될 때, Choreographer_init()함수를 호출을 해줘야 한다.
그럴 일 거의 없겠지만 Choreographer사용을 마치면, Choreographer_destroy()함수로 깨끗이 정리를 해 준다.
기본 사용법은 다음의 예제로 대체한다.
// 전역이나 member변수로 만들어 둔다.
AChoreographerCompat* choreographer = AChoreographerCompat_getInstance();
void MyCallback( int64_t frameTimeNanos, void *data ) {
LOGD( TAG, "frameTimeNanos %" PRId64 " data=%p", frameTimeNanos, data );
if( 애니메이션이 끝나지 않으면 다시 예약 ) {
void* data2 = 0x4567;
AChoreographerCompat_PostFrameCallBack( choreographer, MyCallback, data2 );
}
}
void* data = 0x1234;
AChoreographerCompat_PostFrameCallBack( choreographer, MyCallback, data );
다시 말하지만.. 그냥 Java에서 처리해라... 제발..ㅠㅠ 플리즈..ㅠㅠ
Reference
Google의 ndk api
developer.android.com/ndk/reference/group/choreographer
'Android Develop > helper' 카테고리의 다른 글
android prebuilt curl library (0) | 2021.12.22 |
---|---|
안드로이드 Choreographer 사용하기 (1) | 2021.03.31 |
안드로이드 byte배열을 String 한글 변환(+charsetDecoder) (0) | 2020.12.30 |
Buffer(ByteBuffer, CharBuffer...) flip, compact, clear사용법 (0) | 2020.12.30 |
안드로이드 HttpUrlConnection POST 전송 #2 (3) | 2020.12.25 |
- Total
- Today
- Yesterday
- choreographer
- 컴퓨트쉐이더
- ComputeShader
- Android
- 재테크
- 사용료
- 아끼는 법
- 경제보복
- 적금
- texture
- 애드센스
- 전기요금
- 재태크
- 텍스처
- gpgpu
- 예금
- 에어콘
- 전기세
- 금리
- TTS
- OpenGLes
- 안드로이드
- 공유 컨텍스트
- 컴퓨트셰이더
- 전기료
- 티스토리
- 애드핏
- 에어컨
- OpenGL ES
- 블로그
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |