Android 事件处理探险

Android提供了两套事件处理机制:基于监听的事件处理;基于回调的事件处理

1.基于监听的事件处理

Android的事件处理是一种委派式事件处理方式(事件源将整个事件处理委托给事件监听器),事件监听的处理模型主要涉及:Event Source(事件源)、Event(事件)、Event Listener(事件监听器)。

小技巧:
requestWindowFeature(Window.FEATURE_NO_TITLE); // 去掉窗口标题
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN); // 全屏显示

在基于监听的事件处理模型中,事件监听器必须实现事件监听器接口(通常以内部类的形式存在),以View为例,提供了如下几个接口

View.OnClickListener    // 单击事件
View.OnCreateContextMenuListener    // 创建上下文菜单事件
View.OnFocusChangeListener    // 焦点改变事件
View.OnKeyListener    // 按钮事件
View.OnLongClickListener    // 长点击事件
View.OnTouchListener    // 触摸屏事件

注意:不推荐将业务逻辑实现写在事件监听器中,包含业务逻辑的事件监听器将导致程序的显示逻辑与业务逻辑耦合,增加了后期维护难度。

2.基于回调的事件处理

Android事件处理的回调方法,以View为例,View类包含如下方法

boolean onKeyDown(int keyCode, KeyEvent event)    // 按下
boolean onKeyLongPress(int keyCode, KeyEvent event)    // 长按
boolean onKeyUp(int keyCode, KeyEvent event)    // 松开
boolean onKeyShortcut(int keyCode, KeyEvent event)        // 键盘快捷键触发时
boolean onTouchEvent(MotionEvent event)        // 触摸屏事件

基于回调的事件处理机制可通过自定义View来实现,自定义View时重写该View的事件处理方法即可。 对比Android提供了两套事件处理机制,基于监听的事件处理更有优势:可维护性高、保证监听的事件监听器会被优先触发。 基于回调的事件处理更适合于那些比较固定的View。

3.事件传递

所有基于回调的事件处理的回调方法返回true,表明已处理完成,不会继续传递;返回false,表明未处理完成,该事件继续传递下去。

Button bn = (Button) findViewById(R.id.bn);
// 为bn绑定事件监听器
bn.setOnKeyListener(new OnKeyListener() {
    @Override
    public boolean onKey(View source, int keyCode, KeyEvent event) {
        // 只处理按下键的事件
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            Log.v("-Listener-", "the onKeyDown in Listener");
        }
        return true;  // 返回true,表明该事件不会向外传递
    }
});

当某个键被按下时候,Android最先触发的是该按键上绑定的事件监听器,然后触发该组件提供的事件回调方法,最后传递到该组件所在的Activity。

4.响应系统设置的事件

1)Configuration类 专门用于描述手机设备上的配置信息 获取系统的Configuration对象:

Configuration cfg = getResources().getConfiguration();

2)重写onConfigurationChanged方法可以响应系统设置更改

5.Handler消息传递机制

Handler类的主要机制有两个:在子线程中发送消息;在主线程中获取处理消息。 Handler包含如下方法用于发送处理消息:

void handleMessage(Message msg):处理消息
final boolean hasMessages(int what, Object object):检查消息队列中是否包含what属性为指定值且object属性为指定对象的消息
boolean sendEmptyMessage(int what):发送空消息
boolean sendEmptyMessageDelayed(int what, long delayMillis):延迟发送空消息
boolean sendMessage(Message msg):发送消息
boolean sendMessageDelayed(Message msg, long delayMillis):延迟发送消息

6.Handler、Looper、MessageQueue

Handler、Looper、MessageQueue各自作用如下: Handler:能发送消息给MessageQueue,并能处理Looper分发给它的消息; Looper:每个线程只有一个Looper,负责管理MessageQueue,从MessageQueue中取出消息分发给对应的Handler; MessageQueue:采用先进先出管理Message;

注意:避免在主线程中执行耗时操作,否则会引发ANR异常。

public class MainActivity extends Activity {
    EditText etNum;
    ChildThread childThread;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // ...
        ChildThread = new ChildThread();
        childThread.start();
    }
    /**
     * 定义一个线程类
     */ 
    class ChildThread extends Thread {
        public Handler mHandler;
        public void run() {
            Looper.prepare(); // 创建Looper对象
            mHandler = new Handler() {
                // 定义处理消息的方法
                @Override
                public void handleMessage(Message msg) {
                    if(msg.what == 1) {
                        // 执行操作
                        // ...
                    }
                }
            };
            Looper.loop(); // 启动Looper
        }
    }
    /**
     * 按钮的点击事件
     */ 
    public void down(View view) {
        Message msg = new Message(); // 创建消息
        msg.what = 1;
        Bundle bundle = new Bundle();
        // num = ...
        bundle.putInt("num" ,num);
        msg.setData(bundle);
        childThread.mHandler.sendMessage(msg); // 向新线程中的Handler发送消息
    }
}

Android为了解决子线程不能更新UI组件的问题,已提供了如下解决方案:

使用Handler实现线程间通信
runOnUiThread方法
View.post方法
View.postDelayed方法

用法如下:

/**
 * 1.使用Handler实现线程间通信的用法
 */
