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

Android AsyncTask使用以及源码解析

程序员文章站 2024-02-16 19:25:40
综述   在android中,我们需要进行一些耗时的操作,会将这个操作放在子线程中进行。在子线程操作完成以后我们可以通过handler进行发送消息,通知ui进行一些更新操...

综述

  在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的用法,在这里我们就一个下载的功能,从网络上下载两个文件。我们先来看一下效果演示。

效果演示

Android 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的时候若是适配更多的系统的版本的话,使用的时候就要注意了。

源码下载:

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