26
2017
09

Android 实践之异步线程AsyncTask

为何要引入AsyncTask?

在Android程序开始运行的时候会单独启动一个进程,默认情况下所有这个程序操作都在这个进程中进行。一个Android程序默认情况下只有一个进程,但一个进程中可以有多个线程。

在这些线程中,有一个线程叫做UI线程(也叫Main Thread),除了UI线程外的线程都叫子线程(Worker Thread)。UI线程主要负责控制UI界面的显示、更新、交互等。因此,UI线程中的操作延迟越短越好(流畅)。把一些耗时的操作(网络请求、数据库操作、逻辑计算等)放到单独的线程,可以避免主线程阻塞。

Android给我们提供了一种轻量级的异步任务类AsyncTask。该类中实现异步操作,并提供接口反馈当前异步执行结果及进度,这些接口中有直接运行在主线程中的(如 onPostExecute,onPreExecute等)。

使用的优点:

简单,快捷
过程可控
使用的缺点:

使用多个异步操作并需要进行Ui变更时,就变得复杂起来.
AsyncTask的三种泛型类型

Params 启动任务执行的输入参数,比如HTTP请求的URL。
Progress 后台任务执行的百分比。
Result 后台执行任务最终返回的结果,比如String。
使用过AsyncTask 的同学都知道一个异步加载数据最少要重写以下这两个方法:

doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。
onPostExecute(Result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回
有必要的话你还得重写以下这三个方法,但不是必须的:

onProgressUpdate(Progress…) 可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度。
onPreExecute() 这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。
onCancelled() 用户调用取消时,要做的操作
AsyncTask的局限性

AsyncTask的优点在于执行完后台任务后可以很方便的更新UI,然而使用它存在着诸多的限制。先抛开内存泄漏问题,使用AsyncTask主要存在以下局限性:

在Android 4.1版本之前,AsyncTask类必须在主线程中加载,这意味着对AsyncTask类的第一次访问必须发生在主线程中;在Android 4.1以及以上版本则不存在这一限制,因为ActivityThread(代表了主线程)的main方法中会自动加载AsyncTask
AsyncTask对象必须在主线程中创建
AsyncTask对象的execute方法必须在主线程中调用
一个AsyncTask对象只能调用一次execute方法
AsyncTask的工作原理

首先,让我们来看一下AsyncTask类的构造器都做了些什么:


 1 public AsyncTask() {
 2         mWorker = new WorkerRunnable<Params, Result>() {
 3             public Result call() throws Exception {
 4                 mTaskInvoked.set(true);
 5 
 6                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
 7                 //noinspection unchecked
 8                 Result result = doInBackground(mParams);
 9                 Binder.flushPendingCommands();
10                 return postResult(result);
11             }
12         };
13 
14         mFuture = new FutureTask<Result>(mWorker) {
15             @Override
16             protected void done() {
17                 try {
18                     postResultIfNotInvoked(get());
19                 } catch (InterruptedException e) {
20                     android.util.Log.w(LOG_TAG, e);
21                 } catch (ExecutionException e) {
22                     throw new RuntimeException("An error occurred while executing doInBackground()",
23                             e.getCause());
24                 } catch (CancellationException e) {
25                     postResultIfNotInvoked(null);
26                 }
27             }
28         };
29     }

在第2行到第12行,初始化了mWorker,它是一个派生自WorkRunnable类的对象。WorkRunnable是一个抽象类,它实现了Callable接口。我们再来看一下第4行开始的call方法的定义,首先将mTaskInvoked设为true表示当前任务已被调用过,然后在第6行设置线程的优先级。在第8行我们可以看到,调用了AsyncTask对象的doInBackground方法开始执行我们所定义的后台任务,并获取返回结果存入result中。最后将任务返回结果传递给postResult方法。关于postResult方法我们会在下文进行分析。由此我们可以知道,实际上AsyncTask的成员mWorker包含了AyncTask最终要执行的任务(即mWorker的call方法)。

接下来让我们看看对mFuture的初始化。我们可以看到mFuture是一个FutureTask的直接子类(匿名内部类)的对象,在FutureTask的构造方法中我们传入了mWorker作为参数。我们使用的是FutureTask的这个构造方法:

public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

也就是说,mFuture是一个封装了我们的后台任务的FutureTask对象,FutureTask类实现了FutureRunnable接口,通过这个接口可以方便的取消后台任务以及获取后台任务的执行结果,具体介绍请看这里:Java并发编程:Callable、Future和FutureTask。

从上面的分析我们知道了,当mWorker中定义的call方法被执行时,doInBackground就会开始执行,我们定义的后台任务也就真正开始了。那么这个call方法什么时候会被调用呢?我们可以看到经过层层封装,实际上是mFuture对象封装了call方法,当mFuture对象被提交到AsyncTask包含的线程池执行时,call方法就会被调用,我们定义的后台任务也就开始执行了。下面我们来看一下mFuture是什么时候被提交到线程池执行的。

首先来看一下execute方法的源码:

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
}

