進程,線程講到AsyncTask

一 前言

異步編程是android初學者的一個難點,卻也是始終不能繞過的一個坎。能夠說幾乎每一個app都逃不了網絡編程,而網絡編程又每每創建在異步的機制之上(你不該該也沒法在UI線程裏執行網絡請求,你也不該該在UI線程中頻繁的進行IO操做)。等等,你不知道什麼是線程?那就對了,咱們一塊兒來回憶一下大學課本的知識,一切從進程講起。html

二 進程和線程

我曾經在知乎上聽一個朋友說一個優秀的程序員必定會有着極強的對抽象的理解能力,我很贊同這句話,我內心一直鼓勵本身:當你對抽象再也不害怕的時候,可能你正在成爲一名真正的coder。java

1.進程(process)
A process is the operating system’s abstraction for a running program
這是csapp中的原話,我以爲兩個詞特別重要,一個是abstraction,說明進程是一種抽象,是人爲的一種定義,另外一個是running,說明進程是正在執行的程序,而不是保存在磁盤上的一個程序文件。無論你如今怎麼理解進程,你都得看下面一段代碼:android

#include <stdio.h>
int main()
{
printf("hello, world\n");
return 0;
}

這多是咱們人生寫得第一行代碼,讓咱們在終端裏gcc獲得可執行文件a.out,而後執行它,好,在你按下return鍵的那一瞬間到終端裏打印出hello,world(好吧我認可我詞窮了,其實就是a.out被執行時),進程動態產生,動態消亡。怎麼直觀的感覺它呢,來改一下代碼:程序員

#include <stdio.h>
#include <unistd.h>

int main(){
        printf("Hello World from process ID %ld \n",(long)getpid());
        return 0;
}

編譯,運行獲得:數據庫

Hello World from process ID 20289

在這裏咱們獲得了這個進程的ID(UNIX系統確保每一個進程都有一個惟一的數字標識符,稱爲進程ID,進程ID老是一個非負整數。),這也算進程存在的一點痕跡吧。咱們再改動一下代碼:編程

#include <stdio.h>
#include <unistd.h>

int doSomething();

int main(){
        printf("Hello World from process ID %ld \n",(long)getpid());
        doSomething();
        return 0;
}

