如何将复杂的、不可序列化的对象传递给 android 个片段

How to pass complex, non serializable object to android fragments

你好 Android 开发人员,

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

让我解释一下我的用例:

用例

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

片段构造函数如下所示:

/**
 * 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;
}

我在主机中使用它们 activity 就像这样

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());
}

问题

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

使用父对象访问对象activity

我认为这是最丑陋的解决方案之一,因为它将 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
}

使用广播和接收器

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

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

提前致谢!

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

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

您是否考虑过使用 "Shared" ViewModel?

本质上,ViewModel 的子class(class 旨在以生命周期意识的方式存储和管理与活动和活动相关的 UI 相关数据片段)可以像下面这样创建,

class SharedViewModel : ViewModel() 

在此 class 中,您可以拥有具有正确状态的自定义对象

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

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

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

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

您可以在 SharedViewModel 中编写自己的 logic/method/flow 来操纵任何自定义对象的状态。

完成所有这些后,在您的第二个片段中,您可以创建 SharedViewModel 的句柄,类似于上面的代码并使用 SharedViewModel 对象,您可以检索 "modified" 自定义来自同一 SharedViewModel

的对象

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

为UI相关数据

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

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

我的用例是将复杂的 object 传递给片段,例如管理器、parent activity(通过侦听器)等...我采用的方法是通过从 parent activity.

手动注入这些数据

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

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

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

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

在 parent activity 中,我重写了 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);
    }
}

简单,现在一切正常。