浅谈android中的异步加载一

1、为什么需要异步加载。

    因为我们都知道在Android中的是单线程模型,不允许其他的子线程来更新UI,只允许UI线程(主线程更新UI),否则会多个线程都去更新UI会造成UI的一个混乱有些耗时的操纵(例如网络请求等),如果直接放到主线程中去请求的话则会造成主线程阻塞,而我们系统有规定的响应时间,当响应的时间超过了了阻塞的时间就会造成"Application No Response",也就是我们熟知的ANR错误解决上述问题的时候:我们一般使用的是线程或者线程池+Handler机制如果线程拿到一个数据需要去更新UI,那么就需要Handler把子线程的更新UI的数据发消息给主线程,从而让主线程去更新UI那么还在使用Thread或ThreadPool+Handler的你是否已经厌倦这些繁琐的操纵而且你会发现这些操作的代码都很类似。所以AsyncTask就应运而生了。

那么我们先从源码中的介绍来认识一下AsyncTask.大家别看源码介绍都这么多,实际上看源码更注重是一个整体意思的理解,而不是深入细节,否则深入进去将不可自拔,那么我也是大概从整体的意思来介绍一下源码中所介绍的AsyncTask
大致意思如下:
/**
 * 

AsyncTask enables proper and easy use of the UI thread. This class allows to * perform background operations and publish results on the UI thread without * having to manipulate threads and/or handlers.

上面的大致意思:AsyncTask异步加载可以很合适很容易在UI线程(主线程)使用,这个类允许做一些后台耗时的操作并且发送结果 给主线程而不需要借助多线程和Handler *

AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler} * and does not constitute a generic threading framework. AsyncTasks should ideally be * used for short operations (a few seconds at the most.) If you need to keep threads * running for long periods of time, it is highly recommended you use the various APIs * provided by the java.util.concurrent pacakge such as {@link Executor}, * {@link ThreadPoolExecutor} and {@link FutureTask}.

*上面的大致意思:AsyncTask被设计成一个围绕着Thread和Handler的帮助类并且不需要建立一个泛型Thread线程框架 注意:实际上AsyncTask建议被使用在稍微短时间的耗时操作(最多是十几秒或者几十秒),如果你的操作 需要更长的时间,那么就不建议使用AsyncTask,并且强烈建议你使用java.util.concurrent包中提供的Executor,ThreadPoolExecutor,FutureTask *

An asynchronous task is defined by a computation that runs on a background thread and * whose result is published on the UI thread. An asynchronous task is defined by 3 generic * types, called Params, Progress and Result, * and 4 steps, called onPreExecute, doInBackground, * onProgressUpdate and onPostExecute.

*上面的大致意思:一个异步加载是通过运行在后台的线程并且把结果发送给UI线程定义的,一个异步加载通过三个参数泛型,Params,Progress,Result和四个回调方法onPreExecute,doInBackground,onProgressUpdate,onPostExecute来定义的 *
*

Developer Guides

*

For more information about using tasks and threads, read the * Processes and * Threads developer guide.

*
* *

Usage

*

AsyncTask must be subclassed to be used. The subclass will override at least * one method ({@link #doInBackground}), and most often will override a * second one ({@link #onPostExecute}.)

* 上面的大致意思::它的用法:异步任务必须被子类继承才能被使用,这个子类至少要去实现一个回调方法#doInBackground,并且通常一般还会重写第二个#onPostExecute方法 *

Here is an example of subclassing:

*
 * private class DownloadFilesTask extends AsyncTask {
 *     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");
 *     }
 * }
 * 
* *

Once created, a task is executed very simply:

*
 * new DownloadFilesTask().execute(url1, url2, url3);
 * 
* *

AsyncTask's generic types

*

The three types used by an asynchronous task are the following:

*
    *
  1. Params, the type of the parameters sent to the task upon * execution.
  2. *
  3. Progress, the type of the progress units published during * the background computation.
  4. *
  5. Result, the type of the result of the background * computation.
  6. *
*

Not all types are always used by an asynchronous task. To mark a type as unused, * simply use the type {@link Void}:

*
 * private class MyTask extends AsyncTask { ... }
 * 
*上面意思:介绍了异步加载的泛型,这里会在本文中有介绍 *

The 4 steps

*

When an asynchronous task is executed, the task goes through 4 steps:

*
    *
  1. {@link #onPreExecute()}, invoked on the UI thread before the task * is executed. This step is normally used to setup the task, for instance by * showing a progress bar in the user interface.
  2. *
  3. {@link #doInBackground}, invoked on the background thread * immediately after {@link #onPreExecute()} finishes executing. This step is used * to perform background computation that can take a long time. The parameters * of the asynchronous task are passed to this step. The result of the computation must * be returned by this step and will be passed back to the last step. This step * can also use {@link #publishProgress} to publish one or more units * of progress. These values are published on the UI thread, in the * {@link #onProgressUpdate} step.
  4. *
  5. {@link #onProgressUpdate}, invoked on the UI thread after a * call to {@link #publishProgress}. The timing of the execution is * undefined. This method is used to display any form of progress in the user * interface while the background computation is still executing. For instance, * it can be used to animate a progress bar or show logs in a text field.
  6. *
  7. {@link #onPostExecute}, invoked on the UI thread after the background * computation finishes. The result of the background computation is passed to * this step as a parameter.
  8. *
* 上面意思:介绍了异步加载的四个回调方法执行的时机,这里会在本文中有详细介绍 *

Cancelling a task

*

A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking * this method will cause subsequent calls to {@link #isCancelled()} to return true. * After invoking this method, {@link #onCancelled(Object)}, instead of * {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])} * returns. To ensure that a task is cancelled as quickly as possible, you should always * check the return value of {@link #isCancelled()} periodically from * {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)

*上面意思:删除一个异步任务,一个任务可以在任何时候被删除,有个cancel方法boolean类型,cancel方法调用将会导致随继调用#isCancelled方法,并返回true *

Threading rules

*

There are a few threading rules that must be followed for this class to * work properly:

*
    *
  • The AsyncTask class must be loaded on the UI thread. This is done * automatically as of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.
  • *
  • The task instance must be created on the UI thread.
  • *
  • {@link #execute} must be invoked on the UI thread.
  • *
  • Do not call {@link #onPreExecute()}, {@link #onPostExecute}, * {@link #doInBackground}, {@link #onProgressUpdate} manually.
  • *
  • The task can be executed only once (an exception will be thrown if * a second execution is attempted.)
  • *
*上面的大致意思:线程的规则:1、异步任务类必须在UI线程加载,这个会自动被android.os.Build.VERSION_CODES#JELLY_BEAN完成 2、异步任务的实例化必须在UI线程中实现,即异步任务的对象必须在UI线程创建 3、#execute方法必须在UI线程中被调用 4、不要自己手动去调用#onPreExecute,#onPostExecute,#doInBackground,#onProgressUpdate方法,这些都是自动调用的 5、异步任务只会仅仅执行一次 *

Memory observability

*

AsyncTask guarantees that all callback calls are synchronized in such a way that the following * operations are safe without explicit synchronizations.

*
    *
  • Set member fields in the constructor or {@link #onPreExecute}, and refer to them * in {@link #doInBackground}. *
  • Set member fields in {@link #doInBackground}, and refer to them in * {@link #onProgressUpdate} and {@link #onPostExecute}. *
*上面的大致意思:异步任务保证了所有的回调方法都是异步加载的,并且操作是安全的没有线程同步的冲突 *

Order of execution

*

When first introduced, AsyncTasks were executed serially on a single background * thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed * to a pool of threads allowing multiple tasks to operate in parallel. Starting with * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single * thread to avoid common application errors caused by parallel execution.

*

If you truly want parallel execution, you can invoke * {@link #executeOnExecutor(java.util.concurrent.Executor, Object[])} with * {@link #THREAD_POOL_EXECUTOR}.

*/ 上面的大致意思:当第一次介绍,异步任务被有序地执行在一个后台的单一线程上,开始通过android.os.Build.VERSION_CODES#DONUT 它将被一个线程池允许多任务被执行在一个平行线上而改变。

