欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

AsyncTask异步

程序员文章站 2024-01-28 09:17:28
...

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

AsyncTask本身是一个抽象的泛型类,它提供了Params、Progress、Result 三个泛型参数,其类声明如下:

public abstract class AsyncTask<Params, Progress, Result> {……}

由类声明可以看出AsyncTask抽象类确实定义了三种泛型类型 Params,Progress和Result,它们分别含义如下:

Params :表示参数的类型(简单来说就是异步任务所需要到的参数)
Progress :表示后台任务执行的百分比或者进度(简单来说就是就是ijindu回调用)
Result :后台执行任务最终返回的结果类型(简单来说就是结果回调用)

如果AsyncTask不需要传递具体参数,那么这三个泛型参数可以使用Void代替。现在我们创建一个自定义的AsyncTask,并对使用中要注意到的一些关键事项做说明。
AsyncTask后面的三个参数作用说明:

第一个参数:对应DoInBackground方法里面的参数类型;
第二个参数:对应onProgressUpdate方法里面的参数类型;
第三个参数:对应doInBackground方法的返回值以及onPostExecute方法的参数

现在来看看他的源码及简单执行:

public class CustomAsyncTask extends AsyncTask<String, Integer, Bitmap> {
	public UpdateUI updateUI;
    public CustomAsyncTask(UpdateUI mUpdateUI){
        updateUI = mUpdateUI;
    }
    /**
     * onPreExecute:选择性覆写的方法
     * 在主线程中执行,在异步任务执行之前,该方法将会被调用一般用来在执行后台任务前对UI做一些标记和准备工作,如在界面上显示一个进度条。
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }
    /**
     * 抽象方法,必须实现,在线程池中被调用,耗时的异步任务就在该方法里面执行
     * 将在onPreExecute方法执行后执行。其参数是一个可变类型,表示异步任务的输入参数,在该方法中还可通过publishProgress(Progress… values)
     * 来更新实时的任务进度,而publishProgress方法则会调用onProgressUpdate方法。
     * 此外doInBackground方法会将计算的返回结果传递给onPostExecute方法。
	 * 原本的泛型方法是这样的:doInBackground(Params...params)
     * @return
     */
    @Override
    protected Bitmap doInBackground(String... strings) {
        //TODO:一步任务的具体逻辑在这里实现
        Bitmap bitmap = null;
        try {
            URL imageurl = new URL(strings[0]);
            HttpURLConnection conn = (HttpURLConnection)imageurl.openConnection();
            conn.setDoInput(true);
            conn.connect();
            InputStream is = conn.getInputStream();
            bitmap = BitmapFactory.decodeStream(is);
            is.close();
        }catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }
        return bitmap;
    }
    /**
     * onProgressUpdate选择性覆写的方法
     * 在主线程中执行,当后台任务的执行进度发生改变的时候此方法会被调用。原本的泛型方法是这样的:onProgressUpdate(Progress... values)
     * 当该方法在父类的publishProgress(Progress… values)方法被调用后执行,一般用于更新UI进度,如更新进度条的当前进度。
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        Log.e("TAGTAG", "onProgressUpdate下载进度: " + values );
    }
    /**
     * 选择性覆写的方法,在主线程中执行
     * 在doInBackground执行完成后,被UI线程调用,原本的泛型方法是这样的:onPostExecute (Result... result)
     * doInBackground方法的返回值将作为此方法的参数传递到UI线程中,并执行一些UI更新的相关操作。
     */
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
        if (null != updateUI){
            updateUI.UpdateProgressBar(bitmap);
        }
    }
    /**
     * onCancelled是可以选择性覆写的方法
     * 在主线程中执行,mAsyncTask .cancel(true)的方式调用
     * 当异步任务被取消时,该方法将被调用,要注意的是这个时候onPostExecute将不会被执行
     */
    @Override
    protected void onCancelled() {
        super.onCancelled();
    }
    public interface UpdateUI {
        void UpdateProgressBar(Bitmap bitmap);
    }
}

在activity中使用AsyncTask的时候要注意一下几点必须遵守的规则:

————AsyncTask的实例必须在主线程(UI线程)中创建(因为AsyncTask在构造方法中完成Handler的创建——获取到主线程的Looper对象,所以必须是在主线程里面完成AsyncTask的初始化,否则后面通过Handler回调通信失败),execute方法也必须在主线程中调用;
————不要在程序中直接的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法
————不能在doInBackground(Params… params)中更新UI
————一个AsyncTask对象只能被执行一次,也就是execute方法只能调用一次,多次调用将会抛出异常

