中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

Android 多線程編程的總結(jié)

2018-07-20    來(lái)源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

前言

這幾天在研究Android的多線程方面的知識(shí),閱讀了許多大牛的文章,發(fā)現(xiàn)Android的多線程方式挺多的,關(guān)于各種方式的優(yōu)缺點(diǎn)也都各有看法,所以這部分的知識(shí)還是很容易令人覺(jué)得混亂的,所以自己梳理了相關(guān)知識(shí),用自己的角度去簡(jiǎn)單總結(jié)這些知識(shí),鞏固自己知識(shí)的同時(shí)也希望幫助到其他人。
首先,從兩個(gè)問(wèn)題入手:我們?yōu)槭裁葱枰嗑程機(jī)制?什么時(shí)候需要到多線程?
答:1、因?yàn)锳ndroid官方明確聲明在多線程編程時(shí)有兩大原則:第一、不要阻塞UI線程(即主線程,下文兩個(gè)稱呼可互換)、第二、不要在UI線程之外訪問(wèn)UI組件。這個(gè)話題是老生常談了,想必很多人都明白個(gè)中緣由。
2、我對(duì)多線程的使用情況歸結(jié)為主要有兩種情況:第一、將任務(wù)從主線程拋到工作線程,第二種情況是將任務(wù)從工作線程拋到主線程。這兩種情況其實(shí)跟上面兩個(gè)原則是對(duì)應(yīng)的。當(dāng)我們有耗時(shí)的任務(wù),如果在UI線程中執(zhí)行,那就會(huì)阻塞UI線程了,必須要拋到工作線程中去執(zhí)行;而當(dāng)我們要更新UI組件時(shí),就一定得在UI線程里執(zhí)行了,所以就得把在工作線程中執(zhí)行的任務(wù)結(jié)果返回到UI線程中去更新組件了。

一、將任務(wù)從工作線程拋到主線程

我們先從一段代碼開(kāi)始


protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        button = (Button) findViewById(R.id.button);  
        text = (TextView) findViewById(R.id.text);//耗時(shí)任務(wù)完成時(shí)在該TextView上顯示文本  

        mRunnable = new Runnable() {  
            @Override  
            public void run() {  
                try {  
                    Thread.sleep(5000);//模擬耗時(shí)任務(wù)  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                 text.setText("Task Done!!");//在非UI線程之外去訪問(wèn)UI組件  
            }  
        };  

        button.setOnClickListener(new View.OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                Thread thread = new Thread(mRunnable);  
                thread.start();  
            }  
        });  

    }  


布局上只定義了一個(gè)Button和TextView,Button按下時(shí)會(huì)開(kāi)啟一個(gè)新線程執(zhí)行耗時(shí)任務(wù),任務(wù)完成后更新TextView的文本。有點(diǎn)基礎(chǔ)的都能明白這段代碼是有問(wèn)題的,因?yàn)樗诜荱I線程之外去訪問(wèn)UI組件了。

那這個(gè)時(shí)候就得想辦法讓text.setText("Task Done!!");這句代碼拋到UI線程中去執(zhí)行了。對(duì)此,我們大概有四種方法,下面分別演示。
有如下5種方式

1、Handler.sendXXXMessage()等方法

首先是在上面的Activity中定義一個(gè)Handler

Handler mHandler = new Handler(){  
        @Override  
        public void handleMessage(Message msg){  
            if(msg.what == 0x123){  
                text.setText("Task Done!!");  
            }  
        }  
    }; 

