Android中線程那些事兒

如何理解線程

在操作系統(tǒng)中如捅,線程是操作系統(tǒng)調(diào)度的最小單元,同時線程又是一種受限的系統(tǒng)資源样傍,即線程不可能無限制的產(chǎn)生邮丰,并且線程的創(chuàng)建和銷毀都會有相應(yīng)的開銷,當(dāng)系統(tǒng)中存在大量的線程時铭乾,系統(tǒng)會通過時間片輪轉(zhuǎn)的方式調(diào)度每個線程剪廉,在這么多線程中有一個被稱為主線程,主線程是指進(jìn)程所擁有的線程炕檩,在JAVA中默認(rèn)情況下一個進(jìn)程只有一個線程斗蒋,這個線程就是主線程。主線程主要處理界面交互相關(guān)的邏輯笛质,因?yàn)橛脩綦S時會和界面發(fā)生交互泉沾,因此主線程在任何時候都必須有比較高的響應(yīng)速度,否則就會產(chǎn)生一種界面卡頓的感覺妇押。為了保持較高的響應(yīng)速度跷究,這就要求主線程中不能執(zhí)行耗時的任務(wù),這個時候子線程就派上用場了敲霍。子線程也叫工作線程俊马,除了主線程以外的線程都是子線程。

Android中的線程

Android沿用了JAVA的線程模型肩杈,其中的線程也分為主線程和子線程柴我,其中主線程又叫UI線程。在Android系統(tǒng)中扩然,在默認(rèn)情況下艘儒,一個應(yīng)用程序內(nèi)的各個組件(如Activity、BroadcastReceiver、Service)都會在同一個進(jìn)程(Process)里執(zhí)行界睁,且由此進(jìn)程的主線程負(fù)責(zé)執(zhí)行觉增。如果有特別指定(通過android:process),也可以讓特定組件在不同的進(jìn)程中運(yùn)行翻斟。無論組件在哪一個進(jìn)程中運(yùn)行抑片,默認(rèn)情況下,他們都由此進(jìn)程的主線程負(fù)責(zé)執(zhí)行杨赤。主線程既要處理Activity組件的UI事件敞斋,又要處理Service后臺服務(wù)工作,通常會忙不過來疾牲。為了解決此問題植捎,主線程可以創(chuàng)建多個子線程來處理后臺服務(wù)工作,而本身專心處理UI畫面的事件阳柔。子線程的任務(wù)則是執(zhí)行耗時任務(wù)焰枢,比如網(wǎng)絡(luò)請求,I/O操作等舌剂。從Android4.0開始系統(tǒng)要求網(wǎng)絡(luò)訪問必須在子線程中進(jìn)行济锄,否則網(wǎng)絡(luò)訪問將會失敗并拋出NetWorkOnMainThreadException這個異常,這樣做是為了避免主線程由于被耗時操作阻塞從而出現(xiàn)ANR現(xiàn)象霍转。

為什么會出現(xiàn)ANR

Android希望UI線程能根據(jù)用戶的要求做出快速響應(yīng)荐绝,如果UI線程花太多時間處理后臺的工作,當(dāng)UI事件發(fā)生時避消,讓用戶等待時間超過5秒而未處理低滩,Android系統(tǒng)就會給用戶顯示ANR提示信息。主線程除了處理UI事件之外岩喷,還要處理Broadcast消息恕沫。所以在BroadcastReceiver的onReceive()函數(shù)中,不宜占用太長的時間纱意,否則導(dǎo)致主線程無法處理其它的Broadcast消息或UI事件婶溯。如果占用時間超過10秒,Android系統(tǒng)就會給用戶顯示ANR提示信息偷霉。解決辦法自然還是解放UI主線程迄委,將耗時操作交給子線程,避免阻塞腾它。

Android中也有main()方法

剛接觸Android的開發(fā)者可能會因?yàn)檎也坏絁ava程序的執(zhí)行入口main()方法而覺得疑惑跑筝,其實(shí)Android中當(dāng)然是也有main()方法的(如下)死讹,它被包裝在源碼中的ActivityThread類里瞒滴。ActivityThread為應(yīng)用程序的主線程類,所有的Apk程序都有且僅有一個ActivityThread類,程序的入口為該類中的static main()方法妓忍,ActivityThread所在的線程即為UI線程或主線程虏两。Activity從main()方法開始執(zhí)行,調(diào)用prepareMain()為UI線程創(chuàng)建一個消息隊(duì)列(MessageQueue)世剖。然后創(chuàng)建一個ActivityThread對象定罢,在ActivityThread的初始化代碼中會創(chuàng)建一個H(Handler)對象和一個ApplicationThread(Binder)對象。其中Binder負(fù)責(zé)接收遠(yuǎn)程AmS的IPC調(diào)用旁瘫,接收到調(diào)用后祖凫,則通過Hander把消息發(fā)送到消息隊(duì)列,UI主線程會異步地從消息隊(duì)列中取出消息并執(zhí)行相應(yīng)操作酬凳,比如start,pause,stop等惠况。接著UI主線程調(diào)用Looper.loop()方法進(jìn)入消息循環(huán)體,進(jìn)入后就會不斷地從消息隊(duì)列中讀取并處理消息宁仔。

    public static final void main(String[] args) {
        SamplingProfilerIntegration.start();

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();

        if (Process.supportsProcesses()) {
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }

        thread.detach();
        String name = (thread.mInitialApplication != null)
            ? thread.mInitialApplication.getPackageName()
            : "<unknown>";
        Slog.i(TAG, "Main thread of " + name + " is now exiting");
    }
}

