成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

進(jìn)程,線程講到AsyncTask

姘存按 / 3433人閱讀

摘要:系統(tǒng)創(chuàng)建好進(jìn)程后,實際上就啟動執(zhí)行了該進(jìn)程的主執(zhí)行線程。也就是說,對線程來說,進(jìn)程相當(dāng)于一個容器,可以有許多線程同時在一個進(jìn)程里執(zhí)行。默認(rèn)情況下,同一應(yīng)用程序下的所有組件都運行在相同的進(jìn)程和線程一般稱為程序的主線程中。

一 前言

異步編程是android初學(xué)者的一個難點,卻也是始終不能繞過的一個坎??梢哉f幾乎每個app都逃不了網(wǎng)絡(luò)編程,而網(wǎng)絡(luò)編程又往往建立在異步的機制之上(你不應(yīng)該也無法在UI線程里執(zhí)行網(wǎng)絡(luò)請求,你也不應(yīng)該在UI線程中頻繁的進(jìn)行IO操作)。等等,你不知道什么是線程?那就對了,我們一起來回憶一下大學(xué)課本的知識,一切從進(jìn)程講起。

二 進(jìn)程和線程

我曾經(jīng)在知乎上聽一個朋友說一個優(yōu)秀的程序員一定會有著極強的對抽象的理解能力,我很贊同這句話,我心里一直鼓勵自己:當(dāng)你對抽象不再懼怕的時候,可能你正在成為一名真正的coder。

1.進(jìn)程(process)
A process is the operating system’s abstraction for a running program
這是csapp中的原話,我覺得兩個詞特別重要,一個是abstraction,說明進(jìn)程是一種抽象,是人為的一種定義,另一個是running,說明進(jìn)程是正在執(zhí)行的程序,而不是保存在磁盤上的一個程序文件。不管你現(xiàn)在怎么理解進(jìn)程,你都得看下面一段代碼:

#include 
int main()
{
printf("hello, world
");
return 0;
}

這可能是我們?nèi)松鷮懙玫谝恍写a,讓我們在終端里gcc得到可執(zhí)行文件a.out,然后執(zhí)行它,好,在你按下return鍵的那一瞬間到終端里打印出hello,world(好吧我承認(rèn)我詞窮了,其實就是a.out被執(zhí)行時),進(jìn)程動態(tài)產(chǎn)生,動態(tài)消亡。怎么直觀的感受它呢,來改一下代碼:

#include 
#include 

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

編譯,運行得到:

Hello World from process ID 20289

在這里我們得到了這個進(jìn)程的ID(UNIX系統(tǒng)確保每個進(jìn)程都有一個唯一的數(shù)字標(biāo)識符,稱為進(jìn)程ID,進(jìn)程ID總是一個非負(fù)整數(shù)。),這也算進(jìn)程存在的一點痕跡吧。我們再改動一下代碼:

#include 
#include 

int doSomething();

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

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

編譯,執(zhí)行:

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

可以看到這兩個函數(shù)的進(jìn)程ID是一樣的,其實你進(jìn)一步調(diào)用getppid()函數(shù)得到父進(jìn)程的函數(shù)其實也是一樣的。細(xì)心的朋友就會發(fā)現(xiàn),上一次執(zhí)行后得到的ID是20289,這次執(zhí)行得到的ID卻是20777,同樣的文件為什么每次執(zhí)行得到的ID卻是不同的呢?這就需要我們好好體會進(jìn)程是動態(tài)產(chǎn)生動態(tài)消亡的了,抽象嗎?

2.線程(Thread)
可以這么說,一切的抽象都是為了解放生產(chǎn)力。系統(tǒng)為什么要抽象出進(jìn)程的概念?一個直觀的解釋就是它可以讓每個進(jìn)程獨立的擁有虛擬地址空間、代碼、數(shù)據(jù)和其它各種系統(tǒng)資源,它還可以讓多個進(jìn)程同時執(zhí)行,讓你在寫代碼的同時還能掛著微信,放著音樂??墒沁@還不夠,因為一個進(jìn)程在某一時刻只能做一件事情,為了進(jìn)一步提高效率,又抽象出進(jìn)程的概念,來看下面這段話:

 線程是進(jìn)程內(nèi)部的一個執(zhí)行單元。系統(tǒng)創(chuàng)建好進(jìn)程后,實際上就啟動執(zhí)行了該進(jìn)程的主執(zhí)行線程。主執(zhí)行線程終止了,進(jìn)程也就隨之終止。

