程序员人生 网站导航

基于Android系统应用层与框架层剖析View点击事件

栏目:综合技术时间:2015-01-12 08:40:49

基于Android操作系统的框架层和利用层,介绍了View的绘制、触摸事件的传递流程,分析了View与用户交互时被回调的相干框架层代码和利用层代码,研究了Android利用中事件处理的相干重要机制。通过具体代码详细剖析了在Android系统下用户和View交互进程中折射出的回调机制,回调方法在系统框架的详细履行进程,和基于回调机制的经典事件处理模型。

1 引言

Android是1种基于Linux的自由及开放源代码的操作系统,目前基于Android平台的利用日益广泛。Android利用程序大多基于图形用户界面,因此,需要大量处理界面元素的显示和对相应控件的点击事件。在Android中,界面元素的组成控件都是继承自View类,View的点击事件常常连接着软件功能的各个模块,也最能体现用户在使用Android系统时的人机交互。但是,安卓利用开发者常常只站在操作系统的利用层进行开发,这类开发是浅层次的,没有从系统或引擎的框架源码进行分析,因此研究用户与View交互进程中的回调和具体进程有益于加深对Android系统框架层的认识,通过这个视角打破原本的思惟方式,更快地解决Bug或编写出质量更高的软件。在Android系统中存在很多工作机制,本文研究的View的点击事件实际上体现了Android中触摸事件的传递机制和回调机制。而对核心的回调,首先讨论回调的概念、从理论上分析回调在Android中的基本模型,然后从Android系统框架层的源码上分析触摸事件具体如何被分发,具体回调方法是如何被调用的,最后回归到利用层,构建物理世界和虚拟世界的联系,由下而上总结和归纳View与用户在交互进程中的经典事件处理模型。     

 

2 回调函数和Android系统中的回调机制

 

    软件中相互联系的模块之间常常会产生调用,从调用方式上可分为3类:同步调用、异步调用和回调。同步调用和异步调用都是1种单向调用模式,而回调是1种双向调用模式,被调用方在方法被调用时也会调用对方的方法。回调函数就是1个通过函数指针调用的函数。回调函数其实不是由该函数的实现方法直接去调用,而是把它的函数指针作为参数传递给另外一个函数,在特定的事件或条件产生时由另外1方通过这个指针来调用,用于对该事件或条件进行响应。

 

2.1 Java中的回调函数

 

    Java中不允许直接操作指针,而是通过接口或内部类来实现的。Java方法回调是分离功能定义和功能实现的1种手段,这不但是1种松耦合的设计思想,也是1种接口和对象的组合。开发者可以根据自己不同的需求实现接口,而方法的具体调用则由操作系统来完成。在Java中实现回调也有固定的模式,步骤以下:

a.定义接口Callback ,包括回调方法 callback();
b. 在1个类Caller 中声明1个Callback接口对象 mCallback;
c. 在程序中赋予 Caller类的接口成员(mCallback)1个具体实现了Callback接口的内部类对象。     interface Callback(){
    void callback() ; 

}

ClassCaller{

CallbackmCallback;

PublicCaller(Callback cb){ // 传入实例化以后的内部类对象cb

mCallback=cb;

}

if(根据实际情况回调){

mCallback.callback();
}

}

系统通过Caller类的mCallback成员回调callback()方法完成业务需要的逻辑。Callback并没有通过继承实际类用实际类对象来完成回调,而是通过实现接口用接口成员来完成,这是由于在Java语言中接口本身的设计和抽象类相似,可以对不同的业务需求构成多样的接口实现,有益于程序构成较好的多态性,同时对开发者来讲实现接口比继承实际类完成回调更加便捷并且更符合逻辑性,因此在Java程序中的回调常常是依照上述模式,通过传递已实现了抽象接口的对象援用来完成方法回调。

 

2.2: Android系统利用层中的回调机制

 

在Android系统利用层中,回调机制是非常普遍和重要的。在操作系统层面,回调机制在通讯、消息传递、事件处理等方面也有着较多的利用,以下以Button按钮的onClick方法为例介绍View被点击时的框架模型和相干方法的回调。

a.定义接口,接口内部就是系统要回调的方法,参数是行将产生回调的Button,这个接口常被叫做事件监听器,它相当因而View的1个属性。

