Android AsyncTask實(shí)現(xiàn)原理和使用技巧分享

為什么要用AsyncTask

我們寫App都有一個(gè)原則道媚,主線程不能夠運(yùn)行需要占用大量CPU時(shí)間片的任務(wù),如大量復(fù)雜的浮點(diǎn)運(yùn)算,較大的磁盤IO操作势就,網(wǎng)絡(luò)socket等,這些都會導(dǎo)致我們的主線程對用戶的響應(yīng)變得遲鈍脉漏,甚至ANR苞冯,這些會使應(yīng)用的用戶體驗(yàn)變差,但是有時(shí)又的確需要執(zhí)行這些耗時(shí)的任務(wù)侧巨,那么我們通尘顺可以使用AsyncTask或者new Thread
來處理,這樣把任務(wù)放入工作線程中執(zhí)行司忱,不會占用主線程的時(shí)間片皇忿,所以主線程會及時(shí)響應(yīng)用戶的操作,如果使用new Thread來執(zhí)行任務(wù)坦仍,那么如果需要中途取消任務(wù)執(zhí)行或者需要返回任務(wù)執(zhí)行結(jié)果鳍烁,就需要我們自己維護(hù)很多額外的代碼,而AsyncTask是基于concurrent架包提供的并發(fā)類實(shí)現(xiàn)的繁扎,上面的二個(gè)需求都已經(jīng)幫我們封裝了幔荒,這也是我們選擇AsyncTask的原因。

怎么用AsyncTask

我們還是簡單介紹下AsyncTask一些使用示例锻离。我們先新建一個(gè)類DemoAsyncTask繼承AsyncTask铺峭,因?yàn)锳syncTask是抽象類,其中doInBackground方法必須重寫汽纠。

private class DemoAsyncTask extends AsyncTask<String, Void, Void> {
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected Void doInBackground(String... params) {
        return null;
    }   

    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
    }

    @Override
    protected void onProgressUpdate(Void... values) {
        super.onProgressUpdate(values);
    }

    @Override
    protected void onCancelled(Void aVoid) {
        super.onCancelled(aVoid);
    }

    @Override
    protected void onCancelled() {
        super.onCancelled();
    }
}

DemoAsyncTask task = new DemoAsyncTask();
task.execute("demo test AsyncTask");
//task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, "test");
//myTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "test");

簡單分析下

上面就是AsyncTask最簡單的使用方法卫键,我們上面重寫的方法中,onInBackground方法運(yùn)行在工作線程虱朵,其他的方法全部運(yùn)行在主線程莉炉,另外它的運(yùn)行方式Android提供給我們2個(gè)方法钓账,上面都列出了。

  • 1.第一個(gè)方法會使用默認(rèn)的Executor執(zhí)行我們的任務(wù), 其實(shí)也就是SERIAL_EXECUTOR絮宁,SERIAL_EXECUTOR我們其實(shí)也是可以通過方法去自定義的梆暮,Android幫我們的默認(rèn)實(shí)現(xiàn)是逐個(gè)執(zhí)行任務(wù),也就是單線程的绍昂,關(guān)于AsyncTask的任務(wù)執(zhí)行是單線程實(shí)現(xiàn)還是多線程實(shí)現(xiàn)還有一段很有意思的歷史啦粹,較早的版本是單線程實(shí)現(xiàn),從Android2.X開始窘游,Google又把它改為多線程實(shí)現(xiàn)唠椭,后來Google發(fā)現(xiàn),多線程實(shí)現(xiàn)的話忍饰,會有很多需要保證線程安全的額外工作留給開發(fā)者贪嫂,所以從Android3.0開始,又把默認(rèn)實(shí)現(xiàn)改為單線程了艾蓝,今天我們演示的是Framwork代碼版本是21(Android5.0)力崇。

  • 2.同時(shí)也提供了多線程實(shí)現(xiàn)的接口,即我們上面寫的最后一行代碼 AsyncTask.THREAD_POOL_EXECUTOR赢织。

    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
      return executeOnExecutor(sDefaultExecutor, params);
    }
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    
    