Android中的子線程

Android中開啟一個子線程無非還是這兩種方法

1:繼承Thread類

public class MyThread extends Thread {  
      
    public void run(){  
      
    }  
}

newMyThread().start(); 

2:實(shí)現(xiàn)Runnable接口

public class MyRunnable implements Runnable{

@Override
public void run(){
//TODOAuto-generatedmethodstub
}
}

new MyThread().start(); 

Android APK程序中都有哪些線程稠屠?
通過debug,我們可以捕獲當(dāng)前應(yīng)用程序中的線程(如下圖)翎苫,其中藍(lán)色選中部分即為當(dāng)前應(yīng)用程序的主線程权埠,當(dāng)前程序中還運(yùn)行了三個Binder,每個Binder對象都對應(yīng)一個線程煎谍,這些Binder線程主要負(fù)責(zé)接收Linux Binder驅(qū)動發(fā)送的IPC調(diào)用攘蔽。除此以外還有Java中的守護(hù)線程和垃圾回收線程堆裁剪守護(hù)進(jìn)程等在運(yùn)行。

Paste_Image.png

程序中自定義Thread和UI線程的區(qū)別是什么呐粘?

自定義Thread和UI線程的區(qū)別在于秩彤,UI線程是從ActivityThread運(yùn)行的,在該類中的main()方法中事哭,已經(jīng)使用Looper.prepareMainLooper()為該線程添加了Looper對象漫雷,即已經(jīng)為該線程創(chuàng)建了消息隊(duì)列(MessageQueue),因此鳍咱,程序員才可以在Activity中定義Hander對象(因?yàn)槁暶鱄ander對象時降盹,所在的線程必須已經(jīng)創(chuàng)建了MessageQueue)。而普通的自定義Thread是一個裸線程谤辜,因此蓄坏,不能直接在Thread中定義Hander對象,從使用場景的角度講丑念,即不能直接給Thread對象發(fā)消息涡戳,但卻可以給UI線程發(fā)消息。

子線程為什么不能更新UI

因?yàn)閁I訪問是沒有加鎖的脯倚,在多個線程中訪問UI是不安全的渔彰,如果有多個子線程都去更新UI嵌屎,會導(dǎo)致界面不斷改變而混亂不堪。所以最好的解決辦法就是只有一個線程有更新UI的權(quán)限恍涂,所以這個時候就只能有一個線程振臂高呼:放開那女孩宝惰,讓我來!那么最合適的人選只能是主線程再沧。

子線程也可以更新UI

SurfaceView是 android 里唯一一個可以在子線程更新的控件尼夺。SurfaceView可以在主線程之外的線程中向屏幕繪圖。這樣可以避免畫圖任務(wù)繁重的時候造成主線程阻塞炒瘸,從而提高了程序的反應(yīng)速度淤堵。當(dāng)需要快速,主動地更新View的UI,或者當(dāng)前渲染代碼阻塞GUI線程的時間過長的時候顷扩,SurfaceView就是解決上述問題的最佳選擇粘勒。

子線程可以更新除SurfaceView以外的UI

子線程更新UI?沒錯屎即,不信下面的代碼跑一遍試試庙睡,并不會報錯,而且正確顯示技俐。

public class MainActivity extends AppCompatActivity {

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView=(TextView)findViewById(R.id.textView);

        new Thread(new Runnable() {
            @Override
            public void run() {
                mTextView.setText("Child Thread");
            }
        }).start();
    }
}

這是為什么呢乘陪?一個應(yīng)用程序中有一個主線程和若干個子線程,而線程的檢查工作是由ViewRoot完成的雕擂。ViewRoot是什么呢啡邑?可以簡單的理解為Window和View之前的橋梁或者紐帶。而ViewRoot的創(chuàng)建是在onResume()之后才完成的井赌,也就是說在onResume()之前谤逼,系統(tǒng)本身是無法區(qū)分當(dāng)前線程到底是主線程還是子線程,而上面的代碼中UI的更新操作在onCreate()中完成仇穗,先于onResume()流部,所以上述的子線程才有機(jī)會越俎代庖。

子線程如何與主線程通信

1纹坐、Activity.runOnUiThread(Runnable)

