【问题标题】:How to pass complex, non serializable object to android fragments如何将复杂的、不可序列化的对象传递给android片段
【发布时间】:2019-12-13 06:41:28
【问题描述】:

Android 开发者大家好,

我想知道你们如何将复杂的不可序列化(和不可打包)对象传递给片段。 (比如Listener、Api客户端……)

让我解释一下我的用例:

用例

我正在构建一个由一个“主机”活动和 3 个片段组成的 Android 应用程序。 目前我正在使用片段上的自定义构造函数传递对象(我知道这是不好的做法)。

片段构造函数如下所示:

/**
 * Do not remove ever or you'll face RuntimeException
 */
public FirstFragment() {
}

public FirstFragment(Session session,
                     ApiClient apiClient,
                     FirebaseAnalytics firebaseAnalytics) {
    mSession = session;
    mApiClient = apiClient;
    mFirebaseAnalytics = firebaseAnalytics;
}

我在这样的主机活动中使用它们

private FirstFragment getFirstFragment() {
    if (mFirstFragment == null) {
        mFirstFragment = new FirstFragment(mSession, mApiClient, mFirebaseAnalytics);
    }
    return mHomeFragment;
}

[...]

private void loadFragment(Fragment fragment, String tag) {
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.frame_container, fragment, tag);
    transaction.commit();
}

[...]

private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
            = new BottomNavigationView.OnNavigationItemSelectedListener() {

        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            switch (item.getItemId()) {
                case FIRST_FRAGMENT_RES_ID:
                    toolbar.setTitle(R.string.first_fragment_title);
                    loadFragment(getFirstFragment(), "first_fragment");
                    return true;
                [...]
            }
            return false;
        }
    };

这个解决方案几乎一直都运行良好。但有时(我不知道确切的时间)会调用默认构造函数,因此所有本地成员都是空的。

可能的解决方案

为了解决这个问题,我正在考虑以下解决方案:

单身人士,到处都是单身人士

我传递的大多数对象都是单例,因此我可以在片段的默认构造函数中访问它们:

public FirstFragment() {
    mSession = Session.getInstance(getContext());
    mApiClient = ApiClient.getInstance(getContext());
    mFirebaseAnalytics = FirebaseAnalytics.getInstance(getContext());
}

问题

但是,如果我需要传递回调或其他东西,上述解决方案将不起作用。那怎么能这样呢?

使用父活动访问对象

我认为这是最丑陋的解决方案之一,因为它会将 Fragments 耦合到父 Activity。这个想法是这样的

public FirstFragment() {
    mSession = Session.getInstance(getContext());
    mApiClient = ApiClient.getInstance(getContext());
    mFirebaseAnalytics = FirebaseAnalytics.getInstance(getContext());
    mListener = (Listener) getActivity(); // <- will works because parent activity implement the interface
}

使用广播和接收器

我们的想法是在任何地方都传递单例,并使用广播和接收器而不是侦听器。

你们是如何处理这种情况的?

提前致谢!

【问题讨论】:

  • 不要在构造函数中传递,而是在fragment中创建getter和setter来传递和获取对象。
  • @ShivamYadav 就像我在问题中所说的那样,正在调用片段默认构造函数我不知道何时因此我不知道何时调用设置器
  • 实例化片段时将调用构造函数,一个好的做法是在静态方法中实例化片段并从该方法返回实例。在该实例的帮助下,您可以调用您的设置器。

标签: android android-fragments android-lifecycle


【解决方案1】:

您可能想研究依赖注入(使用 Dagger 等工具或替代工具),尤其是对于像 Api Client 这样的对象。发布设置后,您只需定义一次 Api 客户端实例的构建方式。之后,您几乎可以通过一行语句在任何地方使用它。该实例保证在片段实例化时可用。延伸阅读:https://dagger.dev/tutorial/