其實(shí)相信大家平時(shí)工作學(xué)習(xí)中經(jīng)常使用AsyncTask亮靴,我們直接去看AsyncTask類源碼(插一句題外話,平時(shí)大家也可以把自己工作學(xué)習(xí)中的心得體會總結(jié)一下敌厘,記下來~~)

public abstract class AsyncTask<Params, Progress, Result> {
....
}

AsyncTask抽象類台猴,有三個(gè)泛型參數(shù)類型,第一個(gè)是你需要傳遞進(jìn)來的參數(shù)類型俱两,第二個(gè)是任務(wù)完成進(jìn)度的類型一般是Integer饱狂,第三個(gè)是任務(wù)完成結(jié)果的返回類型,有時(shí)這些參數(shù)不是全部需要宪彩,不需要的設(shè)為Void即可休讳,另外Result只有一個(gè),但Params可以有多個(gè)尿孔。
我們可以看到AsyncTask的默認(rèn)構(gòu)造器初始化了二個(gè)對象俊柔,mWorker和mFuture。

private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);

            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            //noinspection unchecked
            return postResult(doInBackground(mParams));
        }
    };

    mFuture = new FutureTask<Result>(mWorker) {
        @Override
        protected void done() {
            try {
                postResultIfNotInvoked(get());
            } catch (InterruptedException e) {
                android.util.Log.w(LOG_TAG, e);
            } catch (ExecutionException e) {
                throw new RuntimeException("An error occured while executing doInBackground()",
                        e.getCause());
            } catch (CancellationException e) {
                postResultIfNotInvoked(null);
            }
        }
    };

mWoker實(shí)現(xiàn)了Callback接口活合,Callback接口是JDK1.5加入的高級并發(fā)架包里面的一個(gè)接口雏婶,它可以有一個(gè)泛型返回值。

public interface Callable<V> {
/**
 * Computes a result, or throws an exception if unable to do so.
 *
 * @return computed result
 * @throws Exception if unable to compute a result
 */
V call() throws Exception;
}

FutureTask實(shí)現(xiàn)了RunnableFuture接口白指,RunnableFuture接口是JDK提供的留晚,看名稱就知道它繼承了Runnable和Future接口,mFuture是FutureTask的一個(gè)實(shí)例告嘲,可以直接被Executor類實(shí)例execute错维。我們來繼續(xù)看AsyncTask的execute方法奖地。

public final AsyncTask<Params, Progress, Result>     execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}
public final AsyncTask<Params, Progress, Result>    executeOnExecutor(Executor exec,
        Params... params) {
    if (mStatus != Status.PENDING) {
        switch (mStatus) {
            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;
}

先調(diào)用onPreExecute()方法,此時(shí)還在主線程(嚴(yán)格來說是調(diào)用AsyncTask執(zhí)行的線程)赋焕,然后exec.execute(mFuture)参歹,把任務(wù)交給exec處理,execute mFuture其實(shí)就是invoke mWoker隆判,然后調(diào)用postResult(doInBackground(mParams))犬庇,此時(shí)已經(jīng)運(yùn)行在工作線程池,不會阻塞主線程侨嘀。然后給mHandler發(fā)送MESSAGE_POST_RESULT消息械筛,然后調(diào)用finish方法,如果isCancelled飒炎,回調(diào)onCancelled,否則回調(diào)onPostExecute笆豁。

private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}
private static final InternalHandler sHandler = new InternalHandler();
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:
                // There is only one result
                result.mTask.finish(result.mData[0]);
                break;
            case MESSAGE_POST_PROGRESS:
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}
private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}

