股票场内基金交易,没时间盯盘?

   
使用优财助手电脑客户端记录下您的股票买入卖出数据,能帮您时刻盯盘,会根据您记录的未售出买入价计算上涨或下跌幅度,及时弹框通知您。想知道如何使用?快点击左方视频观看了解吧~~下载地址:http://youcaizhushou.com

什么是 AsyncTask

AsyncTask 是一种轻量级异步任务类,内部封装了 Thread 和 Handler,但是它不适合做特别耗时的后台任务(建议采用线程池)。
实现异步通常有两种方式(现在很火的 RxJava 也是一种实现异步的方式):

  • Thread 配合 Handler
  • AsyncTask

前者控制精细,但代码臃肿,且异步任务较多时不易控制,因此 Android 提供了 AsyncTask 类来实现异步,AsyncTask 是对 Handler,Thread 和线程池的良好封装。

AsyncTask 的具体使用不再赘述。

解析 AsyncTask

AsyncTask 内部线程池的配置

既然 AsyncTask 内部封装了线程池,那么我们可以很清楚的看到线程池的配置:
– 核心线程数 = CPU核心数 + 1
– 最大线程数 = 2 * CPU核心数 + 1
– keepAliveTime = 1 s
– 任务队列长度为 128

为什么必须在主线程中加载 AsyncTask?

首先这句话不是绝对的,我们看一下不同版本 AsyncTask 的源码就知道为什么了。

先上结论:API 16 以前必须在主线程加载 AsyncTask,API 16 以后就不用了。

首先,所有版本的 AsyncTask 都是在成员位置直接声明并创建了 Handler 对象,它是一个静态变量,也就是说它是在类加载的时候创建的:

API 16 以前的 AsyncTask

在 Android 4.1(API 16)之前,如果是在子线程加载的 AsyncTask,那么就会导致静态的 Handler 对象创建并初始化(如上),而 Handler 默认采用的是当前线程的 Looper,于是 Handler 用了子线程的 Looper,处理消息时也就无法切换到主线程,而是被 Handler post 到创建它的那个线程执行(即子线程)。

所以那个年代,我们不能在子线程加载 AsyncTask。

API 16 以后,API 22 之前的 AsyncTask

在 API 16 以后,API 22 之前,Google 进行了改动:在 ActivityThread 的 main 函数里面(App启动的入口),直接调用了 AsyncTask 的 init() 方法,该方法代码如下:

main 函数运行在主线程,这样就确保了 AsyncTask 类是在主线程加载。因此 AsyncTask 的 Handler 用的也是主线程的 Looper。

所以在 API 16 以后,我们是可以在子线程中加载 AsyncTask的。但是不管我们需不需要 AsyncTask,它都会在我们 App 启动时就加载,那如果我们完全没有用到它,岂不是很浪费资源?

API 22 之后

在 API 22 之后 Google 又换了一种实现:

删除了 AsyncTask 的 init 方法,main 函数中也不再调用它。

在 AsyncTask 成员变量位置仅仅声明了静态的 Handler,并没有创建对象:

在 Handler 的实现中,添加了一个无参构造:

在 Handler 的实现中,添加了 getHandler() 方法:在需要 Handler 时会调用此方法。

可以看到,这样既保证了 Handler 采用主线程的 Looper 构建,又使得 AsyncTask 在需要时才被加载。

以上,Google 在 API 16 以后通过不同的实现,确保了线程的正常切换。注意一点,onPreExecute()不受 Handler 影响,运行在执行 execute 方法的线程,原因我们后面分析。

AsyncTask 是并行执行的吗?

既然封装了线程池,那么 AsyncTask 执行任务是并行的吗?

结论是:在Android 1.6 之前,是串行执行,从 Android 1.6 开始采用线程池处理并行任务。在 3.0 以后默认串行执行,但提供了 executeOnExecutor()方法并行执行。

为什么默认不并行?

因为 AsyncTask 内部的线程池是静态、单例的,所以在同一进程中所有用到 AsyncTask 的地方都是同一个线程池,如果我们创建了多个 AsyncTask 且在其中访问了共同资源,并且没做同步处理,那么并行时就会出现同步问题。Google 可能觉得这很麻烦,为了避免各种问题,便在 3.0 以后改成了默认串行执行。

如果我们需要并行执行,并自己处理了同步问题,那么可以使用 executeOnExecutor(AsyncTask.THREADPOOLEXECUTOR, Params…..)来并行执行任务,第一个参数这里用的是 AsyncTask 自带的线程池,当然也可以传入自己写的线程池。注意,这个方法是API 11 引入的。

AsyncTask 的缺点

通过以上分析,我们可以发现 AsyncTask 具有如下缺点:

  • 容易崩溃。AsyncTask里面的线程池的核心线程数是 CPU + 1 个(4.4以前是5个),最大线程数是 CPU * 2 + 1 个,任务缓存队列长度为 128。任务缓存满时,使用默认的 Handler 处理(即抛异常)。因为同一个进程里所有用到 AsyncTask 的地方都是同一个线程池,导致任务缓存队列就容易满,一旦满了,再往里面添任务,就抛异常挂掉了。

  • 资源浪费。在 AsyncTask 全部执行完毕之后,进程中还是会常驻 corePoolSize 个线程

  • 必须在主线程中加载,不然在API 16以下完蛋

  • 默认是串行执行任务。API 11 以后才能通过 executeOnExecutor 方法来并行执行

