官术网_书友最值得收藏!

3.6 進程和線程

當一個應用組件啟動,并且該應用沒有別的正在運行的組件,則Android系統(tǒng)會為這個應用程序創(chuàng)建一個包含單個線程的linux進程。默認情況下,同一個應用程序的所有組件都運行在同一個進程與線程中(叫作“main”主線程)。某個應用組件啟動,如果該應用程序的進程已經存在(因為應用程序的其他組件已經在運行了),那么剛剛啟動的組件會在已有的進程和線程中啟動運行。不過,可以指定組件運行在其他進程中,也可以為任何進程創(chuàng)建其他的線程。

本文主要討論進程和線程是如何在Android應用程序中發(fā)揮作用的。

3.6.1 進程(Processes)

默認情況下,同一個應用程序內的所有組件都是運行在同一個進程中的,大部分應用程序也不會去改變它。不過,如果需要指定某個特定組件所屬的進程,則可以利用manifest文件來達到目的。

manifest文件中的每種組件元素(<activity>、<service>、<receiver>和<provider>)都支持android:process屬性,用于指定組件所屬運行的進程。設置此屬性即可實現(xiàn)每個組件在各自的進程中運行,或者某幾個組件共享一個進程而其它組件不可以參與。設置此屬性也可以讓來自于不同應用程序的組件運行在同一個進程中,實現(xiàn)多個應用程序共享同一個Linux user ID,相同的簽名認證。

<application>元素也支持android:process屬性,用于指定所有組件的默認值。

如果內存不足,且又有其他為用戶提供更緊急服務的進程需要更多內存時,Android可能會決定關閉掉一個進程。在此進程中運行著的應用程序組件也會因此被銷毀。當需要再次工作時,會為這些組件重新創(chuàng)建一個進程。

在決定關閉哪個進程的時候,Android系統(tǒng)會權衡它們相對用戶的重要程度。比如,相對于一個擁有可視activity的進程,更有可能去關閉一個持有一組不再對用戶可見的Activity的進程。也就是說,是否終止一個進程,取決于運行在此進程中組件的狀態(tài)。終止進程的判定規(guī)則將在后續(xù)內容中討論。(注:一個進程的關閉級別,按照該進程中最高的級別來定義。如該進程中有activity和service,那么該進程的級別為service。)

Android系統(tǒng)試圖盡可能長時間地保持應用程序進程,但為了新建的或者更為重要的進程,總是需要清除掉舊的進程以回收內存。為了決定保留或終止哪個進程,根據(jù)進程內運行的組件及這些組件的狀態(tài),系統(tǒng)把每個進程都劃入一個“importance hierarchy”中。重要性最低的進程首先會被清除,然后是其次低的進程,依此類推,這都是回收系統(tǒng)資源所必需的。

“importance hierarchy”共有5級,以下列表按照重要程度列出了各類進程(第一類進程是最重要的,將最后一個被終止):

(1)前臺進程(Foreground process)

用戶正在請求的進程。當以下任何一個條件成立時,該進程被認為是前臺進程:


●持有一個用戶正在與之交互的Activity(Activity對象的onResume()方法已被調用)。

●持有一個服務Service,且該服務被綁定到一個正在與用戶交互的Activity上了。

●持有一個服務,且該服務在前臺運行,即該服務startForground()調用。

●持有一個服務,且該服務正在執(zhí)行其生命周期的回調方法(onCreate(),onStart(),onDestroy())。

●持有一個BroadcastReceiver,且其正在執(zhí)行onRecevie()方法。


通常,在一個給定的時間內,只有很少的前臺進程存在。當系統(tǒng)內存較小,以至于他們不能全部繼續(xù)運行時,他們會依序被清除。通常,這時設備已經到了內存分頁狀態(tài)(memory paging state),清除那些前臺進程以確保用戶響應。

(2)可視進程(Visible process)