也就是說,對線程來說,進(jìn)程相當(dāng)于一個容器,可以有許多線程同時在一個進(jìn)程里執(zhí)行。

3.安卓中的進(jìn)程與線程
這里引用官方文檔的解釋,也不知是誰翻譯的,總之獻(xiàn)上膝蓋看原網(wǎng)頁點這里為了閱讀方便把原文貼出來了并改正了一些錯別字

當(dāng)一個Android應(yīng)用程序組件啟動時候,如果此時這個程序的其他組件沒有正在運行,那么系統(tǒng)會為這個程序以單一線程的形式啟動一個新的Linux 進(jìn)程。 默認(rèn)情況下,同一應(yīng)用程序下的所有組件都運行在相同的進(jìn)程和線程(一般稱為程序的“主”線程)中。如果一個應(yīng)用組件啟動但這個應(yīng)用的進(jìn)程已經(jīng)存在了(因為這個應(yīng)用的其他組件已經(jīng)在之前啟動了),那么這個組件將會在這個進(jìn)程中啟動,同時在這個應(yīng)用的主線程里面執(zhí)行。然而,你也可以讓你的應(yīng)用里面的組件運行在 不同的進(jìn)程里面,也可以為任何進(jìn)程添加額外的線程。

這片文章討論了Android程序里面的進(jìn)程和線程如何運作的。

進(jìn)程

默認(rèn)情況下,同一程序的所有組件都運行在相同的進(jìn)程里面,大多數(shù)的應(yīng)用都是這樣的。然而,如果你發(fā)現(xiàn)你需要讓你的程序里面的某個組件運行在特定的進(jìn)程里面,你可以在manifest 文件里面設(shè)置。

manifest 文件里面為每一個組件元素—, , , 和—提供了 android:process 屬 性。通過設(shè)置這個屬性你可以讓組件運行在特定的進(jìn)程中。你可以設(shè)置成每個組件運行在自己的進(jìn)程中,也可以讓一些組件共享一個進(jìn)程而其他的不這樣。你還可以 設(shè)置成不同應(yīng)用的組件運行在同一個進(jìn)程里面—這樣可以讓這些應(yīng)用共享相同的Linux user ID同時被相同的證書所認(rèn)證。

元素也支持 android:process 屬性,設(shè)置這個屬性可以讓這個應(yīng)用里面的所有組件都默認(rèn)繼承這個屬性。

Android 可能在系統(tǒng)剩余內(nèi)存較少,而其他直接服務(wù)用戶的進(jìn)程又要申請內(nèi)存的時候shut down 一個進(jìn)程, 這時這個進(jìn)程里面的組件也會依次被kill掉。當(dāng)這些組件有新的任務(wù)到達(dá)時,他們對應(yīng)的進(jìn)程又會被啟動。

在決定哪些進(jìn)程需要被kill的時候,Android系統(tǒng)會權(quán)衡這些進(jìn)程跟用戶相關(guān)的重要性。比如,相對于那些承載這可見的activities的 進(jìn)程,系統(tǒng)會更容易的kill掉那些承載不再可見activities的進(jìn)程。決定是否終結(jié)一個進(jìn)程取決于這個進(jìn)程里面的組件運行的狀態(tài)。下面我們會討論 kill進(jìn)程時所用到的一些規(guī)則。

進(jìn)程的生命周期

作為一個多任務(wù)的系統(tǒng),Android 當(dāng)然系統(tǒng)能夠盡可能長的保留一個應(yīng)用進(jìn)程。但是由于新的或者更重要的進(jìn)程需要更多的內(nèi)存,系統(tǒng)不得不逐漸終結(jié)老的進(jìn)程來獲取內(nèi)存。為了聲明哪些進(jìn)程需要保 留,哪些需要kill,系統(tǒng)根據(jù)這些進(jìn)程里面的組件以及這些組件的狀態(tài)為每個進(jìn)程生成了一個“重要性層級” 。處于最低重要性層級的進(jìn)程將會第一時間被清除,接著是重要性高一點,然后依此類推,根據(jù)系統(tǒng)需要來終結(jié)進(jìn)程。

在這個重要性層級里面有5個等級。下面的列表按照重要性排序展示了不同類型的進(jìn)程(第一種進(jìn)程是最重要的,因此將會在最后被kill):

