Android下Activity间通信序列化过程中的深浅拷贝浅析

所属分类: 软件编程 / Android 阅读数: 31
收藏 0 赞 0 分享

前言

问题的背景是,视频互动业务需要增加弹幕功能,但是播放器的视图是伪横屏的,即,他是一种类似于使用 rotate(90.0)的方式,旋转横屏的,在 Activity 层面上还是一个竖屏的状态。那么弹幕输入的时候的键盘,也是竖屏的。这会带来比较严重的用户体验问题。

由于屏幕旋转状态在 android 下,是一个 Activity 层面上的事情,而且相当的底层,无从 hook,多方调研以后,决定采拉起一个横屏的 Activity 作为键盘输入的专用 Activity。

这里的代码很快就可以写好,如下所示:

/**
 * Created by DesGemini on 12/09/2017.
 */

public class DialogActivity extends Activity {
 private RelativeLayout mContentView;
 private View vSendBtn;
 private EditText etDanmakuInput;
 private InputMethodManager mInputMethodManager;
 public static WeakReference<DanmakuWriteCallback> danmakuWriteCallback = new WeakReference<>();

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  mInputMethodManager = (InputMethodManager)this.getSystemService(Context.INPUT_METHOD_SERVICE);
  mContentView = (RelativeLayout) getLayoutInflater()
    .inflate(R.layout.hiv_danmaku_input_dialog, null);
  vSendBtn = mContentView.findViewById(R.id.tv_danmaku_send_btn);
  etDanmakuInput = (EditText) mContentView.findViewById(R.id.et_danmaku_input);
  vSendBtn.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View v) {
    // 这里需要处理 Activity 间回传逻辑
   }
  });
  setContentView(mContentView);
  showSoftKeyboard();
 }

 private boolean showSoftKeyboard() {
  if (this.etDanmakuInput == null) {
   return false;
  } else {
   etDanmakuInput.postDelayed(new Runnable() {
    public void run() {
     etDanmakuInput.requestFocus();
     mInputMethodManager.showSoftInput(etDanmakuInput, 0);
    }
   }, 100L);
   return true;
  }
 }

 @Override
 protected void onPause() {
  super.onDestroy();
  danmakuWriteCallback.getAndSet(null);
 }

 @Override
 public void finish() {
  super.finish();
 }
}

DTO 的代码定义如下所示:

public class DanmakuDialogDTO implements Serializable {
 public WeakReference<DWDanmakuWriteController.DanmakuWriteCallback> callback;
 public Map<String, String> utExtraParams;
}

那么现在问题来了,怎么把这个 Activity 获取到的 String 带回去?

最自然的想法是 onActivityResult,然而,播放器是一个 sdk,写不了 Activity 里的代码,也不可能通知许多业务方一一做改动。

那就只能抛开 android 原生的 Activity 间拉起结束中的通信机制了,思考其他可以通信的方法。很自然地,我们想到了 Callback 。结构如下图。但是 Callback 这样的一个非基本数据类型的对象怎么在 Activity 间传递呢?


尝试通过存入 Intent 的 Extras的方式,然而 putExtra 方法并不能 put 一个 object,只能 put 一个 serializable。那就让这个 DTO(Data Transfer Object)implements serializable 接口吧。没有问题。

然而无法启动 Activity,会有一个 crash 抛出:

java.lang.NullPointerException: Expected to unbox a 'int' primitive type but was returned null

报错堆栈如下:

$Proxy1.startActivity(Unknown Source)
android.app.Instrumentation.execStartActivity(Instrumentation.java:1520)
android.taobao.atlas.runtime.InstrumentationHook$2$1.execStartActivity(InstrumentationHook.java:299)

如果把这个 DTO 的成员变量改为 static 类型,则可以启动 Activity。

