Android 中的线程形态 -- AsyncTask,HandlerThread,IntentService

主线程和子线程


主线程是指进程所拥有的线程,在java中默认情况下一个进程只有一个线程,这个线程就是主线程。主线程主要处理界面交互相关的逻辑,因为用户随时会和界面发生交互,因此主线程在任何时候都必须有较高的响应速度,否则就会产生一种界面卡顿的感觉。为了保持较高的响应速度,就要求主线程不能执行耗时的任务,这个时候子线程就派上用场了。子线程也叫工作线程,除了主线程之外的线程都是子线程。

Android 沿用了java、的线程模型,其中的线程也分为主线程和子线程,其中的主线程也叫UI线程。主线程的作用是运行四大组件以及处理他们和用户的交互,而子线程的作用则是执行耗时任务,比如网络请求、I/O操作等。从Android 3.0开始系统要求网络访问必须在子线程中进行,否则网络访问将会失败并抛出NetworkOnMainThreadExceptioin这个异常,这样做是为了避免由于被耗时操作所阻塞从而出现ANR现象。


AsynaTask

AsynaTask 是一种轻量级的异步人物类,他可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程中更新UI。从实现上来说,AnyncTask封装了Thread和Handler,通过AsyncTask可以更加方便的执行后台任务以及在主线程中访问UI,但是AsyncTask并不适合进行特别耗时的任务,对于特别耗时的任务来说,建议使用线程池。

AsynaTask 是一个抽象的泛型类,他提供了Params、Progress和Result这三个泛型参数,其中Params表示参数的类型,Projress表示后台任务的执行进度的类型,而Result则表示后台任务的返回结果的类型,如果AsyncTask确实不需要传递具体的参数,那么这三个泛型参数可以用Void来代替。AsyncTask这个类的申明如下所示。

public abstract class AsyncTask<Params,Progress,Result>


AsynacTask 提供了4个核心方法,他们的含义如下所示。

1)onPreExecute(),在主线程中执行,在异步任务执行之前,此方法会被调用,一般可以用于做一些准备工作。

2)doInBackground(Params...params),在线程池中执行,此方法用于执行异步任务,params参数表示异步任务的输入参数。在此方法中可以通过publishProgress方法来更新任务条的进度,publishProgress方法会调用onProgressUpdate方法。另外此方法需要返回计算结果给onPostExecute方法。

3)onProgressUpdate(Progress... values),在主线程中执行,当后台任务的执行进度发生改变时此方法会被调用。

4)onPostExecute(Result result),在主线程中执行,在异步任务执行之后,此方法会被调用,其中result参数是后台任务的返回值,即doInBackground的返回值。

上面这几个方法,onPreExecute先执行,接着是doInBackground,最后才是onPosttExecute。除了上述四个方法以外,AsyncTask还提供了onCancelled()方法,他同样在主线程中执行,当异步任务呗取消时,onCancelled()方法会被调用,这个时候onPostExecute则不会被调用。下面提供一个典型的实例,如下所示。

 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");
        }
    }


在上面的代码中,实现了一个具体的AsbybcTask类,这个类主要用于模拟文件的下载过程,他的输入参数类型为URL,后台任务的进程参数为Integer,而后台任务的返回结果为Long类型。注意到doInBackground和onRrogressUpdate方法他们的参数中均包含... 的字样,在java中... 表示参数的数量不定,他是一只数组型参数。当要执行上述下载任务时,可以通过如下方式来完成。

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

在DownloadFilesTask中,doInBackground用来执行具体的下载任务并通过publishProjress方法来更新下载的进度,同时还有判断下载任务是否被外界取消了,当下载任务完成后,onPostExecute方法就会被调用,他也是运行在主线程中,这个时候我们就可以在界面上做一些提示,比如弹出一个对话框告知用户下载已经完成了。

AsyncTask在具体的使用过程中也是有一些限制的,,主要有如下几点:

1)AsyncTask的类必须在主线程中加载,这就意味着第一次访问AsyncTask必须发生在主线程

2)AsyncTask的对象必须在主线程中创建。

3)execute方法必须在UI线程调用。

4)不要在程序中直接调用onPreExecute(),onPostExecute,doInBackground和onProgressUpdate方法。

5)一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行时异常。



HandlerThread

HandlerThread继承了Thread,他是一种可以使用Handler的thread,他的实现也很简单,就是在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环,这样在实际的使用中就允许在HandlerThread中创建Handler了。HandlerThread的run方法如下所示。

public void run(){
		mTid = Process.myTid();
		Looper.prepare();
		synchronized (this) {
			mLooper = getMainLooper().myLooper();
			notifyAll();
		}
		Process.setThreadPriority(mPrioroty);
		onLooperPrepared();
		Looper.loop();
		mTid = -1;
	}


从HandlerThread的实现来看,它个普通的Thread有显著的不同之处。普通的Thread主要用于在run方法中执行一个耗时任务,而HandlerThread在内部创建了消息队列,外界需要通过Handler的消息方式来通知HandlerThread在内部创建了消息队列,外界需要通过Handler的消息方式来通知HandlerThread执行一个具体的任务。HandlerThread是一个很有用的类,他在Android 中的一个具体的使用场景是IntentService,IntentSersive将在后面介绍。由于HandlerThread的run方法是一个无线循环,因此当明确不需要再使用HandlerThread时,可以通过他的quit或者quitSafely方法来终止线程的执行,这是一个良好的编程习惯。