2、AsyncTask为何而生
     子线程中更新UI
     封装、简化了异步操作
3、AsyncTask的基本结构
AsyncTask是一个抽象类通常用于被继承,继承Async还必须指定三个泛型参数
params:启动后台任务时输入的参数类型
progress:后台任务执行中返回的进度值的类型
result:后台执行任务完成后返回的结果类型


AsyncTask子类的四个回调方法:
onPreExecute()方法:选择性重写,在执行后台任务前被调用。(注意:选择性重写,在主线程调用,一般用于完成一些初始化的操作)
doInBackground方法:必须重写,在异步执行后台线程将要完成的任务时被调用。(注:必须重写,在子线程调用,用于执行耗时的操作)
onPostExecute方法:选择性重写(虽说是选择性重写,但是一般都会去重写,因为可以仔细想想,我一般开启的异步任务,都需要得到
异步任务后返回的数据,所以这时候你就得重写该方法),在doInBackground方法完后,去调用。并且该方法还有一个参数result就是
doInBackground方法的返回值也就是执行完异步任务后得到的返回值。(注意:一般都会去重写,在主线程调用,一般用于返回执行完异步任务返回的结果)
onProgressUpdate方法:选择性重写,除非你在doInBackground方法中手动调用publishProgress方法时候更新任务进度后,才会去重写和被回调.
(选择性重写(一般当在doInBackground方法中手动调用publishProgress才会去重写),在主线程中调用,一般用于得到子线程执行任务的进度值,并且把该进度值更新到UI线程)