我们可以看到它接收的参数是Params类型的参数,这个参数会一路传递到doInBackground方法中。execute方法仅仅是调用了executeOnExecutor方法,并将executeOnExecutor方法的返回值作为自己的返回值。我们注意到,传入了sDefaultExecutor作为executeOnExecutor方法的参数,那么sDefaultExecutor是什么呢?简单的说,它是AsyncTask的默认执行器(线程池)。AsyncTask可以以串行(一个接一个的执行)或并行(一并执行)两种方式来执行后台任务,在Android3.0及以后的版本中,默认的执行方式是串行。这个sDefaultExecutor就代表了默认的串行执行器(线程池)。也就是说我们平常在AsyncTask对象上调用execute方法,使用的是串行方式来执行后台任务。关于线程池更加详细的介绍与分析请见:深入理解Java之线程池

我们再来看一下executeOnExecutor方法都做了些什么:

 1 public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
 2             Params... params) {
 3         if (mStatus != Status.PENDING) {
 4             switch (mStatus) {
 5                 case RUNNING:
 6                     throw new IllegalStateException("Cannot execute task:"
 7                             + " the task is already running.");
 8                 case FINISHED:
 9                     throw new IllegalStateException("Cannot execute task:"
10                             + " the task has already been executed "
11                             + "(a task can be executed only once)");
12             }
13         }
14 
15         mStatus = Status.RUNNING;
16 
17         onPreExecute();
18 
19         mWorker.mParams = params;
20         exec.execute(mFuture);
21 
22         return this;
23     }

从以上代码的第4行到第12行我们可以知道,当AsyncTask对象的当前状态为RUNNING或FINISHED时,调用execute方法会抛出异常,这意味着不能对正在执行任务的AsyncTask对象或是已经执行完任务的AsyncTask对象调用execute方法,这也就解释了我们上面提到的局限中的最后一条。

接着我们看到第17行存在一个对onPreExecute方法的调用,这表示了在执行后台任务前确实会调用onPreExecute方法。

在第19行,把我们传入的execute方法的params参数赋值给了mWorker的mParams成员变量;而后在第20行调用了exec的execute方法,并传入了mFuture作为参数。exec就是我们传进来的sDefaultExecutor。那么接下来我们看看sDefaultExecutor究竟是什么。在AsyncTask类的源码中,我们可以看到这句:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
sDefaultExecutor被赋值为SERIAL_EXECUTOR,那么我们来看一下SERIAL_EXECUTOR:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
现在,我们知道了实际上sDefaultExecutor是一个SerialExecutor对象,我们来看一下SerialExecutor类的源码:

 1 private static class SerialExecutor implements Executor {
 2         final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
 3         Runnable mActive;
 4 
 5         public synchronized void execute(final Runnable r) {
 6             mTasks.offer(new Runnable() {
 7                 public void run() {
 8                     try {
 9                         r.run();
10                     } finally {
11                         scheduleNext();
12                     }
13                 }
14             });
15             if (mActive == null) {
16                 scheduleNext();
17             }
18         }
19 
20         protected synchronized void scheduleNext() {
21             if ((mActive = mTasks.poll()) != null) {
22                 THREAD_POOL_EXECUTOR.execute(mActive);
23             }
24         }
25     }

我们来看一下execute方法的实现。mTasks代表了SerialExecutor这个串行线程池的任务缓存队列,在第6行,我们用offer方法向任务缓存队列中添加一个任务,任务的内容如第7行到第13行的run方法定义所示。我们可以看到,run方法中:第9行调用了mFuture(第5行的参数r就是我们传入的mFuture)的run方法,而mFuture的run方法内部会调用mWorker的call方法,然后就会调用doInBackground方法,我们的后台任务也就开始执行了。那么我们提交到任务缓存队列中的任务什么时候会被执行呢?我们接着往下看。

