AsyncTask的使用

AsyncTask的简单使用

##先大概认识下Android.os.AsyncTask类: android的类AsyncTask对线程间通讯进行了包装,提供了简易的编程方式来使后台线程和UI线程进行通讯:后台线程执行异步任务,并把操作结果通知UI线程。

public abstract class AsyncTask<Params, Progress, Result> {
...
}
  • AsyncTask是抽象类.AsyncTask定义了三种泛型类型 Params,Progress和Result。
  • Params 启动任务执行的输入参数,比如HTTP请求的URL。
  • Progress 后台任务执行的百分比。
  • Result 后台执行任务最终返回的结果,比如String,Integer等。
  • AsyncTask的执行分为四个步骤,每一步都对应一个回调方法,开发者需要实现这些方法。
    1. 继承AsyncTask
    1. 实现AsyncTask中定义的下面一个或几个方法
    • onPreExecute(), 该方法将在执行实际的后台操作前被UI 线程调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条,或者一些控件的实例化,这个方法可以不用实现。
    • doInBackground(Params...), 将在onPreExecute 方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台处理工作。可以调用 **publishProgress()**方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。
    • onProgressUpdate(Progress...),在publishProgress方法被调用后,UI 线程将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。
    • onPostExecute(Result), 在doInBackground 执行完成后,onPostExecute 方法将被UI 线程调用,后台的计算结果将通过该方法传递到UI 线程,并且在界面上展示给用户.
    • onCancelled(),在用户取消线程操作的时候调用。在主线程中调用onCancelled()的时候调用。
    • 具体串行并行的调用待整理
  • 为了正确的使用AsyncTask类,以下是几条必须遵守的准则:
    1. Task的实例必须在UI 线程中创建
    1. execute方法必须在UI 线程中调用
    1. 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法,需要在UI线程中实例化这个task来调用。
    1. 该task只能被执行一次,否则多次调用时将会出现异常

doInBackground方法和onPostExecute的参数必须对应,这两个参数在AsyncTask声明的泛型参数列表中指定,第一个为doInBackground接受的参数,第二个为显示进度的参数,第第三个为doInBackground返回和onPostExecute传入的参数。

简单示例

public class MainActivity extends Activity {
	Button download;
	ProgressBar pb;
	TextView tv;
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		pb = (ProgressBar) findViewById(R.id.pb);
		tv = (TextView) findViewById(R.id.tv);

		download = (Button) findViewById(R.id.download);
		download.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				DownloadTask dTask = new DownloadTask();
				dTask.execute(1000);
			}
		});
	}
	class DownloadTask extends AsyncTask<Integer, Integer, String> {
		// 后面尖括号内分别是参数(例子里是线程休息时间),进度(publishProgress用到),返回值 类型

		@Override
		protected void onPreExecute() {//前期准备工作1.不能做耗时的任务2.本方法执行完后才能执行 doInBackground(Integer... params)
			tv.setText("开始执行任务");
			
		}
		@Override
		protected String doInBackground(Integer... params) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			// 第二个执行方法,onPreExecute()执行完后执行
			for (int i = 0; i <= 100; i++) {
				pb.setProgress(i);//可以在主线程或子线程里调用 ,子线程时内部使用了View的post方法,
				publishProgress(i);
				try {
					Thread.sleep(params[0]);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			return "执行完毕";
		}
		@Override
		protected void onProgressUpdate(Integer... progress) {
			// 这个函数在doInBackground调用publishProgress时触发,虽然调用时只有一个参数
			// 但是这里取到的是一个数组,所以要用progesss[0]来取值
			// 第n个参数就用progress[n]来取值
			tv.setText(progress[0] + "%");
			//super.onProgressUpdate(progress);
		}

		@Override
		protected void onPostExecute(String result) {
			// doInBackground返回时触发,换句话说,就是doInBackground执行完后触发
			// 这里的result就是上面doInBackground执行后的返回值,所以这里是"执行完毕"
			setTitle(result);
			//super.onPostExecute(result);
		}
	}
}

添加取消任务示例