int doSomething(){
        printf("let us doing something from ID %ld\n",(long)getpid());
        return 0;

編譯,執行:json

Hello World from process ID 20777 
let us doing something from ID 20777

能夠看到這兩個函數的進程ID是同樣的,其實你進一步調用getppid()函數獲得父進程的函數其實也是同樣的。細心的朋友就會發現,上一次執行後獲得的ID是20289,此次執行獲得的ID倒是20777,一樣的文件爲何每次執行獲得的ID倒是不一樣的呢?這就須要咱們好好體會進程是動態產生動態消亡的了,抽象嗎?segmentfault

2.線程(Thread)
能夠這麼說,一切的抽象都是爲了解放生產力。系統爲何要抽象出進程的概念?一個直觀的解釋就是它可讓每一個進程獨立的擁有虛擬地址空間、代碼、數據和其它各類系統資源,它還可讓多個進程同時執行,讓你在寫代碼的同時還能掛着微信,放着音樂。但是這還不夠,由於一個進程在某一時刻只能作一件事情,爲了進一步提升效率,又抽象出進程的概念,來看下面這段話:緩存

線程是進程內部的一個執行單元。系統建立好進程後,實際上就啓動執行了該進程的主執行線程。主執行線程終止了,進程也就隨之終止。

也就是說,對線程來講,進程至關於一個容器,能夠有許多線程同時在一個進程裏執行。安全

3.安卓中的進程與線程
這裏引用官方文檔的解釋,也不知是誰翻譯的,總之獻上膝蓋看原網頁點這裏爲了閱讀方便把原文貼出來了並改正了一些錯別字


當一個Android應用程序組件啓動時候,若是此時這個程序的其餘組件沒有正在運行,那麼系統會爲這個程序以單一線程的形式啓動一個新的Linux 進程。 默認狀況下,同一應用程序下的全部組件都運行在相同的進程和線程(通常稱爲程序的「主」線程)中。若是一個應用組件啓動但這個應用的進程已經存在了(由於這個應用的其餘組件已經在以前啓動了),那麼這個組件將會在這個進程中啓動,同時在這個應用的主線程裏面執行。然而,你也可讓你的應用裏面的組件運行在 不一樣的進程裏面,也能夠爲任何進程添加額外的線程。

這片文章討論了Android程序裏面的進程和線程如何運做的。

進程

默認狀況下,同一程序的全部組件都運行在相同的進程裏面,大多數的應用都是這樣的。然而,若是你發現你須要讓你的程序裏面的某個組件運行在特定的進程裏面,你能夠在manifest 文件裏面設置。

manifest 文件裏面爲每個組件元素—<activity>, <service>, <receiver>, 和<provider>—提供了 android:process 屬 性。經過設置這個屬性你可讓組件運行在特定的進程中。你能夠設置成每一個組件運行在本身的進程中,也可讓一些組件共享一個進程而其餘的不這樣。你還能夠 設置成不一樣應用的組件運行在同一個進程裏面—這樣可讓這些應用共享相同的Linux user ID同時被相同的證書所認證。

<application> 元素也支持 android:process 屬性,設置這個屬性可讓這個應用裏面的全部組件都默認繼承這個屬性。

Android 可能在系統剩餘內存較少,而其餘直接服務用戶的進程又要申請內存的時候shut down 一個進程, 這時這個進程裏面的組件也會依次被kill掉。當這些組件有新的任務到達時,他們對應的進程又會被啓動。

在決定哪些進程須要被kill的時候,Android系統會權衡這些進程跟用戶相關的重要性。好比,相對於那些承載這可見的activities的 進程,系統會更容易的kill掉那些承載再也不可見activities的進程。決定是否終結一個進程取決於這個進程裏面的組件運行的狀態。下面咱們會討論 kill進程時所用到的一些規則。

進程的生命週期

做爲一個多任務的系統,Android 固然系統可以儘量長的保留一個應用進程。可是因爲新的或者更重要的進程須要更多的內存,系統不得不逐漸終結老的進程來獲取內存。爲了聲明哪些進程須要保 留,哪些須要kill,系統根據這些進程裏面的組件以及這些組件的狀態爲每一個進程生成了一個「重要性層級」 。處於最低重要性層級的進程將會第一時間被清除,接着是重要性高一點,而後依此類推,根據系統須要來終結進程。

在這個重要性層級裏面有5個等級。下面的列表按照重要性排序展現了不一樣類型的進程(第一種進程是最重要的,所以將會在最後被kill):

Foreground進程 一個正在和用戶進行交互的進程,若是一個進程處於下面的狀態之一,那麼咱們能夠把這個進程稱爲 foreground 進程:
進程包含了一個與用戶交互的 Activity (這個 Activity的 onResume() 方法被調用)。
進程包含了一個綁定了與用戶交互的activity的 Service 。
進程包含了一個運行在」in the foreground」狀態的 Service —這個 service 調用了 startForeground()方法。
進程包含了一個正在運行的它的生命週期回調函數 (onCreate(), onStart(), oronDestroy())的 Service 。
進程包含了一個正在運行 onReceive() 方法的 BroadcastReceiver 。
通常說來,任什麼時候候,系統中只存在少數的 foreground 進程。 只有在系統內存特別緊張以致於都沒法繼續運行下去的時候,系統纔會經過kill這些進程來緩解內存壓力。在這樣的時候系統必須kill一些 (Generally, at that point, the device has reached a memory paging state,這句如何翻譯較好呢)foreground 進程來保證 用戶的交互有響應。

Visible進程 一個進程沒有任何 foreground 組件, 可是它還能影響屏幕上的顯示。 若是一個進程處於下面的狀態之一,那麼咱們能夠把這個進程稱爲 visible 進程:
進程包含了一個沒有在foreground 狀態的 Activity ,可是它仍然被用戶可見 (它的 onPause() 方法已經被調用)。這種狀況是有可能出現的,好比,一個 foreground activity 啓動了一個 dialog,這樣就會讓以前的 activity 在dialog的後面部分可見。
進程包含了一個綁定在一個visible(或者foreground)activity的 Service 。
一個 visible 進程在系統中是至關重要的,只有在爲了讓全部的foreground 進程正常運行時纔會考慮去kill visible 進程。

Service進程 一個包含着已經以 startService() 方法啓動的 Service 的 進程,同時尚未進入上面兩種更高級別的種類。儘管 service 進程沒有與任何用戶所看到的直接關聯,可是它們常常被用來作用戶在乎的事情(好比在後臺播放音樂或者下載網絡數據),因此係統也只會在爲了保證全部的 foreground and visible 進程正常運行時kill掉 service 進程。

Background進程 一個包含了已不可見的activity的 進程 (這個 activity 的 onStop() 已 經被調用)。這樣的進程不會直接影響用戶的體驗,系統也能夠爲了foreground 、visible 或者 service 進程隨時kill掉它們。通常說來,系統中有許多的 background 進程在運行,因此將它們保持在一個LRU (least recently used)列表中能夠確保用戶最近看到的activity 所屬的進程將會在最後被kill。若是一個 activity 正確的實現了它的生命週期回調函數,保存了本身的當前狀態,那麼kill這個activity所在的進程是不會對用戶在視覺上的體驗有影響的,由於當用戶 回退到這個 activity時,它的全部的可視狀態將會被恢復。查看 Activities 能夠獲取更多若是保存和恢復狀態的文檔。
Empty 進程 一個不包含任何活動的應用組件的進程。 這種進程存在的惟一理由就是緩存。爲了提升一個組件的啓動的時間須要讓組件在這種進程裏運行。爲了平衡進程緩存和相關內核緩存的系統資源,系統須要kill這些進程。
Android是根據進程中組件的重要性儘量高的來評級的。好比,若是一個進程包含來一個 service 和一個可見 activity,那麼這個進程將會被評爲 visible 進程,而不是 service 進程。

另外,一個進程的評級可能會由於其餘依附在它上面的進程而被提高—一個服務其餘進程的進程永遠不會比它正在服務的進程評級低的。好比,若是進程A中 的一個 content provider 正在爲進程B中的客戶端服務,或者若是進程A中的一個 service 綁定到進程B中的一個組件,進程A的評級會被系統認爲至少比進程B要高。

由於進程裏面運行着一個 service 的評級要比一個包含background activities的進程要高,因此當一個 activity 啓動長時操做時,最好啓動一個 service 來 作這個操做,而不是簡單的建立一個worker線程—特別是當這個長時操做可能會拖垮這個activity。好比,一個須要上傳圖片到一個網站的 activity 應當開啓一個來執行這個上傳操做。這樣的話,即便用戶離開來這個activity也能保證上傳動做在後臺繼續。使用 service 能夠保證操做至少處於」service process」 這個優先級,不管這個activity發生了什麼。這也是爲何 broadcast receivers 應該使用 services 而不是簡單的將耗時的操做放到線程裏面。

線程

當一個應用啓動的時候,系統會爲它建立一個線程,稱爲「主線程」。這個線程很重要由於它負責處理調度事件到相關的 user interface widgets,包括繪製事件。你的應用也是在這個線程裏面與來自Android UI toolkit (包括來自 android.widget 和 android.view 包的組件)的組件進行交互。所以,這個主線程有時候也被稱爲 UI 線程。

系統沒有爲每一個組件建立一個單獨的線程。同一進程裏面的全部組件都是在UI 線程裏面被實例化的,系統對每一個組件的調用都是用過這個線程進行調度的。因此,響應系統調用的方法(好比 onKeyDown() 方法是用來捕捉用戶動做或者一個生命週期回調函數)都運行在進程的UI 線程裏面。

好比,當用戶點擊屏幕上的按鈕,你的應用的UI 線程會將這個點擊事件傳給 widget,接着這個widget設置它的按壓狀態,而後發送一個失效的請求到事件隊列。這個UI 線程對請求進行出隊操做,而後處理(通知這個widget從新繪製本身)。

當你的應用與用戶交互對響應速度的要求比較高時,這個單線程模型可能會產生糟糕的效果(除非你很好的實現了你的應用)。特別是,當應用中全部的事情 都發生在UI 線程裏面,那些訪問網絡數據和數據庫查詢等長時操做都會阻塞整個UI線程。當整個線程被阻塞時,全部事件都不能被傳遞,包括繪製事件。這在用戶看來,這個 應用假死了。甚至更糟糕的是,若是UI 線程被阻塞幾秒(當前是5秒)以上,系統將會彈出臭名昭著的 「application not responding」 (ANR) 對話框。這時用戶可能選擇退出你的應用甚至卸載。

另外,Android的UI 線程不是線程安全的。因此你不能在一個worker 線程操做你的UI—你必須在UI線程上對你的UI進行操做。這有兩條簡單的關於Android單線程模型的規則:

不要阻塞 UI 線程
不要在非UI線程裏訪問 Android UI toolkit
Worker 線程

因爲上面對單一線程模型的描述,保證應用界面的及時響應同時UI線程不被阻塞變得很重要。若是你不能讓應用裏面的操做短時被執行玩,那麼你應該確保把這些操做放到獨立的線程裏(「background」 or 「worker」 線程)。

好比,下面這段代碼在一個額外的線程裏面下載圖片並在一個 ImageView顯示:

new Thread(new Runnable(){ 
    public void run(){ 
        Bitmap b = loadImageFromNetwork("http://example.com/image.png"); 
        mImageView.setImageBitmap(b); 
    } 
}).start();}