一個可視進程是沒有前臺組件,但仍會影響用戶在屏幕上所見內容的進程。當以下任何一個條件成立時,該進程被認為是可視進程:


●持有一個Activity,且該Activity沒有處于前臺,但是對于用戶而言他仍然可見(onPause()方法被調用)。這是可能發(fā)生的,例如,一個前臺activity啟動了一個對話框,而之前的activity還允許顯示在后面。

●持有一個服務Service,且該服務被綁定到一個可視(或一個前臺)activity。


一個可視進程是極其重要的,除非無法維持所有前臺進程同時運行了,它們是不會被終止的。

(3)服務進程(Service process)

此進程運行著由startService()方法啟動的服務,它不會升級為上述兩級別。盡管服務進程不直接和用戶所見內容關聯(lián),但他們通常在執(zhí)行一些用戶關心的操作(比如在后臺播放音樂或從網絡下載數(shù)據(jù))。因此,除非內存不足以維持所有前臺、可視進程同時運行,系統(tǒng)會保持服務進程的運行。

(4)后臺進程(Background process)

一個后臺進程持有一個對用戶不可見的Activity(Activity對象的onStop()方法已被調用)。這些進程對用戶體驗沒有直接的影響,系統(tǒng)可能在任意時間終止它們,以回收內存供前臺進程、可視進程及服務進程使用。通常會有許多后臺進程運行,所以它們被保存在一個LRU(least recently used列表中,以確保最近被用戶使用的activity最后一個被終止。如果一個activity正確實現(xiàn)了生命周期方法,并保存了當前的狀態(tài),則終止此類進程不會對用戶體驗產生顯著的影響。因為當用戶回到這個Activity,這個Activity會恢復他的所有的可視的狀態(tài)。關于保存和恢復狀態(tài)的詳細信息,請參閱Activities文檔。

(5)空進程(Empty process)

空進程不含任何活動應用程序組件。保留這種進程的唯一目的就是用作緩存,以改善下次在此進程中運行組件的啟動時間。為了在進程緩存和內核緩存間平衡系統(tǒng)整體資源,系統(tǒng)經常會終止這種進程。

依據(jù)進程中目前活躍組件的重要程度,Android會給進程評估一個盡可能高的等級。例如,一個進程擁有一個服務和一個用戶可見的activity,則此進程會被評定為可視進程,而不是服務進程。

此外,一個進程的等級可能會由于其他進程的依賴而被提高,一個服務于另一個進程的進程永遠不能比另一個進程的等級低。比如,進程A中的content provider為進程B中的客戶端提供服務,或進程A中的服務被進程B中的組件所調用,則進程A被認為其重要等級不低于進程B。

因為運行服務的進程級別高于后臺activity進程的等級,所以,如果activity需要啟動一個長時間運行的操作,則為其啟動一個服務service會比簡單地創(chuàng)建一個工作線程更好些,尤其是在此操作時間比activity本身存在時間還要長久的情況下。比如,一個activity要把圖片上傳至Web網站,就應該創(chuàng)建一個服務來執(zhí)行,即使用戶離開了此activity,上傳還是會在后臺繼續(xù)運行。無論activity發(fā)生什么情況,使用服務可以保證操作至少擁有服務進程(service process)的優(yōu)先級。同理,上一篇中的廣播接收器broadcast receiver也是使用服務而非簡單的啟用一個線程。

3.6.2 線程(Threads)

應用程序啟動時,系統(tǒng)會為它創(chuàng)建一個名為“main”的主線程。主線程非常重要,因為它負責分配事件到合適的用戶接口,包括繪圖事件。它也是應用程序與Android UI組件包(來自android.widget和android.view包)進行交互的線程。因此,主線程有時也被叫作UI線程。

系統(tǒng)并不會為每個組件的實例創(chuàng)建單獨的線程。運行于同一個進程中的所有組件都是在UI線程中實例化的,對每個組件的系統(tǒng)調用也都是由UI線程分配的。因此,對系統(tǒng)回調進行響應的方法(比如報告用戶操作的onKeyDown()或生命周期回調方法)總是運行在UI線程中。

例如,當用戶觸摸屏幕上的按鈕時,應用程序的UI線程會把觸摸事件分發(fā)給widget,widget先把自己置為按下(pressed)狀態(tài),再發(fā)送一個顯示區(qū)域已失效(invalidate)的請求到事件隊列中。UI線程從隊列中取出此請求,并通知widget重繪自己。

如果應用程序在與用戶交互的同時需要執(zhí)行繁重密集的任務,單線程模式可能會導致運行性能很低下,除非應用程序的執(zhí)行時機很合適。如果UI線程需要處理每一件事情,那些耗時很長的操作(諸如訪問網絡或查詢數(shù)據(jù)庫等)將會阻塞整個UI(線程)。一旦線程被阻塞,所有事件都不能被分發(fā),包括屏幕繪圖事件。從用戶的角度來看,應用程序看上去似乎被掛起了。更糟糕的是,如果UI線程被阻塞超過一定時間(目前設置大約是5秒鐘),用戶就會被提示“應用程序沒有響應”(ANR)的對話框。如果引起用戶不滿,可能就會決定退出并刪除這個應用程序。

此外,Andoid的UI組件包并不是線程安全的。因此不允許從工作線程中操作UI,只能從UI線程中操作用戶界面。因此,Andoid的單線程模式必須遵守兩個規(guī)則:


●不允許阻塞UI線程。

●不允許在UI線程之外訪問Andoid的UI組件包。


根據(jù)以上對單線程模式的描述,要想保證程序界面的響應能力,關鍵是不能阻塞UI線程。如果操作不能很快完成,就讓它們在單獨的線程中運行(“后臺”或“工作”線程)。

例如,以下是響應鼠標單擊的代碼,它實現(xiàn)了在單獨線程中下載圖片并在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();
    }