IntentService

IntentService是一种特殊的Service,它继承了Service并且它是一个抽象类,因此必须创建它的子类才能使用IntentService。IntentService可用于执行后台耗时的任务,当任务执行后他会自动停止,同时由于IntentService是服务的原因,这导致他的优先级比单纯的线程高很多,所以IntentService比较适合执行一些高级优先的后台任务,因为它优先级高不容易被系统杀死。在实现上,IntentService封装了HandlerThread和Handler,这一点可以从它的onCreate方法中看出来,如下所示。

public void onCreate(){
		super.onCreate();
		HandlerThread thread = new HandlerThread("IntentService["+mName+"]");
		thread.start();
		
		mServiceLooper = thread.getLooper();
		mServiceLooper = new ServiceHandler(mServiceLooper);
	}

当IntentService 被第一次启动时,他的onCreate方法会被调用,onCreate方法会创建一个HandlerTHread,然后使用它的Looper来构造一个Handler对象mServiceHandler,这样通过mServiceHandler发送的消息最终都会在HandleThread中执行,从这个角度来看,IntentService也可以用于后台任务。每次启动IntentService,他的onStartCommand方法就会调用一次,IntentService方法时如何处理外界的Intent的,onStartCommand调用了onStart,onStart方法如下所示。

public void onStart(Intent intent,int startId){
		Message message = mServiceHandler.obtainMessage();
		message.arg1 = startId;
		message.obj =intent;
		mServiceHandler.sendMessage(msg);
	}


可以看出,IntentService仅仅是通过mServiceHandler发送了一个消息,这个消息会在HandlerThread中被处理。mServiceHandler收到消息后,会将传递给onHabdleIntebt方法去处理。注意这个Intent对象的内容和外界的starrtService(intent)中的intent的内容是完全一致的,通过这个Intent对象的内容和外界的startService(intent) 中的intent的内容是完全一致的,通过这个Intent对象即可解析出外界启动IntentService是所传递的参数,通过这些参数就可以区分具体的后台任务,这样在onHanleIntent方法中就可以对不同的后台任务来做处理了。当onHandleIntent 方法执行结束后,IntentService会通过stopSelf(int startId) 方法来尝试停止服务。这里之所以采用stopSelf(int startId)而不是stopSelf()来停止服务,那是因为stopSelf()会立刻停止服务,而这个时候可能还有其他消息未处理,stopSelf(int startId)则会等待所有的消息都处理完毕后才终止服务。一般来说,stopSelf(int startId)在尝试停止服务之前会判断最近启动服务的次数是否和startId相等,如果相等就立刻停止服务,不相等则不停止服务,这个策略可以从AMS的stopServiceToken方法的实现中找到依据。ServiceHandler的实现如下所示。

private final class ServiceHandler extends Handler{
		public ServiceHandler(Looper looper) {
			super(looper);
		}

		@Override
		public void handleMessage(Message msg) {
			onHandleIntent((Intent)msg.abj);
			super.handleMessage(msg);
		}
		
	}

IntentService 的 onHandleIntent 方法是一个抽象方法,他需要我们在子类中实现,它的作用是从Intent参数中区分具体的任务并执行这些任务,如果目前只存在一个后台任务,那么onHandlerIntent 方法执行完这个任务后,stopSelf(int startId) 就会直接停止服务;如果目前存在多个后台任务,那么当onHandleIntent方法执行完最后一个任务时,stopSelf(int startId) 才会直接停止服务。另外,由于每执行一个后台任务就必须启动一次IntentService,而IntentService内部则通过消息的方式向HandlerThread请求执行任务,Handler中的Looper是顺序处理消息的,这意味着IntentService也是顺序执行后台任务的,当有多个后台任务同时存在时,这些后台任务会按照发起的顺序排队执行。

下面通过一个示例来进一步说明IntentService的工作方式,首先派生一个IntentService的子类,比如LocalIntentService,它的实现如下所示。

public class LocalIntentService extends IntentService {
    private static final String TAG = "LocalIntentService";

    public LocalIntentService() {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        String action = intent.getStringExtra("task_action");
        Log.d(TAG, "receive task :" +  action);
        SystemClock.sleep(3000);
        if ("com.ryg.action.TASK1".equals(action)) {
            Log.d(TAG, "handle task: " + action);
        }
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "service destroyed.");
        super.onDestroy();
    }


这里对LocalIntentService的实现做一下简单的说明。在onHandleIntent方法中会从参数中解析出后台任务的标识,即task-action字段所代表的内容,然后根据不同的任务标识来执行具体的后台任务,这里为了简单起见,直接通过SyetemClock.sleeo(3000)来休眠3000毫秒从而模拟一种耗时的后台任务,另外为了验证IntentService 的停止时机,这里在onDestory()中打印了一句日志。LocalIntentService实现完成了以后,就可以在外面请求执行后台任务了,在下面的代码中先后发起了3个后台任务的请求:

Intent service = new Intent(this,LocalIntentService.class);
		service.putExtra("task_action", "com.ryg.action.TASK1");
		startService(service);
		service.putExtra("task_action", "com.ryg.action.TASK2");
		startService(service);
		service.putExtra("task_action", "com.ryg.action.TASK3");
		startService(service);

运行程序,我们会发现只有执行完TASK1,TASK2,TASK3,才会最后执行onDestory()里面的方法,这也意味着服务正在停止。

你可能感兴趣的:(线程,android)