#### 1.2.1 Activity的LaunchMode
首先說一下**Activity為什么需要啟動模式**。我們知道,在默認情況下,當我們多次啟動同一個Activity的時候,系統會創建多個實例并把它們一一放入任務棧中,當我們單擊back鍵,會發現這些Activity會一一回退。**任務棧是一種“后進先出”的棧結構,這個比較好理解,每按一下back鍵就會有一個Activity出棧,直到棧空為止,當棧中無任何Activity的時候,系統就會回收這個任務棧**。關于任務棧的系統工作原理,這里暫時不做說明,在后續章節會專門介紹任務棧。**知道了Activity的默認啟動模式以后,我們可能就會發現一個問題:多次啟動同一個Activity,系統重復創建多個實例,這樣不是很傻嗎?這樣的確有點傻,Android在設計的時候不可能不考慮到這個問題,所以它提供了啟動模式來修改系統的默認行為。目前有四種啟動模式:standard、singleTop、singleTask和singleInstance**,下面先介紹各種啟動模式的含義:
* (1)**standard:標準模式**,這也是**系統的默認模式。每次啟動一個Activity都會重新創建一個新的實例,不管這個實例是否已經存在**。被創建的實例的生命周期符合典型情況下Activity的生命周期,如上節描述,它的onCreate、onStart、onResume都會被調用。這是一種典型的多實例實現,**一個任務棧中可以有多個Activity實例,每個Activity實例也可以屬于不同的任務棧。在這種模式下,誰啟動了這個Activity,那么這個Activity就運行在啟動它的那個Activity所在的棧中。比如Activity A啟動了Activity B(B是標準模式),那么B就會進入到A所在的棧中**。不知道讀者是否注意到,當我們用ApplicationContext去啟動standard模式的Activity的時候會報錯,錯誤如下:
```
E/AndroidRuntime(674): android.util.AndroidRuntimeException: Calling
startActivity from outside of an Activity context requires the
FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
```
相信這句話讀者一定不陌生,這是因為**standard模式的Activity默認會進入啟動它的Activity所屬的任務棧中**,但是由于**非Activity類型的Context(如ApplicationContext)并沒有所謂的任務棧**,所以這就有問題了。**解決這個問題的方法是為待啟動Activity指定FLAG_ACTIVITY_NEW_TASK標記位(通過[使用intent標記位](https://developer.android.google.cn/guide/components/activities/tasks-and-back-stack#IntentFlagsForTasks), `intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);`
),這樣啟動的時候就會為它創建一個新的任務棧,這個時候待啟動Activity實際上是以singleTask模式啟動的**,讀者可以仔細體會。
* (2)**singleTop:棧頂復用模式**。在這種模式下,**如果新Activity已經位于任務棧的棧頂,那么此Activity不會被重新創建,同時它的onNewIntent方法會被回調,通過此方法的參數我們可以取出當前請求的信息。需要注意的是,這個Activity的onCreate、onStart不會被系統調用,因為它并沒有發生改變。如果新Activity的實例已存在但不是位于棧頂,那么新Activity仍然會重新重建**。舉個例子,假設目前棧內的情況為ABCD,其中ABCD為四個Activity, A位于棧底,D位于棧頂,這個時候假設要再次啟動D,如果D的啟動模式為singleTop,那么棧內的情況仍然為ABCD;如果D的啟動模式為standard,那么由于D被重新創建,導致棧內的情況就變為ABCDD。
* (3)**singleTask:棧內復用模式**。這是一種**單實例模式**,在這種模式下,**只要Activity在一個棧中存在,那么多次啟動此Activity都不會重新創建實例**,和singleTop一樣,系統也會回調其onNewIntent。具體一點,**當一個具有singleTask模式的Activity請求啟動后,比如Activity A,系統首先會尋找是否存在A想要的任務棧,如果不存在,就重新創建一個任務棧,然后創建A的實例后把A放到棧中。如果存在A所需的任務棧,這時要看A是否在棧中有實例存在,如果有實例存在,那么系統就會把A調到棧頂并調用它的onNewIntent方法,如果實例不存在,就創建A的實例并把A壓入棧中**。舉幾個例子:
* 比如目前任務棧S1中的情況為ABC,這個時候Activity D以singleTask模式請求啟動,其所需要的任務棧為S2,由于S2和D的實例均不存在,所以系統會先創建任務棧S2,然后再創建D的實例并將其入棧到S2。
* 另外一種情況,假設D所需的任務棧為S1,其他情況如上面例子1所示,那么由于S1已經存在,所以系統會直接創建D的實例并將其入棧到S1。
* **如果D所需的任務棧為S1,并且當前任務棧S1的情況為ADBC,根據棧內復用的原則,此時D不會重新創建,系統會把D切換到棧頂并調用其onNewIntent方法,同時由于singleTask默認具有clearTop的效果,會導致棧內所有在D上面的Activity全部出棧,于是最終S1中的情況為AD**。這一點比較特殊,在后面還會對此種情況詳細地分析。
通過上述3個例子,讀者應該能比較清晰地理解singleTask的含義了。
* (4)**singleInstance:單實例模式**。這是一種**加強的singleTask模式**,它除了具有singleTask模式的所有特性外,還加強了一點,那就是**具有此種模式的Activity只能單獨地位于一個任務棧中,換句話說,比如Activity A是singleInstance模式,當A啟動后,系統會為它創建一個新的任務棧,然后A獨自在這個新的任務棧中,由于棧內復用的特性,后續的請求均不會創建新的Activity,除非這個獨特的任務棧被系統銷毀了**。
>[info]注意:對于一般App盡量少用singleTask和 singleInstance模式,可能會影響用戶體驗。
上面介紹了幾種啟動模式,這里需要指出一種情況,我們*假設目前有2個任務棧,前臺任務棧的情況為AB,而后臺任務棧的情況為CD,這里假設CD的啟動模式均為singleTask。現在請求啟動D,那么整個后臺任務棧都會被切換到前臺,這個時候整個后退列表變成了ABCD。當用戶按back鍵的時候,列表中的Activity會一一出棧,如圖1-7所示。如果不是請求啟動D而是啟動C,那么情況就不一樣了,請看圖1-8,具體原因在本節后面會再進行詳細分析*。
:-: 
圖1-7 任務棧示例1
:-: 
圖1-8 任務棧示例2
另外一個問題是,在singleTask啟動模式中,多次提到某個Activity所需的任務棧,**什么是Activity所需要的任務棧呢**?這要從一個參數說起:**TaskAffinity**,可以翻譯為**任務相關性**。這個參數**標識了一個Activity所需要的任務棧的名字,默認情況下,所有Activity所需的任務棧的名字為應用的包名。當然,我們可以為每個Activity都單獨指定[TaskAffinity](https://developer.android.google.cn/guide/topics/manifest/activity-element#aff)屬性,這個屬性值必須不能和包名相同,否則就相當于沒有指定。TaskAffinity屬性主要和singleTask啟動模式或者allowTaskReparenting屬性配對使用,在其他情況下沒有意義。另外,任務棧分為前臺任務棧和后臺任務棧,后臺任務棧中的Activity位于暫停狀態,用戶可以通過切換將后臺任務棧再次調到前臺**。
**當TaskAffinity和singleTask啟動模式配對使用的時候,它是具有該模式的Activity的目前任務棧的名字,待啟動的Activity會運行在名字和TaskAffinity相同的任務棧中**。
**當TaskAffinity和allowTaskReparenting結合的時候,這種情況比較復雜,會產生特殊的效果。當一個應用A啟動了應用B的某個Activity后,如果這個Activity的allowTaskReparenting屬性為true的話,那么當應用B被啟動后,此Activity會直接從應用A的任務棧轉移到應用B的任務棧中。這還是很抽象,再具體點,比如現在有2個應用A和B, A啟動了B的一個Activity C,然后按Home鍵回到桌面,然后再單擊B的桌面圖標,這個時候并不是啟動了B的主Activity,而是重新顯示了已經被應用A啟動的Activity C,或者說,C從A的任務棧轉移到了B的任務棧中。可以這么理解,由于A啟動了C,這個時候C只能運行在A的任務棧中,但是C屬于B應用,正常情況下,它的TaskAffinity值肯定不可能和A的任務棧相同(因為包名不同)。所以,當B被啟動后,B會創建自己的任務棧,這個時候系統發現C原本所想要的任務棧已經被創建了,所以就把C從A的任務棧中轉移過來了**。這種情況讀者可以寫個例子測試一下,這里就不做示例了。
如何**給Activity指定啟動模式**呢?有**兩種方法**,第一種是**通過[AndroidMenifest為Activity指定](https://developer.android.google.cn/guide/components/activities/tasks-and-back-stack#ManifestForTasks)啟動模式**,如下所示。
<activity
android:name="com.ryg.chapter_1.SecondActivity"
android:configChanges="screenLayout"
android:launchMode="singleTask"
android:label="@string/app_name" />
另一種情況是**通過[在Intent中設置標志位](https://developer.android.google.cn/guide/components/activities/tasks-and-back-stack#IntentFlagsForTasks)來為Activity指定啟動模式**,比如:
Intent intent = new Intent();
intent.setClass(MainActivity.this, SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
這兩種方式都可以為Activity指定啟動模式,但是二者還是有區別的。
* 首先,優先級上,第二種方式的優先級要高于第一種,當兩種同時存在時,以第二種方式即[在Intent中設置標志位](https://developer.android.google.cn/guide/components/activities/tasks-and-back-stack#IntentFlagsForTasks)為準;
* 其次,上述**兩種方式在限定范圍上有所不同**,比如,第一種方式無法直接為Activity設定FLAG_ACTIVITY_CLEAR_TOP標識,而第二種方式無法為Activity指定singleInstance模式。
關于Intent中為Activity指定的各種標記位,在下面的小節中會繼續介紹。下面通過一個例子來體驗啟動模式的使用效果。還是前面的例子,這里我們**把MainActivity的啟動模式設為singleTask,然后重復啟動它,看看是否會重復創建**,代碼修改如下:
<activity
android:name="com.ryg.chapter_1.MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d(TAG, "onNewIntent, time=" + intent.getLongExtra("time", 0));
}
findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(MainActivity.this, MainActivity.class);
intent.putExtra("time", System.currentTimeMillis());
startActivity(intent);
}
});
根據上述修改,我們做如下操作,連續單擊三次按鈕啟動3次MainActivity,算上原本的MainActvity的實例,正常情況下,任務棧中應該有4個MainActivity的實例,但是我們為其指定了singleTask模式,現在來看一看到底有何不同。
執行`adb shell dumpsys activity`命令:
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Main stack:
TaskRecord{41350dc8 #9 A com.ryg.chapter_1}
Intent { cmp=com.ryg.chapter_1/.MainActivity (has extras) }
Hist #1: ActivityRecord{412cc188 com.ryg.chapter_1/.MainActivity}
Intent { act=android.intent.action.MAIN cat=[android.intent.
category.LAUNCHER] flg=0x
0 cmp=com.ryg.chapter_1/.MainActivity bnds=[160,235][240,335] }
ProcessRecord{411e6898634:com.ryg.chapter_1/10052}
TaskRecord{4125abc8 #2 A com.android.launcher}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.
HOME] flg=0x10000000
m.android.launcher/com.android.launcher2.Launcher }
Hist #0: ActivityRecord{412381f8 com.android.launcher/com.android.
launcher2.Launcher}
Intent { act=android.intent.action.MAIN cat=[android.intent.
category.HOME] flg=0x1000
p=com.android.launcher/com.android.launcher2.Launcher }
ProcessRecord{411d24c8214:com.android.launcher/10013}
Running activities (most recent first):
TaskRecord{41350dc8 #9 A com.ryg.chapter_1}
Run #1: ActivityRecord{412cc188 com.ryg.chapter_1/.MainActivity}
TaskRecord{4125abc8 #2 A com.android.launcher}
Run #0: ActivityRecord{412381f8 com.android.launcher/com.android.
launcher2.Launcher}
mResumedActivity: ActivityRecord{412cc188 com.ryg.chapter_1/.MainActivity}
mFocusedActivity: ActivityRecord{412cc188 com.ryg.chapter_1/.MainActivity}
Recent tasks:
* Recent #0: TaskRecord{41350dc8 #9 A com.ryg.chapter_1}
* Recent #1: TaskRecord{4125abc8 #2 A com.android.launcher}
* Recent #2: TaskRecord{412b60a0 #5 A com.estrongs.android.pop.app.
InstallMonitorActivity}
從上面導出的Activity信息可以看出,**盡管啟動了4次MainActivity,但是它始終只有一個實例在任務棧中.從圖1-9的log可以看出,Activity的確沒有重新創建,只是暫停了一下,然后調用了onNewIntent,接著調用onResume就又繼續了**。
:-: 
圖1-9 系統日志
現在我們**去掉singleTask,再來對比一下,還是同樣的操作,單擊三次按鈕啟動MainActivity三次**。
執行`adb shell dumpsys activity`命令:
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Main stack:
TaskRecord{41325370 #17 A com.ryg.chapter_1}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.
LAUNCHER] flg=0x100000
p=com.ryg.chapter_1/.MainActivity }
Hist #4: ActivityRecord{41236968 com.ryg.chapter_1/.MainActivity}
Intent { cmp=com.ryg.chapter_1/.MainActivity (has extras) }
ProcessRecord{411e6898803:com.ryg.chapter_1/10052}
Hist #3: ActivityRecord{411f4b30 com.ryg.chapter_1/.MainActivity}
Intent { cmp=com.ryg.chapter_1/.MainActivity (has extras) }
ProcessRecord{411e6898803:com.ryg.chapter_1/10052}
Hist #2: ActivityRecord{411edcb8 com.ryg.chapter_1/.MainActivity}
Intent { cmp=com.ryg.chapter_1/.MainActivity (has extras) }
ProcessRecord{411e6898803:com.ryg.chapter_1/10052}
Hist #1: ActivityRecord{411e7588 com.ryg.chapter_1/.MainActivity}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.
LAUNCHER] flg=0x10
0 cmp=com.ryg.chapter_1/.MainActivity }
ProcessRecord{411e6898803:com.ryg.chapter_1/10052}
TaskRecord{4125abc8 #2 A com.android.launcher}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.
HOME] flg=0x10000000 c
m.android.launcher/com.android.launcher2.Launcher }
Hist #0: ActivityRecord{412381f8 com.android.launcher/com.android.
launcher2.Launcher}
Intent { act=android.intent.action.MAIN cat=[android.intent.cate-
gory.HOME] flg=0x100000
p=com.android.launcher/com.android.launcher2.Launcher }
ProcessRecord{411d24c8214:com.android.launcher/10013}
Running activities (most recent first):
TaskRecord{41325370 #17 A com.ryg.chapter_1}
Run #4: ActivityRecord{41236968 com.ryg.chapter_1/.MainActivity}
Run #3: ActivityRecord{411f4b30 com.ryg.chapter_1/.MainActivity}
Run #2: ActivityRecord{411edcb8 com.ryg.chapter_1/.MainActivity}
Run #1: ActivityRecord{411e7588 com.ryg.chapter_1/.MainActivity}
TaskRecord{4125abc8 #2 A com.android.launcher}
Run #0: ActivityRecord{412381f8 com.android.launcher/com.android.
launcher2.Launcher}
mResumedActivity: ActivityRecord{41236968 com.ryg.chapter_1/.MainAc-
tivity}
mFocusedActivity: ActivityRecord{41236968 com.ryg.chapter_1/.MainAc-
tivity}
Recent tasks:
* Recent #0: TaskRecord{41325370 #17 A com.ryg.chapter_1}
* Recent #1: TaskRecord{4125abc8 #2 A com.android.launcher}
* Recent #2: TaskRecord{412c8d58 #16 A com.estrongs.android.pop.app.
InstallMonitorActivity}
上面的導出信息很多,我們可以有選擇地看,比如就看Running activities (most recent first)這一塊,如下所示。
Running activities (most recent first):
TaskRecord{41325370 #17 A com.ryg.chapter_1}
Run #4: ActivityRecord{41236968 com.ryg.chapter_1/.MainActivity}
Run #3: ActivityRecord{411f4b30 com.ryg.chapter_1/.MainActivity}
Run #2: ActivityRecord{411edcb8 com.ryg.chapter_1/.MainActivity}
Run #1: ActivityRecord{411e7588 com.ryg.chapter_1/.MainActivity}
TaskRecord{4125abc8 #2 A com.android.launcher}
Run #0: ActivityRecord{412381f8 com.android.launcher/com.android.
launcher2.Launcher}
我們能夠得出**目前總共有2個任務棧,前臺任務棧的taskAffinity值為com.ryg. chapter_1,它里面有4個Activity,后臺任務棧的taskAffinity值為com.android.launcher,它里面有1個Activity,這個Activity就是桌面**。通過這種方式來分析任務棧信息就清晰多了。
:-: 
圖1-7 任務棧示例1
從上面的導出信息中可以看到,在任務棧中有4個MainActivity,這也就驗證了Activity的啟動模式的工作方式。
上述四種啟動模式,standard和singleTop都比較好理解,singleInstance由于其特殊性也好理解,但是關于singleTask有一種情況需要再說明一下。如圖1-7所示,**如果在Activity B中請求的不是D而是C,那么情況如何呢?這里可以告訴讀者的是,任務棧列表變成了ABC,是不是很奇怪呢?Activity D被直接出棧了**。下面我們再用實例驗證看看是不是這樣。首先,還是使用上面的代碼,但是我們做一下修改:
<activity
android:name="com.ryg.chapter_1.MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
android:launchMode="standard" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.ryg.chapter_1.SecondActivity"
android:configChanges="screenLayout"
android:label="@string/app_name"
android:taskAffinity="com.ryg.task1"
android:launchMode="singleTask" />
<activity
android:name="com.ryg.chapter_1.ThirdActivity"
android:configChanges="screenLayout"
android:taskAffinity="com.ryg.task1"
android:label="@string/app_name"
android:launchMode="singleTask" />
我們將SecondActivity和ThirdActivity都設成singleTask并指定它們的taskAffinity屬性為“com.ryg.task1”,**注意這個taskAffinity屬性的值為字符串,且中間必須含有包名分隔符“.”**。然后做如下操作,*在MainActivity中單擊按鈕啟動SecondActivity,在SecondActivity中單擊按鈕啟動ThirdActivity,在ThirdActivity中單擊按鈕又啟動MainActivity,最后再在MainActivity中單擊按鈕啟動SecondActivity,**然后再按2次back鍵**,然后看到的是哪個Activity?答案是回到桌面*。是不是有點摸不到頭腦了?沒關系,接下來我們分析這個問題。
*首先,從理論上分析這個問題,先假設MainActivity為A, SecondActivity為B,ThirdActivity為C。我們知道A為standard模式,按照規定,A的taskAffinity值繼承自Application的taskAffinity,而Application默認taskAffinity為包名,所以A的taskAffinity為包名。由于我們在XML中為B和C指定了taskAffinity和啟動模式,所以B和C是singleTask模式且有相同的taskAffinity值“com.ryg.task1”。A啟動B的時候,按照singleTask的規則,這個時候需要為B重新創建一個任務棧“com.ryg.task1”。B再啟動C,按照singleTask的規則,由于C所需的任務棧(和B為同一任務棧)已經被B創建,所以無須再創建新的任務棧,這個時候系統只是創建C的實例后將C入棧了。接著C再啟動A, A是standard模式,所以系統會為它創建一個新的實例并將到加到啟動它的那個Activity的任務棧,由于是C啟動了A,所以A會進入C的任務棧中并位于棧頂。這個時候已經有兩個任務棧了,一個是名字為包名的任務棧,里面只有A,另一個是名字為“com.ryg.task1”的任務棧,里面的Activity為BCA。接下來,A再啟動B,由于B是singleTask, B需要回到任務棧的棧頂,由于棧的工作模式為“后進先出”, B想要回到棧頂,只能是CA出棧。所以,到這里就很好理解了,如果再按back鍵,B就出棧了,B所在的任務棧已經不存在了,這個時候只能是回到后臺任務棧并把A顯示出來。注意這個A是后臺任務棧的A,不是“com.ryg.task1”任務棧的A,接著再繼續back,就回到桌面了*。分析到這里,我們得出一條結論,**singleTask模式的Activity切換到棧頂會導致在它之上的棧內的Activity出棧**。
接著我們在實踐中再次驗證這個問題,還是采用dumpsys命令。我們省略中間的過程,直接看C啟動A的那個狀態,執行`adb shell dumpsys activity`命令,日志如下:
Running activities (most recent first):
TaskRecord{4132bd90 #12 A com.ryg.task1}
Run #4: ActivityRecord{4133fd18 com.ryg.chapter_1/.MainActivity}
Run #3: ActivityRecord{41349c58 com.ryg.chapter_1/.ThirdActivity}
Run #2: ActivityRecord{4132bab0 com.ryg.chapter_1/.SecondActivity}
TaskRecord{4125a008 #11 A com.ryg.chapter_1}
Run #1: ActivityRecord{41328c60 com.ryg.chapter_1/.MainActivity}
TaskRecord{41256440 #2 A com.android.launcher}
Run #0: ActivityRecord{41231d30 com.android.launcher/com.android.launch
er2.Launcher}
可以**清楚地看到有2個任務棧,第一個(com.ryg.chapter_1)只有A,第二個(com.ryg.task1)有BCA**,就如同我們上面分析的那樣,然后再從A中啟動B,再看一下日志:
Running activities (most recent first):
TaskRecord{4132bd90 #12 A com.ryg.task1}
Run #2: ActivityRecord{4132bab0 com.ryg.chapter_1/.SecondActivity}
TaskRecord{4125a008 #11 A com.ryg.chapter_1}
Run #1: ActivityRecord{41328c60 com.ryg.chapter_1/.MainActivity}
TaskRecord{41256440 #2 A com.android.launcher}
Run #0: ActivityRecord{41231d30 com.android.launcher/com.android.launch
er2.Launcher}
**可以發現在任務棧`com.ryg.task1`中只剩下B了,C、A都已經出棧了,這個時候再按back鍵,任務棧com.ryg.chapter_1中的A就顯示出來了,如果再back就回到桌面了**。分析到這里,相信讀者對Activity的啟動模式已經有很深入的理解了。下面介紹Activity中常用的標志位。
- 前言
- 第1章 Activity的生命周期和啟動模式
- 1.1 Activity的生命周期全面分析
- 1.1.1 典型情況下的生命周期分析
- 1.1.2 異常情況下的生命周期分析
- 1.2 Activity的啟動模式
- 1.2.1 Activity的LaunchMode
- 1.2.2 Activity的Flags
- 1.3 IntentFilter的匹配規則
- 第2章 IPC機制
- 2.1 Android IPC簡介
- 2.2 Android中的多進程模式
- 2.2.1 開啟多進程模式
- 2.2.2 多進程模式的運行機制
- 2.3 IPC基礎概念介紹
- 2.3.1 Serializable接口
- 2.3.2 Parcelable接口
- 2.3.3 Binder
- 2.4 Android中的IPC方式
- 2.4.1 使用Bundle
- 2.4.2 使用文件共享
- 2.4.3 使用Messenger
- 2.4.4 使用AIDL
- 2.4.5 使用ContentProvider
- 2.4.6 使用Socket
- 2.5 Binder連接池
- 2.6 選用合適的IPC方式
- 第3章 View的事件體系
- 3.1 View基礎知識
- 3.1.1 什么是View
- 3.1.2 View的位置參數
- 3.1.3 MotionEvent和TouchSlop
- 3.1.4 VelocityTracker、GestureDetector和Scroller
- 3.2 View的滑動
- 3.2.1 使用scrollTo/scrollBy
- 3.2.2 使用動畫
- 3.2.3 改變布局參數
- 3.2.4 各種滑動方式的對比
- 3.3 彈性滑動
- 3.3.1 使用Scroller7
- 3.3.2 通過動畫
- 3.3.3 使用延時策略
- 3.4 View的事件分發機制
- 3.4.1 點擊事件的傳遞規則
- 3.4.2 事件分發的源碼解析
- 3.5 View的滑動沖突
- 3.5.1 常見的滑動沖突場景
- 3.5.2 滑動沖突的處理規則
- 3.5.3 滑動沖突的解決方式
- 第4章 View的工作原理
- 4.1 初識ViewRoot和DecorView
- 4.2 理解MeasureSpec
- 4.2.1 MeasureSpec
- 4.2.2 MeasureSpec和LayoutParams的對應關系
- 4.3 View的工作流程
- 4.3.1 measure過程
- 4.3.2 layout過程
- 4.3.3 draw過程
- 4.4 自定義View
- 4.4.1 自定義View的分類
- 4.4.2 自定義View須知
- 4.4.3 自定義View示例
- 4.4.4 自定義View的思想
- 第5章 理解RemoteViews
- 5.1 RemoteViews的應用
- 5.1.1 RemoteViews在通知欄上的應用
- 5.1.2 RemoteViews在桌面小部件上的應用
- 5.1.3 PendingIntent概述
- 5.2 RemoteViews的內部機制
- 5.3 RemoteViews的意義
- 第6章 Android的Drawable
- 6.1 Drawable簡介
- 6.2 Drawable的分類
- 6.2.1 BitmapDrawable2
- 6.2.2 ShapeDrawable
- 6.2.3 LayerDrawable
- 6.2.4 StateListDrawable
- 6.2.5 LevelListDrawable
- 6.2.6 TransitionDrawable
- 6.2.7 InsetDrawable
- 6.2.8 ScaleDrawable
- 6.2.9 ClipDrawable
- 6.3 自定義Drawable
- 第7章 Android動畫深入分析
- 7.1 View動畫
- 7.1.1 View動畫的種類
- 7.1.2 自定義View動畫
- 7.1.3 幀動畫
- 7.2 View動畫的特殊使用場景
- 7.2.1 LayoutAnimation
- 7.2.2 Activity的切換效果
- 7.3 屬性動畫
- 7.3.1 使用屬性動畫
- 7.3.2 理解插值器和估值器 /
- 7.3.3 屬性動畫的監聽器
- 7.3.4 對任意屬性做動畫
- 7.3.5 屬性動畫的工作原理
- 7.4 使用動畫的注意事項
- 第8章 理解Window和WindowManager
- 8.1 Window和WindowManager
- 8.2 Window的內部機制
- 8.2.1 Window的添加過程
- 8.2.2 Window的刪除過程
- 8.2.3 Window的更新過程
- 8.3 Window的創建過程
- 8.3.1 Activity的Window創建過程
- 8.3.2 Dialog的Window創建過程
- 8.3.3 Toast的Window創建過程
- 第9章 四大組件的工作過程
- 9.1 四大組件的運行狀態
- 9.2 Activity的工作過程
- 9.3 Service的工作過程
- 9.3.1 Service的啟動過程
- 9.3.2 Service的綁定過程
- 9.4 BroadcastReceiver的工作過程
- 9.4.1 廣播的注冊過程
- 9.4.2 廣播的發送和接收過程
- 9.5 ContentProvider的工作過程
- 第10章 Android的消息機制
- 10.1 Android的消息機制概述
- 10.2 Android的消息機制分析
- 10.2.1 ThreadLocal的工作原理
- 10.2.2 消息隊列的工作原理
- 10.2.3 Looper的工作原理
- 10.2.4 Handler的工作原理
- 10.3 主線程的消息循環
- 第11章 Android的線程和線程池
- 11.1 主線程和子線程
- 11.2 Android中的線程形態
- 11.2.1 AsyncTask
- 11.2.2 AsyncTask的工作原理
- 11.2.3 HandlerThread
- 11.2.4 IntentService
- 11.3 Android中的線程池
- 11.3.1 ThreadPoolExecutor
- 11.3.2 線程池的分類
- 第12章 Bitmap的加載和Cache
- 12.1 Bitmap的高效加載
- 12.2 Android中的緩存策略
- 12.2.1 LruCache
- 12.2.2 DiskLruCache
- 12.2.3 ImageLoader的實現446
- 12.3 ImageLoader的使用
- 12.3.1 照片墻效果
- 12.3.2 優化列表的卡頓現象
- 第13章 綜合技術
- 13.1 使用CrashHandler來獲取應用的crash信息
- 13.2 使用multidex來解決方法數越界
- 13.3 Android的動態加載技術
- 13.4 反編譯初步
- 13.4.1 使用dex2jar和jd-gui反編譯apk
- 13.4.2 使用apktool對apk進行二次打包
- 第14章 JNI和NDK編程
- 14.1 JNI的開發流程
- 14.2 NDK的開發流程
- 14.3 JNI的數據類型和類型簽名
- 14.4 JNI調用Java方法的流程
- 第15章 Android性能優化
- 15.1 Android的性能優化方法
- 15.1.1 布局優化
- 15.1.2 繪制優化
- 15.1.3 內存泄露優化
- 15.1.4 響應速度優化和ANR日志分析
- 15.1.5 ListView和Bitmap優化
- 15.1.6 線程優化
- 15.1.7 一些性能優化建議
- 15.2 內存泄露分析之MAT工具
- 15.3 提高程序的可維護性