Horloge temps réel Android et JNI

J’ai eu un problème avec une mini application Android et l’utilisation de signaux d’horloge temps réel dans une fonction C (JNI) .

Il semble que l’ Android UI n’aime pas les signaux en temps réel des timers instanciés dans une fonction C.

Dans la PoC suivante, une timer déclenche un signal 5 fois par seconde et si le signal est déclenché pendant la mise à jour de l’interface utilisateur, l’application se bloque.

  • Si je ne démarre pas la timer => pas de crash
  • Si je ne mets rien sur l’interface utilisateur => pas de crash

J’ai écrit cette petite PoC pour démontrer le comportement. La partie Java appelle simplement la fonction JNI et place un bouton à l’écran.

 public class MainActivity extends AppCompatActivity { Button bt; static { System.loadLibrary("testtimer-jni"); } /* JNI ingresso */ public native void jniStartTimer(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); jniStartTimer(); /* load button */ bt = new Button(getBaseContext()); setContentView(bt); } } 

et ceci est le contenu du fichier main.c Une timer est instanciée et démarrée. Toutes les 200ms (5 fois par seconde), la fonction cb() est appelée.

 #include  #include  #include  #include  #include  timer_t timer_id = 0x12; struct itimerspec timer; struct sigevent te; struct sigaction sa; void cb(int sig, siginfo_t *si, void *uc) { __android_log_write(ANDROID_LOG_ERROR, "Test", "Called callback"); } void Java_it_dbtecno_testtimer_MainActivity_jniStartTimer(JNIEnv *env, jobject thiz) { __android_log_write(ANDROID_LOG_ERROR, "Test", "Timer inited"); /* prepare timer to emulate video refresh interrupts */ sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = cb; sigemptyset(&sa.sa_mask); if (sigaction(SIGRTMIN + 7, &sa, NULL) == -1) return; bzero(&te, sizeof(struct sigevent)); /* set and enable alarm */ te.sigev_notify = SIGEV_SIGNAL; te.sigev_signo = SIGRTMIN + 7; te.sigev_value.sival_ptr = &timer_id; timer_create(CLOCK_REALTIME, &te, &timer_id); timer.it_value.tv_sec = 1; timer.it_value.tv_nsec = 1000; timer.it_interval.tv_sec = 0; timer.it_interval.tv_nsec = 1000000000 / 5; /* start timer */ timer_settime(timer_id, 0, &timer, NULL); } 