起先這段代碼看起來不錯,由於它建立一個新的線程來處理網絡操做。然而,它違反來單一線程模型的第二條規則: 不在非UI線程裏訪問 Android UI toolkit—這個例子在一個worker線程修改了 ImageView 。這會致使不可預期的結果,並且還難以調試。

爲了修復這個問題,Android提供了幾個方法從非UI線程訪問Android UI toolkit 。詳見下面的這個列表:

Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
那麼,你可使用 View.post(Runnable) 方法來修改以前的代碼:

public void onClick(View v){ 
    new Thread(new Runnable(){ 
        public void run(){ 
            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); 
            mImageView.post(new Runnable(){ 
                public void run(){ 
                    mImageView.setImageBitmap(bitmap); 
                } 
            }); 
        } 
    }).start();}

如今這個方案的線程安全的:這個網絡操做在獨立線程中完成後,UI線程便會對ImageView 進行操做。

然而,隨着操做複雜性的增加,代碼會變得愈來愈複雜,愈來愈難維護。爲了用worker 線程處理更加複雜的交互,你能夠考慮在worker線程中使用Handler ,用它來處理UI線程中的消息。也許最好的方案就是繼承 AsyncTask 類,這個類簡化了須要同UI進行交互的worker線程任務的執行。

使用 AsyncTask

