Android 進程和線程(二)

來源:互聯網
上載者:User

線程

應用程式啟動時,Android系統會給應用程式建立一個叫做“main”的執行線程。這個線程很重要,因為它負責給適當的使用者介面視窗調度事件,包括描畫事件。它也是你的應用程式與Android UI工具中的組件互動的線程(這些組件來自android.widget和android.view包)。如,“main”線程有時也叫UI線程。

系統不會給每個組件的執行個體建立一個單獨的線程。運行在同一個進程的所有的組件都被執行個體化在UI線程,並且系統會調用每個由線程調度的組件。因此響應系統回調的方法(如報告使用者動作的onKeyDown()方法或一個生命週期的回調方法)始終運行在這個進程的UI線程中。

例如,當使用者觸控螢幕幕上的一個按鈕時,你的應用程式的UI線程會調度對應視窗的touch事件,它會依次設定按鈕的按下狀態,並且把一個有效請求發送給這個事件隊列。UI線程會讓請求出隊並通知對應的視窗應該重繪自己。

當你的應用程式要在響應使用者的互動中執行密集的工作時,除非你正確的實現了應用程式,否則這種單線程模式會降低執行效率。尤其是把每件正在發生的事情都放在UI線程中,如執行像網路訪問、資料庫查詢這樣的長時操作將會使整個UI被阻塞。線程被阻塞時,任何事件都不能被調度,包括描畫事件。從使用者的角度,應用程式似乎被掛起了。甚至更糟,如果UI線程被阻塞幾秒鐘(大約是5秒鐘),使用者就會看到臭名昭著的“應用程式沒有響應”(ANR)對話方塊。那麼使用者就可能決定退出你的應用程式,並且如果他們感覺不好,還會卸載這個應用程式。

另外,Android UI 工具集不是安全執行緒的。因此你不能由一個背景工作執行緒來控制你的UI---你必須用UI線程來對你使用者介面進行所有的控制。這樣對於Android單線程模式,有以下兩個簡單的規則:

1. 不要阻塞UI線程;

2. 不要從UI線程的外部存取Android UI工具集。

背景工作執行緒

因為以上介紹的單線程模式的緣故,你的應用程式UI的響應不阻塞UI線程是至關重要的。如果你執行的不是瞬時操作,就應該用一個單獨的線程(後台或背景工作執行緒)來工作。

例如,一下click監聽器的代碼就是從一個單獨的線程中下載圖片,並在一個ImageView對象中顯示它:

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

初看以上這段代碼,似乎能夠很好的工作,因為它為處理網路操作建立了一個新的線程。但是,它違反了單線程模式的第二個規則:不從外部UI線程訪問Android UI工具集---這個樣本在背景工作執行緒中修改了ImageView對象而不是在UI線程中。這樣會導致未知的和不確定的異常行為,這對於尋找問題是困難和費時的。

要修正這個問題,Android提供了以下幾個方法用於從其他線程訪問UI線程:

1. Activity.runOnUiThread(Runnable )

2. View.post(Runnable )

3. 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();
}

現在的這個實現是安全執行緒的,因為網路操作是在一個單獨的線程中完成的,而對ImageView對象的操作則在UI線程中完成。

但是操作的複雜性增加了,這類型的代碼使代碼的維護變的複雜和困難了。要處理跟背景工作執行緒更複雜的互動,你可能考慮在你的背景工作執行緒中使用Handler對象,來處理由UI線程發送的訊息。繼承AsyncTask類可能是最好的解決方案,它簡化了背景工作執行緒執行需要跟UI互動的任務。

使用AnsyncTask類

AsyncTask類允許你在使用者介面上執行非同步工作。它在背景工作執行緒中執行一個阻塞操作,然後在UI線程上發布執行結果,不要求你自己來處理線程和/或處理器。

