我使用哪个上下文在单例中加载资源?
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
对象,您不应该这样做。您在这里有两个选择:
- 将
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)
[...]
}
- 使此方法
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!!
),但我认为上面的解决方案更好更安全。
我有一个 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
对象,您不应该这样做。您在这里有两个选择:
- 将
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)
[...]
}
- 使此方法
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!!
),但我认为上面的解决方案更好更安全。