Android多线程用法及消息机制

一、线程的基本用法
1、继承Thread类
这种方法定义一个线程只需要新建一个类继承自 Thread,然后重写父类的 run()方法,并在里面编写耗时逻辑即可:

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

启动这个线程只需要 new 出 MyThread 的实例,然后调用它的 start()方法,这样 run()方法中的代码就会在子线程当中运行了,如下所示:

new MyThread().start();

2、实现Runnable接口
那么什么情况下我们会使用这种方法呢?想象下这种情况:当你想要加入线程的那一个类或者活动已经继承了另外一个类,无法再继承Thread类的时候,我们一般选择实现Runnable接口:

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

当然这种方法的启动线程方式又有点不一样:

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

从上面可以看到,Thread 的构造函数接收一个 Runnable 参数,而我们 new 出的 MyThread 正是一个实现了 Runnable 接口的对象,所以可以直接将它传入到 Thread 的构造函数里。接着调用 Thread 的 start()方法, run()方法中的代码就会在子线程当中运行了。

当然,如果你不想专门再定义一个类去实现 Runnable 接口,也可以使用匿名类的方式,这种写法更为常见,如下所示:

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

二、在子线程中更新UI
和许多其他的 GUI 库一样, Android 的 UI 也是线程不安全的。也就是说,如果想要更新应用程序里的 UI 元素,则必须在主线程中进行,不能在子线程中更新UI,否则就会出现异常。
但是有些时候,我们必须在子线程里去执行一些耗时任务,然后根据任务的执行结果来更新相应的 UI 控件,这该如何是好呢?
对于这种情况, Android 提供了一套异步消息处理机制,完美地解决了在子线程中进行UI 操作的问题。
代码
布局文件:设置一个按钮,用于改变位于屏幕中间的“蓝瘦”

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.turo.androidthreadtest.MainActivity">

    <Button
        android:id="@+id/change_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Change Text"
        android:textAllCaps="false"/>

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textSize="30dp"
        android:text="蓝瘦!" />
</RelativeLayout>

再来看MainActivity代码:

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    public static final int UPDATE_TEXT = 1;//用于表示改变text内容的动作
    private Button change_text_btn;
    private TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        change_text_btn = (Button) findViewById(R.id.change_text);
        change_text_btn.setOnClickListener(this);
        tv = (TextView) findViewById(R.id.tv);
    }

    @Override
    public void onClick(View view) {
        switch (view.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);//将message对象发送出去
                   }
               }).start();
                break;
            default:
                break;
        }
    }

    private Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_TEXT:
                // 在这里可以进行UI操作
                    tv.setText("香菇");
                    break;
                default:
                    break;
            }
        }
    };
}

这里我们先是定义了一个整型常量 UPDATE_TEXT,用于表示更新 TextView 这个动作。然后新增一个 Handler 对象,并重写父类的 handleMessage 方法,在这里对具体的 Message进行处理。如果发现 Message 的 what 字段的值等于 UPDATE_TEXT,就将 TextView 显示的内容改成 香菇。
再来看一下 Change Text 按钮的点击事件中的代码。可以看到,我们并没有在子线程里直接进行 UI 操作,而是创建了一个 Message( android.os.Message)对象,并将它的 what 字段的值指定为 UPDATE_TEXT,然后调用 Handler 的 sendMessage()方法将这条Message 发送出去。很快, Handler 就会收到这条 Message,并在handleMessage()方法中对它进行处理。注意此时 handleMessage()方法中的代码就是在主线程当中运行的了,所以我们可以放心地在这里进行 UI 操作。接下来对 Message 携带的 what 字段的值进行判断,如果等于UPDATE_TEXT,就将 TextView 显示的内容改成”香菇”。
现在我们运行程序,可以看到屏幕的正中央显示着 “蓝瘦”。然后点击一下 ChangeText 按钮,显示的内容着就被替换成“香菇”:
这里写图片描述

解析异步消息处理机制:
Android 中的异步消息处理主要由四个部分组成, Message、Handler、 MessageQueue 和Looper。其中 Message和 Handler在上一小节中我们已经接触过了,下面对这四个部分进行一下简要的介绍。
1. Message
Message 是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。上一小节中我们使用到了 Message 的 what 字段,除此之外还可以使用 arg1 和 arg2 字段来携带一些整型数据,使用 obj 字段携带一个 Object 对象。
2. Handler
Handler 顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用 Handler 的 sendMessage()方法,而发出的消息经过一系列地辗转处理后,最终会传递到 Handler 的 handleMessage()方法中。
3. MessageQueue
MessageQueue 是消息队列的意思,它主要用于存放所有通过 Handler 发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个 MessageQueue对象。
4. Looper
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 操作了。整个异步消息处理机制的流程示意图如下图所示。
这里写图片描述

三、使用AsyncTask异步下载图像
使用 AysncTask 能够在异步任务进行的同时,将任务进度状态反馈给 UI 线程(如让 UI 线程更新进度条)。正是由于它与 UI 线程紧密相关,使用的时候要就有一些限制,AysncTask 必须在 UI 线程中创建,并在 UI 线程中启动(通过调用其 execute() 方法);此外,AysncTask 设计的目的是用于一些耗时较短的任务,如果是耗时较长的任务不推荐使用AysncTask。
可以用简化记忆 “三参数,四步骤” 来学习 AysncTask。 即带有三个模板参数 Params, Progress, Result,四个处理步骤onPreExecute,doInBackground,onProgressUpdate,onPostExecute。
三参数:
Params 是异步任务所需的参数类型,也即 doInBackground(Params… params) 方法的参数类型;Progress 是指进度的参数类型,也即 onProgressUpdate(Progress… values) 方法的参数类型;Result 是指任务完成返回的参数类型,也即 onPostExecute(Result result) 或onCancelled(Result result) 方法的参数类型。

