解開Android應用程式元件Activity的"singleTask"之謎(3)

weixin_34377065發表於2011-08-25

   回到前面的startActivityUncheckedLocked函式中,這裡的變數top就為null了,於是執行下面的else語句:

  1.    if (top != null) {   
  2. ......   
  3.    } else {   
  4. // A special case: we need to   
  5. // start the activity because it is not currently   
  6. // running, and the caller has asked to clear the   
  7. // current task to have this activity at the top.   
  8. addingToTask = true;   
  9. // Now pretend like this activity is being started   
  10. // by the top of its task, so it is put in the   
  11. // right place.   
  12. sourceRecord = taskTop;   
  13.    }   

於是,變數addingToTask值就為true了,同時將變數sourceRecord的值設定為taskTop,即前面呼叫findTaskLocked函式的返回值,這裡,它就是表示MainActivity了。

        繼續往下看,下面這個if語句:

  1.    if (r.packageName != null) {   
  2. // If the activity being launched is the same as the one currently   
  3.        // at the top, then we need to check if it should only be launched   
  4. // once.   
  5. ActivityRecord top = topRunningNonDelayedActivityLocked(notTop);   
  6. if (top != null && r.resultTo == null) {   
  7.     if (top.realActivity.equals(r.realActivity)) {   
  8.             if (top.app != null && top.app.thread != null) {   
  9.             ......   
  10.         }   
  11.     }   
  12. }   
  13.    
  14.    } else {   
  15. ......   
  16.    }   

       它是例行性地檢查當前任務頂端的Activity,是否是即將啟動的Activity的例項,如果是否的話,在某些情況下,它什麼也不做,就結束這個函式呼叫了。這裡,當前任務頂端的Activity為MainActivity,它不是SubActivity例項,於是繼續往下執行:

  1.    boolean newTask = false;   
  2.    
  3.    // Should this be considered a new task?   
  4.    if (r.resultTo == null && !addingToTask   
  5. && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {   
  6. ......   
  7.    
  8.    } else if (sourceRecord != null) {   
  9. if (!addingToTask &&   
  10.     (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {   
  11.     ......   
  12. else if (!addingToTask &&   
  13.         (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) {   
  14.     ......   
  15. }   
  16. // An existing activity is starting this new activity, so we want   
  17. // to keep the new one in the same task as the one that is starting   
  18. // it.   
  19. r.task = sourceRecord.task;   
  20.           
  21. ......   
  22.    
  23.    } else {   
  24.        ......   
  25.    }   

        這裡首先將newTask變數初始化為false,表示不要在新的任務中啟動這個SubActivity。由於前面的已經把addingToTask設定為true,因此,這裡會執行中間的else if語句,即這裡會把r.task設定為sourceRecord.task,即把SubActivity放在MainActivity所在的任務中啟動。

        最後,就是呼叫startActivityLocked函式繼續進行啟動Activity的操作了。後面的操作這裡就不跟下去了,有興趣的讀者可以參考兩篇文章Android應用程式啟動過程原始碼分析Android應用程式內部啟動Activity過程(startActivity)的原始碼分析

        到這裡,思路就理清了,雖然SubActivity的launchMode被設定為"singleTask"模式,但是它並不像官方文件描述的一樣:The system creates a new task and instantiates the activity at the root of the new task,而是在跟它有相同taskAffinity的任務中啟動,並且位於這個任務的堆疊頂端,於是,前面那個圖中,就會出現一個帶著"singleTask"標籤的箭頭指向一個任務堆疊頂端的Activity Y了。
        那麼,我們有沒有辦法讓一個"singleTask"的Activity在新的任務中啟動呢?答案是肯定的。從上面的程式碼分析中,只要我們能夠進入函式startActivityUncheckedLocked的這個if語句中:

  1.  if (r.resultTo == null && !addingToTask   
  2.        && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {   
  3. // todo: should do better management of integers.   
  4.        mService.mCurTask++;   
  5.        if (mService.mCurTask <= 0) {   
  6.             mService.mCurTask = 1;   
  7.        }   
  8.        r.task = new TaskRecord(mService.mCurTask, r.info, intent,   
  9.                   (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0);   
  10.        if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r   
  11.                   + " in new task " + r.task);   
  12.         newTask = true;   
  13.         if (mMainStack) {   
  14.               mService.addRecentTaskLocked(r.task);   
  15.         }   
  16.  }   

 

       那麼,這個即將要啟動的Activity就會在新的任務中啟動了。進入這個if語句需要滿足三個條件,r.resultTo為null,launchFlags的Intent.FLAG_ACTIVITY_NEW_TASK位為1,並且addingToTask值為false。從上面的分析中可以看到,當即將要啟動的Activity的launchMode為"singleTask",並且呼叫startActivity時不要求返回要啟動的Activity的執行結果時,前面兩個條件可以滿足,要滿足第三個條件,只要當前系統不存在affinity屬性值等於即將要啟動的Activity的taskAffinity屬性值的任務就可以了。

        我們可以稍微修改一下上面的AndroidManifest.xml配置檔案來做一下這個實驗:

  1. <?xml version="1.0" encoding="utf-8"?>     
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"     
  3.     package="shy.luo.task"     
  4.     android:versionCode="1"     
  5.     android:versionName="1.0">     
  6.     <application android:icon="@drawable/icon" android:label="@string/app_name">     
  7.         <activity android:name=".MainActivity"     
  8.                   android:label="@string/app_name"   
  9.                   android:taskAffinity="shy.luo.task.main.activity">     
  10.             <intent-filter>     
  11.                 <action android:name="android.intent.action.MAIN" />     
  12.                 <category android:name="android.intent.category.LAUNCHER" />     
  13.             </intent-filter>     
  14.         </activity>     
  15.         <activity android:name=".SubActivity"     
  16.                   android:label="@string/sub_activity"   
  17.                   android:launchMode="singleTask"   
  18.                   android:taskAffinity="shy.luo.task.sub.activity">     
  19.             <intent-filter>     
  20.                 <action android:name="shy.luo.task.subactivity"/>     
  21.                 <category android:name="android.intent.category.DEFAULT"/>     
  22.             </intent-filter>     
  23.         </activity>     
  24.     </application>     
  25. </manifest>     

 

        注意,這裡我們設定MainActivity的taskAffinity屬性值為"shy.luo.task.main.activity",設定SubActivity的taskAffinity屬性值為"shy.luo.task.sub.activity"。重新編譯一下程式,在模擬器上把這個應用程式再次跑起來,用“adb shell dumpsys activity”命令再來檢視一下系統執行的的任務,就會看到:

  1. Running activities (most recent first):   
  2.     TaskRecord{4069c020 #4 A shy.luo.task.sub.activity}   
  3.       Run #2: HistoryRecord{40725040 shy.luo.task/.SubActivity}   
  4.     TaskRecord{40695220 #3 A shy.luo.task.main.activity}   
  5.       Run #1: HistoryRecord{406b26b8 shy.luo.task/.MainActivity}   
  6.     TaskRecord{40599c90 #2 A com.android.launcher}   
  7.       Run #0: HistoryRecord{40646628 com.android.launcher/com.android.launcher2.Launcher}   

        這裡就可以看到,SubActivity和MainActivity就分別執行在不同的任務中了。

        至此,我們總結一下,設定了"singleTask"啟動模式的Activity的特點:

        1. 設定了"singleTask"啟動模式的Activity,它在啟動的時候,會先在系統中查詢屬性值affinity等於它的屬性值taskAffinity的任務存在;如果存在這樣的任務,它就會在這個任務中啟動,否則就會在新任務中啟動。因此,如果我們想要設定了"singleTask"啟動模式的Activity在新的任務中啟動,就要為它設定一個獨立的taskAffinity屬性值。

        2. 如果設定了"singleTask"啟動模式的Activity不是在新的任務中啟動時,它會在已有的任務中檢視是否已經存在相應的Activity例項,如果存在,就會把位於這個Activity例項上面的Activity全部結束掉,即最終這個Activity例項會位於任務的堆疊頂端中。

        看來,要解開Activity的"singleTask"之謎,還是要自力更生啊,不過,如果我們仔細閱讀官方文件,在http://developer.android.com/guide/topics/manifest/activity-element.html中,有這樣的描述:

        As shown in the table above, standard is the default mode and is appropriate for most types of activities. SingleTop is also a common and useful launch mode for many types of activities. The other modes — singleTask and singleInstance —are not appropriate for most applications, since they result in an interaction model that is likely to be unfamiliar to users and is very different from most other applications.
        Regardless of the launch mode that you choose, make sure to test the usability of the activity during launch and when navigating back to it from other activities and tasks using the BACK key.

       這樣看,官方文件也沒有坑我們呢,它告誡我們:make sure to test the usability of the activity during launch

 

相關文章