【问题标题】:Which context do i use to load resources in a singleton?我使用哪个上下文在单例中加载资源?
【发布时间】:2019-07-03 10:34:26
【问题描述】:

我有一个 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) 将它加载到我的活动的 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“必需:上下文,找到:MainFragment”。我可以使用PingSoundPool(this.requireContext()).loadPings(2)PingSoundPool(this.context!!).loadPings(2) 解决它,但这似乎不是最好的做法。有什么建议吗?

这是我现在使用的类而不是对象:

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)
[...]
}

}

【问题讨论】:

    标签: android kotlin singleton android-context soundpool


    【解决方案1】:

    如果您从活动的 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 对象,它不会在每次文件时都重新加载:因此您可以在您的活动中执行此操作:

    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()(在Kotlin 中只是context!!)获取Fragment 上下文,但我认为上述解决方案更好更安全。

    【讨论】:

    • 感谢您的回答。你也看一下后续问题吗?我已将其添加到原始帖子中。
    • 完成,检查一下。
    • 我尝试实现您的代码,但是当我尝试从片段播放声音时得到sample 7 not READY。知道为什么吗?
    • 那是什么?日志消息?编译器错误?日志错误?在哪条线上?你能说得清楚一点吗?
    • 它是在运行时从 Logcat 尝试播放声音时发出的。无论如何,我在片段中有一行错误。现在它可以工作了。非常感谢!
    【解决方案2】:

    您不应将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)
    

    【讨论】:

      【解决方案3】:

      问题出在这一行:

      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 的方法。

      希望有帮助!

      【讨论】:

      • 感谢您的回答。您是否也看一下后续问题?我已将其添加到原始帖子中。
      猜你喜欢
      • 1970-01-01
      • 2012-09-24
      • 1970-01-01
      • 2012-04-12
      • 1970-01-01
      • 2023-03-07
      • 2015-03-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多