Android Task、Home 鍵與 Back 鍵 裡講的 Task 預設行為應該滿足大部分 App 的需求,除此之外,Task 也可以應付一些特別的需求:
- 在新的 Task 裡啟動 Activity,而不是在目前的 Task 上堆疊
- 啟動一個已經啟動過的 Activity 時,不要產生新的 instance,而是回到既有的 instance
- 當離開 Task 時,清掉 Task 裡所有的 Activity,除了最後一個 Activity
Launch Mode
Launce Mode 用來設定如何啟動 Activity,尤其是與 Task 的關係。
Launch Mode 可以在 Manifest 與 Intent Flag 裡做設定,如果兩邊都有做相同屬性的設定,Intent Flag 會覆蓋 Mainfest 的設定,這是指兩邊共有的屬性,Manifest 有些屬性是 Intent Flag 沒有的,反之亦然,Intent Flag 有些屬性是 Manifest 沒有的。
使用 Manifest 或 Intent Flag 最大的差異在於,Manifest 是由被請求者設定,而 Intent Flag 是由請求者設定,舉例來說,Activity A (請求者)啟動 Activity B(被請求者),B 可以透過 Manifest 決定自身的 Lanuce Mode,而 A 可以透過 Intent Flag 決定或覆蓋 B 的 Launch Mode。
透過 Manifest 設定 launchMode:
- standard - 預設值,同一個 Activity 可以產生多個 instance,分屬於不同 Task,或同一個 Task 可以有多個 instance。
舉例說明:假設目前堆疊為 ABC,Intent 呼叫 C,C 的 launchMode 為 standard,堆疊就會變成 ABCC,C 有兩個 instance。
- singleTop - 同 standard 的 instance 邏輯「同一個 Activity 可以產生多個 instance,分屬於不同 Task,或同一個 Task 可以有多個 instance」,唯一不同的地方在於,當某個 Activity 在 Task 頂端時,此時若有 Intent 要到同一個 Activity 時,並不會重新產生一個 instance,而是使用目前的 instance,只是會先呼叫該 instance的 onNewIntent() 表示有新的 Intent 進來了。
舉例說明:假設目前堆疊為 ABC,Intent 呼叫 C,C 的 launchMode 為 singleTop,堆疊就會維持 ABC,並呼叫 C 的 onNewIntent();若 Intent 呼叫的是 B,不管 B 的 launchMode 為 standard 或者 singleTop,堆疊都會變成 ABCB,B 有兩個 instance。
有幾點要特別注意:
按下 Back 鍵可以回到前一個 Activity,但是當目前的 Activity 因為 singleTop 的 launchMode 而呼叫過 onNewInent() 時,按下 Back 鍵並不是回到 onNewIntent() 前的狀態,而是回到前一個 Activity,以上面的例子說明,就是回到 B。
假設目前堆疊為 AB,Intent 呼叫 B,B 的 launchMode 為 singleTop,堆疊雖然會維持 AB,但是發生了很多事,首先會呼叫 B 的 onPause(),再呼叫 B 的 onNewIntent(),最後呼叫 B 的 onResume(),雖然是停在同一個 Activity,之所以會呼叫 onPause() 的原因是每個 Activity 在接收一個新的 Intent 之前都會先呼叫 onPause()。
再來就是 intent,onNewIntent() 會傳進一個 intent,這個 intent 不同於 Activity 裡 getIntent() 得到的 intent,因為 getIntent() 紀錄的是當初啟動這個 instance 的 intent,而 onNewIntent() 傳進來的則是由於 singleTop 而導進來的 intent,可以在 onNewIntent() 裡呼叫 setIntent() 來更新 Activity 所持有的 intent。
- singleTask - 只會有一個 instance 存在,啟動時若沒有任何 instance 存在,則新增一個 instance 放到目前的 Task 裡,若已經有 instance 存在,即使不在最上層,也會直接切過去,並呼叫 onNewIntent(),在該 instance 上方的堆疊全部銷毀。
舉例說明:假設目前堆疊為 ABC,Intent 呼叫 B,B 的 launchMode 為 singleTask,堆疊就會變成 AB,C 活生生被拔掉,按下 Back 鍵會到 A;若 Intent 呼叫 D,D 的 launchMode 為 singleTask,而目前的堆疊裡沒有 D,那會在目前 Task 放上 D。
singleTask 可以應用到主畫面的設計上,主畫面加上 singleTask,那麼在任一頁只要呼叫主畫面的 Activity 就可以直接回到主畫面。
- singleInstance - 延伸自 singleTask,唯一不同是 singleInstance 所在的 Task 容不下其他 Activity,所有 singleInstance Activity 啟動的 Activity 都會放到別個 Task 裡。
舉例說明:假設目前堆疊為 AB,Intent 呼叫 C,C 的 launceMode 為 singleInstance,會產生一個新的堆疊放上 C,原堆疊 AB 維持不動並移到背景,此時若再呼叫 C,則會留在新堆疊的 C 上,不會再產生新的堆疊或 C instance,若再呼叫另一個 launceMode 同為 singleInstance 的 D,會產生第三個堆疊並放上 D,這是若再呼叫 C,則是回到第二個堆疊的 C,最後若按下 Back 鍵會離開 C 回到 D,再按下 Back 鍵則會離開 C 回到 B,最後回到 A。
一個有趣的狀況,假設目前堆疊為 AB,先呼叫 launceMode 為 singleInstance 的 C,這時若呼叫 launceMode 不為 singleInstance 的 D,也就是其他三種 launceMode,會回到第一個堆疊並放上 D,第一個堆疊就變成 ABD;另一個有趣的狀況,雖然呼叫的順序為 ABCD,但是在 D 按下 Back 鍵是回到同一堆疊的 B,而不是第二個堆疊的 C,要等到第一個堆疊完全 Back 之後,才會回到第二個堆疊的 C。
因為 singleInstance 會不斷地產生新的堆疊,所以要注意堆疊的轉換,當目前的堆疊消失後會回到最近移到背景的堆疊,也就是說,堆疊間的移動也是遵守後進先出的原則。
有看沒有懂?寫程式是看看就知道,先看畫面:
總共有八個 Activity,分屬四種 launchMode,每種 launchMode 兩個 Activity,Main Activity 是 A,「From」表示哪個 Activity 傳送 Intent 的,「Stack」表示同一個 Task 的堆疊。
a.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Go to:" /> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:orientation="horizontal" > <Button android:id="@+id/A" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClick" android:text="Standard A" /> <Button android:id="@+id/B" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClick" android:text="Standard B" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:orientation="horizontal" > <Button android:id="@+id/C" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClick" android:text="SingleTop C" /> <Button android:id="@+id/D" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClick" android:text="SingleTop D" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:orientation="horizontal" > <Button android:id="@+id/E" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClick" android:text="SingleTask E" /> <Button android:id="@+id/F" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClick" android:text="SingleTask F" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:orientation="horizontal" > <Button android:id="@+id/G" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClick" android:text="SingleInstance G" /> <Button android:id="@+id/H" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClick" android:text="SingleInstance H" /> </LinearLayout> <TextView android:id="@+id/from" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/newIntent" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/stack" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>因為每個 Activity 的程式雷同,所以抽出一個父類別。
ParentActivity.java
public abstract class ParentActivity extends Activity { private static final String EXTRA_FROM = "from"; private static final String EXTRA_STACK_MAP = "stackMap"; private TextView fromTv; private TextView newIntentTv; private TextView stackTv; private HashMap<String, String> stackMap; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.a); this.fromTv = (TextView) this.findViewById(R.id.from); // 一開始就寫死,固定為啟動該 Activity 的 Activity,啟動並非重新進來 this.fromTv.setText("From: " + this.getIntent().getStringExtra(ParentActivity.EXTRA_FROM)); // 給 singleTop、singleTask、singleInstance 使用,表示進入已有的 instance this.newIntentTv = (TextView) this.findViewById(R.id.newIntent); // 堆疊,依 Task id 分堆 this.stackTv = (TextView) this.findViewById(R.id.stack); } @Override protected void onResume() { super.onResume(); this.log("onResume"); // 取出全部 Task id 的堆疊 this.stackMap = (HashMap<String, String>) this.getIntent().getSerializableExtra( ParentActivity.EXTRA_STACK_MAP); if (this.stackMap == null) { this.stackMap = new HashMap<String, String>(); } // 找出目前 Task Id 的堆疊 String stack = this.stackMap.get("stack" + this.getTaskId()); if (stack == null) { stack = ""; } // 新加入的堆疊 String newStack = this.toString() + ", Task: " + this.getTaskId() + "\n"; // 若新加入的堆疊已存在既有的堆疊,且在最上方,則不加入新的堆疊 // 因為可能是 Back 鍵回來或者 onNewIntent() 進來,這兩種情形並不會產生新的堆疊 if (stack.indexOf(newStack) != 0) { stack = newStack + stack; } // 輸出堆疊到畫面 this.stackTv.setText("Stack:\n" + stack); // 紀錄堆疊到 Intent 裡,供各 Activity 使用 this.stackMap.put("stack" + this.getTaskId(), stack); } @Override protected void onPause() { super.onPause(); this.log("onPause"); } public void onClick(View v) { this.log("onClick"); Intent it = new Intent(); switch (v.getId()) { case R.id.A: it.setClass(this, A.class); break; case R.id.B: it.setClass(this, B.class); break; case R.id.C: it.setClass(this, C.class); break; case R.id.D: it.setClass(this, D.class); break; case R.id.E: it.setClass(this, E.class); break; case R.id.F: it.setClass(this, F.class); break; case R.id.G: it.setClass(this, G.class); break; case R.id.H: it.setClass(this, H.class); break; } it.putExtra(ParentActivity.EXTRA_FROM, this.getClass().getSimpleName()); it.putExtra(ParentActivity.EXTRA_STACK_MAP, this.stackMap); this.startActivity(it); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); this.log("onNewIntent"); // 這邊要用傳進來的 intent,不可以用 getIntent(),因為這兩個是不一樣的 // getIntent() 是指一開始建立這個 instance 時傳進來的 intent // 而傳進來的 intent 則是因為 singleTop/singleTask 再次傳進來的 intent // 若要修改 getIntent(),可以用 setIntent(intent) this.newIntentTv.setText("onNewIntent: " + intent.getStringExtra(ParentActivity.EXTRA_FROM)); } public abstract void log(String msg); }A.java
public class A extends ParentActivity { private static final String TAG = "A"; @Override public void log(String msg) { Log.d(TAG, msg); } }B.java 到 H.java 同 A.java,只有 Log 的 TAG 不一樣而已。
AndroidManifest.xml
<application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:label="A" android:name=".A" > <intent-filter > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:label="B" android:name=".B" > </activity> <activity android:label="C" android:launchMode="singleTop" android:name=".C" > </activity> <activity android:label="D" android:launchMode="singleTop" android:name=".D" > </activity> <activity android:label="E" android:launchMode="singleTask" android:name=".E" > </activity> <activity android:label="F" android:launchMode="singleTask" android:name=".F" > </activity> <activity android:label="G" android:launchMode="singleInstance" android:name=".G" > </activity> <activity android:label="H" android:launchMode="singleInstance" android:name=".H" > </activity> </application>
相關文章
沒有留言:
張貼留言