Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 淺談android中的異步加載一

淺談android中的異步加載一

編輯:關於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:

*
*
Params, the type of the parameters sent to the task upon * execution.
*
Progress, the type of the progress units published during * the background computation.
*
Result, the type of the result of the background * computation.
*
*
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:

*
*
{@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.
*
{@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.
*
{@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.
*
{@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.
*
* 上面意思:介紹了異步加載的四個回調方法執行的時機,這裡會在本文中有詳細介紹 *
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中的各個方法參數之間的傳遞關系:
通過總結了上面的類型參數關系實際上他們之間的參數傳遞關系也就出來了下面將通過一張參數傳遞圖來說明他們之間的關系



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


}

運行結果:

 

\

通過以上的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);
	}

}

運行結果:

 

\

下面我們還通過一個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();
	}

}
布局:

 

 



運行結果:

 

\

那麼,關於異步加載的入門也就到這兒,隨後將深入理解異步加載

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved