当我们需要执行一些耗时操作,比如说发起一条网络请求时,考虑到网速等其他原因,服务器未必会立刻响应我们的请求,如果不将这类操作放在子线程里去执行,就会导致主线程被阻塞住,从而影响用户对软件的正常使用。

  

  线程的基本用法

  Android多线程编程其实并不比Java多线程编程特殊,基本都是使用相同的语法。比如说,定义一个线程只需要新建一个类继承自Thread,然后重写父类的run()方法,并在里面编写耗时逻辑即可。

class MyThread extends Thread {
@Override
public void run(){
//处理具体的逻辑
}
}

  启动线程只需要调用它的start()方法,这样run()方法中的代码就会在子线程当中运行了。

new MyThread().start();

  使用继承的方式耦合性有点高,更多的时候我们都会选择使用实现Runnable接口的方式来定义一个线程。

class MyThread implements Runnable {
@Override
public void run(){
//处理具体的逻辑
}
}

  使用这种写法,启动线程的方法也需要进行相应的改变。

MyThread myThread = new MyThread();
new Thread(myThread).start();

  也可以使用匿名类的方式。

new Thread(new Runnable(){
@Override
public void run(){
//处理具体的逻辑
}
}).start();

  在子线程中更新UI

  和许多其他的GUI库一样,Android的UI也是线程不安全的。也就是说,如果想要更新应用程序里的UI,则必须在主线程中进行,否则就会出现异常。

  新建一个AndroidThreadTest项目,修改activity_main,xml中的代码。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"> <Button
android:id="@+id/change_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Change Text"/> <TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:layout_centerInParent="true"
android:textSize="20sp"/> </RelativeLayout>

  布局文件中定义了两个控件,TextView用于在屏幕的正中央显示一个Hello world字符串,Button用于改变TextView中显示的内容,希望的结果是点击Button后可以把TextView中显示的字符串改成Nice to meet you。

  修改MainActivity的代码

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private TextView text;

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text);
Button changeText = (Button) findViewById(R.id.change_text);
changeText.setOnClickListener(this);
} @Override
public void onClick(View v) {
switch (v.getId()){
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
text.setText("Nice to meet you");
}
}).start();
break;
default:
break;
}
}
}

  在Change Text按钮的点击事件里面开启了一个子线程,然后在子线程中调用TextView的setText()方法将显示的字符串改成Nice to meet you。运行程序,点击Change Text按钮,程序就会崩溃。

  

  由此可见,Android是不允许在子线程中进行UI操作的。但有些时候,我们需要在子线程里去执行一些耗时任务,然后根据任务的执行结果来更新相应的UI控件。对于这种情况,Android提供了一套异步消息处理机制,完美地解决了在子线程中进行UI操作的问题。

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    public static final int UPDATE_TEXT = 1;

    private TextView text;

    private Handler handler = new Handler(){
public void handleMessage(Message msg){
switch (msg.what){
case UPDATE_TEXT:
text.setText("Nice to meet you");
break;
default:
break;
}
}
}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text);
Button changeText = (Button) findViewById(R.id.change_text);
changeText.setOnClickListener(this);
} @Override
public void onClick(View v) {
switch (v.getId()){
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message);
}
}).start();
break;
default:
break;
}
}
}

  这里定义了一个整型常量UPDATE_TEXT,用于表示更新TextView这个动作。然后新增一个Handler对象,并重写父类的handleMessage()方法,在这里对具体的Message进行处理。如果发现Message的what字段的值等于UPDATE_TEXT,就将TextView显示的内容改成Nice to meet you。

  再看一下点击事件中的代码,这次没有在子线程里直接进行UI操作,而是创建了一个Message(android.os.Message)对象,并将它的what字段的值指定为UPDATE_TEXT,然后调用Handler的sendMessage()方法将这条Message发送出去。Handler会收到这条Message,并在handleMessage()方法中对它进行处理。此时handleMessage()方法中的代码就是在主线程当中运行的,所以可以在这里进行UI操作。接下来对Message携带的what字段的值进行判断,如果等于UPDATE_TEXT,就将TextView显示的内容改成Nice to meet you。重新运行程序,点击按钮,Hello World就会被替换成Nice to meet you。

  

  这就是Android异步处理的基本用法,使用这种机制可以出色地解决掉在子线程中更新UI的问题。下面来分析一下Android异步消息处理机制的工作原理。

  解析异步消息处理机制

  Android中的异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper。

    ♦ Message:线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。上面我们用到了Message的what字段,除此之外还可以使用arg1和arg2字段来携带一些整型数据,使用obj字段携带一个Object对象。

    ♦ Handler:处理者,它主要是用于发送和处理消息的。发送消息一般是使用Handler的sendMessage()方法,而发出的消息经过一系列地辗转处理后,最终会传递到Handler的handleMessage()方法中。

    ♦ MessagQueue:消息队列,主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。

    ♦ Looper:每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限循环中,然后每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程中也只会有一个Looper对象。

  了解了Message、Handler、MessageQueue和Looper的基本概念后,再把异步消息处理的整个流程梳理一遍,首先需要在主线程当中创建一个Handler对象,并重写handleMessage()方法。然后当子线程需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理的消息,最后分发回Handler的handleMessage()方法中。由于Handler是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行UI操作了。整个异步消息处理机制的流程示意图如图:

  

  一条Message经过这样一个流程的辗转调用后,也就从子线程进入到主线程,从不能更新UI变成了可以更新UI,整个异步消息处理的核心思想就是如此。

