Android监听器序列化

Posted

技术标签:

【中文标题】Android监听器序列化【英文标题】:Android listener serialization 【发布时间】:2014-12-04 01:17:57 【问题描述】:

我有扩展 DialogFragment.CustomDialog 类,我重写了 onCreateDialog 方法,以获得我想要的自定义对话框。

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) 
    dialog = new Dialog(activity, styleId);
    view = activity.getLayoutInflater().inflate(layoutId, null);
    dialog.setContentView(view);
    if (listener != null) 
        listener.onViewInit(view, this);
    
    return dialog;


这是自定义对话框创建代码。视图膨胀后,我调用OnViewInitListener 类型的侦听器方法listener.onViewInit(view, this),它是接口并扩展了Serializable,将自定义代码绑定到视图(查看文本、侦听器等),这样在旋转时我想失去我的按钮按下逻辑。

@Override
public void onSaveInstanceState(Bundle bundle) 
    bundle.putInt("layoutId", layoutId);
    bundle.putInt("styleId", styleId);
    bundle.putSerializable("listener", listener);
    super.onSaveInstanceState(bundle);



public RsCustomDialog setOnListenerAssignment(OnViewInitListener listener) 
    this.listener = listener;
    return this;

当我从 Activity 实现 OnViewInitListener 时,在方向改变时,事情会按预期工作: 每次重新创建片段时都会调用onCreateDialog,并且没有包裹错误,但是当我按下应用程序历史按钮时(在最右边) (来源:cbsistatic.com)

我收到此错误:

10-09 11:09:38.256: E/androidRuntime(24153): FATAL EXCEPTION: main
10-09 11:09:38.256: E/AndroidRuntime(24153): java.lang.RuntimeException: Parcelable encountered IOException writing serializable object (name = base.RsCustomDialog$OnClickListener)
10-09 11:09:38.256: E/AndroidRuntime(24153):    at android.os.Parcel.writeSerializable(Parcel.java:1279)
10-09 11:09:38.256: E/AndroidRuntime(24153):    at android.os.Parcel.writeValue(Parcel.java:1233)
10-09 11:09:38.256: E/AndroidRuntime(24153):    at android.os.Parcel.writeMapInternal(Parcel.java:591)
10-09 11:09:38.256: E/AndroidRuntime(24153):    at android.os.Bundle.writeToParcel(Bundle.java:1627)
10-09 11:09:38.256: E/AndroidRuntime(24153):    at android.os.Parcel.writeBundle(Parcel.java:605)
10-09 11:09:38.256: E/AndroidRuntime(24153):    at android.support.v4.app.FragmentState.writeToParcel(Fragment.java:133)

我猜这是因为,当我从我的活动中实现OnViewInitListener 时,java 隐式地将活动变量放入实现的对象中,而 Parcel 无法处理活动打包。

谁能建议如何处理这个问题,或建议更好的解决方案。

【问题讨论】:

【参考方案1】:

您无法序列化和恢复侦听器。

序列化(包括使用 Parcelable)保存对象实例的状态,反序列化将该状态放入新的对象实例中。

侦听器没有状态 - 这就是您的 Parcelable 实现没有保存或恢复任何内容的原因。侦听器变量是对对象实例(已知实现侦听器接口的实例)的引用。如果创建了该对象的新实例(例如:由于旋转,或由于内存不足而导致进程被终止),则对话框尝试恢复指向前一个实例的指针无济于事。先前的实例不再存在,并且在调用 onSaveInstanceState 时新创建的(正确的)实例不存在。

两种可能的选择:

如果监听器是对话框附加到的 Activity,那么可以在 DialogFragment 的 onAttach 方法中恢复 如果没有,那么 Activity 可以在创建(或重新创建)时设置 Fragment 侦听器。要获得对自动恢复的 Fragment 实例的引用,可以使用 getFragmentManager().findFragmentById(id)getFragmentManager().findFragmentByTag(tag)