如果某一个参数类型没有意义或没有被用到,传递 void 即可。

四步骤:
protected void onPreExecute():在 UI 线程中运行,在异步任务开始之前被执行,以便 UI 线程完成一些初始化动作,如将进度条清零;

protected abstract Result doInBackground(Params… params):在后台线程中运行,这是完成异步任务的地方,它是抽象接口,子类必须提供实现;

protected void onProgressUpdate(Progress… values):在 UI线程中运行,在异步任务执行的过程中可以通过调用 void publishProgress(Progress… values) 方法通知 UI 线程在 onProgressUpdate 方法内更新进度状态;

protected void onPostExecute(Result result):在 UI 线程中运行,当异步任务完成之后被执行,以便 UI 线程更新任务完成状态。

AysncTask 支持取消异步任务,当异步任务被取消之后,上面的步骤四就不会被执行了,取而代之将执行 onCancelled(Result result),以便 UI 线程更新任务被取消之后的状态。谨记:上面提到的这些方法都是回调函数,不需要用户手动去调用。

以前的 AysncTask 是基于单一后台线程实现的,而从 Android 3.0 起 AysncTask 是基于 Android 的并发库(Java.util.concurrent)实现的,本文中不会展开讨论其具体实现,只是演示如何使用 AysncTask。

使用示例:
这是一个使用 AysncTask 从网络上异步下载图片并在 ImageView 中显示的的简单示例。因为需要访问网络,所以要在 manifest.xml 中添加网络访问权限:

<uses-permission android:name="android.permission.INTERNET">  
</uses-permission>

布局文件很简单,一个 Button,一个 ImageView:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical"  
    android:padding="10dip" >  

    <Button  
        android:id="@+id/LoadButton"  
        android:layout_width="fill_parent"  
        android:layout_height="wrap_content"  
        android:text="Load">  
    </Button>  

    <ImageView  
        android:id="@+id/ImageVivew"   
        android:layout_width="match_parent"   
        android:layout_height="400dip"   
        android:scaleType="centerInside"   
        android:padding="2dp">  
    </ImageView>   

</LinearLayout>  

接下来看代码:

import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import java.io.InputStream;

public class MainActivity extends AppCompatActivity {

    private static final String sImageUrl = "http://fashion.qqread.com/ArtImage/20110225/0083_13.jpg";
    private Button mLoadButton;
    private ImageView mImageView;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.i("UI thread", " >> onCreate()");

        mImageView = (ImageView)this.findViewById(R.id.ImageVivew);

        mLoadButton = (Button)this.findViewById(R.id.LoadButton);
        mLoadButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LoadImageTask task = new LoadImageTask(v.getContext());
                task.execute(sImageUrl);
            }
        });
    }

    class LoadImageTask extends AsyncTask<String, Integer, Bitmap>
    {
        private ProgressDialog mProgressBar;

        LoadImageTask(Context context)
        {
            mProgressBar = new ProgressDialog(context);
            mProgressBar.setCancelable(true);
            mProgressBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            mProgressBar.setMax(100);
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            Log.i("Load thread", " >> doInBackground()");

            Bitmap bitmap = null;

            try{
                publishProgress(10);
                Thread.sleep(1000);

                InputStream in = new java.net.URL(sImageUrl).openStream();
                publishProgress(60);
                Thread.sleep(1000);

                bitmap = BitmapFactory.decodeStream(in);
                in.close();
            } catch (Exception e) {
                e.printStackTrace();
            }

            publishProgress(100);
            return bitmap;
        }

        @Override
        protected void onCancelled() {
            super.onCancelled();
        }

        @Override
        protected void onPreExecute() {

            mProgressBar.setProgress(0);
            mProgressBar.setMessage("Image downloading ... %0");
            mProgressBar.show();

            Log.i("UI thread", " >> onPreExecute()");
        }

        @Override
        protected void onPostExecute(Bitmap result) {
            Log.i("UI thread", " >> onPostExecute()");
            if (result != null) {
                mProgressBar.setMessage("Image downloading success!");
                mImageView.setImageBitmap(result);
            }
            else {
                mProgressBar.setMessage("Image downloading failure!");
            }

            mProgressBar.dismiss();
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            Log.i("UI thread", " >> onProgressUpdate() %" + values[0]);
            mProgressBar.setMessage("Image downloading ... %" + values[0]);
            mProgressBar.setProgress(values[0]);
        }
    };
}

在 LoadImageTask 中,前面提到的四个步骤都涉及到了:

首先在任务开始之前在 onPreExecute() 方法中设置进度条的初始状态(UI线程);然后在下载线程中执行 doInBackground() 以完成下载任务,并在其中调用 publishProgress() 来通知 UI 线程更新进度状态;UI 线程在 onProgressUpdate() 中得知进度,并更新进度条(UI线程);最后下载任务完成,UI 线程在 onPostExecute() 中得知下载好的图像,并更新UI显示该图像(UI线程)。

猜你喜欢

转载自blog.csdn.net/turodog/article/details/52875839