然后將工作線程的代碼改為下面的樣子

 
mRunnable = new Runnable() {  
            @Override  
            public void run() {  
                try {  
                    Thread.sleep(5000);//模擬耗時(shí)任務(wù)  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                mHandler.sendEmptyMessage(0x123);//關(guān)于發(fā)消息的方法有很多,比如sendMessage(Message msg),sendMessageDelayed(Message msg, long delayMills)等等,可按具體需求選擇,這里不作擴(kuò)展  

            }  
        };  

這樣程序運(yùn)行起來(lái)后就不會(huì)報(bào)錯(cuò)了。

關(guān)于Handler的底層機(jī)制網(wǎng)上有非常多文章作了詳細(xì)的描述,比如有張鴻洋的Android 異步消息處理機(jī)制 讓你深入理解 Looper、Handler、Message三者關(guān)系,這里也小小地提一下,為后面的內(nèi)容做一些必要的鋪墊。
一個(gè)線程只有一個(gè)Looper, 而一個(gè)Looper持有一個(gè)MessageQueue, 當(dāng)調(diào)用Looper.prepare()時(shí),Looper就與當(dāng)前線程關(guān)聯(lián)起來(lái)了(在Activity里沒(méi)有顯示調(diào)用Looper.prepare()是因?yàn)橄到y(tǒng)自動(dòng)在主線程里幫我們調(diào)用了),而Handler是與Looper的線程是綁定的,查看Handler類(lèi)的源碼可以發(fā)現(xiàn)它幾個(gè)構(gòu)造函數(shù),其中有接收一個(gè)Looper參數(shù)的,也有不接收Looper參數(shù)的,從上面的代碼上看,我們沒(méi)有為Handler指定Looper,那么Handler就默認(rèn)更當(dāng)前線程(即主線程)的Looper關(guān)聯(lián)起來(lái)了,之所以啰嗦那么多就是因?yàn)?strong>這決定了Handler.handlerMessage(msg)方法體里的代碼到底在哪個(gè)線程里執(zhí)行,我們?cè)偈崂硪幌,Looper.prepare調(diào)用決定了Looper與哪個(gè)線程關(guān)聯(lián),間接決定了與這個(gè)Looper相關(guān)聯(lián)的Handler.handlerMessage(msg)方法體里的代碼執(zhí)行的線程。(太啰嗦了)
現(xiàn)在回到上面的代碼,我們的Handler是在主線程里的定義的,所以也默認(rèn)跟主線程的Looper相關(guān)聯(lián),即handlerMessage方法的代碼會(huì)在UI線程執(zhí)行,因此更新TextView就不會(huì)報(bào)錯(cuò)了。下面這張圖是弄清handlerMessage(msg)方法體里的代碼的執(zhí)行線程的思路

2、Handler.post(Runnable)

只要將上面代碼中的

mHandler.sendEmptyMessage(0x123); 

改成

mHandler.post(new Runnable() {  
    @Override  
    public void run() {  
        text.setText("Task Done!!");                 
        }  
 });  

就可以了,可能有人看到new了一個(gè)Runnable就以為是又開(kāi)了一個(gè)新線程,事實(shí)上并沒(méi)有開(kāi)啟任何新線程,只是使run()方法體的代碼拋到與mHandler相關(guān)聯(lián)的線程中執(zhí)行,經(jīng)過(guò)上面的分析我們也知道m(xù)Handler是與主線程關(guān)聯(lián)的,所以更新TextView組件依然發(fā)生在主線程了。

3、Activity.runOnUIThread(Runnable)

將上面的代碼改成

runOnUiThread(new Runnable() {  
        @Override  
        public void run() {  
            text.setText("Task Done!!");  
        }  
    });  

這個(gè)看起來(lái)跟上面的方法很像,差別就是這種方法不需要去定義Handler。

4、View.post(Runnable)

將上面的代碼改為

text.post(new Runnable() { @Override public void run() { text.setText("Task Done!!"); } }); 

這個(gè)看起來(lái)依舊是跟上面的方法很像,依然不用定義Handler。

5、AsyncTask

這種方法要改動(dòng)上面整個(gè)開(kāi)新線程的代碼,具體代碼在入門(mén)書(shū)籍上基本都有,這里就不附上了,思路就是在doInBackground(Params…) 方法里執(zhí)行耗時(shí)邏輯,然后在onPostExecute(Result) 中將結(jié)果更新回UI組件。

使用哪種大多數(shù)情況我還是根據(jù)代碼風(fēng)格和習(xí)慣來(lái)決定,上面這5種方法具體在效率上是否有巨大差異,我沒(méi)有深入研究,這方面有研究的兄弟希望可以在留言里交流一下

二、將任務(wù)從主線程拋到工作線程

