当前位置: 移动技术网 > IT编程>移动开发>Android > Android AsyncTask使用以及源码解析

Android AsyncTask使用以及源码解析

2019年07月24日  | 移动技术网IT编程  | 我要评论

百里挑一郑九成,阿甘正传 下载,美的电磁炉故障维修

综述

  在android中,我们需要进行一些耗时的操作,会将这个操作放在子线程中进行。在子线程操作完成以后我们可以通过handler进行发送消息,通知ui进行一些更新操作(具体使用及其原理可以查看android的消息机制——handler的工作过程这篇文章)。当然为了简化我们的操作,在android1.5以后为我们提供了asynctask类,它能够将子线程处理完成后的结果返回到ui线程中,之后我们便可以根据这些结果进行一列的ui操作了。

asynctask的使用方法

  实际上asynctask内部也就是对handler和线程池进行了一次封装。它是一个轻量级的异步任务类,它的后台任务在线程池中进行。之后我们可以将任务执行的结果传递给主线程,这时候我们就可以在主线程中操作ui了。
  asynctask他是一个抽象的泛型类,所以我们创建一个子类,来实现asynctask中的抽象方法。asynctask中提供了三个泛型参数,下面我们就来看一下这三个泛型参数.
  1. params:在执行asynctask时所传递的参数,该参数在后台线程中使用。
  2. progress:后台任务执行进度的类型
  3. result:后台任务执行完成后返回的结果类型。
  对于以上三个泛型参数我们不需要使用的时候,可以使用void来代替。与activity生命周期类似,在asynctask中也为我们提供了一些方法,我们通过重写这几个方法来完成整个异步任务。我们主要使用的方法有一下四个:
  1. onpreexecute():该方法在异步任务工作之前执行,主要用于一些参数或者ui的初始化操作。
  2. doinbackground(params… params):该方法在线程池中执行,params参数表示异步任务时输入的参数。在这个方法中我们通过publishprogress来通知任务进度。
  3. onprogressupdate(progress… values):当后台任务的进度发生改变的时候会调用该方法,我们可以再这个方法中进行ui的进度展示。values参数表示任务进度。
  4. postresult(result result):在异步任务完成之后执行,result参数为异步任务执行完以后所返回的结果。
  在上面四个方法中只有doinbackground在子线程中运行,其余都三个方法都是在主线程中运行的。其中的…表示参数的数量不定,是一种数组类型的参数。
  下面我们就来写一个例子来看一下asynctask的用法,在这里我们就一个下载的功能,从网络上下载两个文件。我们先来看一下效果演示。

效果演示

代码分析

  由于我们做的下载任务,首先我们就得添加访问网络权限以及一些sd卡相关的权限。

<!-- 在sd卡中创建与删除文件权限 -->
<uses-permission android:name="android.permission.mount_unmount_filesystems"/>
<!-- 向sd卡写入数据权限 -->
<uses-permission android:name="android.permission.write_external_storage"/>
<!-- 授权访问网络 -->
<uses-permission android:name="android.permission.internet"/>

下面我们来看一下activity代码。

package com.example.ljd.asynctask;

import android.app.progressdialog;
import android.os.asynctask;
import android.os.environment;
import android.support.v7.app.appcompatactivity;
import android.os.bundle;
import android.view.view;
import android.widget.button;
import android.widget.toast;

import java.io.bufferedinputstream;
import java.io.file;
import java.io.fileoutputstream;
import java.io.ioexception;
import java.io.inputstream;
import java.net.httpurlconnection;
import java.net.url;

public class mainactivity extends appcompatactivity implements view.onclicklistener{

 private downloadasynctask mdownloadasynctask;

 private button mbutton;
 private string[] path = {
   "http://msoftdl.360.cn/mobilesafe/shouji360/360safesis/360mobilesafe_6.2.3.1060.apk",
   "http://dlsw.baidu.com/sw-search-sp/soft/7b/33461/freeime.1406862029.exe",
 };

