从Zygote孵化frameworks进程,分析StartActivity流程中intent传递数据的最大值。
Pangu-Immortal (Pangu-Immortal) · GitHub
當我們用Intent傳輸大數據時,有可能會出現錯誤:
val?intent?=?Intent(this@MainActivity,?Main2Activity::class.java) val?data?=?ByteArray(1024?*?1024) intent.putExtra("111",?data) startActivity(intent)如上我們傳遞了1M大小的數據時,結果程序就一直反復報如下TransactionTooLargeException錯誤:
但我們平時傳遞少量數據的時候是沒問題的。由此得知,通過intent在頁面間傳遞數據是有大小限制的。本文我們就來分析下為什么頁面數據傳輸會有這個量的限制以及這個限制的大小具體是多少 ?
StartActivity流程探究
首先我們知道Context和Activity都含有startActivity,但兩者最終都調用了Activity中的startActivity:
@Override public?void?startActivity(Intent?intent,?@Nullable?Bundle?options)?{if?(options?!=?null)?{startActivityForResult(intent,?-1,?options);}?else?{//?Note?we?want?to?go?through?this?call?for?compatibility?with//?applications?that?may?have?overridden?the?method.startActivityForResult(intent,?-1);} }而startActivity最終會調用自身的startActivityForResult,省略了嵌套activity的代碼:
public?void?startActivityForResult(@RequiresPermission?Intent?intent,?int?requestCode,@Nullable?Bundle?options)?{options?=?transferSpringboardActivityOptions(options);Instrumentation.ActivityResult?ar?=mInstrumentation.execStartActivity(this,?mMainThread.getApplicationThread(),?mToken,?this,intent,?requestCode,?options);if?(ar?!=?null)?{mMainThread.sendActivityResult(mToken,?mEmbeddedID,?requestCode,?ar.getResultCode(),ar.getResultData());}if?(requestCode?>=?0)?{//?If?this?start?is?requesting?a?result,?we?can?avoid?making//?the?activity?visible?until?the?result?is?received.??Setting//?this?code?during?onCreate(Bundle?savedInstanceState)?or?onResume()?will?keep?the//?activity?hidden?during?this?time,?to?avoid?flickering.//?This?can?only?be?done?when?a?result?is?requested?because//?that?guarantees?we?will?get?information?back?when?the//?activity?is?finished,?no?matter?what?happens?to?it.mStartedActivity?=?true;}cancelInputsAndStartExitTransition(options); }然后系統會調用Instrumentation中的execStartActivity方法:
public?ActivityResult?execStartActivity(Context?who,?IBinder?contextThread,?IBinder?token,?Activity?target,Intent?intent,?int?requestCode,?Bundle?options)?{IApplicationThread?whoThread?=?(IApplicationThread)?contextThread;...try?{intent.migrateExtraStreamToClipData();intent.prepareToLeaveProcess(who);int?result?=?ActivityManager.getService().startActivity(whoThread,?who.getBasePackageName(),?intent,intent.resolveTypeIfNeeded(who.getContentResolver()),token,?target?!=?null???target.mEmbeddedID?:?null,requestCode,?0,?null,?options);checkStartActivityResult(result,?intent);}?catch?(RemoteException?e)?{throw?new?RuntimeException("Failure?from?system",?e);}return?null; }接著調用了ActivityManger.getService().startActivity ,getService返回的是系統進程中的AMS在app進程中的binder代理:
/***?@hide*/ public?static?IActivityManager?getService()?{return?IActivityManagerSingleton.get(); }private?static?final?Singleton<IActivityManager>?IActivityManagerSingleton?=new?Singleton<IActivityManager>()?{@Overrideprotected?IActivityManager?create()?{final?IBinder?b?=?ServiceManager.getService(Context.ACTIVITY_SERVICE);final?IActivityManager?am?=?IActivityManager.Stub.asInterface(b);return?am;} };接下來就是App進程調用AMS進程中的方法了。簡單來說,系統進程中的AMS集中負責管理所有進程中的Activity。app進程與系統進程需要進行雙向通信。
比如打開一個新的Activity,則需要調用系統進程AMS中的方法進行實現,AMS等實現完畢需要回調app進程中的相關方法進行具體activity生命周期的回調。
所以我們在intent中攜帶的數據也要從APP進程傳輸到AMS進程,再由AMS進程傳輸到目標Activity所在進程。有同學可能由疑問了,目標Acitivity所在進程不就是APP進程嗎?
其實不是的,我們可以在Manifest.xml中設置android:process屬性來為Activity, Service等指定單獨的進程,所以Activity的startActivity方法是原生支持跨進程通信的。
接下來簡單分析下binder機制。
binder介紹
普通的由Zygote孵化而來的用戶進程,所映射的Binder內存大小是不到1M的,準確說是 110241024) - (4096 *2) :這個限制定義在
frameworks/native/libs/binder/processState.cpp類中,如果傳輸說句超過這個大小,系統就會報錯,因為Binder本身就是為了進程間頻繁而靈活的通信所設計的,并不是為了拷貝大數據而使用的:
#define?BINDER_VM_SIZE?((1*1024*1024)?-?(4096?*2))并可以通過cat proc/[pid]/maps命令查看到。
而在內核中,其實也有個限制,是4M,不過由于APP中已經限制了不到1M,這里的限制似乎也沒多大用途:
static?int?binder_mmap(struct?file?*filp,?struct?vm_area_struct?*vma){int?ret;struct?vm_struct?*area;struct?binder_proc?*proc?=?filp->private_data;const?char?*failure_string;struct?binder_buffer?*buffer;//限制不能超過4Mif?((vma->vm_end?-?vma->vm_start)?>?SZ_4M)vma->vm_end?=?vma->vm_start?+?SZ_4M;。。。 }其實在TransactionTooLargeException中也提到了這個:
The Binder transaction buffer has a limited fixed size, currently 1Mb, which
is shared by all transactions in progress for the process. Consequently this
exception can be thrown when there are many transactions in progress even when
most of the individual transactions are of moderate size.
只不過不是正好1MB,而是比1MB略小的值。
小結
至此我們來解答開頭提出的問題,startActivity攜帶的數據會經過BInder內核再傳遞到目標Activity中去,因為binder映射內存的限制,所以startActivity也就會這個限制了。
替代方案
一、寫入臨時文件或者數據庫,通過FileProvider將該文件或者數據庫通過Uri發送至目標。一般適用于不同進程,比如分離進程的UI和后臺服務,或不同的App之間。之所以采用FileProvider是因為7.0以后,對分享本App文件存在著嚴格的權限檢查。
二、通過設置靜態類中的靜態變量進行數據交換。一般適用于同一進程內,這樣本質上數據在內存中只存在一份,通過靜態類進行傳遞。需要注意的是進行數據校對,以防多線程Data Racer出現導致的數據顯示混亂。
Pangu-Immortal (Pangu-Immortal) · GitHub
參考資料
聽說你Binder機制學的不錯,來面試下這幾個問題(一)
https://www.jianshu.com/p/adaa1a39a274
源碼分析:startActivity流程
https://www.jianshu.com/p/dc6b0ead30aa
文中提到一個重點類
這個限制定義在frameworks/native/libs/binder/processState.cpp類中
很多開發者可能不知道去哪里看這個類,這里跟大家分享一個非常快速便捷的查看方式,比較適合偶爾查找一兩個類:
直接進入搜索就好了,包含各個版本的源碼:
如果你指定了某個具體的版本,還有非常便利的提示:
生活中信息交流的核心是數據,Android提供了多種數據存儲的方式,文件存儲數據,SharedPreferences存儲數據,SQLite數據庫存儲數據,ContentProvider存儲數據,根據不同的數據文件選擇合適的存儲方式是開發者所具備的基本技能之一。
Pangu-Immortal (Pangu-Immortal) · GitHub
總結
以上是生活随笔為你收集整理的从Zygote孵化frameworks进程,分析StartActivity流程中intent传递数据的最大值。的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android ROM定制 入门到精通(
- 下一篇: Kotlin替换Dagger2/Hilt