【问题标题】:Communication between two activities using interface not working as expected使用接口的两个活动之间的通信未按预期工作
【发布时间】:2019-06-20 15:58:03
【问题描述】:

问题:使用接口将数据从 activity2 发送到 activity1 会导致 activity1 中未分配发送数据strong> 即使它正在被分配。因此,activity1 成员变量会自动设置为 null。

设置详情:点击activity1中的按钮,启动activity2。现在用户可以在 activity2 中执行任何工作。但只要用户在 activity2 中执行工作,我的自定义界面就会向 activity1 发送数据。 Activity1 应该将该发送的值分配给其成员变量,并且当用户导航回 activity1 时,activity1 的 onRestart() 方法被绑定用新值更新用户界面。

设置点数:

1-用户点击ACTIVITY1

上的按钮

2-按钮打开ACTIVITY2

3-用户执行的操作触发我的界面发送数据

4-接口在后台向ACTIVITY1发送数据(ACTIVITY2仍然可见)

5-发送的数据通过接口赋值给ACTIVITY1成员变量(布尔型)

6- 用户按 ACTIVITY2 并导航回 ACTIVITY1

7- ONRESTART()ACTIVITY1 被触发

8- ONRESTART() 检查布尔值是否为真,然后更新 UI

但我的困惑从这里开始。 为什么分配的布尔值即使在分配后仍然为空? 为什么 ACTIVITY1 的成员变量会丢失赋值?

代码:

界面

override fun onReturnedToActivity(actionID: Int) {
    when(actionID)
    {
        3 ->
        {
            newNoteCreated = true
        }
    }
}

ONRESTART()