 @override
 protected void oncreate(bundle savedinstancestate) {
  super.oncreate(savedinstancestate);
  setcontentview(r.layout.activity_main);
  mbutton = (button)findviewbyid(r.id.button);
  mbutton.setonclicklistener(this);
 }

 @override
 protected void ondestroy() {
  if (mdownloadasynctask != null){
   mdownloadasynctask.cancel(true);
  }
  super.ondestroy();
 }

 @override
 public void onclick(view v) {

  mdownloadasynctask = new downloadasynctask();
  mdownloadasynctask.execute(path);
 }

 class downloadasynctask extends asynctask<string,integer,boolean>{

  private progressdialog mpbar;
  private int filesize;  //下载的文件大小
  @override
  protected void onpreexecute() {
   super.onpreexecute();
   mpbar = new progressdialog(mainactivity.this);
   mpbar.setprogressnumberformat("%1d kb/%2d kb");
   mpbar.settitle("下载");
   mpbar.setmessage("正在下载,请稍后...");
   mpbar.setprogressstyle(progressdialog.style_horizontal);
   mpbar.setcancelable(false);
   mpbar.show();
  }

  @override
  protected boolean doinbackground(string... params) {
   //下载图片
   for (int i=0;i<params.length;i++){
    try{
     if(environment.getexternalstoragestate().equals(environment.media_mounted)){
      url url = new url(params[i]);
      httpurlconnection conn = (httpurlconnection) url.openconnection();
      //设置超时时间
      conn.setconnecttimeout(5000);
      //获取下载文件的大小
      filesize = conn.getcontentlength();
      inputstream is = conn.getinputstream();
      //获取文件名称
      string filename = path[i].substring(path[i].lastindexof("/") + 1);
      file file = new file(environment.getexternalstoragedirectory(), filename);
      fileoutputstream fos = new fileoutputstream(file);
      bufferedinputstream bis = new bufferedinputstream(is);
      byte[] buffer = new byte[1024];
      int len ;
      int total = 0;
      while((len =bis.read(buffer))!=-1){
       fos.write(buffer, 0, len);
       total += len;
       publishprogress(total);
       fos.flush();
      }
      fos.close();
      bis.close();
      is.close();
     }
     else{
      return false;
     }
    }catch (ioexception e){
     e.printstacktrace();
     return false;
    }
   }

   return true;
  }

  @override
  protected void onpostexecute(boolean aboolean) {
   super.onpostexecute(aboolean);
   mpbar.dismiss();
   if (aboolean){
    toast.maketext(mainactivity.this,"下载完成",toast.length_short).show();
   } else {
    toast.maketext(mainactivity.this,"下载失败",toast.length_short).show();
   }
  }

  @override
  protected void onprogressupdate(integer... values) {
   super.onprogressupdate(values);
   mpbar.setmax(filesize / 1024);
   mpbar.setprogress(values[0]/1024);
  }
 }

}

在以上代码中有几点我们需要注意一下。
  1. asynctask中的execute方法必须在主线程中执行。
  2. 每个asynctask对象只能执行一次execute方法。
  3. 当我们的activity销毁的时候需要进行取消操作,其中boolean型的参数mayinterruptifrunning表示是否中断后台任务。
  最后的布局代码就很简单了,只有一个button而已。

<?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:padding="@dimen/activity_vertical_margin"
  android:orientation="vertical"
  tools:context="com.example.ljd.asynctask.mainactivity">

  <button
    android:id="@+id/button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="download"/>
</linearlayout>

  这里还有一点需要说明一下,由于在不同的android版本中对asynctask进行了多次修改,所以当我们通过多个asynctask对象执行多次execute方法的时候,它们执行顺序是串行还是并行根据系统不同的版本而出现差异,这里就不再具体分析。

asynctask源码分析

  在这里我们采用android6.0中的asynctask源码进行分析,对于不同系统的asynctask代码会有一定的差异。当创建一个asynctask对象以后我们便可以通过execute方法来执行整个任务。那就在这里首先看一下execute方法中的代码。

