Android下Activity間通信序列化過程中的深淺拷貝淺析
前言
問題的背景是,視頻互動(dòng)業(yè)務(wù)需要增加彈幕功能,但是播放器的視圖是偽橫屏的,即,他是一種類似于使用 rotate(90.0)的方式,旋轉(zhuǎn)橫屏的,在 Activity 層面上還是一個(gè)豎屏的狀態(tài)。那么彈幕輸入的時(shí)候的鍵盤,也是豎屏的。這會(huì)帶來比較嚴(yán)重的用戶體驗(yàn)問題。
由于屏幕旋轉(zhuǎn)狀態(tài)在 android 下,是一個(gè) Activity 層面上的事情,而且相當(dāng)?shù)牡讓?,無從 hook,多方調(diào)研以后,決定采拉起一個(gè)橫屏的 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;
}
那么現(xiàn)在問題來了,怎么把這個(gè) Activity 獲取到的 String 帶回去?
最自然的想法是 onActivityResult,然而,播放器是一個(gè) sdk,寫不了 Activity 里的代碼,也不可能通知許多業(yè)務(wù)方一一做改動(dòng)。
那就只能拋開 android 原生的 Activity 間拉起結(jié)束中的通信機(jī)制了,思考其他可以通信的方法。很自然地,我們想到了 Callback 。結(jié)構(gòu)如下圖。但是 Callback 這樣的一個(gè)非基本數(shù)據(jù)類型的對象怎么在 Activity 間傳遞呢?

嘗試通過存入 Intent 的 Extras的方式,然而 putExtra 方法并不能 put 一個(gè) object,只能 put 一個(gè) serializable。那就讓這個(gè) DTO(Data Transfer Object)implements serializable 接口吧。沒有問題。
然而無法啟動(dòng) Activity,會(huì)有一個(gè) crash 拋出:
java.lang.NullPointerException: Expected to unbox a 'int' primitive type but was returned null
報(bào)錯(cuò)堆棧如下:
$Proxy1.startActivity(Unknown Source) android.app.Instrumentation.execStartActivity(Instrumentation.java:1520) android.taobao.atlas.runtime.InstrumentationHook$2$1.execStartActivity(InstrumentationHook.java:299)
如果把這個(gè) DTO 的成員變量改為 static 類型,則可以啟動(dòng) Activity。
背后的原因是因?yàn)椋诔R?guī)的序列化過程中,淺拷貝其實(shí)是沒什么意義的。淺拷貝意味著復(fù)制一個(gè)引用的地址,是一個(gè)內(nèi)存地址,但是常規(guī)序列化,要么跨進(jìn)程,要么就是網(wǎng)絡(luò)傳輸,序列化為 JSON,在這些常規(guī)場景里內(nèi)存地址沒有意義。因此 Java 序列化沒有淺拷貝的選項(xiàng),也往往是針對一個(gè) POJO 或者 Bean 進(jìn)行序列化,而不會(huì)對一個(gè)一般的含有很多引用的類進(jìn)行序列化。
然而 Android 中的 Activity 與 Activity 間的傳遞對象又有所不同,理論上,都在同一個(gè) Dalvik VM 中運(yùn)行,相互的類引用都是可以訪問到的。但是由于 Android Intent 設(shè)計(jì)為序列化傳遞,序列化過程中沒有設(shè)計(jì)淺拷貝的機(jī)制,因此就無法淺拷貝地傳遞引用過去。
那么為什么設(shè)為 static 以后就可以傳遞,不會(huì)導(dǎo)致 crash 了呢?是因?yàn)殪o態(tài)成員屬于類級(jí)別的,雖然不能序列化,但是因?yàn)槲沂窃谕粋€(gè)機(jī)器(而且是同一個(gè)進(jìn)程),我的jvm已經(jīng)把這個(gè)類連帶著他的靜態(tài)變量一起加載進(jìn)來了,所以獲取到的是類層面上的靜態(tài)變量地址,故,功能正常。
那么就決定是使用public static WeakReference<DWDanmakuWriteController.DanmakuWriteCallback> callback;了。但是事實(shí)上遇到了另一個(gè)問題:
在第一次 startActivity 的時(shí)候,觀察到 Android 做了一次 GC,然后該 WeakReference 就被釋放了,因此 Callback 的業(yè)務(wù)功能也不能正常執(zhí)行。引入 WeakReference,原本是為了避開 static cakllback 導(dǎo)致的可能的內(nèi)存泄漏,然而在這種主動(dòng) GC 的情況下,WeakReference 失效了。如果改用 SoftReference,和強(qiáng)引用并沒有什么區(qū)別,都不能避免內(nèi)存的泄漏。
最終,采用 AtomReference 來持有這個(gè) static callback,在 Activity 退出的時(shí)機(jī)去將 AtomicReference 置空。之所以使用 AtomicReference,是因?yàn)榭紤]到視頻 sdk 有并發(fā)場景的可能性,避免一邊置 null 另一邊準(zhǔn)備使用的可能。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
Android原生TabLayout使用的超全解析(看這篇就夠了)
現(xiàn)在很多app都有頂部可左右切換的導(dǎo)航欄,并且還帶動(dòng)畫效果,要實(shí)現(xiàn)這種導(dǎo)航欄,可以使用Android原生的Tablayout也可以借助第三方框架實(shí)現(xiàn),這篇文章主要給大家介紹了關(guān)于Android原生TabLayout使用的相關(guān)資料,需要的朋友可以參考下2022-09-09
android實(shí)現(xiàn)音樂跳動(dòng)效果的示例代碼
這篇文章主要介紹了android實(shí)現(xiàn)音樂跳動(dòng)效果的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
Android 實(shí)現(xiàn)夜間模式的快速簡單方法實(shí)例詳解
這篇文章主要介紹了Android 實(shí)現(xiàn)夜間模式的快速簡單方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09
Android高版本API方法如何在低版本系統(tǒng)上做兼容性處理淺析
這篇文章主要給大家介紹了關(guān)于Android高版本API方法如何在低版本系統(tǒng)上做兼容性處理的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08
Android學(xué)習(xí)筆記之ContentProvider和Uri詳解
本篇文章主要介紹了Android學(xué)習(xí)筆記之ContentProvider和Uri詳解,對于學(xué)習(xí)Android的朋友具有一定的參考價(jià)值,有需要可以可以了解一下。2016-11-11
android開發(fā)socket編程之udp發(fā)送實(shí)例分析
這篇文章主要介紹了android開發(fā)socket編程之udp發(fā)送,實(shí)例分析了Android開發(fā)socket網(wǎng)絡(luò)編程中udp發(fā)送的相關(guān)技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
教你一步步實(shí)現(xiàn)Android微信自動(dòng)搶紅包
自從微信添加搶紅包的功能,微信的電商之旅算是正式開始正式火爆起來。但是作為Android開發(fā)者來說,我們首先考慮的是如何實(shí)現(xiàn)Android微信自動(dòng)搶紅包呢,下面我們來一起看看吧。2016-08-08