【讨论】:

  • 我知道 Dagger 但对我的用例来说不是太多了吗?反正我去看看。
  • 我没有足够的上下文来判断 :) 但如果这是预期寿命超过几周的生产代码,那么 DI(不一定与 Dagger 一起使用)绝对是有益的。你会想要编写测试,如果没有 DI,这是一个相当大的挑战。
  • 也不会认为在视图层(活动/片段)中存在 API 客户端是完全干净的。但这是一个不同的讨论。
【解决方案2】:

根据您的用例,使用ViewModel 并将您的对象存储在那里可能更容易。您的 ViewModel 将在您的片段和主机之间共享 活动。 见https://developer.android.com/topic/libraries/architecture/viewmodel

【讨论】:

  • 我认为使用 ViewModel 不符合我的用例。我需要传递非序列化数据,例如监听器和 api 客户端。
  • @americium1997 您有一个包含正确片段的活动?
【解决方案3】:

您是否考虑过使用“共享”视图模型?

基本上,ViewModel 的子类(该类旨在以生命周期意识的方式为活动和片段存储和管理 UI 相关数据),如下所示,

class SharedViewModel : ViewModel() 

在这个类中,您可以拥有具有正确状态的自定义对象

接下来,在您的第一个片段中,您可以获得此 SharedViewmodel 的句柄,如下所示,

class MasterFragment : Fragment() {
private lateinit var model: SharedViewModel

并使用下面的代码获取它的句柄,

model = activity?.run {
            ViewModelProviders.of(this)[SharedViewModel::class.java]
}

您可以在SharedViewModel 中编写自己的逻辑/方法/流程来操作任何自定义对象的状态。

一旦这一切完成,在您的第二个片段中,您可以创建SharedViewModel 的句柄,类似于上面的代码并使用SharedViewModel 对象,您可以从相同的SharedViewModel 检索“修改”的自定义对象

【讨论】:

  • 视图模型设计不是只包含可序列化的 UI 相关字段吗?
  • 根据android官方文档,您所说的是正确的视图模型类的“典型”用例。但是在我看来,viewmodel 最大的收获是它们可以在生命周期事件中幸存下来,并且可以在活动/片段上下文中访问并在它们之间共享数据
  • 谢谢,我去看看!
  • 当然 - 在 android 的早期,所有 UI 数据代码都是在活动/片段类代码本身中获取的,因此它们是巨大的上帝对象。没有人推荐这样的使用——但是在那些日子里,开发人员没有其他“干净”的方式来管理活动/片段的数据。因此,在过去的 2/3 年中,Google 提出了这个 Jetpack 库(包括视图模型)来解决架构问题,例如您面临的问题
【解决方案4】:

几个月过去了,我现在想出了一个不同的解决方案。

UI相关数据

对于 UI 相关的东西,我现在使用 androidx livedata

对于复杂的不可序列化数据

我的用例是将复杂对象传递给片段,例如管理器、父活动(通过侦听器)等...我采取的方法是从父活动手动注入这些数据。

首先要做的是从片段构造函数中删除对象并使用默认构造函数,这样我就不会遇到任何实例化错误。

然后我在片段类上创建了一个 inject() 方法,如下所示:

public void inject(BillingManager billingManager, Listener listener) {
    mBillingManager = billingManager;
    mListener = listener;
}

每个片段都有自己的注入方法,宽度应作为参数注入的对象。

在父活动中,我重写了 onAttachFragment() 方法来处理片段附加过程:

@Override
public void onAttachFragment(@NonNull Fragment fragment) {
    super.onAttachFragment(fragment);
    if (fragment.getClass().equals(FirstFragment.class)) {
        ((FirstFragment) fragment).inject(mBillingManager, this);
    } else if (fragment.getClass().equals(HomeFragment.class)) {
        ((HomeFragment) fragment).inject(this);
    }
}

简单,现在一切正常。

【讨论】:

    猜你喜欢
    • 2017-04-03
    • 2021-05-20
    • 1970-01-01
    • 2014-03-12
    • 2015-11-07
    • 2014-03-17
    • 2018-05-22
    • 2012-06-05
    • 1970-01-01
    相关资源
    最近更新 更多