#java #android #android-ndk #crash #java-native-interface
#java #Android #android-ndk #авария #java-native-интерфейс
Вопрос:
Я опубликовал свое приложение в Google Play Store, и я столкнулся с одним сбоем, который затрагивает многих пользователей моего приложения, хотя я не могу воспроизвести его на своих собственных устройствах Android.
Сбой происходит после того, как приложение представляет действие входа в Google Play: когда оно возвращается к основному действию приложения через onActivityResult, вызывается функция JNI и происходит сбой.
Код JNI в основном определяет указатель на функцию C как обратный вызов операции входа, который передается в код Java через getstaticmethod ID (см. Код ниже).
Я должен быть в состоянии устранить этот недостаток, просто удалив вход в Google Play, но я хотел бы понять, почему мой код выходит из строя в некоторых конфигурациях Android, прежде чем принимать такое решение.
В какой-то момент вот как код C запрашивает вход:
/*
void *delegate;
void *(*onSuccess)(void *);
void *(*onError)(void *);
*/
jclass class = (*env)->FindClass(env, "com/xxx/yyy/zzz");
jmethodID method = (*env)->GetStaticMethodID(env, class, "signIn", "(JJJ)V");
if (method)
(*env)->CallStaticVoidMethod(env, class, method, delegate, onSuccess, onError);
Вот реализация метода signIn на стороне Java:
private final static int EXPLICIT_SIGN_IN = 9001;
private static long explicitSignInDelegate = 0;
private static long explicitSignInOnSuccess = 0;
private static long explicitSignInOnError = 0;
public static void signIn(long delegate, long onSuccess, long onError) {
GoogleSignInOptions options = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
.requestServerAuthCode(BuildConfig.SERVER_AUTH_CLIENT_ID)
.build();
GoogleSignInClient signInClient = GoogleSignIn.getClient(activity,
options);
signInClient.silentSignIn().addOnCompleteListener(activity,
new OnCompleteListener<GoogleSignInAccount>() {
@Override
public void onComplete(@NonNull Task<GoogleSignInAccount> task) {
if (task.isSuccessful()) {
Callback(delegate, onSuccess);
}
else {
explicitSignInDelegate = delegate;
explicitSignInOnSuccess = onSuccess;
explicitSignInOnError = onError;
activity.startActivityForResult(signInClient.getSignInIntent(), EXPLICIT_SIGN_IN);
}
}
});
}
Вот как обрабатывается onActivityResult:
public static void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case EXPLICIT_SIGN_IN: {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
if ((result != null) amp;amp; result.isSuccess()) {
Callback(explicitSignInDelegate, explicitSignInOnSuccess);
} else {
Callback(explicitSignInDelegate, explicitSignInOnError);
}
}
break;
}
}
Метод обратного вызова объявлен собственным и определен в коде JNI, как здесь:
JNIEXPORT void JNICALL Java_com_xxx_yyy_zzz_Callback( JNIEnv* env, jobject this, jlong delegate, jlong callback)
{
if (callback)
{
void *(*function)(void *) = (void *(*)(void *))callback;
function((void *)delegate);
}
}
Обратите внимание, что указатели и указатели на функции передаются как «длинные», когда речь заходит о Java.
В настоящее время вот такой аварийный журнал я получаю с панели управления Google Play:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 0, tid: 0 >>> com.xxx.yyy <<<
backtrace:
#00 pc 00000000000f5420 [anon:libc_malloc:89080000]
#01 pc 000000000004568f /data/app/com.xxx.yyy-9vGJILyZlpOQinnnYi0z8g==/lib/arm/libyyy.so (Java_com_xxx_yyy_zzz_Callback 26)
#02 pc 00000000000f1901 /data/app/com.xxx.yyy-9vGJILyZlpOQinnnYi0z8g==/oat/arm/base.odex
Ответ №1:
После того, как я смог воспроизвести сбой на пыльном устройстве, я понял, что проблема была вызвана «длинным» приведением, необходимым для передачи указателя через JNI: на 32-разрядном процессоре указатели (void *) имеют тот же размер, что и ‘int’, поэтому следует выполнить приведение к ‘long’явно.
Из кода C:
/*
void *delegate;
void *(*onSuccess)(void *);
void *(*onError)(void *);
*/
jlong jdelegate = (jlong)delegate;
jlong jonSuccess = (jlong)onSuccess;
jlong jonError = (jlong)onError;
jclass class = (*env)->FindClass(env, "com/xxx/yyy/zzz");
jmethodID method = (*env)->GetStaticMethodID(env, class, "signIn", "(JJJ)V");
if (method)
(*env)->CallStaticVoidMethod(env, class, method, jdelegate, jonSuccess, jonError);