首先我们看到第三行定义了一个Runnable变量mActive,它代表了当前正在执行的AsyncTask对象。第15行判断mActive是否为null,若为null,就调用scheduleNext方法。如第20行到24行所示,在scheduleNext方法中,若缓存队列非空,则调用THREAD_POOL_EXECUTOR.execute方法执行从缓存队列中取出的任务,这时我们的后台任务便开始你真正执行了。

通过以上的分析,我们可以知道SerialExecutor所完成的工作主要是把任务加到任务缓存队列中,而真正执行任务的是THREAD_POOL_EXECUTOR。我们来看下THREAD_POOL_EXECUTOR是什么:

public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
从上面的代码我们可以知道,它是一个线程池对象。根据AsyncTask的源码,我们可以获取它的各项参数如下:

 1 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
 2 private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
 3 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
 4 private static final int KEEP_ALIVE = 1;
 5 
 6 private static final ThreadFactory sThreadFactory = new ThreadFactory() {
 7     private final AtomicInteger mCount = new AtomicInteger(1);
 8 
 9     public Thread newThread(Runnable r) {
10         return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
11     }
12 };
13 
14 private static final BlockingQueue<Runnable> sPoolWorkQueue =
15             new LinkedBlockingQueue<Runnable>(128);

由以上代码我们可以知道:

corePoolSize为CPU数加一;
maximumPoolSize为CPU数的二倍加一;
存活时间为1秒;
任务缓存队列为LinkedBlockingQueue。
现在,我们已经了解到了从我们调用AsyncTask对象的execute方法开始知道后台任务执行完都发生了什么。现在让我们回过头来看一看之前提到的postResult方法的源码:

private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}

在以上源码中,先调用了getHandler方法获取AsyncTask对象内部包含的sHandler,然后通过它发送了一个MESSAGE_POST_RESULT消息。我们来看看sHandler的相关代码:

 1 private static final InternalHandler sHandler = new InternalHandler();
 2 
 3 private static class InternalHandler extends Handler {
 4         public InternalHandler() {
 5             super(Looper.getMainLooper());
 6         }
 7 
 8         @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
 9         @Override
10         public void handleMessage(Message msg) {
11             AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
12             switch (msg.what) {
13                 case MESSAGE_POST_RESULT:
14                     // There is only one result
15                     result.mTask.finish(result.mData[0]);
16                     break;
17                 case MESSAGE_POST_PROGRESS:
18                     result.mTask.onProgressUpdate(result.mData);
19                     break;
20             }
21         }
22 }

从以上代码中我们可以看到,sHandler是一个静态的Handler对象。我们知道创建Handler对象时需要当前线程的Looper,所以我们为了以后能够通过sHandler将执行环境从后台线程切换到主线程(即在主线程中执行handleMessage方法),我们必须使用主线程的Looper,因此必须在主线程中创建sHandler。这也就解释了为什么必须在主线程中加载AsyncTask类,是为了完成sHandler这个静态成员的初始化工作。

在以上代码第10行开始的handleMessage方法中,我们可以看到,当sHandler收到MESSAGE_POST_RESULT方法后,会调用finish方法,finish方法的源码如下:

1 private void finish(Result result) {
2         if (isCancelled()) {
3             onCancelled(result);
4         } else {
5             onPostExecute(result);
6         }
7         mStatus = Status.FINISHED;
8 }

在第2行,会通过调用isCancelled方法判断AsyncTask任务是否被取消,若取消了则调用onCancelled方法,否则调用onPostExecute方法;在第7行,把mStatus设为FINISHED,表示当前AsyncTask对象已经执行完毕。

经过了以上的分析,我们大概了解了AsyncTask的内部运行逻辑,知道了它默认使用串行方式执行任务。那么如何让它以并行的方式执行任务呢? 阅读了以上的代码后,我们不难得到结论,只需调用executeOnExecutor方法,并传入THREAD_POOL_EXECUTOR作为其线程池即可。

项目应用