首先,因為創(chuàng)建了一個新的線程來處理訪問網絡的操作,這段代碼似乎能運行得很好。可是它違反了單線程模式的第二條規(guī)則,即不要在UI線程之外訪問Android的UI組件包。這個例子在工作線程里而不是UI線程里修改了ImageView,這可能導致不明確、不可預見的后果,要跟蹤這種情況也是很困難很耗時的。

為了解決以上問題,Android提供了幾種方法,從其他線程中訪問UI線程。下面列出了有助于解決問題的幾種方法:


●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)在,這個代碼的執(zhí)行線程是安全的了。網絡相關的操作在單獨的線程里完成,而ImageView是在UI線程里操縱的。

不過,隨著操作變得越來越復雜,這類代碼也會變得復雜且難以維護。為了用工作線程完成更加復雜的交互處理,可以考慮在工作線程中用Handler來處理UI線程分發(fā)過來的消息。當然,最好的解決方案也許就是繼承使用異步任務類AsyncTask,此類簡化了一些工作線程和UI交互的操作。

(2)使用異步任務(AsyncTask)

異步任務允許以異步的方式對用戶界面進行操作。它先阻塞工作線程,然后在UI線程中顯示結果,在此過程中不需要對線程和handler進行人工干預。

要使用異步任務,必須繼承AsyncTask類并實現(xiàn)doInBackground()回調方法,該對象將運行于一個后臺線程池中。要更新UI時,須實現(xiàn)onPostExecute()方法來分發(fā)doInBackground()返回的結果。由于此方法運行在UI線程中,所以就能安全地更新UI了。然后就可以在UI線程中調用execute()來執(zhí)行任務了。

例如,可以利用AsyncTask來實現(xiàn)上面的那個例子:

        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);
        }
    }

現(xiàn)在的UI是安全的,代碼也得到了簡化,因為任務分解成了工作線程內完成的部分和UI線程內完成的部分。