override fun onRestart() {
    super.onRestart()

    if(newNoteCreated)  //variable is null not true.
    {
        Toast.makeText(this,"TRUE",Toast.LENGTH_SHORT).show()
        mDrawer!!.updateBadge(3, StringHolder(dbHandler.totalNotes().toString() ))

        newNoteCreated = false
    }

请教我这个问题。我一直试图在 SO 上找到它的解决方案,但还没有回答我的问题。我知道还有其他方法可以在活动之间发送数据,但我正在尝试特别使用接口。

引用自

https://developer.android.com/reference/android/app/Activity#ActivityLifecycle

"如果一个 Activity 完全被另一个 Activity 遮挡,它就会停止。它仍然保留所有状态和成员信息,但是,它不再对用户可见,因此它的窗口是隐藏的,当其他地方需要内存时,它通常会被系统杀死。"

那么为什么我的活动会丢失其可变数据?

编辑

主要活动(ACTIVITY1)

class MainActivity : AppCompatActivity(), ContextBarVisibilityListener, OnActionPerformedBadgeUpdater, ActivityReturningStateNotify {


private val dbHandler: PediaDatabase = PediaDatabase(this)
private var mDrawer: Drawer? = null
private var filePath: String? = null
private var backupFileName: String? = null
private var listBt: MenuItem? = null
private var gridBt: MenuItem? = null
private var frontAppName: TextView? = null
private var buttonStatePref: SharedPreferences? = null
private var speedDialView: SpeedDialView? = null
private var trashNotesCount: String? = null
private var archiveNotesCount: String? = null
private var onLayoutChange: LayoutChangeListener? = null

private var newNoteCreated: Boolean = false //THIS IS THE BOOL

private lateinit var mAdView: AdView



companion object
{
    private const val LAYOUT_USER_PREF = "layout_preference"
}


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


    onLayoutChange = NotesFrag()

    if(savedInstanceState == null) //prevents reloading fragment
    {
        val transaction = supportFragmentManager.beginTransaction()
        transaction.add(R.id.innerFrameLayout,NotesFrag()).commit()
    }



    trashNotesCount = dbHandler.trashedNotesCount().toString()
    archiveNotesCount = dbHandler.archivedNoteCount().toString()

    val drawerTrashBt: PrimaryDrawerItem = PrimaryDrawerItem().withIdentifier(1).withName("Trash Bin")
            .withIcon(R.drawable.delete_note_image)
            .withBadge(trashNotesCount)
            .withSelectedIcon(R.drawable.delete_drawer_selected)

    val drawerArchiveBt: PrimaryDrawerItem = PrimaryDrawerItem().withIdentifier(2).withName("Archive")
            .withIcon(R.drawable.archive_note_img)
            .withBadge(archiveNotesCount)
            .withSelectedIcon(R.drawable.archive_drawer_selected)

    val drawerNotesBt: PrimaryDrawerItem = PrimaryDrawerItem().withIdentifier(3).withName("Notes")
            .withIcon(R.drawable.notes_img)
            .withBadge(dbHandler.totalNotes().toString())
            .withSelectedIcon(R.drawable.notes_drawer_selected)

    val drawerLockedNotesBt: PrimaryDrawerItem = PrimaryDrawerItem().withIdentifier(4).withName("Locked Notes")
            .withIcon(R.drawable.lock_button)
            .withBadge("1")
            .withSelectedIcon(R.drawable.locked_drawer_selected)

    val drawerReminderNotesBt: PrimaryDrawerItem = PrimaryDrawerItem().withIdentifier(5).withName("Reminder Notes")
            .withIcon(R.drawable.reminder_notes)
            .withBadge("1")
            .withSelectedIcon(R.drawable.reminder_drawer_selected)

    val drawerFavouriteNotesBt: PrimaryDrawerItem = PrimaryDrawerItem().withIdentifier(6).withName("Favourite Notes")
            .withIcon(R.drawable.rate_app_star_img)
            .withBadge("1")
            .withSelectedIcon(R.drawable.favourite_drawer_selected)

    val drawerSettingsBt: PrimaryDrawerItem = PrimaryDrawerItem().withIdentifier(7).withName("Settings")
            .withIcon(R.drawable.settings_drawer)



      mDrawer = DrawerBuilder().withActivity(this)
            .withHeader(R.layout.navbar_header)
            .addDrawerItems(drawerNotesBt,drawerArchiveBt,drawerTrashBt,
                    drawerLockedNotesBt,drawerReminderNotesBt,
                    drawerFavouriteNotesBt,DividerDrawerItem(), drawerSettingsBt)

            .withOnDrawerItemClickListener(object: Drawer.OnDrawerItemClickListener{
                override fun onItemClick(view: View?, position: Int, drawerItem: IDrawerItem<*, *>?): Boolean {

                    when(position)
                    {
                        1 ->
                        {
                            frontAppName!!.text = "Wonder Notes"
                            speedDialView!!.show() //show again because traversing to other fragments hides it

                            val transaction = supportFragmentManager.beginTransaction()
                            transaction.replace(R.id.innerFrameLayout, NotesFrag())
                            transaction.addToBackStack(null)
                            transaction.commit()

                        }

                        2 ->
                        {
                            speedDialView!!.hide()
                            supportFragmentManager.beginTransaction().replace(R.id.innerFrameLayout,ArchiveFragment()).commit()
                        }

                        3 ->
                        {
                            frontAppName!!.text = "Trash"
                            speedDialView!!.hide()


                            fabLayoutBehaviourSetter(false)

                            supportFragmentManager.beginTransaction().replace(R.id.innerFrameLayout, TrashFragment()).commit()
                        }
                        4 ->  Log.d("locked notes","1")

                        5 ->  Log.d("reminder","1")

                        6 ->  Log.d("favourite","1")
                    }

                    return false // false closes drawer on click
                }
            }).build()


    /*Below code handles swipe tabs adapter setting and displaying
     * and implements fragments to viewpager programitically */

  //  mainActivityViewPagerID.adapter = ViewPagerAdapter(supportFragmentManager)

   // attachNotesFrag()




    //mainTabsID.setupWithViewPager(mainActivityViewPagerID)
    //mainTabsID.setTabTextColors(Color.DKGRAY, Color.parseColor("white"))




    val drawerOpener = findViewById<ImageButton>(R.id.drawerOpenImgBt)
    val noteCounterAsBt = findViewById<TextView>(R.id.showNotesCountID)




    drawerOpener.setOnClickListener { mDrawer!!.openDrawer() }
    noteCounterAsBt.setOnClickListener { mDrawer!!.openDrawer() }
    //navigationView.setNavigationItemSelectedListener(this)



    val customToolbar: Toolbar = findViewById(R.id.toolbarID)
      setSupportActionBar(customToolbar)


      supportActionBar!!.setDisplayShowTitleEnabled(false) //set main title off

       val toolbarTxtView = findViewById<TextView>(R.id.toolbar_title)
         toolbarTxtView.visibility = View.GONE


    checkForStoragePermission()
}//on create




override fun onContextBarVisibilityChange(isVisible: Boolean) { //custom interface

    if(isVisible)
    {
        speedDialView!!.hide()
        fabLayoutBehaviourSetter(false) //remove scrolling behaviour

    }
    else
    {
        speedDialView!!.show()
        fabLayoutBehaviourSetter(true) //set scrolling behaviour
    }

}


override fun onReturnedToActivity(actionID: Int) { ///it just recieves value from ACTIVITY2 and assign its value to ACTIVTY1`s "newNoteCreated" varaible
    when(actionID)
    {
        3 ->
        {
            Log.d("INTERFACE","CALLED $actionID")
            newNoteCreated = true

            Log.d("INTERFACE","CALLED $newNoteCreated")
        }
    }
}

参考活动(活动2) 由于activity2的代码很长,很可能会混淆读者,所以我只包括接口将数据发送回activity1的部分。

 onActivityReturningStateNotify = MainActivity()


 dbHandler!!.createNote(note)

                //onActionPerformedBadgeUpdater!!.onActionPerformed(3)
                onActivityReturningStateNotify!!.onReturnedToActivity(3)

界面

interface ActivityReturningStateNotify {

fun onReturnedToActivity(actionID: Int)

}

【问题讨论】:

  • 展示你如何定义 onActivityReturningStateNotify
  • 我想你可能对接口可以做什么感到困惑,为了解释我们需要查看实际的对象 onActivityReturningStateNotify。我们看到 Activity1 实现了接口。
  • 我已经添加了接口代码。我不明白您所说的“实际对象”是什么意思?你能澄清一下吗?
  • 好的,所以您正在创建 MainActivity 的一个新实例,它不是启动当前活动的那个。在 android 中,正如我们所希望的那样,您不能简单地持有对先前活动的引用,然后与它进行通信。这会很棒,但你不能这样做
  • 好的,我现在明白了。那么,它的解决方案是什么?广播听众呢?如果我无法与之前持有广播侦听器的主要活动实例进行通信,那是否意味着我也无法以这种方式进行通信?

标签: android android-activity kotlin interface android-lifecycle


【解决方案1】:

您的问题是您在 MainActivity 的一个新实例上调用 onReturnedToActivity(),而不是您开始的那个或您将返回的那个。

Activity之间共享数据有几种方式,一种是使用startActivityForResult()。

我认为你的问题是 Activity1 将被停止,所以你不能真正从 Activity2 发送数据。但是,您可以使用 startActivityForResult 启动 Activity2 并返回结果。 这是来自documentation 的示例。

开始活动。

const val PICK_CONTACT_REQUEST = 1  // The request code
...
private fun pickContact() {
    Intent(Intent.ACTION_PICK, Uri.parse("content://contacts")).also { pickContactIntent ->
        pickContactIntent.type = Phone.CONTENT_TYPE // Show user only contacts w/ phone numbers
        startActivityForResult(pickContactIntent, PICK_CONTACT_REQUEST)
    }
}

获取返回的数据。

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    // Check which request we're responding to
    if (requestCode == PICK_CONTACT_REQUEST) {
        // Make sure the request was successful
        if (resultCode == Activity.RESULT_OK) {
            // The user picked a contact.
            // The Intent's data Uri identifies which contact was selected.

            // Do something with the contact here (bigger example below)
        }
    }
}