@mainthread
public final asynctask<params, progress, result> execute(params... params) {
  return executeonexecutor(sdefaultexecutor, params);
}

  可以看到这个execute方法中的代码是如此的简单,只是执行了executeonexecutor方法并返回asynctask对象。下面我们就来看一下executeonexecutor这个方法。

@mainthread
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所执行的状态进行判断。如果asynctask正在执行任务或者任务已经知己完成就会给我们抛出异常,也就解释上面所说的每个asynctask对象只能执行一次execute方法。紧接着将当前任务状态修改为正在运行以后便开始执行onpreexecute方法。这也说明了在上面我们重写的四个方法中onpreexecute方法最先指向的。下面我们在看一下mworker和mfuture这两个全局变量。
  mfuture是futuretask对象,而mworker是workerrunnable对象,workerrunnable是asynctask中的一个实现callable接口的抽象内部类,在workerrunnable中只定义了一个params[]。

private final workerrunnable<params, result> mworker;
private final futuretask<result> mfuture;

......

private static abstract class workerrunnable<params, result> implements callable<result> {
  params[] mparams;
}

  mfuture和mworker是在asynctask的构造方法中初始化的。我们看一下asynctask的构造方法。

public asynctask() {
  mworker = new workerrunnable<params, result>() {
    public result call() throws exception {
      mtaskinvoked.set(true);

      process.setthreadpriority(process.thread_priority_background);
      //noinspection unchecked
      result result = doinbackground(mparams);
      binder.flushpendingcommands();
      return postresult(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);
      }
    }
  };
}

  在这里首先创建一个workerrunnable对象mworker,并且实现了callable接口的call方法,对于这个call方面里面的内容在后面会进行详细说明。然后我们再通过mworker,创建一个futuretask对象mfuture,并且重写里面的done方法,当我们调用asynctask里面的cancel方法时,在futuretask中会调用这个done方法。在这里介绍完mworker和mfuture后我们再回过头来看executeonexecutor方法。在这里通过我们传入的params参数初始化了mworker中的mparams。而下面的exec则是在execute里面传入的sdefaultexecutor,并且执行sdefaultexecutor的execute方法。下面我们再来看一下这个sdefaultexecutor对象。

private static final int cpu_count = runtime.getruntime().availableprocessors();
private static final int core_pool_size = cpu_count + 1;
private static final int maximum_pool_size = cpu_count * 2 + 1;
private static final int keep_alive = 1;

private static final threadfactory sthreadfactory = new threadfactory() {
  private final atomicinteger mcount = new atomicinteger(1);

  public thread newthread(runnable r) {
    return new thread(r, "asynctask #" + mcount.getandincrement());
  }
};

private static final blockingqueue<runnable> spoolworkqueue =
    new linkedblockingqueue<runnable>(128);

public static final executor thread_pool_executor
    = new threadpoolexecutor(core_pool_size, maximum_pool_size, keep_alive,
        timeunit.seconds, spoolworkqueue, sthreadfactory);

public static final executor serial_executor = new serialexecutor();

......

private static volatile executor sdefaultexecutor = serial_executor;

......

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

  在这里首先整体介绍一下这段代码。这段代码的主要功能就是创建了两个线程池serialexecutor和thread_pool_executor。serialexecutor线程池用于对任务的排队,而thread_pool_executor则是用来执行任务。而sdefaultexecutor就是serialexecutor线程池。
  我们进入serialexecutor代码中看一下里面的内容。serialexecutor中的execute方法内的参数runnable就是我们传入的mfuture。在execute方法中创建了一个runnable对象,并将该队列插入对象尾部。这个时候如果是第一次执行任务。mactive必然为null,这时候便调用schedulenext方法从队列中取出runnable对象,并且通过thread_pool_executor线程池执行任务。我们可以看到在队列中的runnable的run方法中首先执行mfuture中的run方法,执行完之后又会调用schedulenext方法,从队列中取出runnable执行,直到所有的runnable执行完为止。下面就来看一下在线城池中是如何执行这个runnable的。从上面代码可以看出,在线城池中执行runnable其实最核心的部分还是执行mfuture的run方法。那就来看一下这个mfuture中的run方法。