Foreground進(jìn)程 一個正在和用戶進(jìn)行交互的進(jìn)程,如果一個進(jìn)程處于下面的狀態(tài)之一,那么我們可以把這個進(jìn)程稱為 foreground 進(jìn)程:
進(jìn)程包含了一個與用戶交互的 Activity (這個 Activity的 onResume() 方法被調(diào)用)。
進(jìn)程包含了一個綁定了與用戶交互的activity的 Service 。
進(jìn)程包含了一個運行在”in the foreground”狀態(tài)的 Service —這個 service 調(diào)用了 startForeground()方法。
進(jìn)程包含了一個正在運行的它的生命周期回調(diào)函數(shù) (onCreate(), onStart(), oronDestroy())的 Service 。
進(jìn)程包含了一個正在運行 onReceive() 方法的 BroadcastReceiver 。
一般說來,任何時候,系統(tǒng)中只存在少數(shù)的 foreground 進(jìn)程。 只有在系統(tǒng)內(nèi)存特別緊張以至于都無法繼續(xù)運行下去的時候,系統(tǒng)才會通過kill這些進(jìn)程來緩解內(nèi)存壓力。在這樣的時候系統(tǒng)必須kill一些 (Generally, at that point, the device has reached a memory paging state,這句如何翻譯較好呢)foreground 進(jìn)程來保證 用戶的交互有響應(yīng)。

Visible進(jìn)程 一個進(jìn)程沒有任何 foreground 組件, 但是它還能影響屏幕上的顯示。 如果一個進(jìn)程處于下面的狀態(tài)之一,那么我們可以把這個進(jìn)程稱為 visible 進(jìn)程:
進(jìn)程包含了一個沒有在foreground 狀態(tài)的 Activity ,但是它仍然被用戶可見 (它的 onPause() 方法已經(jīng)被調(diào)用)。這種情況是有可能出現(xiàn)的,比如,一個 foreground activity 啟動了一個 dialog,這樣就會讓之前的 activity 在dialog的后面部分可見。
進(jìn)程包含了一個綁定在一個visible(或者foreground)activity的 Service 。
一個 visible 進(jìn)程在系統(tǒng)中是相當(dāng)重要的,只有在為了讓所有的foreground 進(jìn)程正常運行時才會考慮去kill visible 進(jìn)程。

Service進(jìn)程 一個包含著已經(jīng)以 startService() 方法啟動的 Service 的 進(jìn)程,同時還沒有進(jìn)入上面兩種更高級別的種類。盡管 service 進(jìn)程沒有與任何用戶所看到的直接關(guān)聯(lián),但是它們經(jīng)常被用來做用戶在意的事情(比如在后臺播放音樂或者下載網(wǎng)絡(luò)數(shù)據(jù)),所以系統(tǒng)也只會在為了保證所有的 foreground and visible 進(jìn)程正常運行時kill掉 service 進(jìn)程。

Background進(jìn)程 一個包含了已不可見的activity的 進(jìn)程 (這個 activity 的 onStop() 已 經(jīng)被調(diào)用)。這樣的進(jìn)程不會直接影響用戶的體驗,系統(tǒng)也可以為了foreground 、visible 或者 service 進(jìn)程隨時kill掉它們。一般說來,系統(tǒng)中有許多的 background 進(jìn)程在運行,所以將它們保持在一個LRU (least recently used)列表中可以確保用戶最近看到的activity 所屬的進(jìn)程將會在最后被kill。如果一個 activity 正確的實現(xiàn)了它的生命周期回調(diào)函數(shù),保存了自己的當(dāng)前狀態(tài),那么kill這個activity所在的進(jìn)程是不會對用戶在視覺上的體驗有影響的,因為當(dāng)用戶 回退到這個 activity時,它的所有的可視狀態(tài)將會被恢復(fù)。查看 Activities 可以獲取更多如果保存和恢復(fù)狀態(tài)的文檔。
Empty 進(jìn)程 一個不包含任何活動的應(yīng)用組件的進(jìn)程。 這種進(jìn)程存在的唯一理由就是緩存。為了提高一個組件的啟動的時間需要讓組件在這種進(jìn)程里運行。為了平衡進(jìn)程緩存和相關(guān)內(nèi)核緩存的系統(tǒng)資源,系統(tǒng)需要kill這些進(jìn)程。
Android是根據(jù)進(jìn)程中組件的重要性盡可能高的來評級的。比如,如果一個進(jìn)程包含來一個 service 和一個可見 activity,那么這個進(jìn)程將會被評為 visible 進(jìn)程,而不是 service 進(jìn)程。