正如前言所說(shuō),耗時(shí)任務(wù)不能在主線程去進(jìn)行,需要另外開(kāi)一個(gè)線程。分別有下面幾種方法:

1、Thread、Runnable

這個(gè)是最傳統(tǒng)的方法了,相信每個(gè)學(xué)過(guò)Java基礎(chǔ)的人都知道。無(wú)非就是繼承Thread類(lèi)覆寫(xiě)run()然后通過(guò)thread.start()或?qū)崿F(xiàn)Runnable接口復(fù)寫(xiě)run()然后New Thread(Runnable).start(),在上面的例子中就是通過(guò)這種最普通的方法去開(kāi)新線程的,不過(guò)在實(shí)際開(kāi)發(fā)中,這種開(kāi)新線程的方法是很不被推薦的,理由如下:1)當(dāng)你有多個(gè)耗時(shí)任務(wù)時(shí)就會(huì)開(kāi)多個(gè)新線程,開(kāi)啟新線程的以及調(diào)度多個(gè)線程的開(kāi)銷(xiāo)是非常大的,這往往會(huì)帶來(lái)嚴(yán)重的性能問(wèn)題,例如,你有100個(gè)耗時(shí)任務(wù),那就開(kāi)100個(gè)線程。2)如果在線程中執(zhí)行循環(huán)任務(wù),只能通過(guò)一個(gè)Flag來(lái)控制它的停止,如while(!iscancel){//耗時(shí)任務(wù)}。

2、HandlerThread

在正式介紹HandlerThread前,我們先來(lái)看看以下代碼:

 
protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        button = (Button) findViewById(R.id.button);  
        button.setOnClickListener(new View.OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                mOtherHandler.sendEmptyMessage(0x124);  
            }  
        });  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                Looper.prepare();//在新線程中調(diào)用  
                mOtherHandler = new Handler() { //默認(rèn)關(guān)聯(lián)新線程的Looper  
                    @Override  
                    public void handleMessage(Message msg) {  

                        if (msg.what == 0x124) {  
                            try {  
                                Log.d("HandlerThread", Thread.currentThread().getName());//打印線程名  
                                Thread.sleep(5000);//模擬耗時(shí)邏輯  
                            } catch (InterruptedException e) {  
                                e.printStackTrace();  
                            }  
                        }  

                    }  
                };  
                Looper.loop();  
            }  
        }).start();  
    }  

可以看到這里用的是第一種方法開(kāi)啟新線程的,但是在新線程里初始化了Looper(因?yàn)椴皇窃谥骶程,所以要我們自己調(diào)用Looper.prepare()和loop()),還定義了一個(gè)Handler ,前面我之所以那么啰嗦,就是為了讓你明白:這個(gè)Handler的handlerMessage(msg)方法體的代碼是在新線程(工作線程)中執(zhí)行的,而不是主線程(忘了的話拉回去看前面的內(nèi)容),所以我們只需要在Button的點(diǎn)擊事件中調(diào)用sendXXXMessage就可以讓耗時(shí)任務(wù)在新線程中執(zhí)行了。

有意思的是,如果我們以非常快的速度連續(xù)點(diǎn)擊兩次Button,你會(huì)發(fā)現(xiàn)打印出來(lái)的兩條Log是以間隔5秒相繼出現(xiàn)的。這是因?yàn)槊奎c(diǎn)一次按鈕并沒(méi)有開(kāi)啟都開(kāi)啟一個(gè)新線程,而只是發(fā)送了一條消息,我們?cè)趏nCreate()里就已經(jīng)把一個(gè)新線程開(kāi)好了,然后調(diào)用Looper.loop()使這個(gè)線程一直處于循環(huán)狀態(tài)了,而我們每發(fā)一條消息,消息都會(huì)在MessageQueue里排隊(duì)。總而言之,不管我們點(diǎn)多少次按鈕,都只有一個(gè)工作線程,多個(gè)耗時(shí)任務(wù)在這個(gè)工作線程的隊(duì)列中排隊(duì)處理。思路如下圖