要全面理解這個類的使用,須閱讀AsyncTask的參考文檔。以下是關于其工作方式的概述:


●可以用generics來指定參數(shù)的類型、進度值和任務最終值。

●工作線程中的doInBackground()方法會自動執(zhí)行。

●onPreExecute()、onPostExecute()和onProgressUpdate()方法都在UI線程中調用。

●doInBackground()的返回值會傳給onPostExecute()。

●在doInBackground()內的任何時刻,都可以調用publishProgress()來執(zhí)行UI線程中的onProgressUpdate()。

●可以在任何時刻、任何線程內取消任務。


提示

在使用工作線程時,可能遇到的另一個問題是由于運行配置的改變(比如用戶改變了屏幕方向)導致activity意外重啟,這可能會銷毀該工作線程。要了解如何在這種情況下維持任務執(zhí)行以及如何在activity被銷毀時正確地取消任務,請參見Shelves例程的源代碼。

3.6.3 線程安全方法

在一些情況下,實現(xiàn)的方法可能會被多個線程調用,因此他應該設計線程為安全的。

真實存在能被遠程調用的方法(比如,綁定服務(bound service)中的方法),當一個方法(在一個IBinder中實現(xiàn)的)的調用發(fā)起于同一個進程(IBinder正運行的),這個方法在調用者線程中執(zhí)行。但是,如果調用發(fā)起于其他進程,那么這個方法將運行于線程池中選出的某個線程中(而不是運行于進程的UI線程中),該線程池由系統(tǒng)維護且位于IBinder所在的進程中。例如,即使一個服務的onBind()方法是從服務所在進程的UI線程中調用的,實現(xiàn)了onBind()的方法對象(比如,一個子類實現(xiàn)了RPC的方法)仍會從線程池中的線程被調用。因為一個服務可以有多個客戶端,所以同時可以有多個線程池與同一個IBinder方法相關聯(lián)。因此IBinder方法必須實現(xiàn)為線程安全的。

類似的,content provider也能接收來自其他進程的數(shù)據(jù)請求。盡管ContentResolver類、ContentProvider類隱藏了進程間通訊管理的細節(jié),ContentProvider中響應請求的方法有:query()、insert()、delete()、update()和getType()方法,這些方法都會從ContentProvider所在進程的線程池中被調用,而不是進程的UI線程。由于這些方法可能會從很多線程中同時被調用,所以它們也必須實現(xiàn)為線程安全的。

3.6.4 進程間的通信

Android利用遠程過程調用(remote procedure call,RPC)提供了一種進程間通信(IPC)機制,通過這種機制,被activity或其他應用程序組件調用的方法將(在其他進程中)被遠程執(zhí)行,而所有的結果將被返回給調用者。這就要求把方法調用及其數(shù)據(jù)分解到操作系統(tǒng)可以理解的程度,并將其從本地的進程和地址空間傳輸至遠程的進程和地址空間,然后在遠程進程中重新組裝并執(zhí)行這個調用。執(zhí)行后的返回值將被反向傳輸回來。Android提供了執(zhí)行IPC事務所需的全部代碼,因此只要關注定義和實現(xiàn)RPC編程接口上即可。

要執(zhí)行IPC,應用程序必須用bindService()綁定到服務上。詳情請參閱服務Services開發(fā)指南。

主站蜘蛛池模板: 梁山县| 阳原县| 镇宁| 泽普县| 南川市| 淄博市| 共和县| 灵川县| 林甸县| 林周县| 潞西市| 保德县| 如皋市| 开封市| 佛山市| 汉源县| 山东| 论坛| 西吉县| 青河县| 西乌珠穆沁旗| 黑水县| 万州区| 禄丰县| 象州县| 怀来县| 青河县| 合江县| 逊克县| 蓬莱市| 安庆市| 湛江市| 翼城县| 贵南县| 高唐县| 桐庐县| 万安县| 获嘉县| 通江县| 西城区| 秭归县|