Merge "SoundPool: Clean up JNI weak reference handling" into tm-dev

This commit is contained in:
Andy Hung
2022-03-28 19:43:21 +00:00
committed by Android (Google) Code Review
2 changed files with 56 additions and 22 deletions

View File

@@ -29,7 +29,6 @@ import android.util.Log;
import java.io.File; import java.io.File;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@@ -156,8 +155,7 @@ public class SoundPool extends PlayerBase {
super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL); super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL);
// do native setup // do native setup
if (native_setup(new WeakReference<SoundPool>(this), if (native_setup(maxStreams, attributes, getCurrentOpPackageName()) != 0) {
maxStreams, attributes, getCurrentOpPackageName()) != 0) {
throw new RuntimeException("Native setup failed"); throw new RuntimeException("Native setup failed");
} }
mAttributes = attributes; mAttributes = attributes;
@@ -510,7 +508,7 @@ public class SoundPool extends PlayerBase {
private native final int _load(FileDescriptor fd, long offset, long length, int priority); private native final int _load(FileDescriptor fd, long offset, long length, int priority);
private native final int native_setup(Object weakRef, int maxStreams, private native int native_setup(int maxStreams,
@NonNull Object/*AudioAttributes*/ attributes, @NonNull String opPackageName); @NonNull Object/*AudioAttributes*/ attributes, @NonNull String opPackageName);
private native final int _play(int soundID, float leftVolume, float rightVolume, private native final int _play(int soundID, float leftVolume, float rightVolume,
@@ -522,17 +520,11 @@ public class SoundPool extends PlayerBase {
// post event from native code to message handler // post event from native code to message handler
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static void postEventFromNative(Object ref, int msg, int arg1, int arg2, Object obj) { private void postEventFromNative(int msg, int arg1, int arg2, Object obj) {
SoundPool soundPool = ((WeakReference<SoundPool>) ref).get(); Handler eventHandler = mEventHandler.get();
if (soundPool == null) {
return;
}
Handler eventHandler = soundPool.mEventHandler.get();
if (eventHandler == null) { if (eventHandler == null) {
return; return;
} }
Message message = eventHandler.obtainMessage(msg, arg1, arg2, obj); Message message = eventHandler.obtainMessage(msg, arg1, arg2, obj);
eventHandler.sendMessage(message); eventHandler.sendMessage(message);
} }

View File

@@ -262,10 +262,19 @@ using JObjectValue = std::remove_pointer_t<jobject>; // _jobject
// Note std::remove_ptr_t<NonPointerType> == NonPointerType. // Note std::remove_ptr_t<NonPointerType> == NonPointerType.
static_assert(std::is_same_v<JObjectValue*, jobject>); static_assert(std::is_same_v<JObjectValue*, jobject>);
// *jweak is needed to fit the jweak into a std::shared_ptr.
// This is the Android type _jobject, but we derive this type as JWeakValue.
using JWeakValue = std::remove_pointer_t<jweak>; // this is just _jobject
// Check that jweak is really a pointer to JWeakValue.
static_assert(std::is_same_v<JWeakValue*, jweak>);
// We store the ancillary data associated with a SoundPool object in a concurrent // We store the ancillary data associated with a SoundPool object in a concurrent
// hash map indexed on the SoundPool native object pointer. // hash map indexed on the SoundPool native object pointer.
auto& getSoundPoolJavaRefManager() { auto& getSoundPoolJavaRefManager() {
static ConcurrentHashMap<SoundPool *, std::shared_ptr<JObjectValue>> concurrentHashMap; // Note this can store shared_ptrs to either jweak and jobject,
// as the underlying type is identical.
static ConcurrentHashMap<SoundPool *, std::shared_ptr<JWeakValue>> concurrentHashMap;
return concurrentHashMap; return concurrentHashMap;
} }
@@ -284,6 +293,8 @@ auto& getSoundPoolJavaRefManager() {
// https://developer.android.com/training/articles/perf-jni // https://developer.android.com/training/articles/perf-jni
// https://android-developers.googleblog.com/2011/11/jni-local-reference-changes-in-ics.html // https://android-developers.googleblog.com/2011/11/jni-local-reference-changes-in-ics.html
// //
// Consider using a weak reference if this is self-referential.
[[maybe_unused]]
inline auto make_shared_globalref_from_localref(JNIEnv *env, jobject localRef) { inline auto make_shared_globalref_from_localref(JNIEnv *env, jobject localRef) {
return std::shared_ptr<JObjectValue>( return std::shared_ptr<JObjectValue>(
env->NewGlobalRef(localRef), env->NewGlobalRef(localRef),
@@ -292,6 +303,32 @@ inline auto make_shared_globalref_from_localref(JNIEnv *env, jobject localRef) {
}); });
} }
// Create a weak global reference from local ref.
inline auto make_shared_weakglobalref_from_localref(JNIEnv *env, jobject localRef) {
return std::shared_ptr<JWeakValue>(
env->NewWeakGlobalRef(localRef),
[](JWeakValue* weak) { // cannot cache env as don't know which thread we're on.
if (weak != nullptr) AndroidRuntime::getJNIEnv()->DeleteWeakGlobalRef(weak);
});
}
// std::unique_ptr<> does not store a type-erased deleter like std::shared_ptr<>.
// Define a lambda here to use for the std::unique_ptr<> type definition.
auto LocalRefDeleter = [](JObjectValue* object) {
if (object != nullptr) AndroidRuntime::getJNIEnv()->DeleteLocalRef(object);
};
// Create a local reference from another reference.
// This is a unique_ptr to avoid the temptation of sharing with other threads.
//
// This can be used to promote a WeakGlobalRef jweak into a stable LocalRef jobject.
//
inline auto make_unique_localref_from_ref(JNIEnv *env, jobject object) {
return std::unique_ptr<JObjectValue, decltype(LocalRefDeleter)>(
env->NewLocalRef(object),
LocalRefDeleter);
}
} // namespace } // namespace
static const char* const kAudioAttributesClassPathName = "android/media/AudioAttributes"; static const char* const kAudioAttributesClassPathName = "android/media/AudioAttributes";
@@ -438,10 +475,15 @@ static void android_media_callback(SoundPoolEvent event, SoundPool* soundPool, v
return; return;
} }
JNIEnv *env = AndroidRuntime::getJNIEnv(); JNIEnv *env = AndroidRuntime::getJNIEnv();
env->CallStaticVoidMethod( // "promote" the WeakGlobalRef into a LocalRef.
fields.mSoundPoolClass, fields.mPostEvent, auto javaSoundPool = make_unique_localref_from_ref(env, weakRef.get());
weakRef.get(), event.mMsg, event.mArg1, event.mArg2, if (!javaSoundPool) {
nullptr /* object */); ALOGW("%s: weak reference promotes to null (release() not called?), "
"ignoring callback", __func__);
return;
}
env->CallVoidMethod(javaSoundPool.get(), fields.mPostEvent,
event.mMsg, event.mArg1, event.mArg2, nullptr /* object */);
if (env->ExceptionCheck() != JNI_FALSE) { if (env->ExceptionCheck() != JNI_FALSE) {
ALOGE("%s: Uncaught exception returned from Java callback", __func__); ALOGE("%s: Uncaught exception returned from Java callback", __func__);
@@ -451,7 +493,7 @@ static void android_media_callback(SoundPoolEvent event, SoundPool* soundPool, v
} }
static jint static jint
android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, jobject weakRef, android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz,
jint maxChannels, jobject jaa, jstring opPackageName) jint maxChannels, jobject jaa, jstring opPackageName)
{ {
ALOGV("android_media_SoundPool_native_setup"); ALOGV("android_media_SoundPool_native_setup");
@@ -485,7 +527,7 @@ android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, jobject weakRef,
auto oldSoundPool = setSoundPool(env, thiz, soundPool); auto oldSoundPool = setSoundPool(env, thiz, soundPool);
// register Java SoundPool WeakRef using native SoundPool * as the key, for the callback. // register Java SoundPool WeakRef using native SoundPool * as the key, for the callback.
auto oldSoundPoolJavaRef = getSoundPoolJavaRefManager().set( auto oldSoundPoolJavaRef = getSoundPoolJavaRefManager().set(
soundPool.get(), make_shared_globalref_from_localref(env, weakRef)); soundPool.get(), make_shared_weakglobalref_from_localref(env, thiz));
ALOGW_IF(oldSoundPool != nullptr, "%s: Aliased SoundPool object %p", ALOGW_IF(oldSoundPool != nullptr, "%s: Aliased SoundPool object %p",
__func__, oldSoundPool.get()); __func__, oldSoundPool.get());
@@ -565,7 +607,7 @@ static JNINativeMethod gMethods[] = {
(void *)android_media_SoundPool_setRate (void *)android_media_SoundPool_setRate
}, },
{ "native_setup", { "native_setup",
"(Ljava/lang/Object;ILjava/lang/Object;Ljava/lang/String;)I", "(ILjava/lang/Object;Ljava/lang/String;)I",
(void*)android_media_SoundPool_native_setup (void*)android_media_SoundPool_native_setup
}, },
{ "native_release", { "native_release",
@@ -600,8 +642,8 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
return result; return result;
} }
fields.mPostEvent = env->GetStaticMethodID(clazz, "postEventFromNative", fields.mPostEvent = env->GetMethodID(
"(Ljava/lang/Object;IIILjava/lang/Object;)V"); clazz, "postEventFromNative", "(IIILjava/lang/Object;)V");
if (fields.mPostEvent == nullptr) { if (fields.mPostEvent == nullptr) {
ALOGE("Can't find android/media/SoundPool.postEventFromNative"); ALOGE("Can't find android/media/SoundPool.postEventFromNative");
return result; return result;