调用task.cancel(true);的方法在doInBackground()方法里判断任务是否取消,如果取消尽快结束后台方法。复写onCancelled(Result result)任务被取消时的方法。

  • 如果任务没有被执行直接执行onCancelled(Result result)方法,且在此执行该任务时报异常。
  • 如果任务执行时被取消了,doInBackground()方法会一直执行到在次判断是否取消的标示,然后调用onCancelled(Result result)方法。
  • task.cancel(boolean b),b 是否可以打断正在执行的任务
		@Override
		protected String doInBackground(Integer... params) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			// 第二个执行方法,onPreExecute()执行完后执行
			String res = null;
			for (int i = 0; i <= 100; i++) {
				Log.d("jpc",i+" 是否取消了"+isCancelled());
				if(isCancelled()){//添加是否取消的判断,尽快结束doInBackground()方法
					res = "在"+i+"时取消了";
					Log.d("jpc",i+" 取消了");
					break;
				}else{
					pb.setProgress(i);//可以在主线程或子线程里调用 ,子线程时内部使用了View的post方法,
					publishProgress(i);
					try {
						Thread.sleep(params[0]);
					} catch (InterruptedException e) {
						e.printStackTrace();
						Log.d("jpc",i+" "+e);
						res = e.getMessage();
					}
					res = "执行完毕";
				}
				
			}
			return res;
		}

@Override
		protected void onCancelled(String result) {
			Log.d("jpc"," onCancelled  取消了");
			tv.setText(result +"  onCancelled");
		}

开始执行任务

public static void execute(Runnable runnable) public final AsyncTask<Params, Progress, Result> execute(Params... params) public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params)

public static void execute(Runnable runnable):API 11 3.0以后提供的。利用AsyncTask里的串行线程池来执行任务,任务是依次执行,具体执行任务的线程来自线程池中。

private void executeTest1(){
		for(int i = 0;i<200;i++){
			final int index = i;
			Runnable runnable = new Runnable() {
				
				@Override
				public void run() {
					Log.d("jpc",index + " 开始执行   Thread ID = "+Thread.currentThread().getId()+ "    Thread Name = "+Thread.currentThread().getName());
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					Log.d("jpc",index + " 结束执行   Thread ID = "+Thread.currentThread().getId()+ "    Thread Name = "+Thread.currentThread().getName());
				}
			};
			AsyncTask.execute(runnable);
		}
	}

public final AsyncTask<Params, Progress, Result> execute(Params... params):利用串行的线程池依次执行任务,返回任务本身可以用来取消任务等操作。

**public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params)**API 11 3.0以后提供的。 这个接口允许开发者提供自定义的线程池来运行和调度Thread,如果你想让所有的任务都能并发同时运行,那就创建一个没有限制的线程池(Executors.newCachedThreadPool()),并提供给AsyncTask。这样这个AsyncTask实例就有了自己的线程池而不必使用AsyncTask默认的(AsyncTask.THREAD_POOL_EXECUTOR ,核心线程数有限制超过等待,任务队列128有限制超过报错)。

private void executeTest2(){
		for(int i = 0;i<200;i++){
			MyTask task = new MyTask();
			task.execute(i);
		}
	}
	
	@SuppressLint("NewApi") private void executeTest3(){
		for(int i = 0;i<200;i++){
			MyTask task = new MyTask();
			task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,i);
		}
	}
	@SuppressLint("NewApi") private void executeTest4(){
		for(int i = 0;i<2000;i++){
			MyTask task = new MyTask();
			task.executeOnExecutor(Executors.newCachedThreadPool(),i);
		}
	}
	class MyTask extends AsyncTask<Integer, Void, String>{

		@Override
		protected String doInBackground(Integer... params) {
			Log.d("jpc",params[0] + " 开始执行   Thread ID = "+Thread.currentThread().getId()+ "    Thread Name = "+Thread.currentThread().getName());
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			Log.d("jpc",params[0] + " 结束执行   Thread ID = "+Thread.currentThread().getId()+ "    Thread Name = "+Thread.currentThread().getName());

			return params[0]+" 完成";
		}
		
	}