另外,一個進(jìn)程的評級可能會因為其他依附在它上面的進(jìn)程而被提升—一個服務(wù)其他進(jìn)程的進(jìn)程永遠(yuǎn)不會比它正在服務(wù)的進(jìn)程評級低的。比如,如果進(jìn)程A中 的一個 content provider 正在為進(jìn)程B中的客戶端服務(wù),或者如果進(jìn)程A中的一個 service 綁定到進(jìn)程B中的一個組件,進(jìn)程A的評級會被系統(tǒng)認(rèn)為至少比進(jìn)程B要高。

因為進(jìn)程里面運行著一個 service 的評級要比一個包含background activities的進(jìn)程要高,所以當(dāng)一個 activity 啟動長時操作時,最好啟動一個 service 來 做這個操作,而不是簡單的創(chuàng)建一個worker線程—特別是當(dāng)這個長時操作可能會拖垮這個activity。比如,一個需要上傳圖片到一個網(wǎng)站的 activity 應(yīng)當(dāng)開啟一個來執(zhí)行這個上傳操作。這樣的話,即使用戶離開來這個activity也能保證上傳動作在后臺繼續(xù)。使用 service 可以保證操作至少處于”service process” 這個優(yōu)先級,無論這個activity發(fā)生了什么。這也是為什么 broadcast receivers 應(yīng)該使用 services 而不是簡單的將耗時的操作放到線程里面。

線程

當(dāng)一個應(yīng)用啟動的時候,系統(tǒng)會為它創(chuàng)建一個線程,稱為“主線程”。這個線程很重要因為它負(fù)責(zé)處理調(diào)度事件到相關(guān)的 user interface widgets,包括繪制事件。你的應(yīng)用也是在這個線程里面與來自Android UI toolkit (包括來自 android.widget 和 android.view 包的組件)的組件進(jìn)行交互。因此,這個主線程有時候也被稱為 UI 線程。

系統(tǒng)沒有為每個組件創(chuàng)建一個多帶帶的線程。同一進(jìn)程里面的所有組件都是在UI 線程里面被實例化的,系統(tǒng)對每個組件的調(diào)用都是用過這個線程進(jìn)行調(diào)度的。所以,響應(yīng)系統(tǒng)調(diào)用的方法(比如 onKeyDown() 方法是用來捕捉用戶動作或者一個生命周期回調(diào)函數(shù))都運行在進(jìn)程的UI 線程里面。

比如,當(dāng)用戶點擊屏幕上的按鈕,你的應(yīng)用的UI 線程會將這個點擊事件傳給 widget,接著這個widget設(shè)置它的按壓狀態(tài),然后發(fā)送一個失效的請求到事件隊列。這個UI 線程對請求進(jìn)行出隊操作,然后處理(通知這個widget重新繪制自己)。

當(dāng)你的應(yīng)用與用戶交互對響應(yīng)速度的要求比較高時,這個單線程模型可能會產(chǎn)生糟糕的效果(除非你很好的實現(xiàn)了你的應(yīng)用)。特別是,當(dāng)應(yīng)用中所有的事情 都發(fā)生在UI 線程里面,那些訪問網(wǎng)絡(luò)數(shù)據(jù)和數(shù)據(jù)庫查詢等長時操作都會阻塞整個UI線程。當(dāng)整個線程被阻塞時,所有事件都不能被傳遞,包括繪制事件。這在用戶看來,這個 應(yīng)用假死了。甚至更糟糕的是,如果UI 線程被阻塞幾秒(當(dāng)前是5秒)以上,系統(tǒng)將會彈出臭名昭著的 “application not responding” (ANR) 對話框。這時用戶可能選擇退出你的應(yīng)用甚至卸載。

另外,Android的UI 線程不是線程安全的。所以你不能在一個worker 線程操作你的UI—你必須在UI線程上對你的UI進(jìn)行操作。這有兩條簡單的關(guān)于Android單線程模型的規(guī)則:

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

由于上面對單一線程模型的描述,保證應(yīng)用界面的及時響應(yīng)同時UI線程不被阻塞變得很重要。如果你不能讓應(yīng)用里面的操作短時被執(zhí)行玩,那么你應(yīng)該確保把這些操作放到獨立的線程里(“background” or “worker” 線程)。

