本來想分析AppsCustomizePagedView類,不過今天突然接到一個臨時任務。客戶反饋說機器界面的圖標很難點擊啟動程序,經常點擊了沒有反應,Boss說要優先解決這問題。沒辦法,只能看看是怎么回事。今天分析一下Launcher啟動APP的過程。從用戶點擊到程序啟動的流程,下面針對WorkSpace上的快捷圖標點擊啟動流程進行分析。(如果分不清WorkSpace是什么或者不知道快捷方式和其他圖標區別,請看我前面的Launcher分析文章)
PS:新建的QQ群,有興趣可以加入一起討論:Android群:322599434
下面我們先看看Launcher啟動APP的大概流程:
(鑒於很多轉載文章的人把作者信息都刪除了,只能在圖片上加入水印,不會給大家閱讀造成影響)
上面就是手指觸摸屏幕開始,到點擊響應的流程。Launcher里面因為有滑動、拖曳、點擊等手勢操作,所以區分了很多流程判斷。最后調用Launcher.java里面的onClick()方法響應點擊,啟動程序。下面我們針對關鍵流程做分析。
1、WorkSpace觸摸
前面我們分析Launcher的配置文件時就說過,Launcher外面的界面主要就是通過WorkSpace來顯示的。它是一個ViewGroup的自定義類。下面我們先看看WorkSpace的onInterceptTouchEvent做了什么。
//Edited by mythou
//http://www.cnblogs.com/mythou/
public boolean onInterceptTouchEvent(MotionEvent ev) { if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent enter");
//對ACTION_DOWN和ACTION_UP做一些標記處理。 switch (ev.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: mXDown = ev.getX(); mYDown = ev.getY(); break; case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_UP: if (mTouchState == TOUCH_STATE_REST) { final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage); if (!currentPage.lastDownOnOccupiedCell()) { onWallpaperTap(ev); } } } if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent call super InterceptTouch"); //調用父類的onInterceptTouchEvent,這里是調用了SmoothPagedView return super.onInterceptTouchEvent(ev); }
在WorkSpace里面並沒有攔截消息,主要是調用父類的方法,也就是PagedView的onInterceptTouchEvent()方法。因為WorkSpace的直接父類SmoothPagedView也是繼承了PagedView類,有關PagedView的onInterceptTouchEvent()方法,我在前面的文章已經分析過。這里不做多說,不了解的朋友可以看看(點這里)。
2、CellLayout的onInterceptTouchEvent()方法
CellLayout也是一個繼承了ViewGroup的類,主要用來顯示桌面控件。剛開始分析Launcher的時候,我們分析配置文件的時候也說過WorkSpace就是由5個CellLayout組成的。因此我們點擊WorkSpace里面圖標的時候,自然會調用CellLayout里面的東西。CellLayout里面的onInterceptTouchEvent()做的事情並不多:
//Edited by mythou
//http://www.cnblogs.com/mythou/
public boolean onInterceptTouchEvent(MotionEvent ev) { if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent enter"); final int action = ev.getAction(); if (action == MotionEvent.ACTION_DOWN) {
//清除所有觸摸標記 clearTagCellInfo(); } //mInterceptTouchListener是WorkSpace的onTouch方法回調,下面會分析
//掌握這點很重要,因為onInterceptTouchEvent的返回值直接決定了觸摸事件的傳遞方向 mythou if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) { //截斷Touch傳輸,直接處理 ,返回true會直接調用onTouchEvent處理。 mythou if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent return true Intercept msg"); return true; } if (action == MotionEvent.ACTION_DOWN) { setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY()); } if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent return false");
//注意這里返回的是false ,onInterceptTouchEvent的返回值決定了觸摸事件的傳遞方式。mythou return false; }
這里需要注意的是onInterceptTouchEvent()的返回值,如果是返回true,觸摸消息會直接被CellLayout的onTouchEvent()處理,一般點擊啟動程序返回的都是false,因為消息最后是TextView處理(workspace上的快捷方式都是TextView的子類)。Android的觸摸消息傳遞機制和消息攔截機制,需要好好理解好才能明白Launcher的觸摸事件處理。因為個人覺得Launcher里面事件處理層次還是比較多,如果對Android的事件傳遞機制理解不深,就很難理解Launcher的事件處理。如果對這方面不了解的朋友,可以查查網上相關資料。后面有空我也會寫一篇深入分析Android觸摸事件處理的文章。
3、WorkSpace的onTouch()事件
WorkSpace里面還處理了onTouch事件,這里的onTouch事件是因為WorkSpace使用了View.OnTouchListener接口,所以實現了onTouch事件的回調。
//Edited by mythou
//http://www.cnblogs.com/mythou/
public boolean onTouch(View v, MotionEvent event)
{
return (isSmall() || !isFinishedSwitchingState());
}
onTouch里面其實沒做什么事情,就是根據兩個方法返回值,判斷onTouch是返回false還是返回true,返回值是什么決定了觸摸事件的傳遞方向,上面已經說過了。這里的onTouch是給WorkSpace里面的裝載的View使用的,也就是CellLayout。我們可以看看每次調用WorkSpace的onChildViewAdded()方法,會設置CellLayout的onTouch監聽器。
//Edited by mythou
//http://www.cnblogs.com/mythou/
public void onChildViewAdded(View parent, View child) { if (!(child instanceof CellLayout))
{
throw new IllegalArgumentException("A Workspace can only have CellLayout children."); }
CellLayout cl = ((CellLayout) child);
//設置onTouch的監聽器,CellLayout的onInterceptTouchEvent()方法會根據onTouch監聽器判斷是否需要攔截onTouch事件。 cl.setOnInterceptTouchListener(this); cl.setClickable(true); cl.setContentDescription(getContext().getString( R.string.workspace_description_format, getChildCount())); }
上面第二點CellLayout的onInterceptTouchEvent()方法的分析里面,return ture的時候,判斷的依據mInterceptTouchListener和mInterceptTouchListener.onTouch()方法的返回值就是從這里設置的,他們也就是調用了WorkSpace的onTouch方法。
4、BubbleTextView
BubbleTextView是繼承了TextView的子類,在Launcher里面所有的WorkSpace的快捷方式都是使用BubbleTextView繪畫的,它的作用相當於一個按鈕。在分析BubbleTextView的觸摸響應前,我們先看看Launcher里面如何創建快捷方式。前面的文章我分析過Launcher加載界面數據的流程,不過沒有仔細分析如何創建相關對象,下面我們先看看WorkSpace的快捷方式如何創建:
//Edited by mythou
//http://www.cnblogs.com/mythou/
View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info)
{
BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
favorite.applyFromShortcutInfo(info, mIconCache);
//注意這里設置了onClickListener OWL favorite.setOnClickListener(this); return favorite; }
上面就是創建快捷方式的方法,這里面我們需要注意的是,BubbleTextView調用了SetOnClickListener()方法,設置點擊監聽器。監聽器的回調函數就是Launcher.java類里面的onCLick方法。因為Launcher類繼承了View.OnClickListener接口,當然Launcher里面還繼承了其他的接口,例如觸摸、長按的Listener。下面我們先看看BUbbleTextView的onTouchEvent方法:
//Edited by mythou
//http://www.cnblogs.com/mythou/
public boolean onTouchEvent(MotionEvent event)
{ // 調用TextView的onToucEvent方法,主要是獲取返回值,這個返回值作用很大,會響應點擊事件的回調。 // 可以把這返回值打印出來看看。當點擊啟動程序時,這里肯定會返回true。
//原因上面已經說了很多次,這里不再啰嗦 OWL
boolean result = super.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //下面這里主要是根據觸摸狀態值,做快捷方式的狀態顯示,例如是否需要顯示按下狀態。 if (mPressedOrFocusedBackground == null) { mPressedOrFocusedBackground = createGlowingOutline( mTempCanvas, mPressedGlowColor, mPressedOutlineColor); } if (isPressed())
{ mDidInvalidateForPressedState = true; setCellLayoutPressedOrFocusedIcon(); } else { mDidInvalidateForPressedState = false; } mLongPressHelper.postCheckForLongPress(); break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: if (!isPressed()) { mPressedOrFocusedBackground = null; } //這里根據狀態判斷是否取消長按的觸摸 mLongPressHelper.cancelLongPress(); break; }
//返回值十分重要,直接影響是否會掉用onCLick方法。 return result; }
上面就是BubbleTextView的onTouchEvent的方法,上面強調很多次返回值,這里如果是點擊流程,會返回true。然后執行onClick方法。
5、onClick()方法
最后點擊會調用Launcher里面的onClick方法,不管你是點擊快捷方式、文件夾、還是AllAPP的按鈕,都是從這里響應回調。
//Edited by mythou //http://www.cnblogs.com/mythou/ public void onClick(View v) { //.......... Object tag = v.getTag(); if (tag instanceof ShortcutInfo) { //這里點擊打開我們上面分析的快捷方式圖標。打開的方式也是調用startActivity,
// 只是這里調用的startActivitySafely對startActivity進行了封裝 mythou
final Intent intent = ((ShortcutInfo) tag).intent; int[] pos = new int[2]; v.getLocationOnScreen(pos); intent.setSourceBounds(new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight())); boolean success = startActivitySafely(v, intent, tag); if (success && v instanceof BubbleTextView) { mWaitingForResume = (BubbleTextView) v; mWaitingForResume.setStayPressed(true); }
//文件夾點擊響應 } else if (tag instanceof FolderInfo) { if (v instanceof FolderIcon) { FolderIcon fi = (FolderIcon) v; handleFolderClick(fi); }
//AllAPP按鈕點擊響應 } else if (v == mAllAppsButton) { if (isAllAppsVisible()) { showWorkspace(true); } else { onClickAllAppsButton(v); } } }
上面給出了最后點擊啟動快捷方式圖標的方式,最后調用的也是startActivity,不過Launcher對它進行了封裝,里面加入異常處理,包括是否啟動成功等操作。而且里面分開了startActivity和startActivityForResult兩種方式。但是最后啟動的時候,還是調用了我們平時使用的啟動Activity的方法。下面給出我加入打印消息后的代碼流程跟蹤截圖。
6、總結
Launcher里面有關點擊啟動程序的方法流程就是上述所說的那樣,當然,里面有些方法判斷執行了很多方法,這里不做詳細分析,又興趣的朋友可以跟我一樣加入一些打印消息,跟蹤Log分析代碼流程。里面還有一個相當重要的部分就是如何區分是頁面滑動還是按鈕點擊。文章開頭說的問題就是因為把點擊事件判斷為頁面滑動,導致經常點擊按鈕的時候執行了滑動頁面的操作,導致用戶覺得點擊按鈕都沒反應。我最后的修改方法是修改了判斷是否進行頁面滑動的方法,解決了該問題。
今天就說到這里,這個流程分析個人覺得還是有點復雜,因為涉及了Android觸摸事件的傳遞方式,如果不了解這個,無法理解Launcher是如何處理這些事件的。最后歡迎各位轉載Launcher的分析文章,但請不要修改文章內容,並附上原文鏈接和作者信息。
Launcher分析系列文章:
Android Launcher分析和修改1——Launcher默認界面配置(default_workspace)
Android Launcher分析和修改2——Icon修改、界面布局調整、壁紙設置
Android Launcher分析和修改3——Launcher啟動和初始化
Android Launcher分析和修改4——初始化加載數據
Android Launcher分析和修改5——HotSeat分析
Android Launcher分析和修改6——頁面滑動(PagedView)
Android Launcher分析和修改7——AllApp全部應用列表(AppsCustomizeTabHost)
Android Launcher分析和修改8——AllAPP界面拖拽元素(PagedViewWithDraggableItems)
Edited by mythou
原創博文,轉載請標明出處:http://www.cnblogs.com/mythou/p/3187881.html