Parfois, il meurt instantanément, parfois après que j’appuie sur le bouton (je pense que cela dépend du moment de la mise à jour du signal / de l’interface utilisateur) et qu’il le sort dans le journal

 09-22 11:52:12.087 13587-13587/it.dbtecno.testtimer I/Test: Called callback 09-22 11:52:12.288 13587-13587/it.dbtecno.testtimer I/Test: Called callback 09-22 11:52:12.501 13587-13587/it.dbtecno.testtimer I/Test: Called callback 09-22 11:52:12.532 13587-13587/it.dbtecno.testtimer A/OpenGLRenderer: Task is already in the queue! 09-22 11:52:12.532 13587-13587/it.dbtecno.testtimer A/libc: Fatal signal 6 (SIGABRT), code -6 in tid 13587 (tecno.testtimer) 09-22 11:52:12.637 1187-1187/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 09-22 11:52:12.637 1187-1187/? A/DEBUG: Build fingerprint: 'Android/sdk_google_phone_x86/generic_x86:6.0/MASTER/3079352:userdebug/test-keys' 09-22 11:52:12.637 1187-1187/? A/DEBUG: Revision: '0' 09-22 11:52:12.637 1187-1187/? A/DEBUG: ABI: 'x86' 09-22 11:52:12.638 1187-1187/? A/DEBUG: pid: 13587, tid: 13587, name: tecno.testtimer >>> it.dbtecno.testtimer <<< 09-22 11:52:12.638 1187-1187/? A/DEBUG: signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr -------- 09-22 11:52:12.642 1187-1187/? A/DEBUG: Abort message: 'Task is already in the queue!' 09-22 11:52:12.642 1187-1187/? A/DEBUG: eax 00000000 ebx 00003513 ecx 00003513 edx 00000006 09-22 11:52:12.642 1187-1187/? A/DEBUG: esi b77a5c50 edi 0000000b 09-22 11:52:12.642 1187-1187/? A/DEBUG: xcs 00000073 xds 0000007b xes 0000007b xfs 00000007 xss 0000007b 09-22 11:52:12.642 1187-1187/? A/DEBUG: eip b736f666 ebp 00003513 esp bfdc7540 flags 00200202 09-22 11:52:12.644 1187-1187/? A/DEBUG: backtrace: 09-22 11:52:12.645 1187-1187/? A/DEBUG: #00 pc 00084666 /system/lib/libc.so (tgkill+22) 09-22 11:52:12.650 1187-1187/? A/DEBUG: #01 pc 00081608 /system/lib/libc.so (pthread_kill+70) 09-22 11:52:12.651 1187-1187/? A/DEBUG: #02 pc 00027205 /system/lib/libc.so (raise+36) 09-22 11:52:12.651 1187-1187/? A/DEBUG: #03 pc 000209e4 /system/lib/libc.so (abort+80) 09-22 11:52:12.659 1187-1187/? A/DEBUG: #04 pc 0000cbc3 /system/lib/libcutils.so (__android_log_assert+128) 09-22 11:52:12.660 1187-1187/? A/DEBUG: #05 pc 00025e81 /system/lib/libhwui.so (android::uirenderer::renderthread::RenderThread::queue(android::uirenderer::renderthread::RenderTask*)+81) 09-22 11:52:12.660 1187-1187/? A/DEBUG: #06 pc 00021b44 /system/lib/libhwui.so 09-22 11:52:12.660 1187-1187/? A/DEBUG: #07 pc 000243e5 /system/lib/libhwui.so (android::uirenderer::renderthread::RenderProxy::syncAndDrawFrame()+29) 09-22 11:52:12.660 1187-1187/? A/DEBUG: #08 pc 000ba75b /system/lib/libandroid_runtime.so 09-22 11:52:12.660 1187-1187/? A/DEBUG: #09 pc 72dfe20e /data/dalvik-cache/x86/system@framework@boot.oat (offset 0x1eb2000) 09-22 11:52:12.713 1187-1187/? A/DEBUG: Tombstone written to: /data/tombstones/tombstone_08 09-22 11:52:12.713 1187-1187/? E/DEBUG: AM write failed: Broken pipe 

J’ai aussi essayé de changer le numéro du signal (de SIGRTMIN à SIGRTMIN + 20 ) mais pas de chance ….

Ma question est …. Est-il possible d’utiliser des signaux d’horloge en temps réel dans une fonction JNI sans rompre le RenderThread? (Oui, c’est ce dernier qui se bloque)

Est-ce une mauvaise habitude de jouer avec des timers dans les fonctions JNI?

Le signal de la timer est probablement transmis au thread de rendu ou au thread principal. Dans ce cas, il interrompt l’appel système en cours dans ce thread (le cas échéant). Et cette situation peut déclencher des assertions dans le code d’exécution. Vous pouvez jouer avec SIGEV_THREAD_ID pour diriger le signal vers le thread dédié. Sachez toutefois que ce type de notification n’est pas destiné à une utilisation généralisée. De plus, certains signaux en temps réel peuvent être utilisés en mode silencieux par la mise en œuvre du threading bionique ou même par ART. Il est donc très facile de casser quelque chose.

PS si possible – vous devriez préférer SIGEV_THREAD . Il semble plus fiable sur Android puisque vous ne devriez pas penser aux numéros de signaux appropriés, etc.