本来想分析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