Android (Kotlin) - 无法从另一个 class 访问视图

Android (Kotlin) - Cannot access view from another class

我有一个名为 MusicPlayer 的 Activity Class 和一个名为 MpPlayer 的外部 class。在后者中,我的目标是更改 MusicPlayer 的布局。但是,即使我将上下文从 MusicPlayer 传递到 MpPlayer,只要 MpPlayer class 尝试访问布局,应用程序就会继续 return a java.lang.NullPointerException。

我已经清理了很多代码,因此很容易阅读问题;

我的主要class与观点:

 open class MusicPlayer : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_musicplayer)

            mp = MpPlayer(this@MusicPlayer)
            mp.loadMusic //error here
    }
}

我的第二个 class:

class MpPlayer(private var playerReference: MusicPlayer){

    init{
        playerReference = MusicPlayer()
    }

    fun loadMusic(url: String){
            musicPlayer.apply {
                reset()
                setDataSource(url)
                prepareAsync()
                setOnPreparedListener { mp ->
                    totaltime = mp.duration
                    playerReference.seekBar.max = totaltime         //error here
                    playerReference.txt_musicName.text = playerReference.musicNameList[musicCounter]
                    playerReference.txt_musicArtist.text = playerReference.musicArtistList[musicCounter]
                    playerReference.btn_musicplayPause.setImageResource(R.drawable.ic_play)

                    if (wanting2Play) {
                        mp.start()
                        playerReference.btn_musicplayPause.setImageResource(R.drawable.ic_pause)
                    }

                }
                setOnCompletionListener { skipNext() }
            }
        }
}

Logcat:

2020-07-13 17:27:11.278 21742-21742/com.android.slowfy E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.android.slowfy, PID: 21742
    java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.pm.ApplicationInfo android.content.Context.getApplicationInfo()' on a null object reference
        at android.content.ContextWrapper.getApplicationInfo(ContextWrapper.java:159)
        at android.view.ContextThemeWrapper.getTheme(ContextThemeWrapper.java:157)
        at android.content.Context.obtainStyledAttributes(Context.java:675)
        at androidx.appcompat.app.AppCompatDelegateImpl.createSubDecor(AppCompatDelegateImpl.java:692)
        at androidx.appcompat.app.AppCompatDelegateImpl.ensureSubDecor(AppCompatDelegateImpl.java:659)
        at androidx.appcompat.app.AppCompatDelegateImpl.findViewById(AppCompatDelegateImpl.java:479)
        at androidx.appcompat.app.AppCompatActivity.findViewById(AppCompatActivity.java:214)
        at com.android.slowfy.MpPlayer$loadMusic$$inlined$apply$lambda.onPrepared(MpPlayer.kt:41)
        at android.media.MediaPlayer$EventHandler.handleMessage(MediaPlayer.java:3367)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
2020-07-13 17:32:11.329 21742-21775/com.android.slowfy E/Surface: queueBuffer: error queuing buffer to SurfaceTexture, -19
2020-07-13 17:32:11.330 21742-21775/com.android.slowfy E/EGL_emulation: tid 21775: swapBuffers(570): error 0x300d (EGL_BAD_SURFACE)

如有任何想法,我们将不胜感激:)

在您的 init 块中,您已经用新实例化的块覆盖了传入的 Activity:

class MpPlayer(private var playerReference: MusicPlayer){

    init{
        playerReference = MusicPlayer()
    }

首先,删除这个 init 块。请注意,永远不要直接实例化活动,因为这样它们就无法正确设置,无法使用它们的上下文。此外,您需要允许 Activity 在关闭时将自身作为侦听器移除,因此使 属性 可为空并在 Activity 被销毁时将其设置为空。否则,您的 MPPlayer class 可能会在 Activity 已经消失时尝试更新视图,并且它也会泄漏 Activity.

class MpPlayer(var playerReference: MusicPlayer?){
open class MusicPlayer : AppCompatActivity() {
    //...
    override fun onDestroy() {
        super.onDestroy()
        mp.playerReference = null
    }
}

我还会注意到,将 Activity 传递给音乐播放器是对封装的不良使用。您将这两个 class 紧密结合在一起,就像意大利面条一样,而音乐播放器应该能够在不了解 View class 的情况下独立运行。我建议为各种 MusicPlayer 事件创建一个侦听器 class 并让 Activity 实现它们,如下所示:

class MpPlayer(var eventListener: EventListener?){

    interface EventListener {
        fun onPlayerPrepared(mediaPlayer: MediaPlayer)
        fun onPlaybackCompleted(mediaPlayer: MediaPlayer)
        //etc. anything views in Activity might want to respond to
    }

    fun loadMusic(url: String){
            musicPlayer.apply {
                reset()
                setDataSource(url)
                prepareAsync()
                setOnPreparedListener { mp ->
                    eventListener?.onPlayerPrepared(mp)
                }
                setOnCompletionListener { 
                    eventListener?.onPlaybackCompleted(mp)
                    skipNext() 
                }
            }
        }
}

我还建议在任何 Activity class 的名称中加上“Activity”一词以避免混淆。

class MusicPlayerActivity : AppCompatActivity(), MpPlayer.EventListener {

    override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_musicplayer)

            mp = MpPlayer(this)
            mp.loadMusic()
    }

    override fun onPlayerPrepared(mediaPlayer: MediaPlayer){
        totaltime = mp.duration
        seekBar.max = totaltime
        txt_musicName.text = playerReference.musicNameList[musicCounter]
        txt_musicArtist.text = playerReference.musicArtistList[musicCounter]
        btn_musicplayPause.setImageResource(R.drawable.ic_play)
        //...
    }

    override fun onPlaybackCompleted(mediaPlayer: MediaPlayer) {
        //...
    }

    override fun onDestroy() {
        super.onDestroy()
        mp.eventListener = null
    }
}