4、AsyncTask中的三个泛型参数的类型与四个方法中参数的关系。
第一个参数类型为Params的泛型:
                                                ----决定了----->AsyncTask子类的实例调用execute()方法中传入参数类型,参数为该类型的可变长数组
                                                ----决定了----->doInBackground()方法的参数类型,参数为该类型的可变长数组
第二个参数类型为Progress的泛型:
                                               ----决定了------>在doInBackground方法中手动调用publishProgress方法中传入参数的类型,  参数为该类型的可变长数组
      ----决定了----->onProgressUpdate()方法中的参数类型,参数为该类型的可变长数组
第三个参数类型为Result的泛型:
                                               ----决定了------>doInBackground()方法的返回值的类型
      ----决定了------>onPostExecute方法的参数的类型


5、AsyncTask中的各个方法参数之间的传递关系:
通过总结了上面的类型参数关系实际上他们之间的参数传递关系也就出来了下面将通过一张参数传递图来说明他们之间的关系



浅谈android中的异步加载一_第1张图片6、从源码的角度将Thread+Handler机制与AsyncTask类比。
我们应该更熟悉Thread+Handler机制的这种模式来实现异步任务的,对于你还在使用该模式的并想要使用AsyncTask的,
我想将Thread+Handler机制与AsyncTask的类比的方法来学习AsyncTask应该对你更有用。实际上AsyncTask就是对
Thread+Handler的封装,只是一些操作被封装了而已。所以既然是一样的,你肯定就能在AsyncTask中找到Thread+Handler的影子。
类比一:我们首先来从外部的结构来说,外面很简单就是通过AsyncTask子类的一个实例调用execute()方法------->类似于我们在开启子线程的start()方法
类比二:再进入内部看它的四个重写方法,在四个方法中只有一个doInBackground是在子线程中执行的,它将实现耗时操作的代码---这就类似于子线程中
的run方法。
类比三:再来看在doInBackground方法中手动调用的publishProgress方法,它在子线程被调用,并传入子线程执行异步任务的进度-----这就类似于在子线程
中的Handler中的sendMessage方法向主线程发消息的方式,把子线程中的数据发给主线程。
类比四:通过类比三,我们很容易想到接下要做的类比,onProgressUpdate()方法处于主线程,它的参数就是publishProgress传来的,---这就类似于在主线程中的Handler中的handlerMessage
方法用于接收来自子线程中的数据。如果你觉得有所疑问的话,那么我们一起来来看看源码是怎么说的。
publishProgress的源码:

    /**
     * This method can be invoked from {@link #doInBackground} to
     * publish updates on the UI thread while the background computation is
     * still running. Each call to this method will trigger the execution of
     * {@link #onProgressUpdate} on the UI thread.
     *
     * {@link #onProgressUpdate} will note be called if the task has been
     * canceled.
     *
     * @param values The progress values to update the UI with.
     *
     * @see #onProgressUpdate
     * @see #doInBackground
     */
    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult(this, values)).sendToTarget();
        }
    }


    在publishProgress方法中有一个 sHandler的InternalHandler对象,而InternalHandler也就是继承于Handler,通过sHandler的obtainMessage
    方法把我们的传入的values封装成Message,然后通过滴啊用sendToTarget()方法发送我们封装好的Message
    如果还有疑问的话,我们继续来看obtainMessage方法和sendToTarget()的源码你就会明白了。
      /**
     * 
     * Same as {@link #obtainMessage()}, except that it also sets the what and obj members 
     * of the returned Message.
     * 
     * @param what Value to assign to the returned Message.what field.
     * @param obj Value to assign to the returned Message.obj field.
     * @return A Message from the global message pool.
     */
    public final Message obtainMessage(int what, Object obj)
    {
        return Message.obtain(this, what, obj);
    }


