#### 3.4.2 事件分發的源碼解析
上一節分析了View的事件分發機制,本節將會從源碼的角度去進一步分析、證實上面的結論。
**1.Activity對點擊事件的分發過程**
點擊事件用MotionEvent來表示,當一個點擊操作發生時,事件最先傳遞給當前Activity,由Activity的dispatchTouchEvent來進行事件派發,具體的工作是由Activity內部的Window來完成的。Window會將事件傳遞給decor view, decor view一般就是當前界面的底層容器(即setContentView所設置的View的父容器),通過Activity.getWindow. getDecorView()可以獲得。我們先從Activity的dispatchTouchEvent開始分析。
源碼:Activity#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
現在分析上面的代碼。首先事件開始交給Activity所附屬的Window進行分發,如果返回true,整個事件循環就結束了,返回false意味著事件沒人處理,所有View的onTouchEvent都返回了false,那么Activity的onTouchEvent就會被調用。
接下來看Window是如何將事件傳遞給ViewGroup的。通過源碼我們知道,Window是個抽象類,而Window的superDispatchTouchEvent方法也是個抽象方法,因此我們必須找到Window的實現類才行。
源碼:Window#superDispatchTouchEvent
public abstract boolean superDispatchTouchEvent(MotionEvent event);
那么到底Window的實現類是什么呢?其實是PhoneWindow,這一點從Window的源碼中也可以看出來,在Window的說明中,有這么一段話:
> Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc.
> The only existing implementation of this abstract class is android. policy.PhoneWindow, which you should instantiate when needing a Window. Eventually that class will be refactored and a factory method added for creating Window instances without knowing about a particular implementation.
上面這段話的大概意思是:Window類可以控制頂級View的外觀和行為策略,它的唯一實現位于android.policy.PhoneWindow中,當你要實例化這個Window類的時候,你并不知道它的細節,因為這個類會被重構,只有一個工廠方法可以使用。盡管這看起來有點模糊,不過我們可以看一下android.policy.PhoneWindow這個類,盡管實例化的時候此類會被重構,僅是重構而已,功能是類似的。
由于Window的唯一實現是PhoneWindow,因此接下來看一下PhoneWindow是如何處理點擊事件的,如下所示。
源碼:PhoneWindow#superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
到這里邏輯就很清晰了,PhoneWindow將事件直接傳遞給了DecorView,這個DecorView是什么呢?請看下面:
private final class DecorView extends FrameLayout implements RootViewSur-
faceTaker
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
@Override
public final View getDecorView() {
if (mDecor == null) {
installDecor();
}
return mDecor;
}
我們知道,通過((ViewGroup)getWindow().getDecorView().findViewById(android.R.id. content)).getChildAt(0)這種方式就可以獲取Activity所設置的View,這個mDecor顯然就是getWindow().getDecorView()返回的View,而我們通過setContentView設置的View是它的一個子View。目前事件傳遞到了DecorView這里,由于DecorView繼承自FrameLayout且是父View,所以最終事件會傳遞給View。換句話來說,事件肯定會傳遞到View,不然應用如何響應點擊事件呢?不過這不是我們的重點,重點是事件到了View以后應該如何傳遞,這對我們更有用。從這里開始,事件已經傳遞到頂級View了,即在Activity中通過setContentView所設置的View,另外頂級View也叫根View,頂級View一般來說都是ViewGroup。
**2.頂級View對點擊事件的分發過程**
關于點擊事件如何在View中進行分發,上一節已經做了詳細的介紹,這里再大致回顧一下。點擊事件達到頂級View(一般是一個ViewGroup)以后,會調用ViewGroup的dispatchTouchEvent方法,然后的邏輯是這樣的:如果頂級ViewGroup攔截事件即onInterceptTouchEvent返回true,則事件由ViewGroup處理,這時如果ViewGroup的mOnTouchListener被設置,則onTouch會被調用,否則onTouchEvent會被調用。也就是說,如果都提供的話,onTouch會屏蔽掉onTouchEvent。在onTouchEvent中,如果設置了mOnClickListener,則onClick會被調用。如果頂級ViewGroup不攔截事件,則事件會傳遞給它所在的點擊事件鏈上的子View,這時子View的dispatchTouchEvent會被調用。到此為止,事件已經從頂級View傳遞給了下一層View,接下來的傳遞過程和頂級View是一致的,如此循環,完成整個事件的分發。
首先看ViewGroup對點擊事件的分發過程,其主要實現在ViewGroup的dispatchTouch-Event方法中,這個方法比較長,這里分段說明。先看下面一段,很顯然,它描述的是當前View是否攔截點擊事情這個邏輯。
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget ! = null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_
INTERCEPT) ! = 0;
if (! disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
從上面代碼我們可以看出,ViewGroup在如下兩種情況下會判斷是否要攔截當前事件:事件類型為ACTION_DOWN或者mFirstTouchTarget ! = null。ACTION_DOWN事件好理解,那么mFirstTouchTarget ! = null是什么意思呢?這個從后面的代碼邏輯可以看出來,當事件由ViewGroup的子元素成功處理時,mFirstTouchTarget會被賦值并指向子元素,換種方式來說,當ViewGroup不攔截事件并將事件交由子元素處理時mFirstTouchTarget ! = null。反過來,一旦事件由當前ViewGroup攔截時,mFirstTouchTarget ! = null就不成立。那么當ACTION_MOVE和ACTION_UP事件到來時,由于(actionMasked == MotionEvent. ACTION_DOWN || mFirstTouchTarget ! = null)這個條件為false,將導致ViewGroup的onInterceptTouchEvent不會再被調用,并且同一序列中的其他事件都會默認交給它處理。
當然,這里有一種特殊情況,那就是FLAG_DISALLOW_INTERCEPT標記位,這個標記位是通過requestDisallowInterceptTouchEvent方法來設置的,一般用于子View中。FLAG_DISALLOW_INTERCEPT一旦設置后,ViewGroup將無法攔截除了ACTION_DOWN以外的其他點擊事件。為什么說是除了ACTION_DOWN以外的其他事件呢?這是因為ViewGroup在分發事件時,如果是ACTION_DOWN就會重置FLAG_DISALLOW_INTERCEPT這個標記位,將導致子View中設置的這個標記位無效。因此,當面對ACTION_DOWN事件時,ViewGroup總是會調用自己的onInterceptTouchEvent方法來詢問自己是否要攔截事件,這一點從源碼中也可以看出來。在下面的代碼中,ViewGroup會在ACTION_DOWN事件到來時做重置狀態的操作,而在resetTouchState方法中會對FLAG_DISALLOW_INTERCEPT進行重置,因此子View調用request- DisallowInterceptTouchEvent方法并不能影響ViewGroup對ACTION_DOWN事件的處理。
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous
gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
從上面的源碼分析,我們可以得出結論:當ViewGroup決定攔截事件后,那么后續的點擊事件將會默認交給它處理并且不再調用它的onInterceptTouchEvent方法,這證實了3.4.1節末尾處的第3條結論。FLAG_DISALLOW_INTERCEPT這個標志的作用是讓ViewGroup不再攔截事件,當然前提是ViewGroup不攔截ACTION_DOWN事件,這證實了3.4.1節末尾處的第11條結論。那么這段分析對我們有什么價值呢?總結起來有兩點:第一點,onInterceptTouchEvent不是每次事件都會被調用的,如果我們想提前處理所有的點擊事件,要選擇dispatchTouchEvent方法,只有這個方法能確保每次都會調用,當然前提是事件能夠傳遞到當前的ViewGroup;另外一點,FLAG_DISALLOW_INTERCEPT標記位的作用給我們提供了一個思路,當面對滑動沖突時,我們可以是不是考慮用這種方法去解決問題?關于滑動沖突,將在3.5節進行詳細分析。
接著再看當ViewGroup不攔截事件的時候,事件會向下分發交由它的子View進行處理,這段源碼如下所示。
final View[] children = mChildren;
for (int i = childrenCount -1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
if (! canViewReceivePointerEvents(child)
|| ! isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget ! = null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList ! = null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
上面這段代碼邏輯也很清晰,首先遍歷ViewGroup的所有子元素,然后判斷子元素是否能夠接收到點擊事件。是否能夠接收點擊事件主要由兩點來衡量:子元素是否在播動畫和點擊事件的坐標是否落在子元素的區域內。如果某個子元素滿足這兩個條件,那么事件就會傳遞給它來處理。可以看到,dispatchTransformedTouchEvent實際上調用的就是子元素的dispatchTouchEvent方法,在它的內部有如下一段內容,而在上面的代碼中child傳遞的不是null,因此它會直接調用子元素的dispatchTouchEvent方法,這樣事件就交由子元素處理了,從而完成了一輪事件分發。
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
如果子元素的dispatchTouchEvent返回true,這時我們暫時不用考慮事件在子元素內部是怎么分發的,那么mFirstTouchTarget就會被賦值同時跳出for循環,如下所示。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
這幾行代碼完成了mFirstTouchTarget的賦值并終止對子元素的遍歷。如果子元素的dispatchTouchEvent返回false, ViewGroup就會把事件分發給下一個子元素(如果還有下一個子元素的話)。
其實mFirstTouchTarget真正的賦值過程是在addTouchTarget內部完成的,從下面的addTouchTarget方法的內部結構可以看出,mFirstTouchTarget其實是一種單鏈表結構。mFirstTouchTarget是否被賦值,將直接影響到ViewGroup對事件的攔截策略,如果mFirstTouchTarget為null,那么ViewGroup就默認攔截接下來同一序列中所有的點擊事件,這一點在前面已經做了分析。
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
如果遍歷所有的子元素后事件都沒有被合適地處理,這包含兩種情況:第一種是ViewGroup沒有子元素;第二種是子元素處理了點擊事件,但是在dispatchTouchEvent中返回了false,這一般是因為子元素在onTouchEvent中返回了false。在這兩種情況下,ViewGroup會自己處理點擊事件,這里就證實了3.4.1節中的第4條結論,代碼如下所示。
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
注意上面這段代碼,這里第三個參數child為null,從前面的分析可以知道,它會調用super.dispatchTouchEvent(event),很顯然,這里就轉到了View的dispatchTouchEvent方法,即點擊事件開始交由View來處理,請看下面的分析。
**3.View對點擊事件的處理過程**
View對點擊事件的處理過程稍微簡單一些,注意這里的View不包含ViewGroup。先看它的dispatchTouchEvent方法,如下所示。
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
...
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li ! = null && li.mOnTouchListener ! = null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (! result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
View對點擊事件的處理過程就比較簡單了,因為View(這里不包含ViewGroup)是一個單獨的元素,它沒有子元素因此無法向下傳遞事件,所以它只能自己處理事件。從上面的源碼可以看出View對點擊事件的處理過程,首先會判斷有沒有設置OnTouchListener,如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent就不會被調用,可見OnTouchListener的優先級高于onTouchEvent,這樣做的好處是方便在外界處理點擊事件。
接著再分析onTouchEvent的實現。先看當View處于不可用狀態下點擊事件的處理過程,如下所示。很顯然,不可用狀態下的View照樣會消耗點擊事件,盡管它看起來不可用。
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags &
PFLAG_PRESSED) ! = 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
接著,如果View設置有代理,那么還會執行TouchDelegate的onTouchEvent方法,這個onTouchEvent的工作機制看起來和OnTouchListener類似,這里不深入研究了。
if (mTouchDelegate ! = null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
下面再看一下onTouchEvent中對點擊事件的具體處理,如下所示。
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) ! = 0;
if ((mPrivateFlags & PFLAG_PRESSED) ! = 0 || prepressed) {
...
if (! mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the
pressed state
if (! focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual
state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (! post(mPerformClick)) {
performClick();
}
}
}
...
}
break;
}
...
return true;
}
從上面的代碼來看,只要View的CLICKABLE和LONG_CLICKABLE有一個為true,那么它就會消耗這個事件,即onTouchEvent方法返回true,不管它是不是DISABLE狀態,這就證實了3.4.1節末尾處的第8、第9和第10條結論。然后就是當ACTION_UP事件發生時,會觸發performClick方法,如果View設置了OnClickListener,那么performClick方法內部會調用它的onClick方法,如下所示。
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li ! = null && li.mOnClickListener ! = null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
View的LONG_CLICKABLE屬性默認為false,而CLICKABLE屬性是否為false和具體的View有關,確切來說是可點擊的View其CLICKABLE為true,不可點擊的View其CLICKABLE為false,比如Button是可點擊的,TextView是不可點擊的。通過setClickable和setLongClickable可以分別改變View的CLICKABLE和LONG_CLICKABLE屬性。另外,setOnClickListener會自動將View的CLICKABLE設為true, setOnLongClickListener則會自動將View的LONG_CLICKABLE設為true,這一點從源碼中可以看出來,如下所示。
public void setOnClickListener(OnClickListener l) {
if (! isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public void setOnLongClickListener(OnLongClickListener l) {
if (! isLongClickable()) {
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}
到這里,點擊事件的分發機制的源碼實現已經分析完了,結合3.4.1節中的理論分析和相關結論,讀者就可以更好地理解事件分發了。在3.5節將介紹滑動沖突相關的知識,具體情況請看下面的分析。
- 前言
- 第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 提高程序的可維護性