Android AsyncTask 异步任务
Android 提供了一个轻量级的用于处理异步任务的类 AsyncTask
我们一般是继承 AsyncTask
,然后在类中实现异步操作,再将异步执行的进度,反馈给 UI 主线程
在我们继续讲 AsyncTask
之前,我们先来讲讲什么是线程
什么是多线程
理解多线程之前,我们先来了解几个名词 应用程序,进程,线程,多线程
名词 | 说明 |
---|---|
应用程序(Application) | 为了完成特定任务,用某种语言编写的一组指令集合(一组静态代码) |
进程(Process) | 运行中的程序 ,系统调度与资源分配的一个 独立单位 ,操作系统会为每个进程分配一段内存空间,程序的依次动态执行,经历代码加载 -> 执行 -> 执行完毕的完整过程! |
线程(Thread) | 比进程更小的执行单元,每个进程可能有多条线程 线程需要放在一个进程中才能执行 线程是由程序负责管理的,而进程则是由系统进行调度的 |
多线程概念(Multithreading) | 并行地执行多条指令,将CPU的 时间片 按照调度算法,分配给各个线程,实际上是 分时执行 的,只是这个切换的时间很短,用户感觉是同时而已 |
打个比方
我们挂着 QQ,突然想去听歌,那么我们需要把 QQ 关掉,然后再去启动 XX 播放器吗?
答案显然是否定的
我们直接打开播放器放歌就好,QQ 还在运行着,是吧
这就是简单的多线程
在实际开发中,也有这样的例子,比如应用正在运行, 发现新版本了,想后台更新,这个时候一般我们会开辟出一条后台线程,用于下载新版本的apk,但是这个时候 我们还可以使用应用中的其它功能
同步与异步
同步 :当我们执行某个功能时,在没有得到结果之前,这个调用就不能返回!简单点就是说必须 等前一件事做完才能做下一件事;举个简单的例子:比如你啪啪啪,为了避免弄出人命,肯定要先戴好套套, 然后再啪啪啪是吧~套套戴好 -> 然后啪啪啪,比如你没套套,那啪啪啪的操作就要等待了,直到你把 套套买回来带上,这个时候就可以开始啪啪啪了~一个形象地例子,
异步 :和同步则是相对的,当我们执行某个功能后,我们并不需要立即得到结果,我们额可以正常地 做其他操作,这个功能可以在完成后通知或者回调来告诉我们;还是上面那个后台下载的例子,后台下载, 我们执行下载功能后,我们就无需去关心它的下载过程,当下载完毕后通知我们就可以了~
Android 为很么要引入异步任务
Android 程序刚启动时,会同时启动一个对应的主线程 (Main Thread),但这个主线程主要负责处理 与 UI 相关的事件,有时我们也把他称作UI线程
而 Android App 必须遵守这个单线程模型的规则: Android UI操作并不是线程安全的并且这些操作都需要在UI线程中执行
假如我们在非 UI线程 中,比如在主线程中 new Thread()
另外开辟一个线程,然后直接在里面修改 UI控件的值,此时会抛出下述异常
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views
另外,如果我们把耗时的操作都放在 UI 线程中的话,UI 线程超过 5s
没有响应用于请求,那么
这个时候会引发 ANR(Application Not Responding)
异常,就是应用无响应
最后一个原因,就是现在的 Android 4.0+ 禁止在 UI 线程中执行网络操作,不然会报: android.os.NetworkOnMainThreadException
这就是为什么 Android 要引入异步任务的原因
实现异步有很多方法,也不一定就要使用 AsyncTask
,还可以自己开辟一个线程,完成相关操作后,通过下述两种方法进行 UI 更新
-
Handler, 在 Handler 里写好 UI 更新,然后通过
sendMessage()
等的方法通知UI
-
利用
Activity.runOnUiThread(Runnable)
把更新 UI 的代码创建在Runnable
中,更新UI
时,把 Runnable 作为参数传递
AsyncTask
AsyncTask
是一个抽象类,一般我们都会定义一个类继承 AsyncTask 然后重写相关方法
比如下面的代码就定义了一个下载文件的 Task : DownloadFilesTask
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> { protected Long doInBackground(URL... urls) { int count = urls.length; long totalSize = 0; for (int i = 0; i < count; i++) { totalSize += Downloader.downloadFile(urls[i]); publishProgress((int) ((i / (float) count) * 100)); // Escape early if cancel() is called if (isCancelled()) break; } return totalSize; } protected void onProgressUpdate(Integer... progress) { setProgressPercent(progress[0]); } protected void onPostExecute(Long result) { showDialog("Downloaded " + result + " bytes"); } }
AsyncTask<Params, Progress, Result>
三个参数说明
参数 | 说明 |
---|---|
Params | 启动任务执行的是如参数,比如一个网络请求的 URL |
Progress | 后台任务执行的百分比 |
Result | 后台任务执行完毕后返回的结果 |
如果不需要一些参数,可以使用 void
代替
AsyncTask 方法与流程
方法 | 说明 |
---|---|
onPreExecute() | 在执行后台耗时操作前调用,通常用于一些初始化操作,比如显示进度条 |
doInBackground(params...) | 在 onPreExecute() 方法执行完毕后立即执行,该方法运行于后台,主要负责执行耗时的后台处理工作,可调用 publishProgress(progress) 来更新时时的任务进度 |
onPostExecute(Result) | 在 doInBackground(params...) 执行完毕后,该方法会被 UI 线程调用,后台任务的运行结果将通过该方法传递到 UI 线程,然后展示给用户 |
onProgressUpdate(progress) | 在 publishProgress(progress...) 被调用后,UI 线程将调用该方法在界面上展示任务的进度,比如更新进度条 |
onCancelled() | 用户取消线程操作的时候调用,也就是在主线程调用 onCancelled() 的时候调用 |
使用 AsyncTask 几点注意事项
- Task 的实例必须在 UI 线程中创建
execute()
方法必须在 UI 线程中调用- 不要手动调用
onPreExecute()
、doInBackground(params...)
、onPostExecute
、onCancelled()
这几个方法 - Task 只能执行一次,运行多次会出现异常
范例
理论的东西真是枯燥无味,但又要讲清楚,还好已经完了,接下来我们写一个范例来演示下 AsyncTask
很简单,就是模拟下载更新进度条
-
创建一个 空的 Android 项目
cn.twle.android.AsyncTask
-
修改
activity_main.xml
添加一个进度条和一个按钮<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:orientation="vertical"> <TextView android:id="@+id/txttitle" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <!--设置一个进度条,并且设置为水平方向--> <ProgressBar android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/pgbar" style="?android:attr/progressBarStyleHorizontal"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btn_update" android:text="开始下载"/> </LinearLayout>
-
在
MainActivity.java
同一目录下创建Download.java
用于模拟耗时操作package cn.twle.android.asynctask; public class Download { //延时操作,用来模拟下载 public void delay() { try { Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace();; } } }
-
在
MainActivity.java
同一目录下创建MsDownloadTask.java
package cn.twle.android.asynctask; import android.os.AsyncTask; import android.widget.ProgressBar; import android.widget.TextView; public class MsDownloadTask extends AsyncTask<Integer,Integer,String> { private TextView txt; private ProgressBar pgbar; public MsDownloadTask(TextView txt,ProgressBar pgbar) { super(); this.txt = txt; this.pgbar = pgbar; } //该方法不运行在UI线程中,主要用于异步操作,通过调用publishProgress()方法 //触发onProgressUpdate对UI进行操作 @Override protected String doInBackground(Integer... params) { Download dop = new Download(); int i = 0; for (i = 10;i <= 100;i+=10) { dop.delay(); publishProgress(i); } return i + params[0].intValue() + ""; } //该方法运行在UI线程中,可对UI控件进行设置 @Override protected void onPreExecute() { txt.setText("开始下载"); } //在doBackground方法中,每次调用publishProgress方法都会触发该方法 //运行在UI线程中,可对UI控件进行操作 @Override protected void onProgressUpdate(Integer... values) { int value = values[0]; pgbar.setProgress(value); } }
-
最后修改 MainActivity.java
package cn.twle.android.asynctask; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; public class MainActivity extends AppCompatActivity { private TextView txttitle; private ProgressBar pgbar; private Button btnupdate; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); txttitle = (TextView)findViewById(R.id.txttitle); pgbar = (ProgressBar)findViewById(R.id.pgbar); btnupdate = (Button)findViewById(R.id.btn_update); btnupdate.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { MsDownloadTask myTask = new MsDownloadTask(txttitle,pgbar); myTask.execute(1000); } }); } }
是不是很简单