关于Android线程的那些事儿

1. 线程的定义

线程thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

一个进程中可以并发多个线程,每条线程并行执行不同的任务。每个线程都会分配一个私有内存区域,该区域主要用于在执行过程中存储方法、局部变量和参数。一旦线程终止私有内存区将会被销毁。更多关于进程与线程的区别和联系请参照上一篇文章关于Android进程的那些事儿,在此不再重复。


2. Android的单线程模式

在Android的系统启动流程和应用启动流程这篇文章中有提到,Android应用启动时,zygote进程fork自身,开启一个Linux进程和一个主线程(Main Thread),主线程负责UI显示、更新、控件交互,因此主线程又叫做UI线程。

Android中的单线程模式必须遵循两条规则:

  • 不要阻塞 UI 线程
    原因:
    由于UI绘制和事件分发采用单线程模式,当UI线程中执行耗时操作(网络访问或数据库查询)时,UI线程会被阻塞,从而出现ANR(application not responding)或者类似NetworkOnMainThreadException之类的错误。

  • 不要在 UI 线程之外访问 Android UI 工具包
    原因:
    UI的显示、更新和控件交互主要通过Android UI 工具包(Android UI toolkit,包括android.widget和android.view)来实现的,而Android UI toolkit并非线程安全的工具包,如果我们在工作线程中刷新UI的时候,主线程也恰好在刷新UI,就会出现冲突。因此Android不允许在UI线程之外进行UI的相关操作,否则会出现CalledFromWrongThreadException的错误。


3. 工作线程和UI线程(主线程)

为了遵循上述单线程模式的两条规则,我们必须将耗时操作放在工作线程中,将UI操作放在UI线程中,同时有需要两个线程之间的切换。

3.1 主线程中开启工作线程

Java中提供了两种方法:Thread和Runnable

  • Thread
    继承Thread类覆写run()然后thread.start()

  • Runnable
    实现Runnable接口复写run()然后New Thread(Runnable).start()。

但是,在Android中这两种方法是不值得推荐的。最好使用Android自带的Handler,AsyncTask,IntentService, Loader,Service等方式来实现。下面会详细说明。

3.2 工作线程中访问UI线程

下面三个方法可以访问UI线程:

  • Activity.runOnUIThread(Runable)
  • View.post(Runable)
  • View.postDelayed(Runable,long)

但是如果主进程和工作线程之间需要更复杂的交互和操作,上面的方法就无能为力了,现在轮到Handler, AsyncTask, HandlerThread上场了,下面会展开详述。


4. 线程间通信

4.1 Handler

由于Android单线程模式下的两条原则的存在,线程间的通信显得尤为重要,Handler正是用来解决这个问题的。消息(Message)的传递需要Handler,MessageQueue, Looper共同来完成。其中Looper在Handler和MessageQueue之间起到桥梁和纽带的作用。在此之前,我们有必要了解一下相关概念

4.1.1 相关概念
  • Message
    Message类实现了Parcelable接口,因此它可以包含任意的数据对象,并把它传递给Handler。尽管Message的构造方法是public,但是我们还是推荐使用Message.obtain()或者Message.obtainMessage()来得到一个Message实例。

  • MessageQueue
    MessageQueue(消息队列)顾名思义,Message组成的一个List,但是Message并不是直接加入到MessageQueue的,而是通过与Looper绑定的Handler来实现的。

    获取当前线程的MessageQueue的方法是Looper.MyQueue()。

  • Looper
    Looper类用来操作一个线程的消息循环。用法如下:

class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();//创建looper

          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();//
      }
  }
  • Handler
    handler跟一个线程和该线程的messagequeue绑定的,当我们新建一个Handler的时候,它就会与当前线程,和线程的消息队列绑定在一起。Handler负责传递Message或者Runnable对象给MessageQueue,并当他们从消息队列出来的时候执行相关操作。

