Android 线程交互

 在Android开发过程中,耗时操作是不允许写在主线程(UI线程)中的,以免由于等待时间过长而发生ANR。所以耗时操作需要创建子线程来完成,然而往往这些操作都需要与主线程进行通讯交互(例如更新主线程的UI),但android规定除了UI线程外,其他线程都不可以对UI控件进行访问或操控,所以我们需要通过一些方法来实现这些功能。

 

1. Handler:

 

handler是android中专门用来在线程之间传递信息类的工具。

API参考:https://developer.android.google.cn/reference/android/os/Handler

1、在B线程中调用Looper.prepare和Looper.loop。(主线程不需要)
2、编写Handler类,重写其中的handleMessage方法。
3、创建Handler类的实例,并绑定looper
4、调用handler的sentMessage方法发送消息。

 

  • 子线程更新主线程(UI)

因为主线程自带Looper机制,所有我们不用创建Looper:

Handler mHandler = new Handler(){  
 
@Override  
public void handleMessage(Message msg) {  
    super.handleMessage(msg);  
    switch (msg.what) {  
        case 1:  
            //do something,refresh UI;  
            break;  
        default:  
            break;  
        }  
    }    
   
};  

 

然后开启一个子线程,在子线程里直接使用Handler发送消息:

new Thread() {
    public void run() {
    Message message = new Message();
    message.what = 1;     message.obj
= "子线程发送的消息Hi~Hi";     mHandler .sendMessage(message);
  }; }.start();

 

  • 子线程之间交互

public class ThreadActivity extends AppCompatActivity {

  private final String TAG = "ThreadActivity";

  // 子线程Handler
  private Handler mSubHandler = null;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_thread1);
    new MyThread().start();
    createThread();
  }


  /**
   * 创建子线程,用于发送消息
   */
  private void createThread() {
    new Thread() {
      @Override
      public void run() {
        int count = 0;
        while (count < 10) {
          Message msg = new Message();
          msg.obj = "子线程计时器:" + count;
          msg.what = 1;
          // 使用子线程Handler发送消息
          mSubHandler.sendMessage(msg);
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          count++;
        }
      }
    }.start();
  }

  /**
   * 用于接收子线程发送过来的消息
   */
  class MyThread extends Thread {

    @Override
    public void run() {
      Looper.prepare();
      mSubHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
          switch (msg.what) {
            case 1:
            Log.i(TAG, (String) msg.obj);
            break;
          }
        }
      };
      Looper.loop();
    }
  }
}

 

2. HandlerThread:

HandlerThread是一个包含Looper的Thread,我们可以直接使用这个 Looper 创建 Handler。

API参考:https://developer.android.google.cn/reference/android/os/HandlerThread

 

HandlerThread适用于单线程+异步队列模型场景,相对Handler + Thread简洁。

// 也可实现run方法
HandlerThread mHandlerThread = new HandlerThread("HandlerThread_Test"); mHandlerThread.start(); Handler mThreadHandler = new Handler(mHandlerThread.getLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: Log.i(TAG, "threadName--" + Thread.currentThread().getName() + (String) msg.obj); break; } } }; // 发送消息至HanderThread mThreadHandler.sendMessage(msg);

 

3. runOnUiThread

Activity 里面的runOnUiThread( Runnable )方法使子线程更新UI更为简洁。另外还有View.Post(Runnable)和View.PostDelayed(Runnabe,long)方法,用法与runOnUiThread基本相同。

new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep( 1000 );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // 执行UI操作 
                Toast.makeText(MainActivity.this, "Test", Toast.LENGTH_SHORT).show();
            }
        });
    }
}).start();

 

4. AsyncTask

API参考:https://developer.android.google.cn/reference/android/os/AsyncTask

AsyncTask是一个抽象类,它是由Android封装的一个轻量级异步类。它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。AsyncTasks应该用于短操作(最多几秒)。如果您需要保持线程长时间运行,强烈建议您使用java.util.concurrent包提供的各种API,例如Executor,ThreadPoolExecutor和FutureTask。 异步任务由3个泛型类型定义:Params,Progress和Result,以及4个步骤:onPreExecute,doInBackground,onProgressUpdate和onPostExecute组成。

使用AsyncTask时需要继承AsyncTask类并必须实现doInBackground(params...)方法,大多数情况下还需要实现onPostExecute(Result)方法。

 

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
    protected Long doInBackground(URL... urls) {
        int count = urls.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++) {
            totalSize += Downloader.downloadFile(urls[i]);
            publishProgress((int) ((i / (float) count) * 100));
            // Escape early if cancel() is called
            if (isCancelled()) break;
        }
        return totalSize;
    }

    protected void onProgressUpdate(Integer... progress) {
        setProgressPercent(progress[0]);
    }

    protected void onPostExecute(Long result) {
        showDialog("Downloaded " + result + " bytes");
    }
}

 

