我使用哪个上下文在单例中加载资源?

Which context do i use to load resources in a singleton?

我有一个 SoundPool,我想在不同的片段中播放。所以我把它加载到一个单例中。我必须使用什么上下文?

object PingSoundPool {

fun loadpings(note: Int) {

    val context = Application()

    val mAttributes = AudioAttributes.Builder()
        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
        .setUsage(AudioAttributes.USAGE_GAME)
        .build()

    val mSoundPool = SoundPool.Builder()
        .setMaxStreams(9)
        .setAudioAttributes(mAttributes)
        .build()

    val cping = mSoundPool.load(context, R.raw.cping, 1)
    val dbping = mSoundPool.load(context, R.raw.dbping, 1)
    [...]

    if (note == 0) {}
    if(note == 1)
        mSoundPool.play(cping, 1f, 1f, 1, -1, 1f)
    if(note == 2)
    mSoundPool.play(dbping, 1f, 1f, 1, -1, 1f)
    [...]
    }
}

如果我像这样使用它,像这样 PingSoundPool.loadPings(0) 将它加载到我的 activity 的 onCreate 中并使用 PingSoundPool.loadPings(1) 在 onClickListener 中访问它应该可以,不是吗? 在运行时,我得到一个像这样的 NullPointerExeption:

java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.example.soulfetch2/com.example.soulfetch2.FullscreenActivity}:
 java.lang.NullPointerException: Attempt to invoke virtual method 
'android.content.res.Resources android.content.Context.getResources()' 
on a null object reference

异常指出行val cping = mSoundPool.load(context, R.raw.cping, 1) R.raw。文件存在,但无法以某种方式访问​​。我想我可能使用了错误的上下文。或者我以错误的方式实现了正确的上下文。 无论如何,非常感谢您的帮助。


编辑:

原问题已解决,但仍有问题:代码每次尝试播放声音时都会重新加载 SoundPool。嘿,有人知道如何单独加载它,以便 PingSoundPool(this).loadPings(Int) 的调用只是播放声音而不是重新加载所有内容吗?

另一件事:当我从 Activity 执行 PingSoundPool(this).loadPings(Int) 时,一切正常。然而,从一个片段中,我得到了一个 TypeMismatch "Required: Context, Found: MainFragment"。我可以使用 PingSoundPool(this.requireContext()).loadPings(2)PingSoundPool(this.context!!).loadPings(2) 解决它,但这似乎不是最好的做法。有什么建议吗?

这是我现在用来代替对象的 class:

class PingSoundPool(context: Context) {

    val mAttributes = AudioAttributes.Builder()
        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
        .setUsage(AudioAttributes.USAGE_GAME)
        .build()

    val mSoundPool = SoundPool.Builder()
        .setMaxStreams(9)
        .setAudioAttributes(mAttributes)
        .build()

    val cping = mSoundPool.load(context, R.raw.cping, 1)
    val dbping = mSoundPool.load(context, R.raw.dbping, 1)

fun loadPings(note: Int) {

    if(note == 1)
        mSoundPool.play(cping, 1f, 1f, 1, -1, 1f)
    if(note == 2)
    mSoundPool.play(dbping, 1f, 1f, 1, -1, 1f)
[...]
}

}

您不应将 Application 的实例创建为新的 Context,您需要将现有的 Context 传递给您的方法:

fun loadpings(note: Int, ctx: Context) {
    ...
    val cping = mSoundPool.load(ctx, R.raw.cping, 1)
}

如果您从 Activity 调用该方法,只需传递 this@YourActivity:

PingSoundPool.loadpings(0, this@YourActivity)

或者只是

PingSoundPool.loadpings(0, this)

问题出在这一行:

val context = Application()

您在这里创建了一个新的 Application 对象,您不应该这样做。您在这里有两个选择:

  1. PingSoundPool 设为 class 而不是将 Context 作为构造函数参数,并改为使用它:
class PingSoundPool(private val context: Context) {
  ...

  fun loadpings(note: Int) {
    [...]
    val cping = mSoundPool.load(context, R.raw.cping, 1)
    val dbping = mSoundPool.load(context, R.raw.dbping, 1)
    [...]
  }
  1. 使此方法 loadPings 接受 Context 作为参数:
fun loadPings(note: Int, context: Context) {
   [...]
   val cping = mSoundPool.load(context, R.raw.cping, 1)
   val dbping = mSoundPool.load(context, R.raw.dbping, 1)
   [...]
}

然后对于方法 1. 和 2. 传递 Application/Activity/Fragment 作为 Context。就个人而言,我更喜欢方法 1。因为它的扩展性更好,您是否应该在将来添加更多依赖 Context 的方法。

希望对您有所帮助!

如果您从 activity 的 onCreate 调用它,为什么不也将 Context 作为参数传递?

函数将是这样的:

fun loadPings(context: Context, note: Int) {

    //val context = Application()   //Remove this line

    val cping = mSoundPool.load(context, R.raw.cping, 1)    //Here it's used the parameter context
    val dbping = mSoundPool.load(context, R.raw.dbping, 1)
    [...]
}

并且在您的 Activity 的 onCreate 中,您可以这样称呼它:

PingSoundPool.loadPings (this, 0)

编辑:

如果您创建一个 PingSoundPool 对象,它不会在每次文件时重新加载:因此您可以在 activity:

中执行此操作
class YourActivity ... {

    companion object {   //So it is accesible from other classes with YourActivity.pingSoundPool
        lateinit var pingSoundPool: PingSoundPool;
    }

    @Override
    ... onCreate(...) {
        pingSoundPool = PingSoundPool(this)
        ...
    }
}

然后如果你需要播放声音(我建议更改函数名称)你可以用

pingSoundPool.load(1)                 // Inside of YourActivity
YourActivity.pingSoundPool.load(1)    // Outside of YourActivity

通过这种方式我也解决了你的最后一个问题,但也许你想知道如何从 Fragment 传递正确的 Context 对象:在你的 Fragment 中你可以声明一个 Context 对象(或一个 YourActivity 对象,它是 Context 的子对象)并通过 onAttach(..) 方法为其赋值,这样:

class YourFragment ... {

    private lateinit var mContext : Context
    private lateinit var mActivity : YourActivity   // You don't need both

    override fun onAttach(context: Context?) {
        super.onAttach(context)

        mContext = context!!

        if (context is YourActivity)
            mActivity = context
    }
}

然后在你的 Fragment 中你可以调用 PingSoundPool(mContext)(或 PingSoundPool(mActivity))。

请注意 onAttach 在任何其他回调方法之前被调用,因此也在 onCreateView.

之前

你也可以用getContext()获取Fragment上下文(在Kotlin中只是context!!),但我认为上面的解决方案更好更安全。