【讨论】:

第二个选项有点棘手,就像重新创建活动一样,它将丢失任何存储的侦听器......除非它使用静态变量或在应用程序中存储,但这并不好练习... @lxx 我同意,静态变量不是一个好主意 - 也想不出应用程序实例有意义的情况,但这并不意味着它不存在。我对第二个选项的想法是要么创建一个新的侦听器实例,要么将从 FragmentManager 检索到的片段实例设置为侦听器【参考方案2】:

您的 OnViewInitListener 应该是静态的和可序列化的,并且内部包含所有可序列化的字段。如果您从中引用 Activity ,那么您做错了。要解决这个问题,您可以:

    引用存储在静态 WeakReference 变量中的活动实例,该变量在创建活动时填充。 使用广播接收器 使用新的和适当的上下文恢复片段时重新注册侦听器。

【讨论】:

您好,感谢您的回复!我可以说我已经从您的列表中检查了 1 和 3:1)我从静态活动引用中引用变量,即在 BaseActivity 中。 3)我不想重新注册听众,我的目标是注册一次,然后忘记它。在配置更改时,它应该自动恢复侦听器。但我总是在CustomDialog 类中使用适当的上下文。 2- 我不明白你的意思,它有什么帮助?【参考方案3】:

你可以在 onResume 和 onStop 中写一些你监听的东西在 onStop 方法中取消注册,在 onResume 方法中注册

【讨论】:

这看起来更像是评论而不是答案。【参考方案4】:

好吧,我解决了!我所做的是我实现Parcable如下:

public abstract class OnClickListener implements DialogInterface.OnClickListener, Parcelable 
    @Override
    public abstract void onClick(DialogInterface dialog, int which);

    @Override
    public void writeToParcel(Parcel dest, int flags) 

    

    @Override
    public int describeContents() 
        return 0;
    

所以我的ConfirmDialog 代码保持不变:

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) 
    return new AlertDialog.Builder(getActivity())
        .setTitle(title).setMessage(text)
        .setPositiveButton(R.string.yes, onYes)
        .setNegativeButton(R.string.no, onNo).create();


@Override
public void onSaveInstanceState(Bundle bundle) 
    super.onSaveInstanceState(bundle);
    bundle.putParcelable("onYes", onYes);
    bundle.putParcelable("onNo", onNo);

唯一的限制是不要在onClick 方法中使用不是Parcelable 的自动变量。 这是我显示此对话框的示例:

       showConfirmDialog(getString(R.string.sure_want_to_exit), new base.dialog.OnClickListener() 

        @Override
        public void onClick(DialogInterface dialog, int which) 
            ((NewProtocol) getCurrentActivity()).exit = true;               
            getCurrentActivity().finish();
        
    , null);

getCurrentActivity() 是返回当前活动活动的静态方法。

【讨论】:

在包裹上不写任何东西对我们没有任何好处。这根本不是持久化或反序列化任何东西。当实例状态恢复时,您需要从活动中设置监听器。不要坚持指向空的指针。这个答案比崩溃要糟糕得多,因为它会在达到内存限制的边缘情况下静默地破坏正在工作的侦听器。这可能会导致在生产中被忽视的巨大错误。 为什么它仍然有效?实际上,它也可以在没有显式持久化 onSaveInstanceState 的情况下工作。 getArguments().getParcelable("listener") 始终返回传递给 Bundle 的相同对象,无论手机方向更改多少次。

以上是关于Android监听器序列化的主要内容,如果未能解决你的问题,请参考以下文章

Android 功能系列篇

SPRING BATCH:嵌套异常是java.sql.SQLException:ORA-08177:无法序列化此事务的访问权限

Android Serialization序列化

漏洞预警 | Android系统序列化反序列化不匹配漏洞

序列化与 Parcelable Android

android activity之间传递对象 对象为啥要序列化