可以看到实际上就是在封装我们消息,有消息标识what参数,和obj参数也就是我们传入的value,最后通过我们非常熟悉的Message.obtain方法
得到Message对象,看到这里是不是和清楚了,实际上本质还是和原来的Thread-Handler模式一样
消息封装好了,就通过sendToTarget方法发送消息,我们再次来看下sendToTarget的源码,看它是不是做了发送消息的事
    /**
     * Sends this Message to the Handler specified by {@link #getTarget}.
     * Throws a null pointer exception if this field has not been set.
     */
    public void sendToTarget() {
        target.sendMessage(this);
    }


target.sendMessage(this);果然它如我们所料,它的确做了发送消息的事。我相信大家看到这应该明白了异步加载真正原理,实际上它并没有我们想象的那么高大上,只不过是在原来的Thread+Handler的基础上
进行了高度封装而已。

7、AsyncTask中的四个方法执行顺序是怎么样的呢,下面我们将通过一个demo中的Log打印输出来说明。

package com.mikyou.utils;


import android.os.AsyncTask;
import android.util.Log;


public class MikyouAsyncTask extends AsyncTask{
	@Override
	protected void onPreExecute() {
		Log.d("mikyou", "执行后台任务前调用onPreExecute");
		super.onPreExecute();
	}
	@Override
	protected Void doInBackground(Void... params) {
		publishProgress();
		Log.d("mikyou", "正在执行后台任务调用doInBackground");
		return null;
	}
	@Override
	protected void onProgressUpdate(Void... values) {
		Log.d("mikyou", "在doInBackground调用publishProgress后才会调用onProgressUpdate");
		super.onProgressUpdate(values);
	}
	@Override
	protected void onPostExecute(Void result) {
		Log.d("mikyou", "后台任务执行完后调用onPostExecute");
		super.onPostExecute(result);
	}


}

运行结果:

浅谈android中的异步加载一_第2张图片

通过以上的demo我们大致了解AsyncTask几个方法执行情况。

下面我们将通过一个异步加载网络图片的知识体会一下其中的工作原理。


MikyouAsyncTaskImageUtils异步加载的子类:

package com.mikyou.utils;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
//因为是网络加载一张图片,所以传入参数为URL为String类型,并且返回一个Bitmap对象
public class MikyouAsyncTaskImageUtils extends AsyncTask{
	private Bitmap mBitmap;
	private OnAsyncTaskImageListener listener;
	@Override
	protected void onPreExecute() {//在执行异步任务之前调用,做一些初始化的操作
		super.onPreExecute();
	}
	@Override
	protected Bitmap doInBackground(String... params) {
		//写耗时的网络请求操作
		String url=params[0];//因为只传了一个URL参数

		try {
			URL mURL=new URL(url);
			HttpURLConnection conn=(HttpURLConnection) mURL.openConnection();
			Thread.sleep(3000);//为了看到ProgressBar加载的效果,不会因为很快就加载完了,让它sleep 3秒
			InputStream is=conn.getInputStream();
			BufferedInputStream bis=new BufferedInputStream(is);
			mBitmap=BitmapFactory.decodeStream(bis);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return mBitmap;
	}
	@Override
	protected void onPostExecute(Bitmap result) {
		//拿到Bitmap的返回值,就需要将它传递给MainActivity中的iv让它设置这个mBitmap对象
		/**
		 * 这里有三种方法实现:第一直接把该类作为内部类放入Activity中就不需要传递mBitmap
		 * 因为iv在MainActivity中是全局的直接设置就可以了,实现数据共享
		 * 
		 *                                         第二将该类放到另外一个包内,此时就需要将外面的ImageView对象,通过构造器传入
		 *                                         然后再该类中去直接给ImageView对象设置Bitmap即可
		 *                                         
		 *                                         第三则是我这里要使用的,这个类也是在另外一个包内,采用的是把这个Bitmap对象
		 *                                         通过自定义一个监听器,通过监听器的回调方法将我们该方法中的result的Bitmap对象
		 *                                         传出去,让外面直接在回调方法中设置BItmap
		 * 有的人就要疑问,为何我不能直接通过该类公布出去一个mBItmap对象的getter方法,让那个外面
		 * 直接通过getter方法拿到mBitmap,有这种想法,可能你忽略了一个很基本的问题就是,我们
		 * 这个网路请求的任务是异步的,也就是这个BItmap不知道什么时候有值,当主线程去调用
		 * getter方法时候,子线程的网络请求还来不及拿到Bitmap,那么此时主线程拿到的只能为空
		 * 那就会报空指针的错误了,所以我们自己定义一个监听器,写一个回调方法,当网络请求拿到了
		 * Bitmap才会回调即可。
		 * 自定一个监听器:
		 *  1、首先得去写一个接口
		 *  2 然后在类中保存一个接口类型的引用 
		 *  3 然后写一个setOnAsyncTaskImageListener
		 * 方法初始化那个接口类型的引用  
		 * 4、最后在我们的onPostExecute方法中调用接口中的抽象方法,
		 * 并且把我们得到的result作为参数传入即可
		 *                    
		 * */
		if (listener!=null) {
			listener.asyncTaskImageListener(result);
			
		}
		super.onPostExecute(result);
	}
	//
	public void setOnAsyncTaskImageListener(OnAsyncTaskImageListener listener){
		this.listener=listener;
	}
}
OnAsyncTaskImageListener接口
package com.mikyou.utils;


import android.graphics.Bitmap;


public interface OnAsyncTaskImageListener {
	public void asyncTaskImageListener(Bitmap bitmap);
}

MainActivity:

package com.mikyou.asynctask;

import com.mikyou.utils.MikyouAsyncTaskImageUtils;
import com.mikyou.utils.OnAsyncTaskImageListener;

import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;

public class MainActivity extends Activity implements OnAsyncTaskImageListener{
	private ImageView iv;
	private ProgressBar bar;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initView();
		MikyouAsyncTaskImageUtils mikyouAsyncTaskImageUtils=new MikyouAsyncTaskImageUtils();
		mikyouAsyncTaskImageUtils.execute("http://b.hiphotos.baidu.com/image/h%3D360/sign=8918c5efbe3eb1355bc7b1bd961ea8cb/7a899e510fb30f244bb50504ca95d143ad4b038d.jpg");
		mikyouAsyncTaskImageUtils.setOnAsyncTaskImageListener(this);//注册我们自己定义的监听器
	}
	private void initView() {
		iv=(ImageView) findViewById(R.id.iv);
		bar=(ProgressBar) findViewById(R.id.bar);
	}
	@Override
	public void asyncTaskImageListener(Bitmap bitmap) {//实现监听器的接口后,重写回调方法,这里的Bitmap对象就是我们从AsyncTask中的onPostExecute方法中传来的result
		bar.setVisibility(View.INVISIBLE);
		iv.setImageBitmap(bitmap);
	}

}

运行结果:

浅谈android中的异步加载一_第3张图片

下面我们还通过一个demo使用一下onProgressUpdate方法,publishProgress方法,这个demo就用一个模拟下载进度条更新。

MikyouAsyncTaskProgressBarUtils

package com.mikyou.utils;

import android.os.AsyncTask;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MikyouAsyncTaskProgressBarUtils extends AsyncTask{
	private TextView tv;
	private ProgressBar bar;
	public MikyouAsyncTaskProgressBarUtils(TextView tv,ProgressBar bar){//这里我就采用构造器将TextView,ProgressBar直接传入,然后在该类中直接更新UI
		this.bar=bar;
		this.tv=tv;

	}
	@Override
	protected String doInBackground(Void... params) {
		for(int i=1;i<101;i++){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			publishProgress(i);
		}
		return "下载完成";
	}
	@Override
	protected void onProgressUpdate(Integer... values) {
		bar.setProgress(values[0]);
		tv.setText("下载进度:"+values[0]+"%");
		super.onProgressUpdate(values);
	}
	@Override
	protected void onPostExecute(String result) {
		tv.setText(result);
		super.onPostExecute(result);
	}
}

MainActivity

package com.mikyou.asynctask;

import com.mikyou.utils.MikyouAsyncTaskProgressBarUtils;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends Activity {
	private Button downLoad;
	private ProgressBar bar;
	private TextView tv;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initView();
	}
	private void initView() {
		bar=(ProgressBar) findViewById(R.id.bar);
		tv=(TextView) findViewById(R.id.tv);
	}
	public void download(View view){
		MikyouAsyncTaskProgressBarUtils mikyouAsyncTaskProgressBarUtils=new MikyouAsyncTaskProgressBarUtils(tv, bar);
		mikyouAsyncTaskProgressBarUtils.execute();
	}

}
布局:



    


运行结果:

浅谈android中的异步加载一_第4张图片

那么,关于异步加载的入门也就到这儿,随后将深入理解异步加载

你可能感兴趣的:(Android中的多线程,走进Android世界)