Android异步消息处理机制(多线程)的更多相关文章

  1. 【转载】Android异步消息处理机制详解及源码分析

    PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbob ...

  2. Android异步消息处理机制

    安卓子线程无法直接更改UI,所以需要异步消息处理机制来解决 <?xml version="1.0" encoding="utf-8"?><Li ...

  3. Android 异步消息处理机制解析

    Android 中的异步消息处理主要由四个部分组成,Message.Handler.MessageQueue.Looper.下面将会对这四个部分进行一下简要的介绍. 1. Message: Messa ...

  4. 【转】Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38377229 ,本文出自[张鸿洋的博客] 很多人面试肯定都被问到过,请问Andr ...

  5. Android 异步消息处理机制 让你在深入了解 Looper、Handler、Message之间的关系

    转载请注明出处:http://blog.csdn.net/lmj623565791/article/details/38377229 ,本文出自[张鸿洋的博客] 非常多人面试肯定都被问到过,请问And ...

  6. Android 异步消息处理机制终结篇 :深入理解 Looper、Handler、Message、MessageQueue四者关系

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.概述 我们知道更新UI操作我们需要在UI线程中操作,如果在子线程中更新UI会发生异常可能导致崩溃,但是在UI线程中进行耗时操作又会导致ANR,这 ...

  7. Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系

    转自:http://blog.csdn.net/lmj623565791/article/details/38377229 ,本文出自[张鸿洋的博客] 很多人面试肯定都被问到过,请问Android中的 ...

  8. Android异步消息处理机制完全解析,带你从源码的角度彻底理解(转)

    开始进入正题,我们都知道,Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃.相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创建一 ...

  9. 【转】Android异步消息处理机制完全解析,带你从源码的角度彻底理解

    原文网址:http://blog.csdn.net/guolin_blog/article/details/9991569 转载请注明出处:http://blog.csdn.net/guolin_bl ...

随机推荐

  1. ssh连接提示 "Connection closed by remote host"

    如果原来是可以用ssh连接的, 突然连接不上通常是连接数过多导致的. 解决方法一. 把SSH连接数改大 修改服务器上的这个文件:/etc/ssh/sshd_config 找到这行: # MaxSess ...

  2. Codeforces 321E Ciel and Gondolas

    传送门:http://codeforces.com/problemset/problem/321/E [题解] 首先有一个$O(n^2k)$的dp. # include <stdio.h> ...

  3. 【洛谷 P5110】 块速递推(矩阵加速,分块打表)

    题目链接 掌握了分块打表法了.原来以前一直想错了... 块的大小\(size=\sqrt n\),每隔\(size\)个数打一个表,还要在\(0\text{~}size-1\)每个数打一个表. 然后就 ...

  4. Network(POJ3694+边双连通分量+LCA)

    题目链接:http://poj.org/problem?id=3694 题目: 题意:给你一个n个点m条边的无向连通图,进行q次操作,每次操作在u和v之间加一条边,问每次操作之后“桥”的数量. 思路: ...

  5. Intersecting Lines (计算几何基础+判断两直线的位置关系)

    题目链接:http://poj.org/problem?id=1269 题面: Description We all know that a pair of distinct points on a ...

  6. Winform MD5

    1:MD5 http://www.cmd5.com/ 字节数组----字符串 //将字节数组中每个元素按照指定的编码格式解析成字符串//直接将数组ToString()//将字节数组中的每个元素ToSt ...

  7. bzoj 1054 bfs

    就是bfs,对于每个状态存一个hash为当前状态矩阵的二进制表示,然后搜就行了,写成双向bfs会快很多. 反思:对于C++的数组从0开始还不是特别习惯,经常犯错,对于C++的结构体不熟. /***** ...

  8. jq_从右向右的滑入滑出效果

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  9. nodejs安装express以后,使用express显示不是内部或外部命令

    1.问题描述 在命令窗口通过npm install -g express 安装express以后,通过express -e express新建工程失败,提示express不是内部或外部命令 2.解决方 ...

  10. 【转】debian下的update-rc.d的使用

    在Linux系统下,一个Services的启动.停止以及重启通常是通过/etc/init.d目录下的脚本来控制的.然而,在启动或改变运行级别时, 是在/etc/rcX.d中来搜索脚本.其中X是运行级别 ...