再论AsyncTask原理解析

说来有些惭愧,AsyncTask 这么一个当初入门就要学习的东西,这么久来我竟然没有深入了解过它的原理,因为16年从入门的时候就被告知它会引起内存泄漏。正式做项目以来,更是从来没有用过它,所以也从来没有想过了解它的原理。直到前几天,有大佬问起我它的原理,我才发现我对它的了解仅限于下面那段描述性的话,于是决定好好了解一下它。

仔细想想,真的十分可笑,当初带我入行的大佬,我认识他的时候,我现在工作年限和当时的他一样。然而真相是,除了工作年限跟他一样,技术能里却差了十万八千里,根本不在一个次元上😂。最近一直想把自己的知识写下来,但总是觉得自己写的这些玩意儿拿不出手,太小儿科了……一番纠结之后,还是决定写下来,我想我这个笨鸟,虽然飞的晚了,但只要持之以恒一往无前的飞,也可以飞的更高吧😉。

不多废话,开始正文吧。🤗


AsyncTask,我想大家都很熟悉它,简单来说,它对Thread和Handler进行了封装,使得我们不必关注线程创建和线程切换的问题,这给当初还是新手的我们提供了很大的帮助。但是众所周知,因为AsyncTask可能会引起内存泄漏,已经被谷歌官方废弃了。

AsyncTask为什么会内存泄漏?

研究了一番,发现AsyncTask引起的内存泄漏似乎和它本身并没有什么大的关系,真正的原因在于我们自己!是由于我们使用AsyncTask不规范,或者说我们写代码不规范导致。大家使用它的时候,无外乎两种方式:

  • 直接new一个内部类的方式;
  • new 一个继承自AsyncTask的非静态内部类。

众所周知,在activity或fragment里面非静态内部类使用的时候会造成内存泄漏,而我们上面两种方式恰恰就有可能造成内存泄漏。可笑的是,源码的注释里写的使用方法就极易诱导我们写出容易内存泄漏的代码。
解决办法是,使用继承自AsyncTask的静态内部类……AsyncTask为什么被废弃?这篇帖子讲的很好,具体不再赘述。可以参考这篇文章。我只能说AsyncTask真的很冤……


下面开始AsyncTask原理解析。

AsyncTask原理

前面我说:AsyncTask是Thread+Handler的封装,可以说对,但也可以说不对,只能说,没说到点子上。它其实是线程池和Handler的封装。

接下来,我们一步步来解析:
- code 1-

  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);
                    //noinspection unchecked
                    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 static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        Params[] mParams;
    }

看构造函数,其实很简单,我们都知道,开启一个新线程有3中方式:

  • 实例化Thread的子类;
  • 实现Runnable,Thread实例化的时候传入runnable实例;
  • 实现一个Callable,将它实例传给FutureTask,再将其实例传递给Thread;

:这三种之中,FutureTask比较特殊,可以返回结果。

我们在构造函数中,先看两个东西:mWorker,mFuture。我们看他们分别是什么:

不出意外,WorkerRunnable它实现了Callable,并且泛型类型是构造器上的入参和返回类型,由于FutureTask要返回结果,所以看到Callable的泛型声明为我们定的返回类型。
我们再看mFuture他就是FutureTask,入参是我们的mWorker也就是Callable的实例。
当然,也少不了Handler,它负责事件分发线程切换;

我们再看一下,WorkerRunnable对Callable的call的实现:它在里面调用了我们的核心方法doInBackground,这个方法是一个抽象方法,是由我们自己实现的,并返回结果。注意这一步是在新的线程的内部的,也就是说在子线程中。然后通过postResult将结果发送出去

到这里,我们仍然没有触及到AsyncTask的核心,线程是如何管理的,是如何启动的呢?

如何开始任务

我们点进execute方法:
- code 2 -

 	public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }
  
	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;
    }

在这里,我先问一个问题:同一个AsyncTask的实例可以重复execute嘛?答案是不可以。

可以看到,AsyncTask对它的状态做了检查,如果正在执行或已经执行完,会抛出错误,这就确保我们使用过程中不会重复execute。
往下,我们看到一个熟悉的方法,onPreExecute()。这个方法我们知道,是在主任务(耗时任务)也就是doInBackGround()执行之前执行的,做一些(UI的)准备工作,需要注意,到这一步,还是运行在主线程中的
紧接着,就执行了exec.execute(mFuture),那么这是不是意味着,doInBackGround就要开始执行了呢?并不是!

那么,Executor是什么呢?有什么作用呢?我们看一看sDefaultExecutor

- code 3 -

 public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

 private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

sDefaultExecutor 是我们的执行器,引用了一个静态的常量,这个常量是SerialExecutor的实例,我们接着看。

AsyncTask的本质

- code 4 -

  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);
            }
        }
    }
    
	public interface Executor {

    	void execute(Runnable command);
	}

序列执行器SerialExecutor 实现了Executor,在它的execute()实现方法中,它把我们AsyncTask构造函数中实例化的FutureTask(FutureTask也实现了Runnable),进一步包装到Runnable中,通过offer的方式把Runnable加入到了mTask队列的头部。mTask可以看到,它是一个队列。
然后在scheduleNext()中,从队列尾部中取出一个任务,交给THREAD_POOL_EXECUTOR去执行。

那么THREAD_POOL_EXECUTOR又是什么呢?

- code 5 -

   public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>(), sThreadFactory);
        threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

没错,它其实就是一个线程池的实例常量。

在这里我不得不说,谷歌程序员真的厉害!不管是线程池的实例,还是前面序列执行器SerialExecutor 实例,都是常量。这就保证了,不管AsyncTask实例化了多少次,实例化的都是FutureTask(还有Callable)任务,而他们两个都只有类加载时初始化的一个实例!真的太厉害了,看似简单,却不知道这是多少根头发换来的智慧。

然后,后面的任务就正式的交给线程池去处理了……

如何通知主线程任务进度和结果

偶,对了,我忘了一点,AsyncTask时如果将进度通知到主线程任务结果和任务进度呢?
- code 6 -

  @WorkerThread
    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }

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

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

    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:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

我们看它的publicProgress()和postResult()方法,是不是觉得非常熟悉,没错,它其实就是通过Handler将消息发送到主线程的……

好,我们从头捋一下:

  • 加载AsyncTask的时候,实例化一个序列处理器和一个线程池(这一步不用我们来做)
  • 每次new 一个AsyncTask,就new了一个FutureTask和 Callable。我们的doInbackGround主任务就在Callable中;
  • 调用AsyncTask的execute方法,就通过序列处理器,将任务包装称一个Runnable,然后将Runnable加入到双向队列中头部,然后从尾部取出任务放到线程池中,交给线程池执行。

ok,到此结束!就是这么简单,加上那么多的注释,最终800行的代码就执行了这些任务,然而这么几行代码,却蕴含了无数根头发的智慧,头发茂密的我,此生怕是写不出这么棒的代码了……

不积跬步无以至千里,我现在慢慢的开始积累,不断的坚持,希望我不久也可以成为别人眼中的大佬。

哪里有问题的话,欢迎各位批评指正,非常感谢。😘

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值