问题

  • 有时候 AsyncTask.execute()的任务不一定马上执行 AsyncTask主要有二个部分:一个是与主线各的交互,另一个就是线程的管理调度。虽然可能多个AsyncTask的子类的实例,但是AsyncTask的内部Handler和ThreadPoolExecutor都是进程范围内共享的,其都是static的,也即属于类的,类的属性的作用范围是CLASSPATH,因为一个进程一个VM,所以是AsyncTask控制着进程范围内所有的子类实例。 与主线程交互 与主线程交互是通过Handler来进行的 线程任务的调度 内部会创建一个进程作用域的线程池来管理要运行的任务,也就就是说当你调用了AsyncTask#execute()后,AsyncTask会把任务交给线程池,由线程池来管理创建Thread和运行Therad。 所以所有的AsyncTask并不都会运行在单独的线程中,而是被SERIAL_EXECUTOR顺序的使用线程执行。因为应用中可能还有其他地方使用AsyncTask,所以某个AsyncTask也许会等待到其他任务都完成时才得以执行而不是调用executor()之后马上执行。 那么解决方法其实很简单,要么直接使用Thread,要么创建一个单独的线程池(Executors.newCachedThreadPool())。或者最简单的解法就是使用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR),这样起码不用等到前面的都结束了再执行。

  • 生命周期 关于AsyncTask存在一个这样广泛的误解,很多人认为一个在Activity中的AsyncTask会随着Activity的销毁而销毁。然后事实并非如此。AsyncTask会一直执行doInBackground()方法直到方法执行结束。一旦上述方法结束,会依据情况进行不同的操作。

如果cancel(boolean)调用了,则执行onCancelled(Result)方法 如果cancel(boolean)没有调用,则执行onPostExecute(Result)方法 AsyncTask的cancel方法需要一个布尔值的参数,参数名为mayInterruptIfRunning,意思是如果正在执行是否可以打断, 如果这个值设置为true,表示这个任务可以被打断,否则,正在执行的程序会继续执行直到完成。如果在doInBackground()方法中有一个循环操作,我们应该在循环中使用isCancelled()来判断,如果返回为true,我们应该避免执行后续无用的循环操作。 总之,我们使用AsyncTask需要确保AsyncTask正确地取消。

  • 不好好工作的cancel() 有时候起作用。 如果你调用了AsyncTask的cancel(false),doInBackground()仍然会执行到方法结束,只是不会去调用 onPostExecute()方法。但是实际上这是让应用程序执行了没有意义的操作。那么是不是我们调用cancel(true)前面的问题就能解决呢?并非如此。如果mayInterruptIfRunning设置为true,会使任务尽早结束,但是如果的doInBackground()有不可打断的方法会失效,比如这个BitmapFactory.decodeStream() IO操作。但是你可以提前关闭IO流并捕获这样操作抛出的异常。但是这样会使得cancel()方法没有任何意义。

  • 内存泄露 还有一种常见的情况就是,在Activity中使用非静态匿名内部AsyncTask类,由于Java内部类的特点,AsyncTask内部类会持有外部类的隐式引用。由于AsyncTask的生命周期可能比Activity的长,当Activity进行销毁AsyncTask还在执行时,由于AsyncTask持有Activity的引用,导致Activity对象无法回收,进而产生内存泄露。

  • 结果丢失 另一个问题就是在屏幕旋转等造成Activity重新创建时AsyncTask数据丢失的问题。当Activity销毁并创新创建后,还在运行的 AsyncTask会持有一个Activity的非法引用即之前的Activity实例。导致onPostExecute()没有任何作用。

来源

Android实战技巧:深入解析AsyncTask [The Hidden Pitfalls of AsyncTask] (http://blog.danlew.net/2014/06/21/the-hidden-pitfalls-of-asynctask/) [Android中糟糕的AsyncTask] (http://www.open-open.com/lib/view/open1417955629527.html)

转载于:https://my.oschina.net/u/194743/blog/520682

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值