1. 程式人生 > >Android中的線程

Android中的線程

開發者 又是 ace appcompat 技術 ted unknown 消息通知 設計模型

本文轉自:http://www.jianshu.com/p/d59b3cce2b54

如何理解線程

在操作系統中,線程是操作系統調度的最小單元,同時線程又是一種受限的系統資源,即線程不可能無限制的產生,並且線程的創建和銷毀都會有相應的開銷,當系統中存在大量的線程時,系統會通過時間片輪轉的方式調度每個線程,在這麽多線程中有一個被稱為主線程,主線程是指進程所擁有的線程,在JAVA中默認情況下一個進程只有一個線程,這個線程就是主線程。主線程主要處理界面交互相關的邏輯,因為用戶隨時會和界面發生交互,因此主線程在任何時候都必須有比較高的響應速度,否則就會產生一種界面卡頓的感覺。為了保持較高的響應速度,這就要求主線程中不能執行耗時的任務,這個時候子線程就派上用場了。子線程也叫工作線程,除了主線程以外的線程都是子線程。

Android中的線程

Android沿用了JAVA的線程模型,其中的線程也分為主線程和子線程,其中主線程又叫UI線程。在Android系統中,在默認情況下,一個應用程序內的各個組件(如Activity、BroadcastReceiver、Service)都會在同一個進程(Process)裏執行,且由此進程的主線程負責執行。如果有特別指定(通過android:process),也可以讓特定組件在不同的進程中運行。無論組件在哪一個進程中運行,默認情況下,他們都由此進程的主線程負責執行。主線程既要處理Activity組件的UI事件,又要處理Service後臺服務工作,通常會忙不過來。為了解決此問題,主線程可以創建多個子線程來處理後臺服務工作,而本身專心處理UI畫面的事件。子線程的任務則是執行耗時任務,比如網絡請求,I/O操作等。從Android4.0開始系統要求網絡訪問必須在子線程中進行,否則網絡訪問將會失敗並拋出NetWorkOnMainThreadException這個異常,這樣做是為了避免主線程由於被耗時操作阻塞從而出現ANR現象。

為什麽會出現ANR

Android希望UI線程能根據用戶的要求做出快速響應,如果UI線程花太多時間處理後臺的工作,當UI事件發生時,讓用戶等待時間超過5秒而未處理,Android系統就會給用戶顯示ANR提示信息。主線程除了處理UI事件之外,還要處理Broadcast消息。所以在BroadcastReceiver的onReceive()函數中,不宜占用太長的時間,否則導致主線程無法處理其它的Broadcast消息或UI事件。如果占用時間超過10秒,Android系統就會給用戶顯示ANR提示信息。解決辦法自然還是解放UI主線程,將耗時操作交給子線程,避免阻塞。

Android中也有main()方法

剛接觸Android的開發者可能會因為找不到Java程序的執行入口main()方法而覺得疑惑,其實Android中當然是也有main()方法的(如下),它被包裝在源碼中的ActivityThread類裏。ActivityThread為應用程序的主線程類,所有的Apk程序都有且僅有一個ActivityThread類,程序的入口為該類中的static main()方法,ActivityThread所在的線程即為UI線程或主線程。Activity從main()方法開始執行,調用prepareMain()為UI線程創建一個消息隊列(MessageQueue)。然後創建一個ActivityThread對象,在ActivityThread的初始化代碼中會創建一個H(Handler)對象和一個ApplicationThread(Binder)對象。其中Binder負責接收遠程AmS的IPC調用,接收到調用後,則通過Hander把消息發送到消息隊列,UI主線程會異步地從消息隊列中取出消息並執行相應操作,比如start,pause,stop等。接著UI主線程調用Looper.loop()方法進入消息循環體,進入後就會不斷地從消息隊列中讀取並處理消息。

    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:實現Runnable接口

public class MyRunnable implements Runnable{

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

new MyThread().start();

Android APK程序中都有哪些線程?
通過debug,我們可以捕獲當前應用程序中的線程(如下圖),其中藍色選中部分即為當前應用程序的主線程,當前程序中還運行了三個Binder,每個Binder對象都對應一個線程,這些Binder線程主要負責接收Linux Binder驅動發送的IPC調用。除此以外還有Java中的守護線程和垃圾回收線程堆裁剪守護進程等在運行。

技術分享
Paste_Image.png

程序中自定義Thread和UI線程的區別是什麽?

自定義Thread和UI線程的區別在於,UI線程是從ActivityThread運行的,在該類中的main()方法中,已經使用Looper.prepareMainLooper()為該線程添加了Looper對象,即已經為該線程創建了消息隊列(MessageQueue),因此,程序員才可以在Activity中定義Hander對象(因為聲明Hander對象時,所在的線程必須已經創建了MessageQueue)。而普通的自定義Thread是一個裸線程,因此,不能直接在Thread中定義Hander對象,從使用場景的角度講,即不能直接給Thread對象發消息,但卻可以給UI線程發消息。

子線程為什麽不能更新UI

因為UI訪問是沒有加鎖的,在多個線程中訪問UI是不安全的,如果有多個子線程都去更新UI,會導致界面不斷改變而混亂不堪。所以最好的解決辦法就是只有一個線程有更新UI的權限,所以這個時候就只能有一個線程振臂高呼:放開那女孩,讓我來!那麽最合適的人選只能是主線程。

子線程也可以更新UI

SurfaceView是 android 裏唯一一個可以在子線程更新的控件。SurfaceView可以在主線程之外的線程中向屏幕繪圖。這樣可以避免畫圖任務繁重的時候造成主線程阻塞,從而提高了程序的反應速度。當需要快速,主動地更新View的UI,或者當前渲染代碼阻塞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();
    }
}

這是為什麽呢?一個應用程序中有一個主線程和若幹個子線程,而線程的檢查工作是由ViewRoot完成的。ViewRoot是什麽呢?可以簡單的理解為Window和View之前的橋梁或者紐帶。而ViewRoot的創建是在onResume()之後才完成的,也就是說在onResume()之前,系統本身是無法區分當前線程到底是主線程還是子線程,而上面的代碼中UI的更新操作在onCreate()中完成,先於onResume(),所以上述的子線程才有機會越俎代庖。

子線程如何與主線程通信

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(來自網絡的文字);             
                            } 
                        }); 

                    } 
                }); 

            } 
        });

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(來自網絡的文字);    
                            }
                        });

                    }
                });



            }
        });

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(來自網絡的文字);                                
                            }
                        }, 10);

                    }
                });


            }
        });

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

Handler handle = new Handler() {

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

    };

class MyThread extends Thread {

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

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


    }

5、AsyncTask

主線程調用:

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

AsyncTask

private class aTask extends AsyncTask { 

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

總結

最後來個總結,Android中的線程延續了JAVA的設計模型,默認一個應用程序只有一個主線程,主線程的開啟是在Activity的main()方法。主線程實際上是一個死循環,不斷的循環處理系統以及其他子線程發來的消息。主線程的綁定是在DecorView初始化的時候,也就是生命周期的onResume()之後。主線程主要處理UI操作,和Broadcast相關消息,主線程如果長時間無法響應,將出現ANR,為了避免ANR,耗時操作一般都開啟子線程處理。子線程處理完再發消息通知主線程來改變UI。

Android中的線程