Android事件的处理模型和多线程处理
Email: joyfly2006@https://www.sodocs.net/doc/f09885555.html,
QQ: 448086006
Android平台的事件处理机制有两种:一种是基于回调机制的,一种是基于监听接口的。
1.1 事件的回调机制
Android平台中,每个View都有自己的处理事件的回调方法,开发人员可以通过重写View中的这些回调方法来实现需要的响应事件。当某个事件没有被任何一个View处理时,便会调用Activity 中相应的回调方法。Android提供了以下回调方法供用户使用:
1.1.1onKeyDown
功能:该方法是接口KeyEvent.Callback中的抽象方法,所有的View全部实现了该接口并重写了该方法,该方法用来捕捉手机键盘被按下的事件。
声明:public boolean onKeyDown (int keyCode, KeyEvent event)
参数说明:
参数keyCode,该参数为被按下的键值即键盘码,手机键盘中每个按钮都会有其单独的键盘码,在应用程序都是通过键盘码才知道用户按下的是哪个键。
参数event,该参数为按键事件的对象,其中包含了触发事件的详细信息,例如事件的状态、事件的类型、事件发生的时间等。当用户按下按键时,系统会自动将事件封装成KeyEvent对象供应用程序使用。
返回值,该方法的返回值为一个boolean类型的变量,当返回true时,表示已经完整地处理了这个事件,并不希望其他的回调方法再次进行处理,而当返回false时,表示并没有完全处理完该事件,更希望其他回调方法继续对其进行处理,例如Activity中的回调方法。
1.1.2onKeyUp
功能:该方法同样是接口KeyEvent.Callback中的一个抽象方法,并且所有的View同样全部实现了该接口并重写了该方法,onKeyUp方法用来捕捉手机键盘按键抬起的事件。
声明:public boolean onKeyUp (int keyCode, KeyEvent event)
参数说明:同onKeyDown
1.1.3onTouchEvent
功能:该方法在View类中的定义,并且所有的View子类全部重写了该方法,应用程序可以通过该方法处理手机屏幕的触摸事件。
声明:public boolean onTouchEvent (MotionEvent event)
参数说明:
参数event:参数event为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息,
例如触摸的位置、触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创建。
返回值:该方法的返回值机理与键盘响应事件的相同,同样是当已经完整地处理了该事件且不希望其他回调方法再次处理时返回true,否则返回false。
详细说明:
该方法并不像之前介绍过的方法只处理一种事件,一般情况下以下三种情况的事件全部由onTouchEvent方法处理,只是三种情况中的动作值不同。
屏幕被按下:当屏幕被按下时,会自动调用该方法来处理事件,此时
MotionEvent.getAction()的值为MotionEvent.ACTION_DOWN,如果在应用程序中需要处理屏幕被按下的事件,只需重新该回调方法,然后在方法中进行动作的判断即可。
屏幕被抬起:当触控笔离开屏幕时触发的事件,该事件同样需要onTouchEvent方法来捕捉,然后在方法中进行动作判断。当MotionEvent.getAction()的值为MotionEvent.ACTION_UP时,表示是屏幕被抬起的事件。
在屏幕中拖动:该方法还负责处理触控笔在屏幕上滑动的事件,同样是调用MotionEvent.getAction()方法来判断动作值是否为MotionEvent.ACTION_MOVE再进行处理。
注意:ACTION_DOWN事件作为起始事件,他的重要性是超过ACTION_MOVE和ACTION_UP 的,如果发生这两个事件,那么一定曾经发生了ACTION_DOWN事件。
1.1.4onTrackBallEven
功能:接下来将介绍的是手机中轨迹球的处理方法onTrackBallEvent。所有的View同样全部实现了该方法。
声明:public boolean onTrackballEvent (MotionEvent event)
详细说明:该方法的使用方法与前面介绍过的各个回调方法基本相同,可以在Activity中重写该方法,也可以在各个View的实现类中重写。
参数event:参数event为手机轨迹球事件封装类的对象,其中封装了触发事件的详细信息,同样包括事件的类型、触发时间等,一般情况下,该对象会在用户操控轨迹球时被创建。
返回值:该方法的返回值与前面介绍的各个回调方法的返回值机制完全相同,因本书篇幅有限,不再赘述。
轨迹球与手机键盘的区别如下所示:
1)某些型号的手机设计出的轨迹球会比只有手机键盘时更美观,可增添用户对手机的整体印象。
2)轨迹球使用更为简单,例如在某些游戏中使用轨迹球控制会更为合理。
3)使用轨迹球会比键盘更为细化,即滚动轨迹球时,后台的表示状态的数值会变化得更细微、更精准。
提示:在模拟器运行状态下,可以通过F6键打开模拟器的轨迹球,然后便可以通过鼠标的移动来模拟轨迹球事件。
1.1.5onFocusChanged
功能:前面介绍的各个方法都可以在View及Activity中重写,接下来介绍的onFocusChanged却只能在View中重写。该方法是焦点改变的回调方法,当某个控件重写了该方
法后,当焦点发生变化时,会自动调用该方法来处理焦点改变的事件。
声明:protected void onFocusChanged (boolean gainFocus, int direction, Rect previously FocusedRect)
详细说明:
参数gainFocus:参数gainFocus表示触发该事件的View是否获得了焦点,当该控件获得焦点时,gainFocus等于true,否则等于false。
参数direction:参数direction表示焦点移动的方向,用数值表示,有兴趣的读者可以重写View中的该方法打印该参数进行观察。
参数previouslyFocusedRect:表示在触发事件的View的坐标系中,前一个获得焦点的矩形区域,即表示焦点是从哪里来的。如果不可用则为null。
提示:
焦点:焦点描述了按键事件(或者是屏幕事件等)的承受者,每次按键事件都发生在拥有焦点的View上。在应用程序中,我们可以对焦点进行控制,例如从一个View移动另一个View。下面列出一些与焦点有关的常用方法:
setFocusable方法:设置View是否可以拥有焦点。
isFocusable方法:监测此View是否可以拥有焦点。
setNextFocusDownId方法:设置View的焦点向下移动后获得焦点View的ID。
hasFocus方法:返回了View的父控件是否获得了焦点。
requestFocus方法:尝试让此View获得焦点。
isFocusableTouchMode方法:设置View是否可以在触摸模式下获得焦点,在默认情况下是不可用获得的。
1.2 事件的监听机制
1.2.1OnClickListener接口
功能:该接口处理的是点击事件。在触控模式下,是在某个View上按下并抬起的组合动作,而在键盘模式下,是某个View获得焦点后点击确定键或者按下轨迹球事件。
对应的回调方法:public void onClick(View v)
说明:需要实现onClick方法,参数v便为事件发生的事件源。
1.2.2OnLongClickListener接口
功能:OnLongClickListener接口与之前介绍的OnClickListener接口原理基本相同,只是该接口为View长按事件的捕捉接口,即当长时间按下某个View时触发的事件。
对应的回调方法:public boolean onLongClick(View v)
说明:需要实现onLongClick方法。
参数v:参数v为事件源控件,当长时间按下此控件时才会触发该方法。
返回值:该方法的返回值为一个boolean类型的变量,当返回true时,表示已经完整地处理了这个事件,并不希望其他的回调方法再次进行处理;当返回false时,表示并没有完全处理完该事件,更希望其他方法继续对其进行处理。
1.2.3OnFocusChangeListener接口
功能:OnFocusChangeListener接口用来处理控件焦点发生改变的事件。如果注册了该接口,当某个控件失去焦点或者获得焦点时都会触发该接口中的回调方法。
对应的回调方法:public void onFocusChange(View v, Boolean hasFocus)
说明:需要实现onFocusChange方法。
参数v:参数v便为触发该事件的事件源。
参数hasFocus:参数hasFocus表示v的新状态,即v是否是获得焦点。
1.2.4OnKeyListener接口
功能:OnKeyListener是对手机键盘进行监听的接口,通过对某个View注册该监听,当View 获得焦点并有键盘事件时,便会触发该接口中的回调方法。
对应的回调方法:public boolean onKey(View v, int keyCode, KeyEvent event)
说明:需要实现onKey方法。
参数v:参数v为事件的事件源控件。
参数keyCode:参数keyCode为手机键盘的键盘码。
参数event:参数event便为键盘事件封装类的对象,其中包含了事件的详细信息,例如发生的事件、事件的类型等。
1.2.5OnTouchListener接口
功能:OnTouchListener接口是用来处理手机屏幕事件的监听接口,当为View的范围内触摸按下、抬起或滑动等动作时都会触发该事件。
对应的回调方法:public boolean onTouch(View v, MotionEvent event)
说明:需要实现onTouch方法。
参数v:参数v同样为事件源对象。
参数event:参数event为事件封装类的对象,其中封装了触发事件的详细信息,同样包括事件的类型、触发时间等信息。
1.2.6OnCreateContextMenuListener接口
功能:OnCreateContextMenuListener接口是用来处理上下文菜单显示事件的监听接口。该方法是定义和注册上下文菜单的另一种方式。
对应的回调方法:public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo info)
说明:需要实现onCreateContextMenu方法。
参数menu:参数menu为事件的上下文菜单。
参数v:参数v为事件源View,当该View获得焦点时才可能接收该方法的事件响应。
参数info:info对象中封装了有关上下文菜单额外的信息,这些信息取决于事件源View。
该方法会在某个View中显示上下文菜单时被调用,开发人员可以通过实现该方法来处理上下文菜单显示时的一些操作。其使用方法与前面介绍的各个监听接口没有任何区别。
1.3 View与ViewGroup触摸事件传递机制
触摸事件有:ACTION_DOWN, ACTION_MOVE, ACTION_UP 。一个简单的触摸动作触发了一系列Touch事件,如:
(1)ACTION_DOWN -> ACTION_MOVE->.....-> ACTION_MOVE-> ACTION_UP
(2)ACTION_DOWN -> ACTION_UP
当TouchEvent发生时,首先Activity将TouchEvent传递给最顶层的View,TouchEvent最先到达最顶层View的dispatchTouchEvent,然后由dispatchTouchEvent方法进行分发,如果dispatchTouchEvent返回TRUE,则交给这个View的onTouchEvent处理;如果dispatchTouchEvent 返回false,则交给这个View的On interceptTouchEvent方法来决定是否要拦截这个事件,如果
On interceptTouchEvent返回true,也就拦截了,则交给它的onTouchEvent来处理;如果
On interceptTouchEvent返回false,那就传递给子View,由子View的dispatchTouchEvent再来开始这个事件的分发,如果事件传递到某一层View的onTouchEvent了,这个方法返回false,那么这个事件从这个View往上传递,都是onTouchEvent来接收,而如果传递到onTouchEvent返回是false,这个事件“消失”,而且接收不到下一次事件。
其分发和传递的过程如下:
1.4 Handler消息传递机制
解决的问题:
每一个应用程序都是一个单独的进程,运行于单独的Dalvik虚拟机实例中,在运行与单独Linux 进程中。每一个进程默认只有一个线程即UI主线程,因为他是以UI界面更新为主要任务的主线程。
同样继承于context的Activity和Serivce都是跑在同一个线程里的,即UI主线程。这样他们之间是相互阻塞的,当Serivce运行较为费时的工作,而Activity上的UI 界面需要更新导致程序卡住
时,就会导致程序被系统Kill掉。解决办法是Serivce需要用更多的线程来运行费时的动作。Google 为了解决此问题提供了Handler类。
UI主线程里初始化时自带一个消息队列(MessageQueen),需要用Looper进行管理。Looper 的主要作用是循环迭代MessageQueen,加入新的Message,轮到这条消息时在发送出去,而Handler 就可以直接操作Looper了。比如:
Handler mHandler = new Handler(Looper.getMainLooper);用mHandler对象控制UI主线程的Looper对象。
Handler mHandler = new Handler(Looper.myLooper);或Handler mHandler = new Handler(); 就是控制当前线程的MessageQueen。
1.4.1Looper类、Message类和Handler类之间的关系
Looper中有一个Message队列,里面存储的是一个个待处理的Message;Message中有一个Handler,这个Handler是用来处理Message的,其中,Handler类封装了很多琐碎的工作。
一个简单例子如下:
1.class LooperThread extends Thread {
2. public Handler mHandler;
3. public void run() {
4. //①调用prepare。
5. Looper.prepare();
6. ......
7. //②进入消息循环。
8. Looper.loop();
9. }
10.}
11.//应用程序使用LooperThreadActivity。
12.{
13.
14.LooperThread mTread = new LooperThread();
15.
16.......
17. mTread.start();//启动新线程,线程函数是run
18.}
1.4.2Looper类
Android中的Looper类用来封装消息循环和消息队列的一个类,用于在android线程中进行消息处理,handler其实可以看做是一个工具类,用来向消息队列中插入消息的。
(1)Looper类用来为一个线程开启一个消息循环,在默认情况下android中新诞生的线程是没有开启消息循环的,主线程除外。Looper对象通过MessageQueue来存放消息和事件,一个线程只能有一个Looper和对应的一个MessageQueue。
(2)通常通过Handler对象来与Looper进行交互的,Handler可看作是Looper的一个接口,用来指定的Looper发送消息及定义处理方法。
(3)在非主线程下直接new Handler()会报错,原因是该线程还没有创建Looper以及消息队列。synchronized static Looper getMainLooper():获取当前主线程的Looper实例
Thread getThread():获取当前调用处所在的线程
static void loop():进入消息循环
static Looper myLooper():获取当前线程的Looper
static MessageQueue myQueue():获取当前线程的消息队列句柄
static void prepare():创建当前线程的消息队列
static void prepareMainLooper():创建主线程的消息队列
void quit():退出当前线程
在Android中基本上每一个线程都有一个Looper,但是我们自己通过Thread建立的线程是没有Looper的,此时Android为我们提供一个集成自Tread的HanderTread类来处理。
Looper主要是为Handler服务的,Looper是一种循环,这种循环用来监听线程的消息队列,将对应的消息交给Handler处理。
1. public static final void prepare() {
2. //一个Looper只能调用一次prepare。
3. if (sThreadLocal.get() != null) {
4. throw new RuntimeException("Only one
Looper may be created per thread");
5. }
6. //构造一个Looper对象,设置到调用线程的局部变量中。
7. sThreadLocal.set(new Looper());
8.}
9.//sThreadLocal定义
10.private static final ThreadLocal
sThreadLocal = new ThreadLocal();
1.public static final void loop() {
2. Looper me = myLooper();//myLooper
返回保存在调用线程TLV中的Looper对象。
3. //取出这个Looper的消息队列。
4. MessageQueue queue = me.mQueue;
5. while (true) {
6. Message msg = queue.next();
7. //处理消息,Message对象中有一个target,它是Handler类型。
8. //如果target为空,则表示需要退出消息循环。
9. if (msg != null) {
10. if (msg.target == null) {
11. return;
12. }
13. //调用该消息的Handler,交给它的dispatchMessage函数处理。
14. msg.target.dispatchMessage(msg);
15. msg.recycle();
16. }
17. }
18.}
19.//myLooper函数返回调用线程的线程局部变量,也就是存储在其中的Looper对象。
20.public static final Looper myLooper() {
21. return (Looper)sThreadLocal.get();
22.}
通过上面的分析会发现,Looper的作用是:封装了一个消息队列。Looper的prepare函数把这个Looper和调用prepare的线程(也就是最终的处理线程)绑定在一起了
1.4.3Handler类
Handler类构造函数分析:
1.public Handler() {
2. //获得调用线程的Looper。
3.mLooper = Looper.myLooper();
4. if (mLooper == null) {
5. throw new RuntimeException(......);
6. }
7. //得到Looper的消息队列。
8.mQueue = mLooper.mQueue;
9. //无callback设置。
10.mCallback = null;
11. }
12.
13. //构造函数2
14. public Handler(Callback callback) {
15.mLooper = Looper.myLooper();
16. if (mLooper == null) {
17. throw new RuntimeException(......);
18. }
19. //和构造函数1类似,只不过多了一个设置callback。
20.mQueue = mLooper.mQueue;
21.mCallback = callback;
22. }
23.//构造函数3
24. public Handler(Looper looper) {
25.mLooper = looper; //looper由外部传入,是哪个线程的Looper不确定。
26.mQueue = looper.mQueue;
27.mCallback = null;
28. }
29.//构造函数4,和构造函数3类似,只不过多了callback设置。
30. public Handler(Looper looper, Callback callback) {
31.mLooper = looper;
32.mQueue = looper.mQueue;
33.mCallback = callback;
34.}
在上述构造函数中,Handler中的消息队列变量最终都会指向Looper的消息队列
Handler是如何向消息队列中添加消息的呢?Handler提供了一系列函数,帮助我们完成创建消息和插入消息队列的工作。这里只列举其中一二。要掌握详细的API,则需要查看相关的文档。
1.//查看消息队列中是否有消息码是what的消息。
2.final boolean hasMessages(int what)
3.//从Handler中创建一个消息码是what的消息。
4.final Message obtainMessage(int what)
5.//从消息队列中移除消息码是what的消息。
6.final void removeMessages(int what)
7.//发送一个只填充了消息码的消息。
8.final boolean sendEmptyMessage(int what)
9.//发送一个消息,该消息添加到队列尾。
10.final boolean sendMessage(Message msg)
11.//发送一个消息,该消息添加到队列头,所以优先级很高。
12.final boolean sendMessageAtFrontOfQueue(Message msg)
只需对上面这些函数稍作分析,就能明白其他的函数。现以sendMessage为例,其代码如下所示:
1.public final boolean sendMessage(Message msg)
2.{
3. return sendMessageDelayed(msg, 0); //调用sendMessageDelayed
4. }
5.[-->Handler.java]
6.// delayMillis是以当前调用时间为基础的相对时间
7.public final boolean sendMessageDelayed(Message
msg, long delayMillis)
8.{
9. if (delayMillis <0) {
10.delayMillis = 0;
11. }
12. //调用sendMessageAtTime,把当前时间算上
13. return sendMessageAtTime(msg,SystemClock.
uptimeMillis() + delayMillis);
14.}
15.[-->Handler.java]
16.//uptimeMillis 是绝对时间,即sendMessageAtTime函数处理的是绝对时间
17.public boolean sendMessageAtTime(Message msg, long uptimeMillis){
18. boolean sent = false;
19. MessageQueue queue = mQueue;
20. if (queue != null) {
21. //把Message的target设置为自己,然后加入到消息队列中
22.msg.target = this;
23.sent = queue.enqueueMessage(msg, uptimeMillis);
24. }
25. return sent;
26.}
看到上面这些函数我们可以预见,如果没有Handler的辅助,当我们自己操作MessageQueue的enqueueMessage时,得花费多大工夫!Handler把Message的target设为自己,是因为Handler除了封装消息添加等功能外还封装了消息处理的接口。
Handler的消息处理消息处理方法:
1.public void dispatchMessage(Message msg) {
2. //如果Message本身有callback,则直接交给Message的callback处理
3. if (msg.callback != null) {
4. handleCallback(msg);
5. } else {
6. //如果本Handler设置了mCallback,则交给mCallback处理
7. if (mCallback != null) {
8. if (mCallback.handleMessage(msg)) {
9. return;
10. }
11. }
12. //最后才是交给子类处理
13. handleMessage(msg);
14. }
15. }
dispatchMessage定义了一套消息处理的优先级机制,它们分别是:
Message如果自带了callback处理,则交给callback处理。
Handler如果设置了全局的mCallback,则交给mCallback处理。
如果上述都没有,该消息则会被交给Handler子类实现的handleMessage来处理。当然,这需要从Handler派生并重载handleMessage函数。
在通常情况下,我们一般都是采用第三种方法,即在子类中通过重载handleMessage来完成处理工作的。
1.4.4Looper和Handler的同步关系
例子:
1.//先定义一个LooperThread类
2.class LooperThread extends Thread {
3. public Looper myLooper = null;//
定义一个public的成员myLooper,初值为空。
4.public void run() { //假设run在线程2中执行
5. Looper.prepare();
6. // myLooper必须在这个线程中赋值
7.myLooper = Looper.myLooper();
8. Looper.loop();
9. }
10.}
11.//下面这段代码在线程1中执行,并且会创建线程2
12.{
13. LooperThread lpThread= new LooperThread;
14. lpThread.start();//start后会创建线程2
15. Looper looper = lpThread.myLooper;//<======注意
16. // thread2Handler和线程2的Looper挂上钩
17. Handler thread2Handler = new Handler(looper);
18. //sendMessage发送的消息将由线程2处理
19. threadHandler.sendMessage(...)
20.}
线程1中创建线程2,并且线程2通过Looper处理消息。线程1中得到线程2的Looper,并且根据这个Looper创建一个Handler,这样发送给该Handler的消息将由线程2处理。但很可惜,上面的代码是有问题的。如果我们熟悉多线程,就会发现标有“注意”的那行代码存在着严重问题。myLooper 的创建是在线程2中,而looper的赋值在线程1中,很有可能此时线程2的run函数还没来得及给myLooper赋值,这样线程1中的looper将取到myLooper的初值,也就是looper等于null。
上面问题采用同步的方式来解决,其代码如下:
2.public class HandlerThread extends Thread{
3.//线程1调用getLooper来获得新线程的Looper
4. public Looper getLooper() {
5. ......
6. synchronized (this) {
7. while (isAlive() && mLooper == null) {
8. try {
9. wait();//如果新线程还未创建Looper,则等待
10. } catch (InterruptedException e) {
11. }
12. }
13. }
14. return mLooper;
15. }
16.
17.//线程2运行它的run函数,looper就是在run线程里创建的。
18. public void run() {
19.mTid = Process.myTid();
20. Looper.prepare(); //创建这个线程上的Looper
21. synchronized (this) {
22.mLooper = Looper.myLooper();
23. notifyAll();//通知取Looper的线程1,此时Looper已经创建好了。
24. }
25. Process.setThreadPriority(mPriority);
26. onLooperPrepared();
27. Looper.loop();
28.mTid = -1;
29. }
30.}
1.4.5Message类
消息对象,记录消息信息的类,这个类有几个比较重要的字段:
public int arg1; 和public int arg2:可以使用两个字段用来存放我们需要传递的整型值,在service中,我们可以用来存放service的ID。
public Object obj:该字段Object类型,可以让该字段传递某个多项到消息的接受者中。
public int what:可以说是消息的标志,在消息处理中,可以根据这个字段的不同的值进行不同的处理。在使用Message时,可以通过new Message()创建一个实例,但是Android更推荐我们通过Message.obtain或者Handler.obtainMessage()获取Message对象,这并不一定直接创建一个新的实例,而是从消息池中没有可用的Message实例,则根据给定的参数new一个新Message对象,通过分析源码可得知,Android系统默认情况下在消息池中实例化10个Message对象。
获取一个新的Message对象方法有:
static Message obtain(Handler h, int what, int arg1, int arg2, Object obj)
static Message obtain(Handler h, int what, Object obj)
static Message obtain(Handler h, int what)
static Message obtain(Handler h)
static Message obtain(Handler h, Runnable callback)
static Message obtain()
static Message obtain(Handler h, int what, int arg1, int arg2)
static Message obtain(Message orig)
Handler target: 处理当前Message的Handler
private static Message sPool和private static int sPoolSize = 0:
private static final int MAX_POOL_SIZE = 10; 内存池的大小
private static final Object sPoolSync = new Object():同步操作Message的对象
Message内存池,sPool下一个分配的对象,sPoolSize表示当前内存池可分配Message 对象的个数。
Bundle data;绑定在线程中传递的数据。
long when:发送该消息的时间。
Message next:标记下一个空闲的Message
int flags:标记当前Message所处的状态
1.4.6MessageQueue类
Message Queue消息队列,用来存放通过Handler发布的消息,按照先进先出执行。每个message queue都会有一个对应的Handler。Handler会向message queue通过两种方法发送消息:sendMessage或post。这两种消息都会插在message queue队尾并按先进先出执行。但通过这两种方法发送的消息执行的方式略有不同:通过sendMessage发送的是一个message对象,会被Handler的handleMessage()函数处理;而通过post方法发送的是一个runnable对象,则会自己执行。
每一个线程最多只可以拥有一个MessageQueue数据结构,创建一个线程时并不会自动创建其MessageQueue,通常使用一个Looper对象对线程的MessageQueue进行管理,主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个MessageQueue,其他非主线程不会自动创建Looper,需要的时候,通过调用prepare函数来实现。
Message mMessages:采用线性链表的方式来维护的。
Final addIdleHandler(MessageQueue.IdleHandler handler)
Final V oid removeIdleHandler(MessageQueue.IdleHandler handler)
V oid finalize()
1.5 AsyncTask异步任务
AsyncTask是android提供的轻量级的异步类,可以直接继承该类来实现异步操作,并提供接口反馈当前异步执行的程度,其优点是简单、快捷,过程可控;但使用多个异步操作和需要进行UI更新时就比较复杂。
1.5.1原理介绍
AsyncTask实际上主要对Thread+Handler的封装,封装了一个多线程,向开发者屏蔽了考虑多线程的问题。开发人员只需要去重写AsynTask中的doInBackground、onProgressUpdate、onPreExecute、onPostExecute等方法,然后生成对象并执行execute方法即可以实现异步操作。
AsyncTask的线程启动其实是采用了一个线程池来进行管理,以便能有效利用资源,下面是创建线程池的初始化代码:
关键点:这里使用了原子计数器mCount,来区分线程。
1.5.1.1 内部类
private static final InternalHandler sHandler = new InternalHandler();
在第一调用new AsyncTask()会初始化,且这个调用一定是在UI的主线程下,因此sHandler的处理将运行在UI的主线程下。由此可知,这样目的在于将处理的结果传递到UI主线程中来处理。
AsyncTaskResult类:将数据传递到AsyncTask实体当中,从函数可以看出:
private Result postResult(Result result)
WorkerRunnable类:表示一个运行实体,携带输入参数、处理回调函数等信息,一旦该任务启动后,将调用该类中call函数来处理函数,并返回处理后的结果。
protected abstract Result doInBackground(Params... params)
巧妙之处一:通过在UI线程创建一个处理句柄,这个句柄能获取到UI主线程消息队列,且发送该句柄的消息处理都运行在UI主线程中。
巧妙之处二:WorkerRunnable类封装了传递的参数、处理过程和提供执行器调用。
1.5.1.2 A syncTask函数
FutureTask将分配给该任务一个线程实体,当调用mWorker中的call后处理子类doInBackground 函数后将调用mFutrueTask中done函数表示处理已经结束。
1.5.1.3 E xecute函数
当创建完以后,调用execute就开始执行,这个过程最终是如何调用到doInBackground呢?实际上执行Executor中的execute,而Executor的默认处理由静态变量传递过来的,
默认的执行器实现如下,在执行execute时,会新建一个Runnable,并插入到mTasks队列中,通过ScheduleNext函数启动线程,调用run来处理传递过来的run。最后追溯到FeatureTask中的call 函数,执行子类的接口。
1.5.2线程池TreadPoolExecutor
1.5.
2.1 T readPoolExecutor函数
corePoolSize:线程池维护线程的最少数量
maximumPoolSize: 线程池维护线程的最大数量
keepAliveTime和unit:决定线程池维护线程所允许的空闲时间的单位
workQueue:线程池所使用的缓冲队列
handler:线程池对拒绝任务的处理策略,有四种处理情况:
ThreadPoolExecutor.AbortPolicy() 抛出java.util.concurrent.RejectedExecutionException异常ThreadPoolExecutor.CallerRunsPolicy() 重试添加当前的任务,他会自动重复调用execute()方法ThreadPoolExecutor.DiscardOldestPolicy() 抛弃旧的任务
ThreadPoolExecutor.DiscardPolicy() 抛弃当前的任务
1.5.
2.2 e xecute函数
一个任务通过execute(Runnable)方法被添加到线程池,任务就是一个Runnable类型对象,任务执行了Runnable类型对象的run()方法。该函数的处理机制:
●如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创
建新的线程来处理被添加的任务。
●如果此时线程池中的数量不小于corePoolSize时,当缓冲队列workQueue未满,则将任务
放入缓冲队列;当缓冲队列已经满了,并且线程池中的数量小于maximumPoolSize,新建
线程来处理被添加的任务;当缓冲队列已满,且线程池数量不小于maximumPoolSize时,
则通过handler所指定策略来处理此任务。
1.5.
2.3 a ddWorker函数
execute函数会启动一个线程,并在线程内执行command中run函数,这是如何实现的呢?其实在addWorker函数中通过Worker类中的runWorker函数封装对command的调用。
getThreadFactory().newThread(this):将创建一个线程,且线程入口函数是Worker类下run函数,而run函数回调runWorker函数,这个函数将处理实际的任务实体。
addWorker将Worker加入到队列中,并且启动了线程。
第一种:匿名内部类作为事件监听器类 大部分时候,事件处理器都没有什么利用价值(可利用代码通常都被抽象成了业务逻辑方法),因此大部分事件监听器只是临时使用一次,所以使用匿名内部类形式的事件监听器更合适,实际上,这种形式是目前是最广泛的事件监听器形式。上面的程序代码就是匿名内部类来创建事件监听器的!!! 对于使用匿名内部类作为监听器的形式来说,唯一的缺点就是匿名内部类的语法有点不易掌握,如果读者java基础扎实,匿名内部类的语法掌握较好,通常建议使用匿名内部类作为监听器。 第二种:内部类作为监听器 将事件监听器类定义成当前类的内部类。1、使用内部类可以在当前类中复用监听器类,因为监听器类是外部类的内部类,2、所以可以自由访问外部类的所有界面组件。这也是内部类的两个优势。上面代码就是内部类的形式!! 第三种:Activity本身作为事件监听器
这种形式使用activity本身作为监听器类,可以直接在activity类中定义事件处理器方法,这种形式非常简洁。但这种做法有两个缺点:(1)这种形式可能造成程序结构混乱。Activity 的主要职责应该是完成界面初始化;但此时还需包含事件处理器方法,从而引起混乱。(2)如果activity界面类需要实现监听器接口,让人感觉比较怪异。 上面的程序让Activity类实现了OnClickListener事件监听接口,从而可以在该Activity类中直接定义事件处理器方法:onClick(view v),当为某个组件添加该事件监听器对象时,直接使用this作为事件监听器对象即可。 第四种:外部类作为监听器 ButtonTest类 当用户单击button按钮时,程序将会触发MyButtonListener监听器 外部MyButtonListener类
《Android基础应用》 AndroidUI基本控件与事件处理 ?本章任务 ?使用Android开发本息计算器程序 ?使用Android开发华氏-摄氏温度转换器 ?本章目标 ?熟悉掌握本章基本控件的使用 ?熟练掌握Android常用事件 1.Android基本控件 Android应用开发的一项内容就是用户界面的开发,Android提供了大量功能丰富的UI组件,大部分放在android.widget包及其子包android.view包及其子包 在Android当中View类是最基本的一个UI类,基本上所有的高级UI组件都是继承View类而实现的。如Button(按钮),list(列表),EditText(编辑框),RadioButton(多选按钮),Checkbox(选择框)等都是View类 在Android中,我们可以在Xml文件中使用UI组件也可以在java文件中创建UI组件官方建议采用xml方式,这样的话能够实现界面和代码分离 1.1TextView和EditText TextView是一种用于显示字符串的控件 EditText则是用来输入和编辑字符串的控件,EditText是一个具有编辑功能的TextView
TextView和EditText基本属性 ●android:id设置ID,通过编码可以找到这个组件 ●android:layout_width设置在屏幕上的宽度 ●android:layout_height设置在屏幕上的高度 fill_parent强制性地使构件扩展,以填充布局单元内尽可能多的空间 wrap_content强制性地使视图扩展以显示全部内容 ●android:text设置显示的文本信息 ●android:textColor设置文本颜色 ●android:textSize设置文本尺寸
Android OnTouchListener触屏事件接口 OnTouchListener接口是用来处理手机屏幕事件的监听接口,当为View的范围内触摸按下、抬起或滑动等动作时都会触发该事件。该接口中的监听方法签名如下。 Java代码: public boolean onT ouch(View v, MotionEvent event) 参数v:参数v同样为事件源对象。 参数event:参数event为事件封装类的对象,其中封装了触发事件的详细信息,同样包括事件的类型、触发时间等信息。 节中介绍了一个在屏幕中拖动矩形移动的案例,本节将继续采用该案例的思路,通过监听接口的方式实现在屏幕上拖动按钮移动的案例。开发步骤如下。 创建一个名为Sample的Android项目。 准备字符串资源,打开strings.xml文件,用下列代码替换原有代码。 Java代码:
Java代码:
广播事件处理 一.Broadcast Receiver 比如打电话等等; 广播接收器,它和事件处理机制类似,只不过事件处理机制是程序组件级别,而广播事件处理机制是系统级别。 二.使用Broadcast Receiver 1.编写类继承BroadcaseReceiver,复写onReceiver()方法 2.在AndroidManifest.xml文件中注册BroadcaseReceiver 3.构建Intent对象 4.调用sendBroadcase()方法发送广播 三.BroadcaseReceiver生命周期 BroadcastReceiver对象仅在调用onReceive()方法时有效,当该方法执行完毕后,系统认为销毁该对象。 四.标准广播Action 五.注册Broadcast Receiver的方法 1.在AndroidManifest.xml文件中进行注册//有缺陷,不会因为Activity被销毁而销毁,一般不用
Android进阶——Android事件分发机制之dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent 前言 Android事件分发机制可以说是我们Android工程师面试题中的必考题,弄懂它的原理是我们避不开的任务,所以长痛不如短痛,花点时间干掉他,废话不多说,开车啦 Android事件分发机制的简介 Android事件分发机制的发生在View与View之间或者ViewGroup与View之间具有镶嵌的视图上,而且视图上必须为点击可用。当一个点击事件产生后,它的传递过程遵循如下顺序:Activity->Window->View,即事件先传递给Activity,再到Window,再到顶级View,才开始我们的事件分发 Android事件分发机制的相关概念 Android事件分发机制主要由三个重要的方法共同完成的 dispatchTouchEvent:用于进行点击事件的分发 onInterceptTouchEvent:用于进行点击事件的拦截 onTouchEvent:用于处理点击事件 这里需要注意的是View中是没有onInterceptTouchEvent()方法的 Android事件分发机制的分发例子 这里以两个ViewGroup嵌套View来演示,下面是演示图 一、MyView 继承View并覆写其三个构造方法,覆写dispatchTouchEvent和onTouchEvent,前面已经说
了View是没有onInterceptTouchEvent方法的 public class MyView extends View { public MyView(Context context) { super(context); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public MyView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent event) { System.out.println("MyView dispatchTouchEvent"); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { System.out.println("MyView onTouchEvent"); return super.onTouchEvent(event); } } 二、MyViewGroup01和MyViewGroup02 MyViewGroup01和MyViewGroup02是一样的代码,这里以01为例,继承ViewGroup并覆写其三个构造方法,覆写dispatchTouchEvent和onTouchEvent和onInterceptTouchEvent方法 public class MyViewGroup01 extends LinearLayout { public MyViewGroup01(Context context) { super(context); } public MyViewGroup01(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public MyViewGroup01(Context context, AttributeSet attrs) {
Android广播事件处理闹钟实例 对应AlarmManage有一个AlarmManagerServie服务程序,该服务程序才是正真提供闹铃服务的,它主要维护应用程序注册下来的各类闹铃并适时的设置即将触发的闹铃给闹铃设备(在系 统中,linux实现的设备名为”/dev/alarm”),并且一直监听闹铃设备,一旦有闹铃触发或 者是闹铃事件发生,AlarmManagerServie服务程序就会遍历闹铃列表找到相应的注册闹铃并 发出广播。该服务程序在系统启动时被系统服务程序system_service启动并初始化闹铃设备(/dev/alarm)。当然,在JAVA层的AlarmManagerService与Linux Alarm驱动程序接口之间 还有一层封装,那就是JNI。 AlarmManager将应用与服务分割开来后,使得应用程序开发者不用关心具体的服务,而 是直接通过AlarmManager来使用这种服务。这也许就是客户/服务模式的好处吧。AlarmManager与 AlarmManagerServie之间是通过Binder来通信的,他们之间是多对一的关系。 在android系统中,AlarmManage提供了3个接口5种类型的闹铃服务。 3个接口: 1. // 取消已经注册的与参数匹配的闹铃 2. void cancel(PendingIntent operation) 1. 2. //注册一个新的闹铃 3. void set(int type, long triggerAtTime, PendingIntent operation) 4. //注册一个重复类型的闹铃 5. void setRepeating(int type, long triggerAtTime, long interval, PendingIntent operation) 6. //设置时区 7. void setTimeZone(String timeZone) 5个闹铃类型 public static final int ELAPSED_REALTIME 1. //当系统进入睡眠状态时,这种类型的闹铃不会唤醒系统。直到系统下次被唤醒才传 递它,该闹铃所用的时间是相对时间,是从系统启动后开始计时的,包括睡眠 2. 时间,可以通过调用SystemClock.elapsedRealtime()获得。系统值是 3
Testing和Instrumentation Android提供了一系列强大的测试工具,它针对Android的环境,扩展了业内标准的JUnit测试框架。尽管你可以使用JUnit 测试Android工程,但Android工具允许你为应用程序的各个方面进行更为复杂的测试,包括单元层面及框架层面。Android测试环境的主要特征有: ●可以访问Android系统对象。 ●Instrumentation框架可以控制和测试应用程序。 ●Android系统常用对象的模拟版本。 ●运行单个test或test suite的工具,带或不带Instrumentation。 ●支持以Eclipse的ADT插件和命令行方式管理Test和Test工程。 这篇文章是对Android测试环境和测试方法的简要介绍,并假设你已经拥有一定的Android应用程序编程及JUnit测试的经验。概要 Android测试环境的核心是一个Instrumentation框架,在这个框架下,你的测试应用程序可以精确控制应用程序。使用Instrumentation,你可以在主程序启动之前,创建模拟的系统对象,如Context;控制应用程序的多个生命周期;发送UI事件给应用程序;在执行期间检查程序状态。Instrumentation框架通过将主程序和测试程序运行在同一个进程来实现这些功能。 通过在测试工程的manifest文件中添加
Android开发-触屏事件的获取和触摸屏幕位置 在修改后的工厂测试程序中,用到了关于触摸事件的获取,顺便学习关于触摸事件和触摸位 Java代码 1.publicbooleanonTouchEvent(MotionEventevent){ 2. 3.//获得触摸的坐标 4.floatx=event.getX(); 5.floaty=event.getY();switch(event.getAction()) 6.{ 7.//触摸屏幕时刻 8.caseMotionEvent.ACTION_DOWN: 9. 10.break; 11.//触摸并移动时刻 12.caseMotionEvent.ACTION_MOVE: 13. 14.break; 15.//终止触摸时刻 16.caseMotionEvent.ACTION_UP: 17.break; 18.} 19.returntrue; 20.} 的知识,其方法如下: 关于publicbooleanonTouchEvent(MotionEventevent)方法: 参数event:参数event为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息,例如触摸的位置、触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创建。 返回值:该方法的返回值机理与键盘响应事件的相同,同样是当已经完整地处理了该事件且不希望其他回调方法再次处理时返回true,否则返回false。 该方法并不像之前介绍过的方法只处理一种事件,一般情况下以下三种情况的事件全部由onTouchEvent方法处理,只是三种情况中的动作值不同。 屏幕被按下:当屏幕被按下时,会自动调用该方法来处理事件,此时MotionEvent.getAction()的值为MotionEvent.ACTION_DOWN,如果在应用程序中需要处理屏幕被按下的事件,只需重新该回调方法,然后在方法中进行动作的判断即可。 屏幕被抬起:当触控笔离开屏幕时触发的事件,该事件同样需要onTouchEvent方法来捕捉,然后在方法中进行动作判断。当MotionEvent.getAction()的值为MotionEvent.ACTION_UP时,表示是屏幕被抬起的事件。 在屏幕中拖动:该方法还负责处理触控笔在屏幕上滑动的事件,同样是调用MotionEvent.getAction()方法来判断动作值是否为MotionEvent.ACTION_MOVE再进行处理。
完全理解Android TouchEvent事件分发 机制(二) 可以看出来,事件一旦被某一层消费掉,其它层就不会再消费了 到这里其实对事件分发的机制就有个大概了解,知道里面的原理是怎么回事。 下面就让我们来去梳理一下这个事件分发所走的逻辑。 我们仔细思考一下,为什么有的事件有UP有的没有? 为什么Up和Down的顺序不同呢? 为什么要按照这个顺序执行呢? 这个例子主要是为了说明分发、拦截、消费的流程 以例一为例,在每个View 都不拦截down 事件的情况下,down 事件是这样传递的 super.dispatchTouchEvent方法,上面我们介绍过, 这个方法内部实际上是调用的onTouchEvent方法
所以最后的输出日志顺序就是从父到子依次调用分发和拦截,然后从子到父依次调用消费。 例二也是同理,区别在于 当Father拿到事件的时候,选择了拦截下来不再询问其他, 但是Father也没消费,直接又还回给了Grandpa, Grandpa同样也没有消费这个事件。 所以最终的顺序就是,从Grandpa到Father再返回Grandpa就结束了,没有经过LogImageView。 例三的情况就不太一样了 当Grandpa->Father->LogImageView 传递到LogImageView时,LogImageView不消费又返回给了Father,Father在onTouchEvent消费掉了事件。 然后反馈给Father说事件已经消费。,就等于parent.dispatchTouchEvent返回true给上一级的Grandpa, Grandpa不会再调用grandpa.onTouchEvent方法。 从这里我们可以总结出来: **dispatchTouchEvent返回值的作用是用于标志这个事件是否“消费了”, 无论是自己或者下面的子一级用掉了都算是消费掉。** 再如这个例子中,如果我们让LogImageView消费掉事件, 那么Father收到LogImageView的消息后,也会调用parent.dispatchTouchEvent返回true给Grandpa, 所以这个方法返回值的true是只要用掉就行,无论自己还是下面某一级, 而非我把事件传递下去就是true了,下面没人消费最终其实还是返回false的。 至此,我们来总结一下这三个方法的大致作用: dispatchTouchEvent方法内容里处理的是分发过程。可以理解为从Grandpa->Father->LogImageView一层层分发的动作 dispatchTouchEvent的返回值则代表是否将事件分发出去用掉了,自己用或者给某一层子级用都算分发成功。比如Father消费了事件,或者他发出去给的LogImageView消费了事件,
Android按键事件处理流程 -- KeyEvent 2014/6/24 13:18:58 xiaoweiz 程序员俱乐部我要评论(0) ?摘要:刚接触Android开发的时候,对touch、key事件的处理总是一知半解,一会是Activity 里的方法,一会是各种View中的,自己始终不清楚到底哪个在先哪个在后,总之对整个处理流程没能很好的把握。每次写这部分代码的时候都有些心虚,因为我不是很清楚什么时候、以什么样的顺序被调用,大都是打下log看看,没问题就算ok了。但随着时间流逝,这种感觉一直折磨着我。期间也在网上搜索了相关资料,但总感觉不是那么令人满意。自打开始研究Android源码起 ?标签:事件android KEY流程事件处理 ? 刚接触Android开发的时候,对touch、key事件的处理总是一知半解,一会是Activity里的方法,一会是各种View 中的,自己始终不清楚到底哪个在先哪个在后,总之对整个处理流程没能很好的把握。 每次写这部分代码的时候都有些心虚, 因为我不是很清楚什么时候、以什么样的顺序被调用,大都是打下log看看,没问题就算ok了。但随着时间流逝,这种感觉一直 折磨着我。期间也在网上搜索了相关资料,但总感觉不是那么令人满意。自打开始研究Android源码起,这部分内容的分析早就 被列在我的TODO list上了。因为弄懂这部分处理逻辑对明明白白地写android程序实在是太重要了,所以今天我就带领大家看看 这部分的处理逻辑。touch事件的处理我将放在另一篇博客中介绍(相比KeyEvent,大体都一样,只是稍微复杂些)。 为了突出本文的重点,我们直接从事件被派发到View层次结构的根节点DecorView 开始分析,这里我们先来看看DecorView# dispatchKeyEvent方法,代码如下: @Override publicboolean dispatchKeyEvent(KeyEvent event) { finalint keyCode = event.getKeyCode(); finalint action = event.getAction(); finalboolean isDown = action == KeyEvent.ACTION_DOWN; /// 1. 第一次down事件的时候,处理panel的快捷键 if (isDown && (event.getRepeatCount() == 0)) {
浅谈android单击和键盘事件 一,概念和种类 (1) 二,事件的用法。 (1) 三,事件的过程及原理 (2) 四,android事件的常用方法 (5) 1)onClick:按钮单击事件。 (5) 2)onLongClick:长按事件。 (5) 3)onCreateContextMenu:上下文菜单事件。 (6) 4)onFocusChange:焦点事件。 (7) 5)onTouchEvent:触屏事件。 (7) 6)onKeyUp、onKeyDown:键盘或遥控事件。 (8) 7)onTrackballEvent:轨迹球事件。 (8) 一,概念和种类 事件是用户与应用的UI交互的动作。在android中有许许多多的事件,即使是一个简单的单击事件,也有按下、弹起、长按之分。它们以监听接口和基于回调方式进行划分的。下面列出几种常用的事件: 1,onClick:按钮单击事件。 2,onLongClick:长按事件。 3,onCreateContextMenu:上下文菜单事件。 4,onFocusChange:焦点事件。 5,onTouchEvent:触屏事件。 6,onKeyUp、onKeyDown:键盘或遥控事件。 7,onTrackballEvent:轨迹球事件。 二,事件的用法。 在android平台中,每个View都有自己处理事件的回调方法,开发人员只需要重写这些回调方法,就可以实现需要的响应事件。
上图是一个简单的应用实例。程序的主入口是MainActivity,与之对应的布局文件是res/layout/main.xml ,在布局文件中有一个id为button的按钮。 单击事件只需要注册相应的监听器(setOnClickListener)监听事件的来源,当事件发生时作出相应的处理。 键盘或遥控事件只需重写父类Activity的(onKeyDown)方法(父类已经封装好了接口),通过接收不同的键值作出相应的处理。 三,事件的过程及原理 事件过程原理:
本文由我司收集整编,推荐下载,如有疑问,请与我司联系关于Android触摸事件TouchEvent的传递及截取,研究心得。 2014/07/23 868 之前一直经常使用Touch的相关操作,但是对其中的具体细节一直没有详细的研究一下,今天研究了一下,感觉思路有点清晰了很多,再次记录一下。 ?其中关于Android触摸事件相关的函数有:dispatchTouchEvent() | onInterceptTouchEvent()| onTouchEvent() ?由函数的名字也大概能够知道具体的作用是什么了:dispatchTouchEvent()负责触摸事件的分发,onInterceptTouchEvent()决定事件是不是在当前的View下拦截,onTouchEvent()负责触摸事件的处理消化。 ?先说一下传递的机制和顺序吧。 ?我们建立一个这样的场景,就是在一个Activity中有一个View,名叫:Layout_out_0,在Layout_out_0中还有嵌套在里面的一个View,叫Layout_inner_0,相信大家也能够想象是个什么东西吧。 ? ?当你触摸屏幕的时候,首先产生事件的顺序为:DOWN—— MOVE(这个事件的产生和你触摸时的触摸时间的长度有关,如果时间极短,就不会产生,如果一直按住,就会一直产生MOVE)—— UP(手指抬起的时候);当然这个顺序是固定的,事件的处理也是一个传递消化完毕之后,才开始第二个事件的传递消化,很固定。 ?好进入重点,DOWN事件产生首先经由Activity的dispatchTouchEvent(),在Activity中是没有方法onInterceptTouchEvent()方法的,也就无法使产生的事件被拦截了,接着进入等级仅次于Activity的容器组件View中,也是经由dispatchTouchEvent()(不过我在测试程序中尝试把该函数的返回值 super.dispatchTouchEvent()修改为false或者是true结果发现导致事件无法正常的分发,因此不建议大家重写该回调函数),在经过onInterceptTouchEvent()决定是不是要拦截该事件,如果onInterceptTouchEvent()返回true则表示要拦截,那么事件就不会再继续往子控件传递了(否则继续重复上面的步骤,再次进入子控件的
一、实验名称:实验5 事件处理 二、实验日期: 三、实验目的: ◆基于监听的事件处理 ◆基于回调的事件处理 四、实验用的仪器和材料:Windows+Eclipse+jdk+sdk+adt 五、实验的步骤和方法: 实验一:基于监听的事件处理机制 Activity.java package com.my; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; public class Week05Activity extends Activity implements OnClickListener{ EditText txt; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(https://www.sodocs.net/doc/f09885555.html,yout.main); //获取button对象 Button btn1 = (Button)findViewById(R.id.button1);
Button btn2 = (Button)findViewById(R.id.button2); Button btn3 = (Button)findViewById(R.id.button3); Button btn4 = (Button)findViewById(R.id.button4); Button btn5 = (Button)findViewById(R.id.button5); //获取edittext对象 txt = (EditText)findViewById(R.id.edittext); //定义一个单击事件的监听器(内部类) class MyClickListener implements OnClickListener{ public void onClick(View v) { EditText txt = (EditText)findViewById(R.id.edittext); txt.setText("内部类"); } } //定义一个单击事件的监听器(外部类) class ExtentEvent implements OnClickListener{ private Activity act; private EditText txt; public ExtentEvent(Activity act,EditText txt){ this.act=act; this.txt=txt; } public void onClick(View v) { txt.setText("外部类"); } } //为按钮绑定事件监听(内部类) btn1.setOnClickListener(new MyClickListener()); //为按钮绑定事件监听(外部类) btn2.setOnClickListener(new ExtentEvent(this,txt)); //直接使用Activity作为事件监听器 btn4.setOnClickListener(this); //匿名内部类 btn3.setOnClickListener(new OnClickListener(){ public void onClick(View v) { txt.setText("匿名内部类"); } }); } //直接绑定到标签 public void clickHandler(View source){
实验3 android事件处理 学时:2学时 一、实验目的: 1、了解Android 的事件处理机制, 2、掌握Handler 消息传递机制,学会如何利用 3、AsyncTask 进行异步任务处理。 二、实验内容: 1.运行书上例题(EventTransferTest,见P89页),观察并分析程序的输出结果. 2.实现的是当开始按钮按下时,会启动一个线程,并绑定到handler中,该线程发送带有参数的message到handler的消息队列中,消息队列的另一端获取该消息,并且用该消息的参数来更新进度条。(请将该题做好后保存,保存名为“你的学号”,发给你班的学习委员,各班学习委员统一发到我的邮箱610083060@https://www.sodocs.net/doc/f09885555.html,) 3.将上一题用AsyncTask实现. 第2题的代码参考: 实验主要部分代码和注释: 参考代码如下: MainActivity.java: import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message;
import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ProgressBar; public class MainActivity extends Activity { private ProgressBar progress_bar = null; private Button start = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(https://www.sodocs.net/doc/f09885555.html,yout.activity_main); progress_bar = (ProgressBar)findViewById(R.id.progress_bar); start = (Button)findViewById(R.id.start); start.setOnClickListener(new StartOnClickListenr()); } private class StartOnClickListenr implements OnClickListener { public void onClick(View v) { //让进度条显示出来 progress_bar.setVisibility(View.VISIBLE); //将线程加入到handler的线程队列中 update_progress_bar.post(update_thread); } } //创建一个handler,内部完成处理消息方法 Handler update_progress_bar = new Handler() { @Override
View的事件分发: 对于事件分发机制,举个简单的例子,在一个Activity中只有一个按钮,如果我们想给这个按钮注册一个点击事件,只需要调 用setOnClickListener方法,这样在onClick方法里面写实现的代码,就可以在按钮被点击的时候执行.我们再给这个按 钮添加一个touch事件,只需要调用setOnTouchListener方法,onTouch方法里能做的事情比onClick要多一些,比如判断手指按下、抬起、移动等事件。如果我两个事件都注册了,我之前做过一个实验,运行程序点击按钮的结果是onTouch是优先于onClick执行的,并且onTouch执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(你还可能会有多次ACTION_MOVE的执行,如果你手抖了一下)。因此事件传递的顺序是先经过onTouch,再传递到onClick。 onTouch方法是有返回值的,如果我们尝试把onTouch方法里的返回值改成true,再运行一次就会发现onClick方法不再执行了,这是因为onTouch方法返回true就认为这个事件被onTouch消费掉了,因而不会再继续向下传递。 我看过事件分发的一些源码, 只要你触摸到了任何一个控件,就一定会调用该控件的dispatchTouchEvent方法。那当我们去点击按钮的时候,就会去调用Button类里的dispatchTouchEvent方法,可是你会发现Button类里并没有这个方法,那么就到它的父类TextView 里去找一找,你会发现TextView里也没有这个方法,那没办法了,只好继续在TextView的父类View里找一找,这个时候你终于在View里找到了这个方法,示意图如下: 对于View中的dispatchTouchEvent方法,在这个方法内,首先是进行了一个判断,里面有三个条件,如果这三个条件都满足,就返回true,否则就返回onTouchEvent方法执行的结果。对于第一个条件是一个mOnTouchListener变量,这个变量是在View中的setOnTouchListener方法里赋值的,也就是说只要我们给控件注册了touch事件,mOnTouchListener 就一定被赋值了。第二个条件是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true。第三个条件最为关键,mOnTouchListener.onTouch(this, event),其实也就是去回调控件注册touch事件时的onTouch 方法。也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,从而整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。 结合我之前讲的例子,首先在dispatchTouchEvent中最先执行的就是onTouch方法,因此onTouch肯定是要优先于onClick 执行的,而如果在onTouch方法里返回了true,就会让dispatchTouchEvent方法直接返回true,不会再继续往下执行。而我做的实验的结果也证实了如果onTouch返回true,onClick就不会再执行了。 这里还讲到了onTouchEvent方法,这个方法要稍微复杂一点,如果我们的控件是可以点击的,就会进入到switch判断中去,而如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。在经过种种判断之后,会执行到performClick()方法。 对于performClick()方法,只要mOnClickListener不是null,就会去调用它的onClick方法。 而刚刚说过,当我们通过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值。然后每当控件被点击时,都会在performClick()方法里回调被点击控件的onClick方法。
Android平台的事件处理机制有两种,一种是基于回调机制的,一种是基于监听接口的,现介绍第一种:基于回调机制的事件处理。Android平台中,每个View都有自己的处理事件的回调方法,开发人员可以通过重写View中的这些回调方法来实现需要的响应事件。当某个事件没有被任何一个View处理时,便会调用Activity中相应的回调方法。Android提供了以下回调方法供用户使用: 1. onKeyDown: 功能:该方法是接口KeyEvent.Callback中的抽象方法,所有的View全部实现了该接口并重写了该方法,该方法用来捕捉手机键盘被 按下的事件。 声明:public boolean onKeyDown (int keyCode, KeyEvent event) 参数说明: 参数keyCode,该参数为被按下的键值即键盘码,手机键盘中每个按钮都会有其单独的键盘码,在应用程序都是通过键盘码才知道用户按 下的是哪个键。 参数event,该参数为按键事件的对象,其中包含了触发事件的详细信息,例如事件的状态、事件的类型、事件发生的时间等。当用户按下按键时,系统会自动将事件封装成KeyEvent对象供应用程序使 用。 返回值,该方法的返回值为一个boolean类型的变量,当返回true 时,表示已经完整地处理了这个事件,并不希望其他的回调方法再次进行处理,而当返回false时,表示并没有完全处理完该事件,更希望其他回调方法继续对其进行处理,例如Activity中的回调方法。 2. onKeyUp: 功能:该方法同样是接口KeyEvent.Callback中的一个抽象方法,并且所有的View同样全部实现了该接口并重写了该方法,onKeyUp 方法用来捕捉手机键盘按键抬起的事件。 声明:public boolean onKeyUp (int keyCode, KeyEvent event) 参数说明:同onKeyDown 3. onTouchEvent: 功能:该方法在View类中的定义,并且所有的View子类全部重写了该方法,应用程序可以通过该方法处理手机屏幕的触摸事件。 声明:public boolean onTouchEvent (MotionEvent event) 参数说明: 参数event:参数event为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息,例如触摸的位置、触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创建。