public class MainActivity extends Activity {
    protected static final int MSG_ONE = 1;
    protected static final int MSG_TWO = 2;
    private Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_ONE:
                    // 执行1的操作
                    break;
                case MSG_TWO:
                    // 执行2的操作
                    break;
            };
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 耗时操作要在子线程中操作
        new Thread() {
            public void run() {
                Message message = Message.obtain(); //获取消息的载体
                if (condition) {
                    message.what = MSG_ONE;
                } else {
                    message.what = MSG_TWO;
                }
                handler.sendMessage(message);
            };
        }.start();
    }
}

/**
 * 2.runOnUiThread方法的用法
 */
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 耗时操作要在子线程中操作
        new Thread() {
            public void run() {
                //执行耗时操作
                //更新主线程UI
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {

                    }
                });
            };
        }.start();
    }
}

7.异步加载(AsyncTask)

1)先自己手写一个异步加载框架,其中涵盖了异步加载框架核心原理,如下:

public abstract class MyAsycnTask {
    private Handler handler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            postTask();
        };
    };
    /**
     * 在子线程之前执行的操作
     */
    public abstract void preTask();
    /**
     * 在子线程之中执行的操作
     */
    public abstract void doinBack();
    /**
     * 在子线程之后执行的操作
     */
    public abstract void postTask();
    /**
     * 执行
     */
    public void execute(){
        preTask();
        new Thread(){
            public void run() {
                doinBack();
                handler.sendEmptyMessage(0);
            };
        }.start();
    }
}

调用手写异步加载框架

new MyAsycnTask() {
    @Override
    public void preTask() {
        //在子线程之前执行的操作
    }
    @Override
    public void postTask() {
        //在子线程之中执行的操作
    }
    @Override
    public void doinBack() {
        //在子线程之后执行的操作
    }
}.execute();

2)系统异步加载框架AsycnTask

系统异步加载框架AsycnTask三个参数:提高兼容性(如果某个泛型参数不需要指定类型,可将其指定为void) 参数1:子线程执行所需的参数;参数2:显示当前的加载进度;参数3:子线程执行的结果;

接下来开始调用系统异步加载框架,用法如下:

new AsyncTask<String, Integer, String>(){
    //在子线程之中执行的操作
    @Override
    protected String doInBackground(String... params) {
        // TODO Auto-generated method stub
        return null;
    }
    //在子线程之前执行的操作
    @Override
    protected void onPreExecute() {
        // TODO Auto-generated method stub
        super.onPreExecute();
    }
    //在子线程之后执行的操作
    @Override
    protected void onPostExecute(String result) {
        // TODO Auto-generated method stub
        super.onPostExecute(result);
    }
    //显示当前加载进度
    @Override
    protected void onProgressUpdate(Integer... values) {
        // TODO Auto-generated method stub
        super.onProgressUpdate(values);
    }
}.execute();

本篇是对Android界面编程的补充,而且都是高频常用的一些方法机制,建议重点学习。

原文发布于微信公众号 - 非著名程序员(non-famous-coder)

原文发表时间:2016-05-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ASP.NET MVC5 后台权限管理系统

.Net 转战 Android 4.4 日常笔记(4)--按钮事件和国际化

我们知道资源被注册到R.java我们通过R.java就可以读取到界面中的组件。跟我们.net一样,通过ID来读取组件 知识点: 通过R.java读取组件 Mai...

19850
来自专栏学海无涯

Android开发之连续点击返回键退出程序

简介 在很多程序中,都有这样一个功能,就是在主界面,连续点击返回键会退出程序。它一般是这样显示的:第一次按下提示你 再按一次退出程序 ,如果此时立马点击返回键会...

36760
来自专栏Android相关

Android的NestedScroll机制

这种事件传递的结果就会导致以下问题: 一个View把Touch事件消费之后,其他View就无法接收到该事件,也就无法根据这个事件完成与用户的交互

24130
来自专栏分享达人秀

Activity初入门,创建和配置如此简单

Activity是Android应用的重要组成单元之一,也是Android应用最常见的组件之一。前面看到的示例通常都只包含一个Activity或一个Ap...

23050
来自专栏Java呓语

Tips·检测应用程序被卸载

我们知道广播ACTION_PACKAGE_REMOVED可以监听应用程序卸载,但不幸的是这个意图被卸载的程序是不可知的,所以无法监听到自己的程序被卸载。

17630
来自专栏QQ音乐技术团队的专栏

[Android] Toast问题深度剖析(一)

伴随着我们开发的深入,Toast 的问题也逐渐暴露出来。本文章就将解释 Toast 这些问题产生的具体原因。

2.4K150
来自专栏Android源码框架分析

SharePreference原理及跨进程数据共享的问题

SharedPreferences是Android提供的数据持久化的一种手段,适合单进程、小批量的数据存储与访问。为什么这么说呢?因为SharedPrefere...

21060
来自专栏Android中高级开发

Android开发之漫漫长途 番外篇——内存泄漏分析与解决

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索...

9620
来自专栏Android学习之路

Android 导入现有数据库

277110
来自专栏Android先生

(新瓶旧酒)谷歌官方MVP项目学习--浅入源码

项目的目的是通过展示各种架构app的不同方式来帮助开发者解决架构问题。项目中通过不同的架构概念及方式实现了功能相同的app。你可以用示例来当做参考,或是干脆拿来...

14410

扫码关注云+社区

领取腾讯云代金券