public interface OnClickListener{

    public void onClick(Buttonb); 

}

b.定义Button类,这里代表View类

public class Button{

//申明Button类的接口(监听器)成员

OnClickListener listener;

public void click(){

    listener.onClick(this);

}

//set函数用于接收外部传入的被实例化了的监听器实体对象,实际上是1种面向对象的语言多态性的体现

  public void setOnClickListener(OnClickListenerlistener) {

      this.listener = listener;

}}

c.定义测试类,将上述监听器实体对象以匿名内部类的情势传入Button类

public class Activity{

  public Activity(){

 }

  public static void main(String[]args) {

   Button b = new Button();//实例化Button类

   b.setOnClickListener(new OnClickListener(){

      @Override

       public void onClick(Buttonb) {//onClick中的内容是对接口的不同实现

                System.out.println("clicked");

      }   

});

b.click(); //在主函数中进行回调的摹拟测试

 }

}

Android事件监听器是包括在View类的1个接口,包括1个单独的回调方法onClick(button b),如上,接口被具体实现并表现为1种内部类的情势,onClick方法中的具体内容将在按钮被点击时由Android系统框架进行回调。因此,若需对点击事件做处理,应当首先定义1个OnClickListener 接口(事件监听器),然后通过set()或构造方法将已具体实现了该接口的实际对象或匿名对象赋予Button类的接口成员,最后操作系统通过该接口成员在适合的情形下回调onClick()的具体实现内容。在main()函数中,setOnClicklistener()就是这样1个set()函数用来接收已实例化监听器接口的匿名类对象并将之赋予之前申明的接口成员;而另外1个click()方法,实际上是1个摹拟用户点击时状态环境的1个方法,表示用户点击这个按钮时的环境。此方法在主程序中被主动调用,但该方法实际上应当是屏幕捕捉到1个用户点击的动作以后转换成某1坐标A,如果这个坐标A处于该View所在的范围,那末才调用click方法,也就是说系统先是获得点击信号,如果在View的范围就会触发click事件,进而回调Button接口成员(事件监听器)的onClick方法,而这也完全符合实际中Android利用程序与用户交互时View的回调。

从回调本身来看,onClick()被调用后终究会在外部履行实际的接口(监听器)实现代码,而onClick()回调却在Android系统内部实现,这很好地体现了Android系统本身设计的层次性,一样体现了接口和对象的组合在实际开发中的作用。在实际的Android操作系统中,接口是系统框架提供的,接口的实现是上层的开发者来实现的,通过回调这类方式就能够到达接口统1而实现不同。系统在不同的状态“回调”实现类中的具体方法,以此到达接口和实现的分离,对象和接口的组合。

在Android系统中,回调不单单体现在View与用户交互的时候,在其他场景(消息传递、事件处理等)中也有广泛的利用,比如Activity生命周期中的onCreate()、onStop()等方法,其中的具体实现也需要开发者根据实际情况来书写,这些方法被封装在1个类似的接口中,一样在系统运行时系统会根据某1状态值回调相应的生命周期中的方法。Android中很多回调在设计上都是相似的,都是通过在主类里面封装1个接口然后在上层实现这个接口,接着系统底层在适合的时候回调接口里面已实现的方法,这样上层和底层就被很好的分开。

 

3  Android系统框架层中回调函数的具体调用流程


                                          图1:View树图

Android系统中回调机制的概念和在利用层中摹拟的回调进程在前面被讨论,下面将从源代码的角度深入探访在Android系统框架层中,View点击时触发的方法是如何在框架源码里面具体被回调的。源码来源于:Copyright(C) 2006 the Android Open Source Project,系统架构内核编号是Android 2.3

3.1 触摸事件的传递机制                                                                                      

    

在Android系统中,每一个View或View的子类都具有下面3个和触摸事件处理密切相干的方法:dispatchTouchEvent方法用来分发TouchEvent;onTouchEvent方法用来处理TouchEvent;如果这个View同时也是ViewGroup类型的话,那末还具有1个onInterceptTouchEvent方法,它主要用来拦截TouchEvent。