mHandle.setOnClickListener(new OnClickListener() { 
             
            @Override 
            public void onClick(View v) { 
                new Thread(new Runnable() { 
                     
                    @Override 
                    public void run() { 
                                     // 耗時操作            
                                     loadNetWork();   
                        MainActivity.this.runOnUiThread(new Runnable() {                              
                        @Override 
                            public void run() { 
                                mTextView.setText(來自網(wǎng)絡(luò)的文字);             
                            } 
                        }); 
                         
                    } 
                }); 
                 
            } 
        }); 

2枝冀、 View.post(Runnable)

mHandle.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    
                    @Override
                    public void run() {
                              // 耗時操作           
                              loadNetWork();      
                        mTextView.post(new Runnable() {
                            
                            @Override
                            public void run() {
                                mTextView.setText(來自網(wǎng)絡(luò)的文字);    
                            }
                        });
                        
                    }
                });
                    
                
                
            }
        });

3、View.postDelayed(Runnable,long)

mHandle.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    
                    @Override
                    public void run() {
                                // 耗時操作           
                                loadNetWork();  
                        mTextView.postDelayed(new Runnable() {
                            
                            @Override
                            public void run() {
                                mTextView.setText(來自網(wǎng)絡(luò)的文字);                                
                            }
                        }, 10);
                        
                    }
                });
                
                
            }
        });

4耘子、Handler(子線程調(diào)用Handler的
handle.sendMessage(msg);

Handler handle = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mTextView.setText(來自網(wǎng)絡(luò)的文字);
        }
        
    };

class MyThread extends Thread {

        @Override
        public void run() {
             // 耗時操作           
            loadNetWork();  

            Message msg = new Message();
            handle.sendMessage(msg);
            super.run();
        }
        
        
    }

5果漾、AsyncTask

主線程調(diào)用:

aTask ak = new aTask();
ak.execute();

AsyncTask

private class aTask extends AsyncTask { 

    //后臺線程執(zhí)行時 
    @Override 
    protected Object doInBackground(Object... params) { 
        // 耗時操作             
        return loadNetWork(); 
    } 
    //后臺線程執(zhí)行結(jié)束后的操作,其中參數(shù)result為doInBackground返回的結(jié)果 
    @Override 
    protected void onPostExecute(Object result) { 
        super.onPostExecute(result); 
        mTextView.setText(result); 
    } 
    } 

總結(jié)

最后來個總結(jié)谷誓,Android中的線程延續(xù)了JAVA的設(shè)計(jì)模型绒障,默認(rèn)一個應(yīng)用程序只有一個主線程,主線程的開啟是在Activity的main()方法捍歪。主線程實(shí)際上是一個死循環(huán)户辱,不斷的循環(huán)處理系統(tǒng)以及其他子線程發(fā)來的消息鸵钝。主線程的綁定是在DecorView初始化的時候,也就是生命周期的onResume()之后焕妙。主線程主要處理UI操作蒋伦,和Broadcast相關(guān)消息弓摘,主線程如果長時間無法響應(yīng)焚鹊,將出現(xiàn)ANR,為了避免ANR韧献,耗時操作一般都開啟子線程處理末患。子線程處理完再發(fā)消息通知主線程來改變UI。

本文首發(fā):CSDN锤窑,次發(fā):簡書
相關(guān)閱讀:
我是一個線程
線程璧针、多線程與線程池總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市渊啰,隨后出現(xiàn)的幾起案子探橱,更是在濱河造成了極大的恐慌,老刑警劉巖绘证,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隧膏,死亡現(xiàn)場離奇詭異,居然都是意外死亡嚷那,警方通過查閱死者的電腦和手機(jī)胞枕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來魏宽,“玉大人腐泻,你說我怎么就攤上這事《友” “怎么了派桩?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蚌斩。 經(jīng)常有香客問我窄坦,道長,這世上最難降的妖魔是什么凳寺? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任鸭津,我火速辦了婚禮,結(jié)果婚禮上肠缨,老公的妹妹穿的比我還像新娘逆趋。我一直安慰自己,他們只是感情好晒奕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布闻书。 她就那樣靜靜地躺著名斟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪魄眉。 梳的紋絲不亂的頭發(fā)上砰盐,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天,我揣著相機(jī)與錄音坑律,去河邊找鬼岩梳。 笑死,一個胖子當(dāng)著我的面吹牛晃择,可吹牛的內(nèi)容都是我干的冀值。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼宫屠,長吁一口氣:“原來是場噩夢啊……” “哼列疗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起浪蹂,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抵栈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后坤次,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體古劲,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年浙踢,在試婚紗的時候發(fā)現(xiàn)自己被綠了绢慢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡洛波,死狀恐怖胰舆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蹬挤,我是刑警寧澤缚窿,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站焰扳,受9級特大地震影響倦零,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吨悍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一扫茅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧育瓜,春花似錦葫隙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腺办。三九已至,卻和暖如春糟描,著一層夾襖步出監(jiān)牢的瞬間怀喉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工船响, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留躬拢,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓灿意,卻偏偏與公主長得像估灿,于是被迫代替她去往敵國和親崇呵。 傳聞我的和親對象是個殘疾皇子缤剧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評論 2 354

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