鋪墊了這么多,可以把HandlerThread拉出來(lái)了,查看源碼,你會(huì)發(fā)現(xiàn)HandlerThread也是Thread的子類(lèi),那豈不是還是跟第一種方法一樣,說(shuō)是也是,說(shuō)不是也不是。其實(shí)呢,HandlerThread就是對(duì)上面的代碼的一種封裝,我們來(lái)看看它是怎么用的

handlerThread = new HandlerThread("MyNewThread");//自定義線程名稱  handlerThread.start(); mOtherHandler = new Handler(handlerThread.getLooper()){ @Override public void handleMessage(Message msg){ if (msg.what == 0x124){ try { Log.d("HandlerThread", Thread.currentThread().getName()); Thread.sleep(5000);//模擬耗時(shí)任務(wù)  } catch (InterruptedException e) { e.printStackTrace(); } } } }; 

這段代碼跟前面那一段代碼是完全等價(jià)的,HandlerThread的好處是代碼看起來(lái)沒(méi)前面的版本那么亂,相對(duì)簡(jiǎn)潔一點(diǎn)。還有一個(gè)好處就是通過(guò)handlerThread.quit()或者quitSafely()使線程結(jié)束自己的生命周期。
可能有人問(wèn)了,那用以上方式執(zhí)行完耗時(shí)任務(wù)后怎么更新UI組件了,很簡(jiǎn)單,完全照著面前所說(shuō)的將任務(wù)從工作線程拋到主線程的五種方法去做就可以了。
可能又有人問(wèn)了,那mOtherHandler.post(new Runnable())里的Runnable在哪個(gè)線程運(yùn)行,還是工作線程,只不過(guò)這樣就避開(kāi)了handlerMessage的步驟而已,跟前面的分析還是一樣的原理的。

3、AsyncTask

沒(méi)錯(cuò),又是它。具體的使用代碼就不貼上來(lái)了,到處都有。但值得一說(shuō)的是,上面說(shuō)過(guò)HandlerThread只開(kāi)一條線程,任務(wù)都被阻塞在一個(gè)隊(duì)列中,那么就會(huì)使阻塞的任務(wù)延遲了,而AsyncTask開(kāi)啟線程的方法asyncTask.execute()默認(rèn)是也是開(kāi)啟一個(gè)線程和一個(gè)隊(duì)列的,不過(guò)也可以通過(guò)asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 0)開(kāi)啟一個(gè)含有5個(gè)新線程的線程池,也就是說(shuō)有個(gè)5個(gè)隊(duì)列了,假如說(shuō)你執(zhí)行第6個(gè)耗時(shí)任務(wù)時(shí),除非前面5個(gè)都還沒(méi)執(zhí)行完,否則任務(wù)是不會(huì)阻塞的,這樣就可以大大減少耗時(shí)任務(wù)延遲的可能性,這也是它的優(yōu)點(diǎn)所在,當(dāng)你想多個(gè)耗時(shí)任務(wù)并發(fā)的執(zhí)行,那你更應(yīng)該選擇AsyncTask。

4、IntentService

最后再小小地提一下IntentService,相信很多人也不陌生,它是Service的子類(lèi),用法跟Service也差不多,就是實(shí)現(xiàn)的方法名字不一樣,耗時(shí)邏輯應(yīng)放在onHandleIntent(Intent intent)的方法體里,它同樣有著退出啟動(dòng)它的Activity后不會(huì)被系統(tǒng)殺死的特點(diǎn),而且當(dāng)任務(wù)執(zhí)行完后會(huì)自動(dòng)停止,無(wú)須手動(dòng)去終止它。例如在APP里我們要實(shí)現(xiàn)一個(gè)下載功能,當(dāng)退出頁(yè)面后下載不會(huì)被中斷,那么這時(shí)候IntentService就是一個(gè)不錯(cuò)的選擇了。

筆者寫(xiě)技術(shù)文章經(jīng)驗(yàn)很少,如有紕漏錯(cuò)誤,歡迎指正交流!
原文地址訪問(wèn):Android多線程編程的總結(jié)

標(biāo)簽: 代碼

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:Python性能優(yōu)化的建議

下一篇:redis 常用命令