如图1所示(图1  View树图)1个Android利用程序的界面可以看成是1颗View树,所有界面的视觉显现实际上源于View树的自上而下的绘制。ViewGroup类也是继承自View,View和ViewGroup之间相互组合和嵌套构成View树。当TouchEvent产生时,首先系统将用户的触摸信号传递给最顶层的View,也就是最外层的ViewGroup(实际中可以是Relativelayout等容器),接着TouchEvent从上层逐步传入到各分支:



                       图2:触摸流程图

a 对比(图2  触摸流程图),当用户的手指触摸得手机屏幕,会触发最顶层View的dispatchTouchEvent()。最顶层的View首先检查该函数的返回结果:
     (1)如果返回true ,则交给这个View自己的onTouchEvent方法处理,不会向下传递。
     (2)如果返回false,则交给这个View的 interceptTouchEvent 方法来决定是不是要拦截这个事件。
b 接着到 onInterceptTouchEvent履行:
     (1)如果返回 true ,即拦截掉了,则还是交给此View的onTouchEvent处理。

(2)如果返回 false,则传递给它的子View ,由子 View的dispatchTouchEvent 再来开始这个事件的分发。
c  以此类推,通常TouchEvent会传递到某1层子View 的 onTouchEvent 内,而接下来研究的回调的具体履行进程就是基于这1触摸事件的传递机制进行展开并在onTouchEvent源码内进行研究。

 

3.2 Android系统框架层下View内部回调函数的详细回调进程

 

还是以Button作为View的1个例子,源码位于系统结构路径的位置是 /frameworks/base/core/java/android/view/View.java。

首先是系统框架层下对OnClickListener接口的定义:

    public interface OnClickListener {

        void onClick(Viewv)

   }

}

