本週來寫關於如何在兩個Activity之間傳值、如何使用EventBus以及出現failed binder transaction的解決辦法
好長的標題...跟我身體某一部位一樣(`▽´)
今天要來講關於在兩個Activity之間傳值的方法(重點是這一個)
但為何要講EventBus? 這又是什麼東東= =???
別急,接下來會說(笑)
上面也說了,今天的重點是在兩個Activity之間傳值
傳值的方法,仿間大概就兩種答案
一個是用Intent傳值,另一個是用Bundle傳值
沒錯~這是正解(・ω・)b
那接下來可能要打破你的思維了XD因為接下來要講第三種
EventBus
雖然正確來說他不是專門用來傳遞值的,但是就結論而言他可以做到一樣的事情
那麼就來看今天的範例
以及Github
->https://github.com/thumbb13555/Intent_And_EventBusExample
那就...開始吧(*゚ー゚)ゞ
0.請容我解釋一下範例到底在幹嘛
對...我覺得如果我不解釋一下範例在幹嘛,我想沒人看得懂範例在幹嘛(´・_・`)
再強調一次,這篇文章的重點在"如何在兩個Activity之間傳值"
具體實現的方法有三,1是Intent,2是Bundle,3就是EventBus
而這篇的重點就是除了教你如何傳值之外,也教你"如何傳送大量的資料"
在畫面的上方,有個輸入;那個輸入的重點是"決定要製造的陣列之陣列長度"
其實我的兩個Activity之間,是傳送“ArrayList”的
在第二個畫面是用來顯示取得到的ArrayLsit他所擁有的陣列長度
也就是說,上頭數字輸入越大,陣列就會越大;陣列越大的話表示該陣列容量就大
這篇的副標題同時也是"如何傳送大量資料"
也就是...如果用一般的Intent傳大容量值的話似乎會有問題(???)
沒錯!的確會閃退!你猜對了XD
而解決方法就是改用EventBus
詳細分析後面會講,先知道就好(`・ω・´)+
解釋完了範例內容後,接著就是來實作囉
1.建立介面與載入EventBus第三方庫
EventBus是一種框架類型的第三方庫
我自己使用的感覺是...他接近於MVVM架構的一種,但又完全不是MVVM架構
簡單來說就是個事件管理平台,他會把所有註冊過的事件加入監聽,並由一個接口統一管理
詳細的官網有這張圖,可以去看看,真要解釋起來一篇文章打不完(不過紅字是我自己寫的..)
那就來載入庫啦~開啟build.grade
找到dependencies
放入以下文字
implementation 'org.greenrobot:eventbus:3.2.0'
並按下Sync,即完成囉~
2.準備所有類別以及創立新介面
這次要準備的介面如下
1.一個完整的Activity
2.一個setter-getter類別檔
在這邊我把activity_main.xml,activity_2.xml以及GetEvent.java這三個檔案內容直接貼出來~
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button_IntentByEvent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="以EventBus傳送大量資料的方法" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button_Intent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="以一般Intent傳送資料的方法" app:layout_constraintBottom_toTopOf="@+id/button_IntentByEvent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button_Test" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="測試EventBus" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/button_IntentByEvent" /> <EditText android:id="@+id/editText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:ems="10" android:hint="請輸入想要的陣列長度" android:inputType="number" app:layout_constraintBottom_toTopOf="@+id/button_Intent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".Activity2"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:fontFamily="monospace" android:text="TextView" android:textAppearance="@style/TextAppearance.AppCompat.Large" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
public class GetEvent { private ArrayList<String> arrayList; public GetEvent() { } public ArrayList<String> getArrayList() { return arrayList; } public void setArrayList(ArrayList<String> arrayList) { this.arrayList = arrayList; } }
3.撰寫功能
首先我們先從被跳轉的那頁Activity2.java開始撰寫(¬_¬)ノ
因為這頁東西較少,比較單純,比較好解釋~
我先把整個內容貼出來吧,再針對重點解釋
public class Activity2 extends AppCompatActivity { boolean eventSelect = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_2); /**從Intent拿取資料*/ Intent intent = getIntent(); ArrayList<String> arrayList = intent.getStringArrayListExtra("MyArray"); TextView textView = findViewById(R.id.textView); if (arrayList != null) { /**如果有接收到資料,將EventBus的資料設為不顯示*/ eventSelect = true; textView.setText("陣列長度為:"+arrayList.size()); } } /**開啟EventBus監聽*/ @Override protected void onStart() { super.onStart(); EventBus.getDefault().register(this); } /**關閉EventBus監聽*/ @Override protected void onStop() { super.onStop(); EventBus.getDefault().unregister(this); } /**由EventBus所取得到的監聽資料在此*/ @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) public void getThisActivityEvent(GetEvent event) { TextView textView = findViewById(R.id.textView); /**如果Intent沒有資料,將EventBus的資料設為顯示*/ if (!eventSelect){ textView.setText("陣列長度為(Event):"+event.getArrayList().size()); } } }
雖然我自認為註解寫得很清楚(自己講...(´・д・`)),不過還是針對幾個部分做詳細講解
首先是跟EventBus無關,普通的intent讀取資料的部分
/**從Intent拿取資料*/ Intent intent = getIntent(); ArrayList<String> arrayList = intent.getStringArrayListExtra("MyArray"); TextView textView = findViewById(R.id.textView);
再來是onStart()跟onStop()的部分
/**開啟EventBus監聽*/ @Override protected void onStart() { super.onStart(); EventBus.getDefault().register(this); } /**關閉EventBus監聽*/ @Override protected void onStop() { super.onStop(); EventBus.getDefault().unregister(this); }
這兩個部分在生命週期中分別是座落於(大約)畫面開始及畫面結束
而EventBus.getDefault().register(this);的這行程式代表的是開始針對這個畫面所有被註冊過的事件做監聽
EventBus.getDefault().unregister(this);的這行程式則是註銷針對此畫面的監聽
這兩個事件也不一定要寫在onStart跟onStop內,你也可以將註冊寫在onCreate或是反註冊寫在onDestroy都可以
但是為要注意的是:有註冊過的話不可以再未取消註冊註冊一次~不然程式會閃退喔(・`ω´・)
接著是這裡
/**由EventBus所取得到的監聽資料在此*/ @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) public void getThisActivityEvent(GetEvent event) { TextView textView = findViewById(R.id.textView); /**如果Intent沒有資料,將EventBus的資料設為顯示*/ if (!eventSelect){ textView.setText("陣列長度為(Event):"+event.getArrayList().size()); } }
這裏是關於收到監聽資料的部分,只要傳過來的值有資料的話,就會顯示在這邊哦~
比較需要注意的是這邊
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
其中,sticky的中文是黏的意思,也就是說這個監聽能接收"黏"在資料池相對應資料,反之的話就是不行
什麼意思?就是指說通常EventBus在推播完資料後資料會立刻消失,但如果有在發送端設定.postSticky();的話,資料就會附著在資料池內
也就是資料池會一直留存到他所拿到的最後一筆資料,直到新的資料覆蓋上去(就是靜態變數的概念...)
而threadMode共有五種,分別為
Posting:直接在事件發佈者所在線程執行事件處理方法
Main:直接在主線程中執行事件處理方法
MainOrdered:直接在主線程中執行事件處理方法。但與Main方式不同的是,不論發佈者所在線程是不是主線程,發佈的事件都會進入隊列按事件串列順序依次執行
Background:直接在背景處理,相當於使用Thread()的效果
Async:直接強制所有處理都在後台處理,若有連接網路API,或是各項費時操作,都建議使用這個
大致就是這樣..._(:3」∠)_
最後是第一個介面MinaActivity.java的部分
這部分是撰寫主要功能,程式100多行有點長,但我一樣全PO在針對重點解釋
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btIntent, btEventIntent, btTest; btIntent = findViewById(R.id.button_Intent); btEventIntent = findViewById(R.id.button_IntentByEvent); btTest = findViewById(R.id.button_Test); EditText editText = findViewById(R.id.editText); /**以普通的Intent帶資料傳送*/ btIntent.setOnClickListener((v) -> { new Thread(() -> { ArrayList<String> arrayList = makeArray(editText); if (arrayList == null) return; runOnUiThread(() -> { Intent intent = new Intent(MainActivity.this, Activity2.class); intent.putExtra("MyArray", arrayList); startActivity(intent); }); }).start(); }); /**以EventBus傳送資料*/ btEventIntent.setOnClickListener(v -> { new Thread(() -> { ArrayList<String> arrayList = makeArray(editText); if (arrayList == null) return; GetEvent getEvent = new GetEvent(); getEvent.setArrayList(arrayList); EventBus.getDefault().postSticky(getEvent);//要畫面之間傳資料請務必用postSticky runOnUiThread(() -> { Intent intent = new Intent(MainActivity.this, Activity2.class); startActivity(intent); }); }).start(); }); /**測試EventBus在同一個Activity內的效果*/ btTest.setOnClickListener(v -> { new Thread(() -> { ArrayList<String> arrayList = makeArray(editText); if (arrayList == null) return; runOnUiThread(() -> { GetEvent getEvent = new GetEvent(); getEvent.setArrayList(arrayList); EventBus.getDefault().post(getEvent);//在同個畫面傳資料可使用一般post }); }).start(); }); } /** * 統一製作陣列 */ private ArrayList<String> makeArray(EditText editText) { if (editText.getText().toString().length() == 0) return null; ArrayList<String> arrayList = new ArrayList<>(); int size = Integer.parseInt(editText.getText().toString()); for (int i = 0; i < size; i++) { arrayList.add(String.valueOf(i)); } return arrayList; } /**開啟EventBus監聽*/ @Override protected void onStart() { super.onStart(); EventBus.getDefault().register(this); } /**關閉EventBus監聽*/ @Override protected void onStop() { super.onStop(); EventBus.getDefault().unregister(this); } /**由EventBus所取得到的監聽資料在此*/ @Subscribe(threadMode = ThreadMode.POSTING) public void getEvent(GetEvent event) { Toast.makeText(this, "陣列長度:" + event.getArrayList().size(), Toast.LENGTH_SHORT).show(); } }
大致上,註冊監聽跟反監聽還有讀取資料池的方法都一致,我就不再特別多說。
一樣挑幾個重點說~
首先是一般intent傳送的部分
Intent intent = new Intent(MainActivity.this, Activity2.class); intent.putExtra("MyArray", arrayList); startActivity(intent);
恩...就這樣而已,沒什麼要注意的。
唯一要注意的是,intent.putExtra("MyArray", arrayList);
綠色字的部分,發送的部分要跟接收的部分一定要一樣,不然就會接收失敗喔(・ωー)~☆
接著是EventBus發送資料的部分
也就是這個上面有用粉底白字畫出來的部分
該兩處我一處一處講,首先是EventBus配合Intent的部分
/**以EventBus傳送資料*/ btEventIntent.setOnClickListener(v -> { new Thread(() -> { ArrayList<String> arrayList = makeArray(editText); if (arrayList == null) return; GetEvent getEvent = new GetEvent(); getEvent.setArrayList(arrayList); EventBus.getDefault().postSticky(getEvent);//要畫面之間傳資料請務必用postSticky runOnUiThread(() -> { Intent intent = new Intent(MainActivity.this, Activity2.class); startActivity(intent); }); }).start(); });
註解也寫了,在兩個畫面之間的務必使用postSticky
前面也說了,sticky代表的是資料會附著在特定的值上
所以這邊在發送時使用postSticky的話,值才能正確傳送喔
至於另一處在這
/**測試EventBus在同一個Activity內的效果*/ btTest.setOnClickListener(v -> { new Thread(() -> { ArrayList<String> arrayList = makeArray(editText); if (arrayList == null) return; runOnUiThread(() -> { GetEvent getEvent = new GetEvent(); getEvent.setArrayList(arrayList); EventBus.getDefault().post(getEvent);//在同個畫面傳資料可使用一般post }); }).start(); });
至於post的部分,簡單來說就是如果傳值的畫面跟接收的畫面都在同一個註冊與反註冊的範圍內,使用post就行囉~
最後是接收資料的部分,這部分主要就是接收剛剛上面那一處發送post的資料,挑戰一下能不能理解吧!(•ө•)♡
/**由EventBus所取得到的監聽資料在此*/ @Subscribe(threadMode = ThreadMode.POSTING) public void getEvent(GetEvent event) { Toast.makeText(this, "陣列長度:" + event.getArrayList().size(), Toast.LENGTH_SHORT).show(); }
4.坑
做到這邊,沒意外的話執行就可以發揮正常功能囉(´υ`)
檢查一下跟這張GIF有沒有一樣吧~
接下來要講重點了
為什麼好好的intent不用,卻要使用EventBus呢?
那你可以在現有的情形下,輸入這個
然後,請點一般Intent的按鈕
應該會這樣
閃退( ´_ゝ`)( ´_ゝ`)( ´_ゝ`)
看一下Log日誌,應該會發現這一行
!!! FAILED BINDER TRANSACTION !!!
白話文講,就是資料太大了啦(╯=▃=)╯︵┻━┻
我之前有查過,Intent整體傳送上,他的資料量只能小於40KB
過大的話就會發生這個問題。
聊一下當時是怎麼發現的吧( ・ω・)ノ
我當時在做這個的時候,是要處理從藍牙裝置上下載過來的6000筆環境紀錄資料ORZ
然後為了資料要在幾個Activity內要一致...我就都使用intent傳送
起初4000筆,5000比都沒問題;但是5500之後就有出狀況了
故,我後來就將所有的intent都改為EventBus了(*゚ー゚)ゞ
...大致就是這樣的過程,希望大家看完後可以少採一點坑...
結語:
解是這東西還真不容易啊...((茶
EventBus在這個案例中,感覺是沒有很強烈展示他的必要性
但是在實際專案中,這個框架對於在程式的美觀與邏輯上給本農提供了不少方便
雖然在一般專案中還不常用...但是至少解決我遇到的intent容量上限問題是很OK的
感謝各位到此一遊,希望文章有幫助到你~