出于性能优化考虑,Android 的 UI 操作并不是线程安全的,这意味着如果多个线程并发操作 UI 组件,可能导致线程安全问题。为了解决这个问题,Android 制定了一条简单的规则:只允许 UI 线程修改 Activity 里的 UI 组件

当一个程序启动时,Android 会同时启动一个主线程(Main Thread),主线程主要负责处理与 UI 相关的事件,如用户的按键事件、触屏事件及屏幕绘制事件,并把相关的事件分发到对应的组件进行处理。所以主线程也称为 UI 线程。

Android 的消息传递机制是另一种形式的“事件处理”,这种机制主要是为了解决 Android 应用的多线程问题 —— Android 平台只允许 UI 线程修改 Activity 里的 UI 组件,这样就会导致新启动的线程无法动态改变界面组件的属性值。此时,就需要借助于 Handler 的消息传递机制来实现新启动的线程改变界面组件的属性值。

Handler 类简介###

Handler 类的主要作用有两个:
1.在子线程中发送消息;
2.在主线程中获取、处理消息。

例如,下面的使用示例,自动播放图片:
主布局文件的内容:

<?xml version="1.0" encoding="utf-8"?>

主程序文件的代码:

package com.toby.personal.testlistview;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.support.v7.app.AppCompatActivity;import android.widget.ImageView;import java.util.Timer;import java.util.TimerTask;public class MainActivity extends AppCompatActivity {    final private static String TAG = "Toby_Test";    final private static int MsgWhat = 20170326;    final private static int Duration = 1200;    private int currentImageId = 0;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        final int[] imageIds = new int[]{                R.drawable.dog_001, R.drawable.dog_002, R.drawable.dog_003, R.drawable.dog_004,                R.drawable.dog_005,        };        final ImageView imageView = (ImageView) findViewById(R.id.imageView);        final Handler handler = new Handler() {            @Override            public void handleMessage(Message msg) {                if (msg.what == MsgWhat) {                    imageView.setImageResource(imageIds[currentImageId++ % imageIds.length]);                }                super.handleMessage(msg);            }        };        new Timer().schedule(new TimerTask() {            @Override            public void run() {                handler.sendEmptyMessage(MsgWhat);            }        }, 0, Duration);    }}

程序运行之后,将开始自动播放图片,具体使用效果,各位可自行尝试运行。

Handler、Loop、MessageQueue 的工作原理###

与 Handler 一起工作的几个组件:
Message —— Handler 接收和处理的消息对象;
Looper —— 每个线程只能拥有一个 Looper。它的 loop 方法负责读取 MessageQueue 中的消息,读到消息之后就把消息交给发送该消息的 Handler 进行处理。
MessageQueue —— 消息队列,它采用先进先出的方式来管理 Message,程序创建 Looper 对象时会在它的构造器中创建 Looper 对象。Looper 提供的构造器源代码如下:

private Looper() {    mQueue = new MessageQueue();    mRun = true;    mThread = Thread.currentThread();}

Handler 的作用是发送和处理消息,程序使用 Handler 发送消息,消息需要使用 MessageQueue 对象进行保存,而 MessageQueue 是由 Looper 进行管理的,因此必须在当前线程中有一个 Looper 对象。可分为如下两种情况:
主线程中 —— 系统已经初始化了 Looper 对象,因此程序直接创建 Handler 即可,然后就可以通过 Handler 来发送和处理消息;
子线程中 —— 必须自己创建一个 Looper 对象,并启动它:创建 Looper 对象并调用它的 prepare() 方法即可。

prepare() 方法保证每个线程最多只有一个 Looper 对象。prepare() 方法的源代码如下:

    public static void prepare() {        prepare(true);    }    private static void prepare(boolean quitAllowed) {        if (sThreadLocal.get() != null) {            throw new RuntimeException("Only one Looper may be created per thread");        }        sThreadLocal.set(new Looper(quitAllowed));    }

然后调用 Looper 的静态 loop() 方法来启动它。loop() 方法使用一个死循环不断取出 MessageQueue 中的消息,并将取出的消息分给该消息对应的 Handler 进行处理。下面是 Looper 类的 loop() 方法的源代码:

public static void loop() {        final Looper me = myLooper();        if (me == null) {            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");        }        final MessageQueue queue = me.mQueue;        // Make sure the identity of this thread is that of the local process,        // and keep track of what that identity token actually is.        Binder.clearCallingIdentity();        final long ident = Binder.clearCallingIdentity();        for (;;) {            Message msg = queue.next(); // might block            if (msg == null) {                // No message indicates that the message queue is quitting.                return;            }            // This must be in a local variable, in case a UI event sets the logger            final Printer logging = me.mLogging;            if (logging != null) {                logging.println(">>>>> Dispatching to " + msg.target + " " +                        msg.callback + ": " + msg.what);            }            final long traceTag = me.mTraceTag;            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));            }            try {                msg.target.dispatchMessage(msg);            } finally {                if (traceTag != 0) {                    Trace.traceEnd(traceTag);                }            }            if (logging != null) {                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);            }            // Make sure that during the course of dispatching the            // identity of the thread wasn't corrupted.            final long newIdent = Binder.clearCallingIdentity();            if (ident != newIdent) {                Log.wtf(TAG, "Thread identity changed from 0x"                        + Long.toHexString(ident) + " to 0x"                        + Long.toHexString(newIdent) + " while dispatching to "                        + msg.target.getClass().getName() + " "                        + msg.callback + " what=" + msg.what);            }            msg.recycleUnchecked();        }    }

总结一下,Looper、MessageQueue、Handler 各自的作用如下。
Looper:每个线程只有一个 Looper,它负责管理 MessageQueue,会不断地从 MessageQueue 中取出消息,并将消息分给对应的 Handler 处理。
MessageQueue:由 Looper 负责管理。它采用先进先出的方式管理 Message。
Handler:它能把消息发送给 Looper 管理的 MessageQueue,并负责处理 Looper 分给它的消息。

在线程中使用 Handler 的步骤如下。
1.调用 Looper 的 prepare() 方法为当前线程创建 Looper 对象,创建 Looper 对象时,它的构造器会创建与之配套的 MessageQueue。
2.有了 Looper 之后,创建 Handler 子类的实例,重写 HandleMessage() 方法,该方法负责处理来自于其他线程的消息。
3.调用 Looper 的 loop() 方法启动 Looper。

Looper 与 Handler 的用法实例,使用新线程计算质数
布局文件:

<?xml version="1.0" encoding="utf-8"?>        

主程序文件的代码如下:

package com.toby.personal.testlistview;import android.os.Bundle;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.EditText;import android.widget.Toast;import java.util.ArrayList;import java.util.List;public class MainActivity extends AppCompatActivity {    final private static String TAG = "Toby_Test";    final private static String KEY_NUM = "KEY_NUM_TEST";    final private static int MsgWhat = 20170326;    private EditText editText = null;    private CalculationThread calculationThread = null;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        editText = (EditText) findViewById(R.id.editText);        calculationThread = new CalculationThread();        calculationThread.start();    }    public void calculation(View view) {        Message message = new Message();        message.what = MsgWhat;        Bundle bundle = new Bundle();        String numb = editText.getText().toString();        bundle.putInt(KEY_NUM, Integer.parseInt(numb.isEmpty() ? "100" : numb));        message.setData(bundle);        calculationThread.handler.sendMessage(message);    }    private class CalculationThread extends Thread {        Handler handler;        @Override        public void run() {            Looper.prepare();            handler = new Handler() {                @Override                public void handleMessage(Message msg) {                    if (msg.what == MsgWhat) {                        final int upper = msg.getData().getInt(KEY_NUM);                        List numbs = new ArrayList<>();                        outer:                        for (int i = 2; i <= upper; ++i) {                            for (int j = 2; j <= Math.sqrt(i); ++j) {                                if (i != 2 && i % j == 0) {                                    continue outer;                                }                            }                            numbs.add(i);                        }                        Toast.makeText(MainActivity.this, numbs.toString(),                                Toast.LENGTH_LONG).show();                    }                    super.handleMessage(msg);                }            };            Looper.loop();            super.run();        }    }}

该示例的运行效果如下图所示:


显示效果

参考文献:《疯狂Android讲义(第2版)》

更多相关文章

  1. 教你如何在 Android(安卓)使用多线程下载文件
  2. Android(安卓)多线程编程:IntentService & HandlerThread
  3. Android的消息机制
  4. Android使用http协议与服务器通信
  5. 学习Android(安卓)Handler消息传递机制
  6. Android串口通信(Android(安卓)Studio)
  7. Andriod数据推送方案
  8. Android(安卓)GLES多线程处理
  9. Android输入分析

随机推荐

  1. android 连接USB按power键锁屏2声锁屏音
  2. Android(安卓)dialog 去除虚拟按键
  3. Android中文API(142) —— Gravity
  4. 总结android音频视频操作
  5. android intent.setType("type");的含义
  6. linux 配置安装android sdk自动下载缺少
  7. 去除listBView的抖动,判断textView中文本
  8. struts2中获取request、response,与androi
  9. android4.2 修改设置背景
  10. Android(安卓)模糊搜索rawquery bind or