背后的原因是因为,在常规的序列化过程中,浅拷贝其实是没什么意义的。浅拷贝意味着复制一个引用的地址,是一个内存地址,但是常规序列化,要么跨进程,要么就是网络传输,序列化为 JSON,在这些常规场景里内存地址没有意义。因此 Java 序列化没有浅拷贝的选项,也往往是针对一个 POJO 或者 Bean 进行序列化,而不会对一个一般的含有很多引用的类进行序列化。

然而 Android 中的 Activity 与 Activity 间的传递对象又有所不同,理论上,都在同一个 Dalvik VM 中运行,相互的类引用都是可以访问到的。但是由于 Android Intent 设计为序列化传递,序列化过程中没有设计浅拷贝的机制,因此就无法浅拷贝地传递引用过去。

那么为什么设为 static 以后就可以传递,不会导致 crash 了呢?是因为静态成员属于类级别的,虽然不能序列化,但是因为我是在同一个机器(而且是同一个进程),我的jvm已经把这个类连带着他的静态变量一起加载进来了,所以获取到的是类层面上的静态变量地址,故,功能正常。

那么就决定是使用public static WeakReference<DWDanmakuWriteController.DanmakuWriteCallback> callback;了。但是事实上遇到了另一个问题:

在第一次 startActivity 的时候,观察到 Android 做了一次 GC,然后该 WeakReference 就被释放了,因此 Callback 的业务功能也不能正常执行。引入 WeakReference,原本是为了避开 static cakllback 导致的可能的内存泄漏,然而在这种主动 GC 的情况下,WeakReference 失效了。如果改用 SoftReference,和强引用并没有什么区别,都不能避免内存的泄漏。

最终,采用 AtomReference 来持有这个 static callback,在 Activity 退出的时机去将 AtomicReference 置空。之所以使用 AtomicReference,是因为考虑到视频 sdk 有并发场景的可能性,避免一边置 null 另一边准备使用的可能。

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

更多精彩内容其他人还在看

Android异常 java.lang.IllegalStateException解决方法

这篇文章主要介绍了Android异常 java.lang.IllegalStateException解决方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Android中Split()字符串分割特殊用法案例详解

本文通过案例的形式给大家详细介绍了android中split()字符串分割特殊用法的知识,非常不错具有参考借鉴价值,感兴趣的朋友参考下
收藏 0 赞 0 分享

Android仿新浪微博启动界面或登陆界面(1)

这篇文章主要为大家详细介绍了Android仿新浪微博启动界面或登陆界面的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Android仿新浪微博oauth2.0授权界面实现代码(2)

这篇文章主要为大家详细介绍了Android仿新浪微博oauth2.0授权界面实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Android开发中使用sqlite实现新闻收藏和取消收藏的功能

本篇文章主要介绍了sqlite实现新闻收藏和取消收藏功能,主要涉及到oracle数据库方面的内容,对于Android开发sqlite实现收藏和取消功能感兴趣的朋友可以参考下本文
收藏 0 赞 0 分享

Android仿新浪微博分页管理界面(3)

这篇文章主要为大家详细介绍了Android仿新浪微博分页管理界面,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Android UI自定义ListView实现下拉刷新和加载更多效果

这篇文章主要介绍了Android UI自定义ListView实现下拉刷新和加载更多效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Android—基于微信开放平台v3SDK开发(微信支付填坑)

这篇文章主要介绍了Android—基于微信开放平台v3SDK开发(微信支付填坑),具有一定的参考价值,有需要的可以了解一下。
收藏 0 赞 0 分享

Android仿新浪微博自定义ListView下拉刷新(4)

这篇文章主要为大家详细介绍了Android仿新浪微博自定义ListView下拉刷新,重点介绍了Adapter的详细代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Android控件之使用ListView实现时间轴效果

这篇文章主要介绍了Android基础控件之使用ListView实现时间轴效果的相关资料,本文是以查看物流信息为例,给大家介绍了listview时间轴的实现代码,需要的朋友可以参考下
收藏 0 赞 0 分享
查看更多