現(xiàn)在其實(shí)我們已經(jīng)把AsyncTask整個(gè)執(zhí)行任務(wù)的過程走完了郎汪,其中暴露給我們的那幾個(gè)回調(diào)方法也都走到了。現(xiàn)在我們回過頭來看闯狱,AsyncTask其實(shí)只是對JDK 1.5提供的高級并發(fā)特性煞赢,concurrent架包做的一個(gè)封裝,方便開發(fā)者來處理異步任務(wù)哄孤,當(dāng)然里面還有很多細(xì)節(jié)處理的方法值得大家學(xué)習(xí)照筑,如任務(wù)執(zhí)行進(jìn)度的反饋,任務(wù)執(zhí)行原子性的保證等瘦陈,這些留給大家自己學(xué)習(xí)了凝危。

使用AsyncTask一點(diǎn)小技巧

我們以一個(gè)實(shí)例來說明,“點(diǎn)擊按鈕開始下載QQAndroid安裝包晨逝,然后顯示一個(gè)對話框來反饋下載進(jìn)度”蛾默。我們先初始化一個(gè)對話框,由于要顯示進(jìn)度捉貌,我們用Github上面一個(gè)能夠顯示百分比的進(jìn)度條 NumberProgressbar支鸡,啟動(dòng)任務(wù)的按鈕我們使用* circlebutton*,一個(gè)有酷炫動(dòng)畫的按鈕趁窃,Github上面有很多非常好的開源項(xiàng)目牧挣,當(dāng)然炫酷的控件是其中一部分了,后面有機(jī)會醒陆,會去學(xué)習(xí)一些比較流行的控件它們的實(shí)現(xiàn)原理瀑构,今天就暫且拿來主義了~~。

  • 1.先初始化進(jìn)度條提示對話框统求。
    builder = new AlertDialog.Builder(
    MainActivity.this);
    LayoutInflater inflater = LayoutInflater.from(MainActivity.this);
    mDialogView = inflater.inflate(R.layout.progress_dialog_layout, null);
    mNumberProgressBar = (NumberProgressBar)mDialogView.findViewById(R.id.number_progress_bar);
    builder.setView(mDialogView);
    mDialog = builder.create();

*   2.設(shè)置按鈕點(diǎn)擊事件检碗。
    findViewById(R.id.circle_btn).setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View v) {
    dismissDialog();
    mNumberProgressBar.setProgress(0);
    myTask = new MyAsyncTask();
    myTask.execute(qqDownloadUrl);
    }
    });
  • 3.DownloadAsyncTask實(shí)現(xiàn)据块,有點(diǎn)長。
    private class DownloadAsyncTask extends AsyncTask<String , Integer, String> {

    ```
      @Override
      protected void onPreExecute() {
          super.onPreExecute();
          mDialog.show();

      }

      @Override
      protected void onPostExecute(String aVoid) {
          super.onPostExecute(aVoid);
          dismissDialog();
      }

      @Override
      protected void onProgressUpdate(Integer... values) {
          super.onProgressUpdate(values);

          mNumberProgressBar.setProgress(values[0]);
      }

      @Override
      protected void onCancelled(String aVoid) {
          super.onCancelled(aVoid);
          dismissDialog();
      }

      @Override
      protected void onCancelled() {
          super.onCancelled();
          dismissDialog();
      }

      @Override
      protected String doInBackground(String... params) {
          String urlStr = params[0];
          FileOutputStream output = null;
          try {
              URL url = new URL(urlStr);
              HttpURLConnection connection = (HttpURLConnection)url.openConnection();
              String qqApkFile = "qqApkFile";
              File file = new File(Environment.getExternalStorageDirectory() + "/" + qqApkFile);
              if (file.exists()) {
                  file.delete();
              }
              file.createNewFile();
              InputStream input = connection.getInputStream();
              output = new FileOutputStream(file);
              int total = connection.getContentLength();
              if (total <= 0) {
                  return null;
              }
              int plus = 0;
              int totalRead = 0;
              byte[] buffer = new byte[4*1024];
              while((plus = input.read(buffer)) != -1){
                  output.write(buffer);
                  totalRead += plus;
                  publishProgress(totalRead * 100 / total);
                  if (isCancelled()) {
                      break;
                  }
              }
              output.flush();
          } catch (MalformedURLException e) {
              e.printStackTrace();
              if (output != null) {
                  try {
                      output.close();
                  } catch (IOException e2) {
                      e2.printStackTrace();
                  }
              }
          } catch (IOException e) {
              e.printStackTrace();
              if (output != null) {
                  try {
                      output.close();
                  } catch (IOException e2) {
                      e2.printStackTrace();
                  }
              }
          } finally {
              if (output != null) {
                  try {
                      output.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
          return null;
      }

    ```

    }
    這樣一個(gè)簡單的下載文件文件就基本實(shí)現(xiàn)了折剃,到目前為止談不上技巧另假,但是現(xiàn)在我們有一個(gè)問題,就是如果我們的Activity正在后臺執(zhí)行一個(gè)任務(wù)怕犁,可能耗時(shí)較長边篮,那用戶可能會點(diǎn)擊返回退出Activity或者退出App,那么后臺任務(wù)不會立即退出奏甫,如果AsyncTask內(nèi)部有Activity中成員變量的引用戈轿,還會造成Activity的回收延時(shí),造成一段時(shí)間內(nèi)的內(nèi)存泄露阵子,所以我們需要加上下面的第四步處理思杯。

