安卓和JNI实时时钟

4
我遇到了一个小型Android应用程序的问题,涉及使用C (JNI)函数中的实时时钟信号。
似乎Android UI不喜欢来自C函数的实时信号定时器。
在以下的PoC中,一个定时器每秒触发5次信号,如果信号在UI更新时被触发,应用程序会崩溃。
  • 如果我不启动计时器 => 不会崩溃
  • 如果我没有在UI上放置任何内容 => 不会崩溃
我编写了这个小PoC来证明这种行为。Java部分只是调用JNI函数并在屏幕上放置一个按钮。
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);
    }
}    

以下是main.c文件的内容。实例化并启动了一个定时器。每200ms(每秒5次)调用一次cb()函数。

#include <jni.h>
#include <android/log.h>
#include <signal.h>
#include <time.h>
#include <strings.h>

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);
}

有时候它会立即停止工作,有时候我按下按钮后才停止(我认为这取决于信号/用户界面更新的时间),并且在日志中输出以下内容。
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

我尝试修改信号编号(从 SIGRTMINSIGRTMIN + 20),但没有成功...

我的问题是... 在不破坏 RenderThread 的情况下,能否在 JNI 函数中使用实时时钟信号?(是的,后者会崩溃)

在 JNI 函数中玩定时器是一个坏习惯吗?

1个回答

3
可能计时器信号传递到渲染线程或主线程。在这种情况下,它会中断该线程正在进行的系统调用(如果有)。这种情况可能会触发运行时代码中的某些断言。您可以尝试使用 SIGEV_THREAD_ID 将信号定向到专用线程。但要注意,此通知类型不适用于广泛使用。还有一些实时信号可能被 bionic 线程实现甚至 ART 静默使用。因此,很容易破坏某些东西。
附言:如果可能的话,您应该优先考虑使用 SIGEV_THREAD。在 Android 上看起来更可靠,因为您不必考虑正确的信号编号等。

是的!它可以使用SIGEV_THREAD方法运行...您认为与SIGEV_SIGNAL方法相比,我能获得同样的性能水平吗?我认为每次计时器触发时生成线程会更加苛刻。 - Davide Berra
我不确定安卓是否在每次计时器触发时生成新线程,但是,无论如何,其他通知方法或完全中断应用程序或使用过于冒险。遗憾的是,SIGEV_THREAD 是您唯一可靠的选择。 - Sergio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接