public void login ( ) {
    AsyncTask<Void, String, Void> login = new AsyncTask<Void, String, Void>() {
        @Nullable
        protected Void doInBackground(Void... arg0) {
            try {
                socket = new Socket(host, Integer.valueOf(port));                               //连接服务器
                writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));  //创建写缓冲
                reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));    //创建读缓冲
                writer.write(account+psw);
                writer.flush();
                String line;
                line=reader.readLine();
                if (line!=null&&line.equals("Ca/ok")) {
                    publishProgress("@link_success");
                } else if(line!=null&&line.equals("Ca/ok no")){
                    publishProgress("@link_another");
                }else{
                    publishProgress("@link_defeat");
                }
                while((line=reader.readLine())!="Cb/ok"){
                    publishProgress(line);
                }
            } catch (IOException e) {
                publishProgress("@link_error");
                e.printStackTrace();
            }
            return null;
        }
        protected void onProgressUpdate(String... values) {
            if(values[0].equals("@link_error")) {
                Toast.makeText(LoginActivity.this, "连接错误", Toast.LENGTH_SHORT).show();
                progressBar_state.setProgressTintList(ColorStateList.valueOf(0xffcc0000));
            }else if(values[0].equals("@link_success")){
                Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
                progressBar_state.setProgressTintList(ColorStateList.valueOf(0xff99cc00));
                progressBar_state.setProgress(100);
            } else if(values[0].equals("@link_defeat")){
                Toast.makeText(LoginActivity.this, "登录失败", Toast.LENGTH_SHORT).show();
                progressBar_state.setProgressTintList(ColorStateList.valueOf(0xffffbb33));
                progressBar_state.setProgress(50);
            }
            else if(values[0].equals("@link_another")){
                Toast.makeText(LoginActivity.this, "重复登录", Toast.LENGTH_SHORT).show();
                progressBar_state.setProgressTintList(ColorStateList.valueOf(0xffffbb33));
                progressBar_state.setProgress(50);
            }else {
                Locale local=new Locale("CHINESE");
                SimpleDateFormat formatter = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss",local);
                Date curDate = new Date(System.currentTimeMillis());//获取当前时间
                String str = formatter.format(curDate);
                str=str.substring(12);
                Toast.makeText(LoginActivity.this, "["+str+"] Receive:"+values[0], Toast.LENGTH_SHORT).show();
                super.onProgressUpdate(values);
            }
        }
    };
    login.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}

public void logout( ) {
    AsyncTask<Void, String, Void> logout = new AsyncTask<Void, String, Void>() {
        @Nullable
        protected Void doInBackground(Void... arg0) {
            try {
                writer.write("Cb\n");
                writer.flush();
                String line;
                line=reader.readLine();
                if (line != null && line.equals("Cb/ok")) {
                    socket.close();
                    publishProgress("@logout_success");
                } else {
                    publishProgress("@logout_defeat");
                }
            } catch (IOException e) {
                publishProgress("@logout_error");
                e.printStackTrace();
            }
            return null;
        }
////////////////////////////////////////////////////////////
        protected void onProgressUpdate(String... values) {
            if (values[0].equals("@logout_success")) {
                Toast.makeText(LoginActivity.this, "退出成功", Toast.LENGTH_SHORT).show();
                progressBar_state.setProgressTintList(ColorStateList.valueOf(0xffffbb33));
                progressBar_state.setProgress(50);
            } else if (values[0].equals("@logout_error")) {
                Toast.makeText(LoginActivity.this, "退出失败", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(LoginActivity.this, "退出错误", Toast.LENGTH_SHORT).show();
                progressBar_state.setProgressTintList(ColorStateList.valueOf(0xffcc0000));
            }
        }
    };
    logout.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}

此项目使用AsyncTask来进行异步无阻塞的网络连接,网络处理部分放在doInBackground()方法里面,对处理信息以字符串的的形式,通过publishProgress()方法返回,然后在onProgressUpdate()方法里针对返回信息更改UI。

项目目的是开启一个异步线程进行登录服务器,服务器响应是否可以连接,连接成功后可一直读取服务器端发送的数据指令。退出登录时需给服务器发送断开连接的请求,并等待服务器的回复后断开连接。

项目最初尝试使用AsyncTask的executy()方法执行线程,因其默认以串行方式运行,当需要在连接之后一直接受服务器发送的指令时,无法在主线程里响应注销登录的线程操作。这时便适合使用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)方法进行并行操作。

上一篇:可拖拽可点击,可吸附button(2) 下一篇:[日推荐]『足球欧洲』.这里有关于足球的一切!