在activity中的使用方式很简单:

public class RecyclerViewActivity extends BaseActivity implements CustomAsyncTask.UpdateUI {
    private static String DOWNLOAD_FILE_JPG_URL = "http://img4.imgtn.bdimg.com/it/u=68625945,479175229&fm=26&gp=0.jpg";
    	private ImageView ivImage;
    	private CustomAsyncTask downLoadAsyncTask;
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler_view);
        ivImage = findViewById(R.id.ivImage);
        downLoadAsyncTask = new CustomAsyncTask(this);
        findViewById(R.id.downloadBtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                downLoadAsyncTask.execute(DOWNLOAD_FILE_JPG_URL);
            }
        });
    }
    @Override
    public void UpdateProgressBar(Bitmap bitmap) {
        ivImage.setImageBitmap(bitmap);
    }
}

现在我们从源码角度分析AsyncTask的运行:

我们使用AsyncTask的第一步是通过new关键字创建AsyncTask的对象,在构造方法中传值,这个没什么好说的。然后是通过对象调用. execute(Params… params)方法,在execute(Params… params)的源码里面我们可以看到,他直接调用了executeOnExecutor(Executor exec, Params… params)方法。这里仔细看方法的第一个参数:是Executor,它代表什么?代表的是线程池。这个我们后面细说,这里先提一下。executeOnExecutor()方法的源码如下:

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,Params... params) {
	if (mStatus != Status.PENDING) {
	    switch (mStatus) {
		case RUNNING:
		    throw new IllegalStateException("Cannot execute task:" + " the task is already running.");
		case FINISHED:
		    throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)");
	    }
	}
	mStatus = Status.RUNNING;
	onPreExecute();
	mWorker.mParams = params;
	exec.execute(mFuture);
	return this;
}

我们可以看到:

	首先,只有当mStatus!=Status.PENDING的时候,也就是任务尚未执行的状态下,异步任务才会被调用执行,否则抛出“Cannot execute task……”的异常。
	然后,改变mStatus = Status.RUNNING状态。
	紧接着,调用onPreExecute()方法,这也证明了我们前面所说的,onPreExecute()方法是最先被执行的,而且从执行的过程来看,到这一步,代码任务还是在主线程里面执行,并没有被切换到工作线程中去,所以onPreExecute()方法里面可以更新UI。
	再然后便是异步任务所需要的参数的赋值:mWorker.mParams = params。mWorker 是一个工作任务队列WorkerRunnable。mWorker.mParams这个参数最终被封装为FutureTask对象,而FutureTask对象本质上是一个Runnable角色的并发类。
	最后才是调用exec.execute(mFuture)开始执行异步任务,到了这一步,线程才被切换到工作线程。

这里我们再来说说Executor exec这个线程池接口对象,他是一个串行的线程池,一个进程中所有的AsyncTask全部在这个串行的线程池中排队执行。系统首先会把Params参数封装为WorkerRunnable对象,然后传给FutureTask,最后FutureTask对象被交给Executor的实现类的execute()方法去处理。如下:

private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    Runnable mActive;
    public synchronized void execute(final Runnable r) {
        mTasks.offer(new Runnable() {
            public void run() {
                try {
                    r.run();
                } finally {
                    scheduleNext();
                }
            }
        });
        if (mActive == null) {
            scheduleNext();
        }
    }
    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

execute()方法会首先把FutureTask对象插入mTasks任务队列中去,如果这个时候没有正在活动的AsyncTask任务,那么就会调用scheduleNext()方法来执行异步任务AsyncTask。当AsyncTask把任务执行完后,会继续执行其他的任务直到所有的任务都被执行完为止。因此我们可以认为AsyncTask是串行执行的。

从前面的代码中我们可以得知:AsyncTask里面有两个线程池(Executor 的实现类SerialExecutor和Executor THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler)。线程池SerialExecutor用于任务排队,THREAD_POOL_EXECUTOR才是真正用于异步任务执行,之所以还要一个这样的异步的执行队列,主要就是为了满足Android3.0以上系统的并发需求。而InternalHandler则主要是用于工作线程和UI线程之间的环境切换以及数据传递。

现在我们在回到AsyncTask初始的地方——构造方法。源码如下:

public AsyncTask(@Nullable Looper callbackLooper) {
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper() ? getMainHandler() : new Handler(callbackLooper);
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);
            Result result = null;
            try {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                result = doInBackground(mParams);
                Binder.flushPendingCommands();
            } catch (Throwable tr) {
                mCancelled.set(true);
                throw tr;
            } finally {
                postResult(result);
            }
            return result;
        }
    };
    mFuture = new FutureTask<Result>(mWorker) {
        @Override
        protected void done() {
            try {
                postResultIfNotInvoked(get());
            } catch (InterruptedException e) {
                android.util.Log.w(LOG_TAG, e);
            } catch (ExecutionException e) {
                throw new RuntimeException("An error occurred while executing doInBackground()", e.getCause());
            } catch (CancellationException e) {
                postResultIfNotInvoked(null);
            }
        }
    };
}
private void postResultIfNotInvoked(Result result) {
        final boolean wasTaskInvoked = mTaskInvoked.get();
        if (!wasTaskInvoked) {
            postResult(result);
        }
}
private Result postResult(Result result) {
	@SuppressWarnings("unchecked")
	Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result));
	message.sendToTarget();
	return result;
}

线程池主要完成两个功能:完成mWorker对象和mFuture对象的创建。FutureTask的run方法会调用mWorker的call()方法,因此mWorker的call()方法最终还是在工作线程里面执行的。在mWorker的call()方法中,首先执行的是mTaskInvoked.set(true),表示当前任务已经被调用过了。接着是设置工作线程的优先等级。紧接着是执行doInBackground(mParams)并将结果返回给postResult()方法。

而postResult()方法里面主要是通过getHandler()获取一个handler对象,然后发送一条”MESSAGE_POST_RESULT”的消息(通过message.sendToTarget()的方式发出去的,sendToTarget()方法源码中的target实际上就是一个Handler)。这个handler有点特殊:

InternalHandler sHandler = null;
private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            return sHandler;
        }
}
private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }
        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
}

从这里我们可以知道,InternalHandler是一个静态的单例,为了能够将执行环节切换到主线程,就必须保证InternalHandler必须在主线程中被创建。由于静态成员变量会在加载类(class文件)的时候就被初始化,因此这就变相要求AsyncTask的实现类必须在主线程中加载,否则同一个进程中的AsyncTask都将无法正常工作。InternalHandler收到MESSAGE_POST_RESULT标识的消息后就执行finish()方法:

result.mTask.finish(result.mData[0])

具体如下:

rivate void finish(Result result) {
    if (isCancelled()) {            
    	onCancelled(result);       
     } else {            
     	onPostExecute(result);        
     }
    mStatus = Status.FINISHED;
}

inish方法逻辑很简单,就是判断请求是否被取消,如果取消的话,就调用onCancelled()方法,否则执行onPostExecute()方法,而onPostExecute()方法的参数正是doInBackground的返回结果,在这里我们可以通过回调去更新UI。到这里AsyncTask的工作过程分析完毕。

另外有一点需要注意:AsyncTask的execute方法是串行执行的,不能实现并发。如果要实现并发功能的话,要用到executeOnExecutor()方法,他的具体用法如下:

downLoadAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, DOWNLOAD_FILE_JPG_URL);

我们来验证一下:

private void syncAndAsync(){
    new CustomAsyncTask(this, "00001").execute(DOWNLOAD_FILE_JPG_URL);
    new CustomAsyncTask(this, "00002").execute(DOWNLOAD_FILE_JPG_URL);
    new CustomAsyncTask(this, "00003").execute(DOWNLOAD_FILE_JPG_URL);
    new CustomAsyncTask(this, "00004").execute(DOWNLOAD_FILE_JPG_URL);
    new CustomAsyncTask(this, "00005").execute(DOWNLOAD_FILE_JPG_URL);
    new CustomAsyncTask(this, "qqqqq").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, DOWNLOAD_FILE_JPG_URL);
    new CustomAsyncTask(this, "wwwww").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, DOWNLOAD_FILE_JPG_URL);
    new CustomAsyncTask(this, "eeeee").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, DOWNLOAD_FILE_JPG_URL);
    new CustomAsyncTask(this, "rrrrr").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, DOWNLOAD_FILE_JPG_URL);
    new CustomAsyncTask(this, "ttttt").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, DOWNLOAD_FILE_JPG_URL);
}

执行结果如下:

AsyncTask异步
我们可以看到名称以“0000”开头的异步任务,执行都是按我们调用的顺序执行的。名称为字母的异步任务,执行顺序上,跟我们的调用顺序没有关联。这正好印证了我们上面的结论。

相关标签: 原创