AsyncTask 能讓你在UI上進行異步操做。它在一個worker線程裏進行一些阻塞操做而後把結果交給UI主線程,在這個過程當中不須要你對線程或者handler進行處理。

使用它,你必須繼承 AsyncTask 並實現 doInBackground() 回調方法,這個方法運行在一個後臺線程池裏面。若是你須要更新UI,那麼你應該實現onPostExecute(),這個方法從 doInBackground() 取出結果,而後在 UI 線程裏面運行,因此你能夠安全的更新你的UI。你能夠經過在UI線程調用 execute()方法來運行這個任務。

好比,你能夠經過使用 AsyncTask來實現以前的例子:

public void onClick(View v){ 
    new DownloadImageTask().execute("http://example.com/image.png"); 
} 
private class DownloadImageTask extends AsyncTask<String,Void,Bitmap>{ 

protected Bitmap doInBackground(String... urls){ 
    return loadImageFromNetwork(urls[0]); 
} 

protected void onPostExecute(Bitmap result){ 
    mImageView.setImageBitmap(result); 
}}

如今UI是安全的了,代碼也更加簡單了,由於AsyncTask把worker線程裏作的事和UI線程裏要作的事分開了。

你應該閱讀一下 AsyncTask 的參考文檔以便更好的使用它。下面就是一個對 AsyncTask 如何做用的快速的總覽:

你能夠具體設置參數的類型,進度值,任務的終值,使用的範型
doInBackground() 方法自動在 worker 線程執行
onPreExecute(), onPostExecute(), 和 onProgressUpdate() 方法都是在UI線程被調用
doInBackground() 的返回值會被送往 onPostExecute()方法
你能夠隨時在 doInBackground()方法裏面調用 publishProgress() 方法來執行UI 線程裏面的onProgressUpdate() 方法
你能夠從任何線程取消這個任務
注意: 你在使用worker線程的時候可能會碰到的另外一個問題就是由於runtime configuration change (好比用戶改變了屏幕的方向)致使你的activity不可預期的重啓,這可能會kill掉你的worker線程。爲了解決這個問題你能夠參考 Shelves 這個項目。

線程安全的方法

在某些狀況下,你實現的方法可能會被多個線程所調用,所以你必須把它寫出線程安全的。


你們先不要困在上面這篇文章中的具體代碼實現上,把關注點放在Android中進程,線程和android基本組件之間的關係上。咱們看完了如何在java中進行線程操做以後再去學習Android相關機制就會相對容易一些。

三 java併發編程

java併發編程是一個很龐大的話題,我不會也沒有能力講得過於深刻,我沒辦法告訴你淘寶網是怎麼處理每秒成千上萬次的點擊而屹立不倒,我只會講一下爲何咱們能夠利用java併發編程讓應用在下載文件的同時UI不會卡頓。java併發操做可讓咱們把一個程序分紅幾部分,各自獨立的去完成任務。好首先咱們來定義一下這裏的任務(tasks)。

1.定義tasks
一個線程承載着一個任務,如何描述它呢?java中提供Runnable這個接口,來,上代碼:

public class ExampleTask implements Runnable {
    private static int taskCount = 0;
    private final int id = taskCount++;
    protected int count = 10;

    private String status(){
        return "#"+id+": "+"count is "+count;
    }

    @Override
    public void run() {
        while (count -- > 0){
            System.out.println(status());
            Thread.yield();//the part of the Java threading mechanism that moves the CPU from one thread to the next
        }
    }
}

注意靜態變量taskCount和final變量int,是爲了該類每次被實例化時能有一個獨一無二的id。
在覆寫的run方法中咱們一般放入一個循環,先不用理會yield方法。
而後咱們在一個線程中將它實例化並調用run方法:

public class MainThread {
    public static void main(String args[]){
        ExampleTask exampleTask = new ExampleTask();
        exampleTask.run();
    }
}

結果以下:

#0: count is 9
#0: count is 8
#0: count is 7
#0: count is 6
#0: count is 5
#0: count is 4
#0: count is 3
#0: count is 2
#0: count is 1
#0: count is 0

這裏並無什麼特別之處,只是被main方法調用而已(也就是存在於系統分配給main的線程中)。

2 Thread類
Thread類被實例化時,即在當前進程中建立一個新的線程,來看代碼:

public class BasicThread {
    public static void main(String[] args){
        Thread t = new Thread(new ExampleTask());
        t.start();
        System.out.println("ExampleTask任務即將開始");
    }
}

能夠看出咱們須要將ExampleTask傳給Thread的構造方法,上面說過任務是對線程的描述,這裏也就不難理解了。咱們先看一下執行結果:

結果一

#0: count is 9
ExampleTask任務即將開始
#0: count is 8
#0: count is 7
#0: count is 6
#0: count is 5
#0: count is 4
#0: count is 3
#0: count is 2
#0: count is 1
#0: count is 0

結果二

ExampleTask任務即將開始
#0: count is 9
#0: count is 8
#0: count is 7
#0: count is 6
#0: count is 5
#0: count is 4
#0: count is 3
#0: count is 2
#0: count is 1
#0: count is 0

不用奇怪我爲何給出這兩種結果(尤爲是第一種),由於在屢次運行試驗中確確實實出現了這兩種結果。咱們來分析一下,當咱們實例化Thread並將Task傳遞給它時,當前進程將在main()線程以外從新建立一個t線程,而後咱們執行t.start(),這個方法會作一些必要的線程初始化的工做而後就通知t線程裏的ExampleTask任務須要執行run方法了,而後start會迅速return到main()線程,因此咱們沒必要等到ExampleTask裏的run方法裏面的循環執行完就能夠看見