其他方案包括全局单例,使用 Intent 启动活动,或使用 SharedPreferences 在一个活动中写入数据,然后在另一个活动中读取数据。 如果您在 android share data between activities 上进行搜索,您会看到有多种选择可以实现此目的的最佳方式。

【讨论】:

  • 您好,刚刚添加了代码。但是,我试图仅通过界面(如果可能)完成我的工作。我知道我可以为结果启动活动,但将来如何将数据返回到活动 1 将变得更加复杂,因此试图避免“startActivityForResult”。 onReturnedToActivity() 是我在activity2中实现的接口,它将数据发送到activity1。我只是不明白为什么 activity1 不再接受新数据,只要它不在前台,更特别是为什么它将成员变量重置回其原始数据?
  • 根据您更新的答案,我正在考虑选择单例类来保存数据。我现在将尝试实施它。感谢您的热情回复!
  • 如果回答了您的问题,请记住接受答案。
  • 顺便说一句,请确保您了解 Android 配置更改的含义,即旋转设备,它甚至可能对 Singleton 产生影响。
  • 我还是个安卓开发新手,从错误中吸取教训。我的应用程序选择了 1 个活动,许多片段设计。我已经(也许不是一个好方法)通过检查保存的实例是否为空然后加载片段来阻止活动重新加载当前持有的片段,否则不加载。
【解决方案2】:

通过接口将 Activity1 分配给 Activity2 是个坏主意,尝试在活动 1 中使用:startActivityForResult(intent, requestCode) 在活动 2 中制作任何你想要的内容,然后调用:onActivityResult(requestCode, resultCode, intent); 并在活动 1 中覆盖函数:onActivityResult() 并使用来自意图的布尔数据。

【讨论】:

  • 用户可以通过某些方式启动第二个活动,而无需实际按下按钮并启动 startActivityForResult。有一个循环视图填充卡片视图,当用户点击卡片视图时,它会进入第二个活动。这就是为什么不会调用 onActivityResult 的原因。数据的处理方式和活动的启动方式有点复杂,这就是我避免它的原因。
  • 不管你在哪里点击你都不能在不调用 startActivity() 的情况下开始活动,所以你可以用 startActivityForResult() 替换它,如果你真的不能正确调用 startActivity() 你的架构是相当混乱,所以我建议你在添加更多混乱之前花一些时间来修复它。
  • 我无法在我的适配器中实现 startActivityForResult()
  • 是的,你不应该这样做,你可以在持有适配器并在那里调用 startActivity 的活动中实现 onItemClickListener() 。如果你使用 RecyclerViewAdapter 你可以实现自定义 itemClickListener
猜你喜欢
  • 2013-10-02
  • 2016-02-08
  • 1970-01-01
  • 1970-01-01
  • 2011-09-10
  • 1970-01-01
  • 2018-08-02
  • 2023-04-11
  • 1970-01-01
相关资源
最近更新 更多