创建成功后执行任务非常简单:

new DownloadFilesTask().execute(url1, url2, url3); // Params

 

AsyncTask的泛型类型:

  • Params:在执行时传递给任务的参数类型;
  • Progress:异步任务执行过程中,返回执行进度值的类型;
  • Result:后台任务执行结果类型。

并非所有类型都必须在异步任务中使用,如果不需要使用,则用Void来代替。

rivate class MyTask extends AsyncTask<Void, Void, Void> { ... }

 

当执行一个异步任务时,需要经历4个步骤:

  • onPreExecute():在异步任务执行前,在UI主线和中调用。此步骤通常用于设置任务,例如在用记界面中显示进度条。
  • doInBackground(Params...):onPreExecute()执行完后,立即在后台线程中调用此方法。此步骤用于执行运行时间可能较长的后台任务,参数由execute(params...)传入。通过此步骤得到结果并返回到最后一步。在计算过程中,可通过调用publishProgress(Progress...)方法,并通过onProgressUpdate(Progress...)更新UI显示任务执行进度。
  • onProgressUpdate(Progress...):当publishProgress(Progress...)方法执行后,此步骤在UI主线程中被调用,并更新UI当前任务进度。
  • onPostExecute(Result):在后台线程计算完成后在UI线程上调用。 后台线程计算的结果作为参数传递给此步骤。

AsyncTask还提供了cancelled(boolean)方法,它同样在主线程中执行,当异步任务取消时,onCancelled()会被调用,这个时候onPostExecute()则不会被调用,但是要注意的是,AsyncTask中的cancel(boolean)方法并不是真正去取消任务,只是设置这个任务为取消状态,我们需要在doInBackground()判断终止任务。就好比想要终止一个线程,调用interrupt()方法,只是进行标记为中断,需要在线程内部进行标记判断然后中断线程。参数true表示立即取消任务(不一定成功),false则表示允许任务执行完成后再取消。

使用AsyncTask还需要注意以下问题:

  • 异步任务的实例必须在UI线程中创建,即AsyncTask对象必须在UI线程中创建。
  • execute(Params...) 必须在UI线程上执行。
  • 不要手动调用onPreExecute(), onPostExecute(Result), doInBackground(Params...), onProgressUpdate(Progress...)方法。
  • 该任务只能执行一次(如果尝试执行第二次,则会引发异常)。
  • 不能在doInBackground(Params... params)中更改UI组件的信息。
  • AsyncTask不与任何组件绑定生命周期,所以在Activity/或者Fragment中创建执行AsyncTask时,最好在Activity/Fragment的onDestory()调用 cancel(boolean);
  • 如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。
  • 屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask(非静态的内部类)会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。

AsyncTask里面有两种线程池供我们调用,默认使用SERIAL_EXECUTOR。AsyncTask的核心线程是5,队列容量是128,最大线程数是9。

  • THREAD_POOL_EXECUTOR, 异步线程池。
  • SERIAL_EXECUTOR,同步线程池。

 

 一个简单的AsyncTask例子:

自定义AsyncTask:

class TestAsyncTask extends AsyncTask<Integer, Integer, Integer> {

    @Override
    protected Integer doInBackground(Integer... integers) {
        int count = integers[0];
        int len = integers[1];
        while (count < len) {
            int pro = 100 * count / len;
            Log.i(TAG, "----------" + pro + ":" + count + ":" + len);
            publishProgress(pro);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count++;
        }
        return count;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        mText.setText("Progress:" + values[0] + "%");
    }

    @Override
    protected void onPostExecute(Integer integer) {
        mText.setText("Finished:" + integer + "%");
    }
}

 

创建TestAsyncTask实例:

mAsyncTask.execute(0, 100);

 

关键词:线程 asynctask 任务 执行 nbsp void ui handler 方法 msg

相关推荐:

四. 线程管理之Android中的多线程

Android 的线程和线程池

Android源码系列(15) -- AsyncTask

Andriod中的线程形态

Android AsyncTask 源码分析

android的线程和线程池

Android查缺补漏(线程篇)-- AsyncTask的使用及原理详细分析

Android 多线程:这是一份详细的AsyncTask使用教程

Android面试收集录7 AsyncTask详解

Android 源码分析(三)安卓中的线程