ExampleTask任務即將開始

至於爲何會發現第一種狀況,我猜想是因爲start返回的不夠快,讓t線程搶先了(對,就這麼生動的理解線程你就不會怕了,雖然解釋的很糟糕)
再看看下面這代碼:

public class MoreBasicThread {
    public static void main(String args[]){
        for (int i=0;i<5;i++){
            Thread t = new Thread(new ExampleTask());
            t.start();
        }
        System.out.println("前方高能!多個線程即將開始打架!");
    }
}

很混亂吧
如今你能夠回過頭去看一下這段代碼了:

public void onClick(View v){ 
        new Thread(new Runnable(){ 
            public void run(){ 
                final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); 
                mImageView.post(new Runnable(){ 
                    public void run(){ 
                        mImageView.setImageBitmap(bitmap); 
                    } 
                }); 
            } 
        }).start();}

四 AsyncTask

有了上面這些知識的鋪墊,咱們回到Android中。咱們設想一個場景,當用戶點擊某個Button時,咱們想從網絡上加載一些文本到當前UI,前面說了咱們沒辦法在UI線程中直接進行網絡請求(由於可能會有阻塞UI線程的風險),如今咱們很容易想到在當前進程中再建立一個線程,讓其執行網絡請求,請求完成後再來更新UI,好比上面的方案,咱們還能夠用安卓給咱們提供的AsyncTask,使用起來更加方便,也更容易維護,操做起來:

1.準備工做

public class Loader {
    public byte[] getUrlBytes(String urlSpecfic)throws IOException{
        URL url = new URL(urlSpecfic);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        try {
            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK){
                throw new IOException(connection.getResponseMessage()+"with"+urlSpecfic);
            }
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            InputStream in = connection.getInputStream();
            byte[] buffer = new byte[1024];
            int byteRead = 0;
            while ((byteRead = in.read(buffer))>0){
                out.write(buffer,0,byteRead);
            }
            out.close();
            return out.toByteArray();
        }finally {
            connection.disconnect();
        }
    }

    public String getUrlString(String urlSpecific) throws IOException{
        return new String(getUrlBytes(urlSpecific));
    }
}

這個類的主要做用是請求特定url的網絡資源,不理解的話要麼跳過,要麼去找一本java書回顧一下java網絡編程。
接下來是佈局文件:很簡單,一個TextView,一個Button

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="aaa"
        android:id="@+id/url_text"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/url_button"
        android:text="urlButton"/>
</LinearLayout>

2.使用AsyncTask

public class MainActivity extends AppCompatActivity {
    private TextView urlText;
    private Button urlButton;
    private Loader loader = new Loader();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        urlText = (TextView) findViewById(R.id.url_text);
        urlButton = (Button) findViewById(R.id.url_button);
        urlButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new DownLoader().execute("https://segmentfault.com/");
            }
        });


    }

 private class DownLoader extends AsyncTask<String,Void,String>{

        @Override
        protected String doInBackground(String... params) {
            try {
                return loader.getUrlString(params[0]);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(String s) {
            urlText.setText(s);
        }
    }
}

這裏覆寫了兩個方法,doInBackground會在一個新的線程裏執行,參數類型由AsyncTask的第一個泛型參數決定,返回參數由AsyncTask的第三個泛型參數決定,其返回值會傳遞給onPostExecute方法。而onPostExecute方法是能夠操做UI線程的,故用其爲urlText賦值。好,編譯,運行,點擊按鈕,幾秒鐘後urlText裏的內容便被請求回來的segmentfault的首頁html所替換。

五 後記

設想若是咱們須要請求的內容遠不止一個html文件,多是一個很是龐大的json數據或者是無窮無盡的圖片資源,若是還用上面的方法,恐怕用戶會在urlText前等到終老,別擔憂,安卓提供了很是使人頭痛可是也一樣很是高效的異步機制HandlerThread,Looper,Handler以及Message。別怕,別虛。下次咱們一塊兒征服。

相關文章
相關標籤/搜索