要使用這個類,必須繼承AsyncTask類並且實現doInBackground()回調方法,它在背景線程池中運行。要更新UI,你應該實現onPostExecute()方法,它提供了來自doInBackground()方法的結果,並且在UI線程中運行,因此你能安全的更新你的UI。然後你能夠通過execute()方法從UI線程中運行任務。

例如,你能夠使用AsyncTask類的方法來實現前面的例子:

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

private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
    /** The system calls this to perform work in a worker thread and
      * delivers it the parameters given to AsyncTask.execute() */
    protected Bitmap doInBackground(String... urls) {
        return loadImageFromNetwork(urls[0]);
    }
   
    /** The system calls this to perform work in the UI thread and delivers
      * the result from doInBackground() */
    protected void onPostExecute(Bitmap result) {
        mImageView.setImageBitmap(result);
    }
}

現在的UI是安全的並且代碼也簡化了,因為把它把工作分為在背景工作執行緒中執行的部分和在UI線程中執行的部分。

要完全理解這個類應該讀AsyncTask參考,但是以下可以快速探索一下這個類的工作方式:

1. 你能夠指定常用類型的參數,如進度值、任務結束的值;

2. doInBackground()方法在背景工作執行緒上自動的執行;

3. onPreExecute()、onPostExecute()、和onProgressUpdate()方法都在UI線程上調用;

4. 由doInBackground()方法傳回值被發送給onPostExecute()方法

5. 你能夠在任何時候調用doInBackground()方法中的publishProgress()方法在UI線程上執行onProgressUpdate()方法;

6. 你能夠在任何時候取消來自任何線程的任務。

警告:當你使用背景工作執行緒的Activity由於運行時配置的改變而導致異常重啟(如使用者改變了螢幕的方向)時,你可能遇到另外一個問題:可能銷毀你的背景工作執行緒。要瞭解在Activity重啟期間如何保持你的背景工作和在Activity被銷毀時如何正確的取消任務等資訊,請看Shelves應用程式範例的原始碼。(http://code.google.com/p/shelves/)

安全執行緒的方法

在某些情況中,你實現的方法可能可能被多個線程調用,因此,必須要寫安全執行緒的方法。

安全執行緒主要是針對能夠被遠程調用的方法---如綁定類型Service中的方法。當IBinder實作類別中的一個方法調用源於與IBinder對象運行那個進程時,這個方法就會在調用者的線程中執行。但是,當調用源於另一個進程時,就會從系統維護的與IBinder對象相同的進程那個線程池中選擇執行這個方法的線程(不會在進程的UI線程中執行)。如,一個Service的onBind()方法將會被這個Service進程的主線程調用,由onBind()方法返回的對象中實現的方法(如實現RPC方法的一個子類)將會從線程池中調用。因為一個服務能夠有多個用戶端,多個線程能夠同時調用同一個IBinder對象方法,因此IBinder方法必須實現安全執行緒。

類似地,一個內容提供器能夠接受源自其他進程的資料申請。儘管ContentResolver類和ContentProvider類隱藏了如何管理進程處理序間通訊的細節,但是響應那些請求的ContentProvider方法(query()、insert()、delete()、update()和getType())是從內容提供器進程中的線程池調用的,而不是進程的UI線程。因為這些方法可能同時被多個線程調用,因此它們也必須實現安全執行緒。

處理序間通訊

Android使用遠端程序呼叫(RPCs)給處理序間通訊(IPC)提供了一種機制,在這種機制中一個被Activity或其他應用程式組件調用的方法,不是在本地執行,而是在遠程(其他進程)執行,它會給調用者返回一個結果。這就必需把一個方法分解為作業系統層面上能夠理解的調用和資料,並且把它從本地進程和地址空間中傳遞給遠程進程和它的地址空間,然後再在調用的地方重新組裝和執行。然後把傳回值反向傳輸回來。Android提供了所有的執行這些IPC傳輸的代碼,因此你能夠關注RPC編程介面的定義和實現。

 要執行IPC,你的應用程式必須使用bindService()方法綁定一個Sevice。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.