比如,下面這段代碼在一個額外的線程里面下載圖片并在一個 ImageView顯示:

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

起先這段代碼看起來不錯,因為它創(chuàng)建一個新的線程來處理網(wǎng)絡(luò)操作。然而,它違反來單一線程模型的第二條規(guī)則: 不在非UI線程里訪問 Android UI toolkit—這個例子在一個worker線程修改了 ImageView 。這會導(dǎo)致不可預(yù)期的結(jié)果,而且還難以調(diào)試。

為了修復(fù)這個問題,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();} 

現(xiàn)在這個方案的線程安全的:這個網(wǎng)絡(luò)操作在獨立線程中完成后,UI線程便會對ImageView 進(jìn)行操作。

然而,隨著操作復(fù)雜性的增長,代碼會變得越來越復(fù)雜,越來越難維護(hù)。為了用worker 線程處理更加復(fù)雜的交互,你可以考慮在worker線程中使用Handler ,用它來處理UI線程中的消息。也許最好的方案就是繼承 AsyncTask 類,這個類簡化了需要同UI進(jìn)行交互的worker線程任務(wù)的執(zhí)行。

使用 AsyncTask

AsyncTask 能讓你在UI上進(jìn)行異步操作。它在一個worker線程里進(jìn)行一些阻塞操作然后把結(jié)果交給UI主線程,在這個過程中不需要你對線程或者h(yuǎn)andler進(jìn)行處理。

使用它,你必須繼承 AsyncTask 并實現(xiàn) doInBackground() 回調(diào)方法,這個方法運行在一個后臺線程池里面。如果你需要更新UI,那么你應(yīng)該實現(xiàn)onPostExecute(),這個方法從 doInBackground() 取出結(jié)果,然后在 UI 線程里面運行,所以你可以安全的更新你的UI。你可以通過在UI線程調(diào)用 execute()方法來運行這個任務(wù)。

比如,你可以通過使用 AsyncTask來實現(xiàn)之前的例子:

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

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

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

現(xiàn)在UI是安全的了,代碼也更加簡單了,因為AsyncTask把worker線程里做的事和UI線程里要做的事分開了。

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

你可以具體設(shè)置參數(shù)的類型,進(jìn)度值,任務(wù)的終值,使用的范型
doInBackground() 方法自動在 worker 線程執(zhí)行
onPreExecute(), onPostExecute(), 和 onProgressUpdate() 方法都是在UI線程被調(diào)用
doInBackground() 的返回值會被送往 onPostExecute()方法
你可以隨時在 doInBackground()方法里面調(diào)用 publishProgress() 方法來執(zhí)行UI 線程里面的onProgressUpdate() 方法
你可以從任何線程取消這個任務(wù)
注意: 你在使用worker線程的時候可能會碰到的另一個問題就是因為runtime configuration change (比如用戶改變了屏幕的方向)導(dǎo)致你的activity不可預(yù)期的重啟,這可能會kill掉你的worker線程。為了解決這個問題你可以參考 Shelves 這個項目。

線程安全的方法

在某些情況下,你實現(xiàn)的方法可能會被多個線程所調(diào)用,因此你必須把它寫出線程安全的。

大家先不要困在上面這篇文章中的具體代碼實現(xiàn)上,把關(guān)注點放在Android中進(jìn)程,線程和android基本組件之間的關(guān)系上。我們看完了如何在java中進(jìn)行線程操作之后再去學(xué)習(xí)Android相關(guān)機制就會相對容易一些。

三 java并發(fā)編程

java并發(fā)編程是一個很龐大的話題,我不會也沒有能力講得過于深入,我沒辦法告訴你淘寶網(wǎng)是怎么處理每秒成千上萬次的點擊而屹立不倒,我只會講一下為什么我們可以利用java并發(fā)編程讓應(yīng)用在下載文件的同時UI不會卡頓。java并發(fā)操作可以讓我們把一個程序分成幾部分,各自獨立的去完成任務(wù)。好首先我們來定義一下這里的任務(wù)(tasks)。

1.定義tasks
一個線程承載著一個任務(wù),如何描述它呢?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
        }
    }
}