public void run() {
  if (state != new ||
    !u.compareandswapobject(this, runner, null, thread.currentthread()))
    return;
  try {
    callable<v> c = callable;
    if (c != null && state == new) {
      v result;
      boolean ran;
      try {
        result = c.call();
        ran = true;
      } catch (throwable ex) {
        result = null;
        ran = false;
        setexception(ex);
      }
      if (ran)
        set(result);
    }
  } finally {
    // runner must be non-null until state is settled to
    // prevent concurrent calls to run()
    runner = null;
    // state must be re-read after nulling runner to prevent
    // leaked interrupts
    int s = state;
    if (s >= interrupting)
      handlepossiblecancellationinterrupt(s);
  }
}

  在这个方法中的callable对象就是我们asynctask中的mworker对象。在这里面也正是执行mworker中的call方法来完成一些耗时任务,于是我们就能想到我重写的doinbackground应该就在这个call方法中执行了。现在再回到asynctask的构造方法中看一下这个mworker中的call方法。

public result call() throws exception {
  mtaskinvoked.set(true);

  process.setthreadpriority(process.thread_priority_background);
  //noinspection unchecked
  result result = doinbackground(mparams);
  binder.flushpendingcommands();
  return postresult(result);
}

  这里我们很清楚的看到它执行我们重写的doinbackground方法,并且将返回的结果传到postresult方法中。下面就在看一下这个postresult方法。

private result postresult(result result) {
  @suppresswarnings("unchecked")
  message message = gethandler().obtainmessage(message_post_result,
      new asynctaskresult<result>(this, result));
  message.sendtotarget();
  return result;
}

  对与这个postresult方法里面也就是在我们子线程的任务处理完成之后通过一个handler对象将message发送到主线程中,并且交由主线程处理。asynctaskresult对象中存放的是子线程返回的结果以及当前asynctask对象。下面再看一下这和handler中所处理了哪些事情。

private static class internalhandler extends handler {
  public internalhandler() {
    super(looper.getmainlooper());
  }

  @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;
    }
  }
}

  可以看到在这个handler的handlemessage中会接受到两种message。在message_post_progress这个消息中主要是通过publishprogress方法将子线程执行的进度发送到主线程中并且通过onprogressupdate方法来更新进度条的显示。在message_post_result这个消息中通过当前的asynctask对象调用了finish方法,那么就来看一下这个finish方法。

private void finish(result result) {
  if (iscancelled()) {
    oncancelled(result);
  } else {
    onpostexecute(result);
  }
  mstatus = status.finished;
}

  这时候就可以看出若是我们取消了asynctask的话就不在执行onpostexecute方法,而是执行oncancelled方法,所以我们可以通过重写oncancelled方法来执行取消时我们需要处理的一些操作。当然若是asynctask没有被取消,这时候就回执行onpostexecute方法。到这里整个asynctask任务也就完成了。

总结

  在上面的serialexecutor线程池中可以看出,当有多个异步任务同时执行的时候,它们执行的顺序是串行的,会按照任务创建的先后顺序进行一次执行。如果我们希望多个任务并发执行则可以通过asynctask中的setdefaultexecutor方法将线程池设为thread_pool_executor即可。
  对于asynctask的在不同版本之间的差异不得不提一下。在android1.6,asynctask采用的是串行执行任务,在android1.6的时候采用线程池处理并行任务,而在3.0以后才通过serialexecutor线程池串行处理任务。在android4.1之前asynctask类必须在主线程中,但是在之后的版本中就被系统自动完成。而在android5.0的版本中会在activitythread的main方法中执行asynctask的init方法,而在android6.0中又将init方法删除。所以在使用这个asynctask的时候若是适配更多的系统的版本的话,使用的时候就要注意了。

源码下载:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网