編輯:關於Android編程
1.AsyncTask使用介紹
AsyncTask封裝了Thread和Handler,通過AsyncTask可以很方便地在執行完後台任務後更新UI。
1.1 AsyncTask實例使用
下面是一個使用AsyncTask的實例,利用網絡下載某URL裡的字符串,以模擬耗時任務。在下載過程中,會通過進度條對話框向用戶展示進度。在完成任務後將字符串展示在TextView上。具體實現細節後面會加以講述,順便引出AsyncTask的知識。
public class MainActivity extends Activity{
private TextView show; @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); show = (TextView) findViewById(R.id.show); } //按鈕事件響應方法 URL可自定義 public void download(View source) throws Exception{ DownTask task = new DownTask(this); task.execute(new URL(URL)); } class DownTask extends AsyncTask{ //自定義Task類繼承AsyncTask ProgressDialog pdialog; int hasRead = 0; Context mContext; public DownTask(Context ctx){ mContext = ctx; } @Override protected String doInBackground(URL... params){ //doInBackground方法在子線程執行耗時任務 StringBuilder sb = new StringBuilder(); try{ URLConnection conn = params[0].openConnection(); BufferedReader br = new BufferedReader( new InputStreamReader(conn.getInputStream(), "utf-8")); String line = null; while ((line = br.readLine()) != null){ sb.append(line + "\n"); hasRead++; publishProgress(hasRead); } return sb.toString(); } catch (Exception e){ e.printStackTrace(); } return null; } @Override protected void onPostExecute(String result){ //主線程執行 // 展示下載下來的字符串 並將進度條對話框dismiss show.setText(result); pdialog.dismiss(); } @Override protected void onPreExecute(){ //主線程執行 pdialog = new ProgressDialog(mContext); pdialog.setTitle("任務正在執行中"); pdialog.setMessage("請等待..."); // 設置對話框不能用“取消”按鈕關閉 pdialog.setCancelable(false); pdialog.setMax(MAX); // 設置對話框的進度條風格 pdialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); // 設置對話框的進度條是否顯示進度 pdialog.setIndeterminate(false); pdialog.show(); } @Override protected void onProgressUpdate(Integer... values){ //主線程執行 // 更新進度 show.setText("已經讀取了" + values[0] + "行"); pdialog.setProgress(values[0]); } } }
1.2 AsyncTask參數介紹
看了上面的例子,我們會解釋例子中涉及到的AsyncTask知識,首先是參數介紹:
AsyncTask定義了三種泛型類型 Params,Progress和Result。也是可以指定為空的,如AsyncTask
Params:啟動任務執行的輸入參數的類型,本例中為URL。
Progress:後台任務執行的進度值類型,這裡為Integer。
Result:後台執行任務最終返回的結果類型,這裡為字符串String。
1.3 AsyncTask回調方法介紹
下面是介紹使用AsyncTask需要了解的方法:
onPreExecute():運行在主線程。調用excute()方法時執行,當任務執行之前調用此方法。通常用來完成一些初始化的准備工作。本例中是顯示一個進度條。
doInBackground(Params…):運行在子線程。執行比較耗時的操作。在執行過程中可以調用publishProgress(Progress…values)來更新任務的進度。
doInBackground的參數對應AsyncTask的第一個參數,publishProgress(Progress…)的參數對應AsyncTask的第二個參數,其返回值對應AsyncTask的第三個參數。
onPostExecute(Result result) :運行在主線程。相當於Handler 處理UI的方式, doInBackground 執行完畢後回調該方法,參數為doInBackground的返回值。
onProgressUpdate(Progress…):運行在主線程。用於顯示任務執行的進度,在doInBackground方法中調用publishProgress更新進度時會回調此方法。
onCancelled():運行在主線程。用戶調用了cancel(true)取消操作。
task.cancle(true);
1.4 使用AsyncTask的注意事項
(1)AsyncTask必須在主線程中創建,execute方法也必須在UI線程調用,原因後面分析源碼自然會明白。
(2)不要手動的調用onPreExecute(), onPostExecute(Result),doInBackground(Params...),onProgressUpdate(Progress...)這幾個方法。
(3)task只能被執行一次,多次調用時將會出現異常。
(4)Android1.6之前,AsyncTask是串行執行任務的,1.6之後采用線程池處理並行任務,又從3.0開始為了避免並發錯誤,又采用了一個線程來串行執行任務。
因此在3.0以後就不可以用AsyncTask並行執行任務了嗎,當然不是,我們仍然可以通過executeOnExecutor方法執行並行任務。
下面的源碼解析基於Android4.0版本。
2.AsyncTask源碼解析
AsyncTask在Android中是如何實現的,下面進行源碼分析。
2.1 AsyncTask的構造函數
public AsyncTask() { mWorker = new WorkerRunnable() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); return postResult(doInBackground(mParams)); } }; mFuture = new FutureTask (mWorker) { @Override protected void done() { ... } }; }
構造函數中創建了WorkerRunnable和FutureTask兩個實例,並把mWorker傳遞給了mFuture。讓我們看一下WorkerRunnable類。
2.2 WorkerRunnable抽象類
private static abstract class WorkerRunnableWorkerRunnable是Callable的子類,且包含一個mParams用於保存我們傳入的參數。在AsyncTask中構造方法中完成了初始化,並且因為是一個抽象類,在這裡new了一個實現類,並且實現了call方法,call方法中設置mTaskInvoked=true,且最終調用doInBackground(mParams)方法,並返回Result值作為參數給postResult方法。implements Callable { Params[] mParams; }
2.3 execute方法執行
我們在使用AsyncTask執行一個任務時,會調用execute方法,那讓我們看一下這個方法。
public final AsyncTaskexecute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } public final AsyncTask executeOnExecutor(Executor exec, Params... params) { if (mStatus != Status.PENDING) { switch (mStatus) { //switch意味著不能重復執行execute方法 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; }
可以看出onPreExecute()是首先被執行的,然後將參數通過mWorker封裝為FutureTask對象。接著調用了exec.execute(),從上面的代碼中我們看到exec就是sDefaultExecutor,繼續研究sDefaultExecutor。
2.4實際上執行的是SerialExecutor的execute()方法
public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); …… private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; private static class SerialExecutor implements Executor { final ArrayDequemTasks = new ArrayDeque (); 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的execute()方法。上面也分析過了,我們的參數被封裝為了FurtherTask對象,並在這裡充當了Runnable的作用。
execute首先將FurtherTask對象插入到任務隊列mTasks中。
第一個任務入隊,調用offer方法將傳入的Runnable對象添加到隊列尾部。判空後進入scheduleNext方法。然後在mActive =mTasks.poll()) != null被取出,從隊列的頭部取值,並且賦值給mActivte,然後交給線程池THREAD_POOL_EXECUTOR去執行(而SerialExecutor用於任務的排隊)。
然後第二個任務入隊,繼續入列,但是此時mActive並不為null,並不會執行scheduleNext()。所以如果第一個任務比較慢,很多個任務都會進入隊列等待。
真正執行下一個任務的時機是,線程池執行完成第一個任務以後,調用Runnable中的finally代碼塊中的scheduleNext。
這樣就形成了一種鏈狀調用結構,只要mTasks裡面還有任務,就會不斷逐一調用,如果後面有任務進來,就只要添加到mTasks裡面即可。
這裡給execute()傳遞的參數是mFuture,所以會執行到mFuture的run()方法,而run()方法最終會調用callable.call(),而callable就是mWorker,便回到了我們在2.1中看到的代碼。
public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); return postResult(doInBackground(mParams)); }
doInBackground()方法將它的返回值傳給了postResult。繼續查看postResult的實現。
2.4postResult的實現
private Result postResult(Result result) { Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult(this, result)); message.sendToTarget(); return result; }
原來是使用了Handler機制發送消息,那我們看下處理消息的地方。
2.5消息處理
private static class InternalHandler extends Handler { @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) @Override public void handleMessage(Message msg) { AsyncTaskResult result = (AsyncTaskResult) msg.obj; switch (msg.what) { case MESSAGE_POST_RESULT: result.mTask.finish(result.mData[0]); break; case MESSAGE_POST_PROGRESS: result.mTask.onProgressUpdate(result.mData); break; } } }
收到MESSAGE_POST_RESULT就執行finish(),繼而查看finish的實現。
2.6 MESSAGE_POST_RESULT消息的詳細處理
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }
可以看到,如果當前任務被取消掉了,就會調用onCancelled()方法,如果沒有被取消,則調用onPostExecute()方法,這樣當前任務的執行就全部結束了。
我們注意到還有一種MESSAGE_POST_PROGRESS的消息類型,這種消息是用於當前進度的,調用的正是onProgressUpdate()方法,那很自然想到的是publishProgress()方法。查看該方法源碼。
2.7 MESSAGE_POST_PROGRESS消息的發出
protected final void publishProgress(Progress... values) { if (!isCancelled()) { sHandler.obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult
可以看出AsyncTask也是使用的異步消息處理機制,只是做了非常好的封裝而已。
所以在doInBackground()方法中調用publishProgress()方法才可以從子線程切換到UI線程,從而完成對UI元素的更新操作。
3 Android3.0以前(1.6以後)
Android 3.0之前是並沒有SerialExecutor這個類的,那個時候是直接在AsyncTask中構建了一個sExecutor常量,並對線程池總大小,同一時刻能夠運行的線程數做了規定,參數設置如下。
private static final int CORE_POOL_SIZE = 5; private static final int MAXIMUM_POOL_SIZE = 128; private static final int KEEP_ALIVE = 10; …… private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
3.0以前規定同一時刻能夠運行的線程數為5個,線程池總大小為128,排隊等待數量10個。也就是說當我們啟動了10個任務時,只有5個任務能夠立刻執行,另外的5個任務則需要等待,當有一個任務執行完畢後,第6個任務才會啟動,以此類推。而線程池中最大能存放的線程數是128個,當我們嘗試去添加第129個任務時,程序就會崩潰,發出java.util.concurrent.RejectedExecutionException異常。
上面通過源碼也分析過,3.0之後的AsyncTask同時只能有1個任務在執行。如果不想使用默認的線程池,還可以自由地進行配置。比如使用如下的代碼,不是使用SerialExecutor,允許在同一時刻有10個任務正在執行,並且最多能夠存儲100個任務。
Executor exec = new ThreadPoolExecutor(15, 200, 10, TimeUnit.SECONDS, new LinkedBlockingQueue()); new DownloadTask().executeOnExecutor(exec);
最近在學Android 學的不好 然後看到了用.9.png寫對話框的哪裡,但是書上寫的太簡單 感覺做出來和書上的不一樣 然後就去各種百度 感覺網上關於這個東西的資料都是粘
前言:緊接著上一篇的微信支付,本篇是集成支付寶支付,相對於微信支付,支付寶的集成就簡單了很多。話不多說,我們來看看怎麼簡單集成支付寶支付。1.看文檔:我們先要去螞蟻金服開
android-pdf-viewer在android studio應用問題說明小白一枚,之前一直是做.NET開發的,最近需要弄一個新聞app,能力有限,只能借助HTML5
超詳細解析定位坐標—LatLng定位中用得最多的是坐標(也就是經緯度),那麼我們首先搞清楚什麼是坐標:LatLng 類:地理坐標基本數據結構。 描述