注意靜態(tài)變量taskCount和final變量int,是為了該類每次被實例化時能有一個獨一無二的id。
在覆寫的run方法中我們通常放入一個循環(huán),先不用理會yield方法。
然后我們在一個線程中將它實例化并調(diào)用run方法:

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

結(jié)果如下:

#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方法調(diào)用而已(也就是存在于系統(tǒng)分配給main的線程中)。

2 Thread類
Thread類被實例化時,即在當(dāng)前進(jìn)程中創(chuàng)建一個新的線程,來看代碼:

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

可以看出我們需要將ExampleTask傳給Thread的構(gòu)造方法,上面說過任務(wù)是對線程的描述,這里也就不難理解了。我們先看一下執(zhí)行結(jié)果:

結(jié)果一

#0: count is 9
ExampleTask任務(wù)即將開始
#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

結(jié)果二

ExampleTask任務(wù)即將開始
#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

不用奇怪我為什么給出這兩種結(jié)果(尤其是第一種),因為在多次運行試驗中確確實實出現(xiàn)了這兩種結(jié)果。我們來分析一下,當(dāng)我們實例化Thread并將Task傳遞給它時,當(dāng)前進(jìn)程將在main()線程之外重新創(chuàng)建一個t線程,然后我們執(zhí)行t.start(),這個方法會做一些必要的線程初始化的工作然后就通知t線程里的ExampleTask任務(wù)需要執(zhí)行run方法了,然后start會迅速return到main()線程,所以我們不必等到ExampleTask里的run方法里面的循環(huán)執(zhí)行完就可以看見

 ExampleTask任務(wù)即將開始

至于為什么會發(fā)現(xiàn)第一種情況,我猜測是由于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("前方高能!多個線程即將開始打架!");
    }
}


現(xiàn)在你可以回過頭去看一下這段代碼了:

 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中。我們設(shè)想一個場景,當(dāng)用戶點擊某個Button時,我們想從網(wǎng)絡(luò)上加載一些文本到當(dāng)前UI,前面說了我們沒辦法在UI線程中直接進(jìn)行網(wǎng)絡(luò)請求(因為可能會有阻塞UI線程的風(fēng)險),現(xiàn)在我們很容易想到在當(dāng)前進(jìn)程中再創(chuàng)建一個線程,讓其執(zhí)行網(wǎng)絡(luò)請求,請求完成后再來更新UI,比如上面的方案,我們還可以用安卓給我們提供的AsyncTask,使用起來更加方便,也更容易維護(hù),操作起來:

1.準(zhǔn)備工作

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的網(wǎng)絡(luò)資源,不理解的話要么跳過,要么去找一本java書回顧一下java網(wǎng)絡(luò)編程。
接下來是布局文件:很簡單,一個TextView,一個Button




    

    

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{

        @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會在一個新的線程里執(zhí)行,參數(shù)類型由AsyncTask的第一個泛型參數(shù)決定,返回參數(shù)由AsyncTask的第三個泛型參數(shù)決定,其返回值會傳遞給onPostExecute方法。而onPostExecute方法是可以操作UI線程的,故用其為urlText賦值。好,編譯,運行,點擊按鈕,幾秒鐘后urlText里的內(nèi)容便被請求回來的segmentfault的首頁html所替換。

五 后記

設(shè)想如果我們需要請求的內(nèi)容遠(yuǎn)不止一個html文件,可能是一個非常龐大的json數(shù)據(jù)或者是無窮無盡的圖片資源,如果還用上面的方法,恐怕用戶會在urlText前等到終老,別擔(dān)心,安卓提供了非常令人頭痛但是也同樣非常高效的異步機制HandlerThread,Looper,Handler以及Message。別怕,別虛。下次我們一起征服。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/65602.html

相關(guān)文章

  • 串行還是并行?——記一次 AsyncTask 問題排查

    摘要:當(dāng)然,如果你的核心數(shù)夠多,到個線程的并行度不滿足的話,也可以自定義一個線程池來執(zhí)行,不過這樣的話,要注意自己維護(hù)這個線程池的初始化,釋放等等操作了。 事情起源于一個bug排查,一個AsyncTask的子類,執(zhí)行的時候發(fā)現(xiàn)onPreExecute方法執(zhí)行了,doInBackground卻遲遲沒有被調(diào)用。懂AsyncTask一些表面原理的都知道,onPreExecute方法是在主線程執(zhí)行,...

    mo0n1andin 評論0 收藏0

發(fā)表評論

0條評論

姘存按

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<