实际使用中我还发现了一些缺点:

  • 一个 AsyncTask 对象只能执行一次,多次执行就报错。

  • 必须在主线程执行 execute 方法,不然会导致 onPreExecute 不在主线程执行

  • 出现错误只能在 doInBackground 里 try…catch。后续处理也很麻烦

  • cancel 方法不好用。调用 cancel 后只是不会去执行 onPostExecute 和 onProgressUpdate,而任务依然在 doInBackGround 中执行。调用cancel(true)虽然会使任务尽早结束,但是如果 doInBackground() 中有不可打断的方法就失效了,比如BitmapFactory.decodeStream()。

  • 结果丢失。屏幕旋转等原因造成 AsyncTask 销毁并重建时,正在运行的 AsyncTask 持有的是之前的 Activity 实例,这样任务执行完后 onPostExecute 方法就没用了。

AsyncTask 工作原理

分析工作原理,那我们就从执行任务的起点:execute 方法开始分析,该方法就一行代码:

可以看到这里使用了 AsyncTask 自带的线程池。这里先提一下:sDefaultExecutor 是个串行的线程池,一个进程的所有 AsyncTask 都在该线程池排队执行。

再来看看 executeOnExecutor 方法:

可以看到,该方法先进行了一些状态上的判断和更新,如果该任务正在执行或已经完成,都会抛异常。然后就调用了 onPreExecute 方法。这段代码充分解释了为什么必须在主线程执行 execute 方法,以及为什么一个 AsyncTask 对象只能执行一次。

然后它又调用了线程池的 execute 执行任务,并传入了 mFuture 对象(这是一个 FutureTask 对象)。

接下来再看看这个线程池的 execute 方法是如何执行的

可以看到,传进来的 mFuture 被当做 Runnable 使用。这里的 mTasks 就是任务队列,调用 offer 方法将 mFuture 对象插入到任务队列尾部。如果没有正在执行的任务,就调用 scheduleNext 执行下一个任务,finally 语句块确保了每个任务执行完后自动执行下一个任务。这段代码证明了 AsyncTask 默认串行执行任务。

等等,这个 sDefaultExecutor 只是给任务进行排队了啊,并没有真正的执行任务!

那我们再进入 scheduleNext 方法看看是怎么回事。

该方法内部调用了 THREADPOOLEXECUTOR 这个线程池来执行,这个线程池才是真正执行任务的,我们再来看看这个线程池的构造。

可以看到该线程池的配置:CPU数 + 1 的核心线程数,128 长度的工作队列等等。

任务的执行就这么开始了。我们再来看看 AsyncTask 的构造方法,了解一下那个 mFuture 是个啥。

构造 mFuture 时传入了 mWorker 对象,而通过查看 FutureTask 源码可以看到,它的 run 方法会调用这个 mWorker 的 call 方法,线程池最终执行任务时就是调用的这个 call 方法。

再来看看这个 mWorker 的 call 方法是如何执行的:先执行 mTaskInvoked.set(true);将该任务标记为已经被调用。然后执行 doInBackGround 传入参数并执行任务。然后将返回值作为参数,调用 postResult 方法。postResult 方法不再贴代码了,它内部就是创建了一个 Message,将当前 AsyncTask 对象和形参 result 包装起来作为 obj,然后调用 Handler 发送消息。

运行在主线程的 Handler 接收到这个消息时会调用 AsyncTask 的 finish 方法。

该方法根据 AsyncTask 是否被取消,取消就执行 onCancelled(),没取消就执行 onPostExecute(),然后更新状态为 finish。至此 AsyncTask 的整个执行流程就走完了。

哦对了,还有 onProgressUpdate 去哪了?。。因为我们更新进度都是在 doInBackGround 方法中手动调用 publishProgress 方法来更新的,那么我们看一下这个方法:

很简单,就是在任务未被取消时创建 Message,将当前 AsyncTask 对象和形参 progress 包装起来作为 obj,然后用 Handler 发送。运行在主线程的 Handler 接收到该消息后就调用了抽象的 onProgressUpdate 方法。

优化 AsyncTask

这是我平常使用 AsyncTask 时对其进行的一些优化(现在已经不怎么用它了),是直接在最新的 API AsyncTask 类基础上进行的优化:

  • 在 executeOnExecutor 方法中对 exec.execute(mFuture); 进行 try…catch

  • 取消任务队列的大小限制: 这里不传入参数表示队列长度为 Integer 的上限,可以视为无上限了。

  • 适当提高 keepAliveTime
   

想获得去掉 5 元限制的证券账户吗?

证券交易股票基金的佣金,不足 5 元会按照 5 元收取。比如交易 1000 元的股票,按照普遍的证券佣金手续费率万 2.5,其交易佣金为 0.25 元,小于 5 元,实际会收取佣金 5 元,买卖两次需要支付 10 元佣金成本,1% 的利润就这样没了。

如果您想去掉最低交易佣金 5 元限制,使用微信扫描左边小程序二维码,访问微信小程序「优财助手」,点击底部菜单「福利」,阅读文章「通过优财开证券账户无最低交易佣金 5 元限制」,按照文章步骤操作即可获得免 5 元证券账户,股票基金交易手续费率万 2.5。

请注意,一定要按照文章描述严格操作,如错误开户是无法获得免 5 元证券账户的。

发表评论

电子邮件地址不会被公开。 必填项已用*标注