Handler有两个用途:
(1)安排message和runnable在将来的某个时刻得到执行
常用实现方法有
post(Runnable)
[postAtTime(Runnable, long)](https://developer.android.com/reference/android/os/Handler.html#postAtTime(java.lang.Runnable, long))
[postDelayed(Runnable, long)](https://developer.android.com/reference/android/os/Handler.html#postDelayed(java.lang.Runnable, long))
sendEmptyMessage(int)
sendMessage(Message)
[sendMessageAtTime(Message, long)](https://developer.android.com/reference/android/os/Handler.html#sendMessageAtTime(android.os.Message, long))
[sendMessageDelayed(Message, long)](https://developer.android.com/reference/android/os/Handler.html#sendMessageDelayed(android.os.Message, long))
其中,Message的处理是通过Handler的 handleMessage(Message)来实现的(需要继承Handler的基类)。
(2)在其他线程中执行某些操作。
Handler可以在线程间传递消息,工作原理如下

4.1.2 工作机制

Handler用来发送和处理Message或者Runnable对象,每个Handler实例都会绑定到一个线程和这个线程的MessageQueue。当创建Handler的时候,他会默认绑定到创建它的线程上,他会给MessageQueue发送message和runnable,在Looper轮训到该条消息的时候,回调创建该消息的Handler的handlerMessage方法,处理从message queue出来的message和runnable。
具体流程如下图:

关于Android线程的那些事儿_第1张图片
Handler,MessageQueue,Looper的工作流程

当应用启动时,新的进程被创建的时候,它的主线程会运行一个message queue来管理上层应用对象(Activity,Broadcast Receiver)和创建的窗口。也可以创建工作线程,然后创建Handler,在工作线程中调用postRunnable或者sendMessage类方法来传递消息给主线程。这时message或者Runnable对象将会在handler的message queue中排队并在它出站时得到执行。

关于Android线程的那些事儿_第2张图片
Looper, Handler and MessageQueue之间的关系

4.2 AsyncTask

4.2.1 使用方法

首先,继承AsyncTask类,至少重写doInBackground这个方法。比如

private class DownloadFilesTask extends AsyncTask {
@Override
     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;
     }
@Override
     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }
@Override
        protected void onPreExecute() {
        }
@Override
     protected void onPostExecute(Long result) {//doInBackground执行完成之后调用UI线程
         showDialog("Downloaded " + result + " bytes");
     }
 } 

然后在主线程中执行:

new DownloadFilesTask().execute(url1, url2, url3);
4.2.2 执行过程

一个异步任务的4步走:

  1. onPreExecute

  2. doInBackground

  3. onProgressUpdate

  4. onPostExecute
    从方法名可以理解这四个方法的功能和执行顺序,不再赘述。

4.2.3 注意事项
  1. 上述四个方法不可以手动调用
  2. 每个task的加载,创建,执行都必须在UI线程中执行。
  3. 该任务只能执行一次,第二次执行会抛异常
  4. AsyncTask开启线程的方法asyncTask.execute()默认是也是开启一个线程和一个队列的,不过也可以通过asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 0)开启一个含有5个新线程的线程池,也就是说有个5个队列了,假如说你执行第6个耗时任务时,除非前面5个都还没执行完,否则任务是不会阻塞的,这样就可以大大减少耗时任务延迟的可能性。

除了常用的Handler和AsyncTask之外,IntentService和HandlerThread也为线程间的通信提供了极大地便利

4.3 IntentService

有了intentService就可以不用自己启动和结束线程了,IntentService内部会自动执行线程的开启和销毁。

4.3.1 使用步骤

(1)继承IntentService类,并重写onHandleIntent()方法,这个方法中,执行耗时操作

public class myIntentService extends IntentService {

    public myIntentService() {
        super("myIntentService");
        // 注意构造函数参数为空,这个字符串就是worker thread的名字
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        //根据Intent的不同进行不同的事务处理 
        String taskName = intent.getExtras().getString("taskName");  
        switch (taskName) {
        case "task1":
            Log.i("myIntentService", "do task1");
            break;
        case "task2":
            Log.i("myIntentService", "do task2");
            break;
        default:
            break;
        }       
    }
  //--------------------用于打印生命周期--------------------    
   @Override
  public void onCreate() {
        Log.i("myIntentService", "onCreate");
    super.onCreate();
}

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("myIntentService", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.i("myIntentService", "onDestroy");
        super.onDestroy();
    }
}

然后通过startService(Intent i)启动服务,Intent 会携带相关数据,onHandleIntent会对这些数据进行识别,。

//同一服务只会开启一个worker thread,在onHandleIntent函数里依次处理intent请求。

        Intent i = new Intent("cn.scu.finch");  
        Bundle bundle = new Bundle();  
        bundle.putString("taskName", "task1");  
        i.putExtras(bundle);  
        startService(i);  
4.3.2 执行过程

onCreate--->onStartCommand-->onHandleIntent-->onDestroy

  • intentservice的oncreate方法中会通过HandlerThread 开启新线程,创建对应的Looper,和MessageQueue。

  • 通过onStartCommand()传递给服务intent被依次插入到工作队列中。工作队列又把intent逐个发送给onHandleIntent()。

  • 通过在onHandleIntent((Intent)msg.obj)中调用你的处理程序.

  • 处理完后会自动停止自己的服务Ondestroy

4.3.3 注意事项
  • IntentService 会创建一个线程,来处理所有传给onStartCommand()的Intent请求。
  • 对于startService()请求执行onHandleIntent()中的耗时任务,会生成一个消息队列,每次只有一个Intent传入onHandleIntent()方法并执行。也就是同一时间只会有一个耗时任务被执行,其他的请求还要在后面排队, onHandleIntent()方法不会多线程并发执行。
  • 当所有startService()请求被执行完成后,IntentService 会自动销毁,所以不需要自己写stopSelf()或stopService()来销毁服务。
  • 提供默认的onBind()实现 ,即返回null,不适合绑定的 Service。采用StartService来启动,即使启动它的Activity被销毁也不会影响service的生命周期
  • 提供默认的 onStartCommand() 实现,将intent传入等待队列中,然后到onHandleIntent()的实现。
  • Service等四大组件默认是运行在主线程上的!没有界面不代表不再UI线程。

4.4. HandlerThread

HandlerThread是Thread的子类,可以创建一个带有looper的线程,并创建与这个looper绑定的Handler,代码如下

HandlerThread mThread = new HandlerThread("message");
mThread.start();
Handler mHandler = new Handler(mThread.getLooper())
        {
            @Override
            public void handleMessage(Message msg)
            {
//   to do sth     
            }
        };

可以看出,跟Handler非常类似,只是新建HandlerThread时,与该线程绑定的looper也被创建了,所以不再需要我们自己去调用Looper.prepare(),Loop.loop()。直接通过thread.getLooper()就可以获取到当前线程的looper,类似于根据looper获取MesssageQueue的方法:Looper.MyQueue()。

5. 总结

由于Android系统的单线程模式,线程之间的通信必不可少,必须掌握Handler,AsyncTask, IntentService, HandlerThread的工作原理并加以熟练使用。其中Handler跟HandlerThread工作原理是一样的,很多情况下使用HandlerThread可以简化很多工作。

你可能感兴趣的:(关于Android线程的那些事儿)