然后是对接口成员实例化的set函数,需要传入1个实现了OnClickListener接口的实际类对象if()语句表示Button的属性1定是clickable(可点击)的,这也解释了为何在Button的xml里面不需要申明这个属性,而TextView需要这个属性,由于系统会自动对Button设置可点击这个属性值。

        public void setOnClickListener(OnClickListenerl) {

           if (!isClickable()) {

             setClickable(true);

           getListenerInfo().mOnClickListener = l;

 }                                                                                                        

getListenerInfo()方法 是返回所有自定义监听器的1个静态内部类ListenerInfo的对象,通过它寻觅想要注册到Button上的某种监听器(这里使用的是OnClickListener),该内部类在View.java的前半部份被申明:

static class ListenerInfo {

…………

       private OnKeyListener mOnKeyListener;

       private OnTouchListener mOnTouchListner;

       private OnHoverListener mOnHoverListener;

       private OnGenericMotionListener mOnGenericMotionListener;

    }

………

ListenerInfo getListenerInfo() {

ListenerInfo mListenerInfo;

if (mListenerInfo != null) {

        return mListenerInfo;

 }

 mListenerInfo = new ListenerInfo();

 return mListenerInfo;

}

根据3.1中介绍的触摸事件的传递机制,TouchEvent终究会被传递到View树最底层的View,交给该View的onTouchEvent进行处理,在这个方法里面大体要处理3种情况:当手指按下,手指移动和手指松开,系统会在这3种不同情况中进行相应的处理,而手指松开是最重要的情况,由于每个TouchEvent终究都会经历这1步,onTouchEvent方法的具体代码以下所示:

 

   public  boolean onTouchEvent(MotionEvent event) {

       final int viewFlags = mViewFlags;

//这里首先判断这个View是否是可用的,避免之前被设置成禁用

       if ((viewFlags & ENABLED_MASK) == DISABLED) {

       …………

//下面1句话是说不管是不是可点击,但总要把这次事件先消耗掉

        return (((viewFlags & CLICKABLE) == CLICKABLE ||

                  (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));

       }

//下面是当View可以被点击时的状态

       if (((viewFlags & CLICKABLE) == CLICKABLE ||

                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {

           switch (event.getAction()) {

//第1个case分句处理按下的时候

                case MotionEvent.ACTION_DOWN:

/*首先遍历整棵View树,isInScrollingContainer方法内部用1个循环判断当前View外层的父类是否是1个可滑动的容器*/

  booleanisInScrollingContainer = isInScrollingContainer();

/*如果是1个可滑动的容器,就设置1下相干标志位,mPendingCheckForTap表示1个消息线程,它在postDelayed()方法中会被延时发送出去,由于要肯定用户在这个容器下是点击还是滑动,如果1定时间内用户没有滑动会自动履行mPendingCheckForTap消息线程,该消息内部接着会判断是否是长按操作,如果在这段时间内用户进行了滑动那末就立即删除掉这个消息。*/

 

                    if (isInScrollingContainer) {

                        mPrivateFlags |= PREPRESSED;

                        if (mPendingCheckForTap == null) {

                            mPendingCheckForTap = new CheckForTap();

                        }

                        postDelayed(mPendingCheckForTap);

                    } else {

/*如果父类的容器不可滑动说明1定是点击操作。接着用上1个子句一样的思想“等等再履行”检查是否是长按的操作,如果1段时间用户没有松开就会触发长按消息的线程checkForLongClick。反之,就将这个消息移除。在这里,可以体会到判断长按短按,滑动点击独有的算法:“等等再履行”。*/

                        mPrivateFlags |= PRESSED;

                        refreshDrawableState();

                        checkForLongClick(0);

                    }

                   break;

//处理移动的情况

                case MotionEvent.ACTION_MOVE:

                    final int x = (int) event.getX();

                    final int y = (int) event.getY();

//在这里记录下用户移动的位置坐标,其中mTouchSlop表示判断触摸点是不是在此View中时,向上下左右增大mTouchSlop个像素(允许的误差范围)      

                    if (!pointInView(x, y, mTouchSlop)) {

//如果不在View的范围里面,那末将处理点击的消息移除

                        removeTapCallback();

                        if ((mPrivateFlags & PRESSED) != 0) {

//进1步判断如果都已准备长按了,则将长按的消息移除,并将View的按下状态设置为false

                            removeLongPressCallback();

                            mPrivateFlags &= ~PRESSED;

                           refreshDrawableState();

                        }

                    }

                    break;

//处理松开的情况

case MotionEvent.ACTION_UP:

                        ………

                        if (!mHasPerformedLongPress) {

 //履行到该标志量表明了这不是长按,是1个点击,所以移除长按的相干消息。                     

                           removeLongPressCallback();

                            if (!focusTaken) {

                                if (mPerformClick == null) {

                                    mPerformClick = new PerformClick();

                                }

                                if (!post(mPerformClick)) {

//直到最后我们看到了performClick()被履行。                             

performClick();

                                }

                            }

                        }

…………

                    }

                    break;

           }

           return true;

       }

       return false;

    }

// 在performClick()中回调方法被履行,回调的所有流程到此结束。

public booleanperformClick() {

………

        ListenerInfo li = mListenerInfo;

       if (li != null && li.mOnClickListener != null) {

          //回调方法在这里被履行

           li.mOnClickListener.onClick(this);

           return true;

       } 

return false;

}

 

                                                           

4 Android系统利用层中折射的事件处理模型

                                                                                                                       

从利用层的角度上来讲,View与用户交互进程中体现的不单单是之前在框架层里面详细分析的回调机制,同时也是沿袭了java可视化编程开发的1种经典的事件处理方式。这类机制本身包括触发事件、事件源、事件监听器、响应事件4个方面。触发事件指的是用户操作手机的交互方式,如长按、点击、触摸、回滚等操作;事件源是指产生事件的组件,也就是各种各样的View控件(按钮、图片等等);事件监听器是组件产生事件时响应的接口,如OnClickListener;最后响应事件指的是OnClickListener里面的回调方法onClick()。


                               图3 现实场景图

这类方式非常类似现实生活中的1种场景(如图3 现实场景图):现在的高级轿车都有防盗功能,当车产生剧烈震动时就会报警,报警时报警灯会不断闪烁发出提示音。在这里,事件源是车,事件监听器是车上的报警器,触发事件就是感应到的震动,而报警灯的闪烁及发出提示音指的是响应事件。联系到实际的Android开发中,Button按钮就是1个接收事件的事件源;事件监听器就是OnClickListener接口,需要用户实例化传入;触发事件就是用户的点击操作;点击以后产生的回调onClick就是响应事件。这模样的类比应当更能让读者理解这类机制。

接下来编写1段实际的java代码摹拟汽车防盗的经典事件处理模型:

public classCar {//事件源

       intColor;

       intstyle;

       floatprice;

       String name;

       PreventTheftListenerlistener;

       staticboolean isDanger=true;

       //监听器的定义

       interfacePreventTheftListener{

              publicvoid  onTheft(Car car);//触发事件

       }

       publicCar(String name) {

              this.name=name;

       }

       privateboolean isDanger() {

              returnisDanger;

       }

       publicvoid setPrevent_Theft_Listener(PreventTheftListener  listener) {

              this.listener=listener;

       }     

       publicvoid doListener(){

              if(listener!=null)

                     listener.onTheft(this);

       }

       //监听器的实例化

       staticclass Wuhan_CarListener implements PreventTheftListener{

              privateint color;

              privatefloat price;

              Stringname;

 

              publicWuhan_CarListener(String name) {

                     isDanger=true;

                     this.name=name;

                     System.out.println("我已安装到你的汽车上,我已对周围的环境处于监听状态 ");

              }

              publicvoid onTheft(Car car) {//回调事件        

                     System.out.println("主人,有震动,小偷要偷你的汽车");

                     noise();

                     flash();   

              }

              privatevoid noise() {

                     System.out.println("摹拟报警器的功能之1-----发出响声"); 

              }

              privatevoid flash() {

                     System.out.println("摹拟报警器的功能之1-----不断闪光"); 

              }

       }

       publicstatic void main(String[] args) {

              Car c=newCar(“甲壳虫”);

              c.setPrevent_Theft_Listener(newWuhan_CarListener("##牌防盗监听器"));//装上防盗监听器

              if(c.isDanger()==true){

                     c.doListener();              }

       }            

}


图4 事件处理模型图

(如图4所示 事件处理模型图)Android中View点击事件的回调都存在事件源对象(Button),监听器对象(OnClickListener),事件的触发者和事件的响应这4样东西,4者实际上构成了1种经典的事件处理模型,它能够灵敏的捕捉到外界的触发动作并在适合的场合履行回调方法,这模样就实现了接口定义和接口实现的分离,突出了接口和对象的组合,也体现了软件设计中的松耦合性和多态性。只不过这个经典的模型在实际的Android系统中,需要斟酌更多实际系统的元素,因此显得复杂,但在利用层可以看出其本质是相似的。

汽车里面给用户留下1个接口,试想,如果把防盗的功能直接作为1个实际类成员绑定在汽车上面,那末在有震动的时候固然也能利用该成员调用防盗的动作,但这样就不够灵活,不能够让每一个设计防盗器的厂商设计自己的防盗器性能,可扩大性非常的差。所以,在车里面定义1个监听器的接口,而接口的实现交给每个专门设计防盗器的厂商来做,在适合的时候利用监听器对象来调用不同厂商的实现方式,这也体现出了多态。这模样,在汽车行业,做防盗器的公司就和做汽车的公司分开了,其经济效益和生产效力也会加倍。在Android系统中,接口是系统框架提供的,接口的实现是各种公司不同的上层利用开发者实现的。如(图5 接口设计图)这样就能够到达接口统1,而实现不同,系统只需要在不同状态回调我们的实现类来到达接口和实现的分离。



                          图5 接口设计图

 

5 结语

 

方法回调是功能定义和功能实现相分离的1种手段,是1种松耦合的设计思想。Android作为1种优秀的移动智能终端系统架构,有自己的运行环境同时也在不同的场合为开发者设置了不同的开发接口,而操作系统通过在不同的情形“回调”开发者对接口的不同实现来到达接口定义和接口实现的统1。在Android利用程序中用户与View的交互是非常普遍和重要的,本文详细研究了Android系统底层的框架代码到上层的利用代码,构建出了虚拟世界和物理世界的联系,介绍了 Android系统下的回调机制、用户和View交互时触发的回调方法在系统框架的详细履行进程和基于这1机制下的经典事件处理模型。借助这篇文章1方面希望读者借此理解Android中的回调机制和经典的事件处理模型,另外一方面希望读者多从系统源码的角度研究和解决问题。但是,关于这1课题的研究毕竟目前还只是剖析Android框架的1小部份,在这1课题或其他框架理论的发现和创新还有待于进1步的探索和研究。

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