*   4.onPause中判斷應(yīng)用是否要退出,從而決定是否取消AsyncTask執(zhí)行挠进。
@Override
protected void onPause() {
super.onPause();
if (myTask != null && isFinishing()) {
myTask.cancel(false);
}
}

這樣我們的異步任務(wù)就會在Activity退出時(shí)色乾,也隨之取消任務(wù)執(zhí)行,順利被系統(tǒng)銷毀回收领突,第四步很多時(shí)候會被遺漏暖璧,而且一般也不會有什么致命的問題,但是一旦出問題了君旦,就很難排查陕贮,所以遵循編碼規(guī)范還是有必要的柿顶。

小結(jié)

AsyncTask的基本實(shí)現(xiàn)原理我們已經(jīng)清楚了,同時(shí)我們也介紹了一個(gè)使用AsyncTask要注意的一個(gè)小技巧,希望大家讀完能有所收獲

歡迎大家一起交流討論哈搜囱!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末篮绰,一起剝皮案震驚了整個(gè)濱河市仅父,隨后出現(xiàn)的幾起案子用含,更是在濱河造成了極大的恐慌,老刑警劉巖谱俭,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奉件,死亡現(xiàn)場離奇詭異,居然都是意外死亡昆著,警方通過查閱死者的電腦和手機(jī)县貌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凑懂,“玉大人煤痕,你說我怎么就攤上這事。” “怎么了摆碉?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵塘匣,是天一觀的道長。 經(jīng)常有香客問我巷帝,道長忌卤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任楞泼,我火速辦了婚禮驰徊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘堕阔。我一直安慰自己棍厂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布超陆。 她就那樣靜靜地躺著牺弹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪时呀。 梳的紋絲不亂的頭發(fā)上例驹,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音退唠,去河邊找鬼。 笑死荤胁,一個(gè)胖子當(dāng)著我的面吹牛瞧预,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仅政,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼垢油,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了圆丹?” 一聲冷哼從身側(cè)響起滩愁,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辫封,沒想到半個(gè)月后硝枉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡倦微,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年妻味,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片欣福。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡责球,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情雏逾,我是刑警寧澤嘉裤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站栖博,受9級特大地震影響屑宠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜笛匙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一侨把、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧妹孙,春花似錦秋柄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嚣崭,卻和暖如春笨触,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背雹舀。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工芦劣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人说榆。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓虚吟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親签财。 傳聞我的和親對象是個(gè)殘疾皇子串慰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容