Android Notes 03 - Process and Thread

Processes and Threads:進程與線程

當程序的第一個組件開始啓動時,Android系統會爲這個程序啓動一個新的Linux進程。默認的,程序中的後續其他組件都是運行在這個進程的線程中(這個線程被成爲"主"線程:main thread)。如果程序的組件在啓動時發現已經存在這個程序的進程了(因爲其他組件正在運行), 那麼這個組件將啓動在該進程中,並使用同一線程。然而,你可以安排程序中的不同組件運行在另外一個進程中,而且你可以爲任何進程創建其它的線程。

Process:進程

默認的,同一程序的所有組件都是運行在一個Proces裏面的,並且大多數程序都不應該去改變這一規則。然而,如果你需要控制某一確定的組件的Proces,你可以在manifest文件中做特殊設置。Music播放器的Playback Service就可以這樣做

manifest中的activity,service,receiver與provider的標籤都可以支持android:process的屬性,它可以爲這個組件的運行指定一個特定的進程。這樣你可以爲某些組件設置運行的進程而其他組件共享一個進程。你還可以通過設置進程屬性使得不同程序的運行在同一個進程,共享同一個Linux ID,並且簽有同樣的簽名。

application標籤也可以支持設置android:process屬性,這樣會給所有的組件設置一個默認的進程值。

Android會在系統內存緊張時決定關閉某些進程。那麼程序中的運行的組件會因此被摧毀掉。當他們需要再次運行時會重新啓動一個進程。

當決定殺掉哪一個進程時,Android系統會自動衡量進程的重要性。例如,一個在屏幕上不再可見的進程相對於那些有組件正在被顯示的進程更容易被殺掉是顯得合理的。那麼衡量的權重後面會講到。

Process lifecycle:進程生命週期

Android系統會嘗試儘可能的維持程序的存在。但是當需要爲新的或者更重要的進程開闢內存空間的時候,最終某些程序是會要被拿掉。爲了決定存活當中的程序哪些該拿掉,哪些該留下,系統會根據每一個進程的組件與組件運行狀態來生成一個"importance hierarchy"(權重層級)。那些權重低的進程將依次被移除,直到系統恢復了足夠的資源。

在權重層級中,一個有5個層次。下面列出了不同類型進程的權重:

  1. Foreground process
    用戶目前正在使用的進程。要成爲此類型的進程需要滿足下面的任意一點:
    • 該進程擁有一個用戶正在交互的頁面。(onResume方法正在執行)
    • 該進程擁有一個Service,該Service綁定到正在與用戶交互的Activity中。
    • 該進程擁有一個in the foreground的Service,通過執行startForeground().
    • 該進程擁有一個Service,正在執行Service的某些callbacks方法(onCreate(), onStart(),或者onDestroy()).
    • 該進程擁有一個BroadcastReceiver,並且在執行它的onReceive()方法。
  2. Visible process 一個沒有任何foreground組件,但是仍然能夠影響屏幕呈現內容的進程。需要滿足下麪條件之一:
    • 該進程沒有任何foreground的組件,但是仍然對用戶可見。例如onPause()被調用的情況,started dialog。
    • 該進程擁有一個一個Service,並且該Service綁定到某個Visible的activity上。
  3. Service process
    一個進程擁有正在運行的Service,該Service是通過startService()的方式被啓動的,並且不會進入到前面的兩種高權重的層級。儘管Service進程沒有與用戶看到的部分有直接關係,但是他們通常是在做用戶關心在意的工作(例如後臺播放音樂,後臺下載網絡數據),因此係統會保持他們能夠運行,除非現有的內存已經不夠維持前面2個權重層級的進程使用。
  4. Background process
    進程擁有一個activity,並且這個activity不被用戶所見(例如activity的onStop方法被執行)。這些進程對用戶體驗沒有直接的影響,系統可以殺掉這些進程爲前面三個層級的進程空出內存。通常來說,系統中存在許多後臺進程正在運行,因此他們被保存爲一個LRU(least recently used)列表,用來確保最近被使用過的activity會被最後殺死。如果一個activity正確的實現了它的生命週期函數,可以保存它的當前狀態。那麼殺掉該進程並不會對用戶體驗有明顯的影響。因爲當用戶重新回到這個Activity時,activity可以所有可見時的狀態。
  5. Empty process
    該進程沒有擁有任何激活狀態的程序組件。保持該進程存在的唯一理由是爲了緩存。使用緩存可以提升程序的下次啓動時間。系統通常會權衡所有的資源來決定殺掉哪些緩存程序。

取最高優先級
如果一個程序中有好幾種優先級的組件,Android系統會把其中最高級別的當作整個程序的權重。例如,如果一個進程擁有一個service與一個visible activity,這個進程會被當作是一個visible進程而不是service進程。

提升優先級
另外,一個進程的排名會因爲其依賴的組件的權重提升而提升。例如,進程A本來是權重爲3的,但是它的某個組件與另外一個權重爲1的進程B進行綁定後,進程A的權重也會被提升爲1。

因爲一個執行service的進程的排名比一個後臺activity的進程排名要高,所以,如果一個activity啓動時要執行一段長時間的操作,應該選擇使用Service而不是創建一個worker thread。例如,一個activity做上傳圖片的操作,應該選擇啓動一個Service做上傳的動作。使用service能確保這個操作會至少有"service process"的優先級。

Thread

當一個程序首次啓動,系統會爲這個程序創建一個"main thread"。這個線程非常重要,因爲它將肩負起UI的控制調度,還包含繪製圖像的事件。同時,它還是與UI相關的組件(來自android.widget與android.view下的組件)進行交互的中介。因此,有些時候main thread 也被成爲"UI thread".

系統不會爲每一個組件的實例創建單獨的線程。所有運行在同一個進程中的組件都會在UI Thread中被實例化。系統調用組件與他們自身的回調函數都是運行在UI Thread的。

例如,當用戶點擊屏幕上的一個button,程序的UI thread會把這個事件分發至button這個組件上,然後button會執行它的presss state並post an invalidate請求到事件隊列中。UI thread然後從事件隊列中取出消息並通知組件進行重繪。

當你的app執行一個比較重的工作時,單線程模式有可能會卡到UI。特別是,在UI線程裏面做網絡請求操作或者是db查詢會嚴重卡到整個UI。當UI thread被阻塞時,沒有事件能夠繼續被分發,包括繪製事件。那麼在用戶看來,這樣的程序是糟糕的。更糟糕的是,如果UI線程被阻塞超過5秒,程序會就出現ANR的錯誤提示。那麼用戶可能會決定推出程序,並對該程序進行卸載。

另外,Andoid的UI組件不是thread-safe的。因此,你不應該在另外一個線程去操控UI組件。有兩個原則需要遵守:
* 不要阻塞UI線程。
* 不要在UI線程之外訪問UI組件。

Worker threads

爲了實現執行耗時的操作,你應該確保那些動作執行在另外一個線程("background" or "worker" threads)。

例如,下面的代碼演示了點擊事件後開啓另外一個線程來下載並顯示圖片的操作:

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線程之外訪問UI組件。 Android提供了下面三個方法來解決這個問題:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable, long)

例如下面就是使用View.post的方式實現的代碼示例

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

上面的代碼雖然實現了功能,可是當系統變複雜時,會顯得不好處理。也許我們可以考慮使用Handler,但是更好的方案也許是使用AsyncTask。

Using AsyncTask

關於什麼是AsyncTask與如何使用AsyncTask,不再贅述。 下面是使用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);
    }
}

Thread-safe methods

在某些情況下,你實現的一些方法有可能會被不止一個線程中執行到,因此這些方法必須是線程安全的。

在bound service的情況下,回調函數通常都需要是線程安全的。如果IBinder的Client與Server是在同一進程的話,那麼被Client調用的方法是執行在Client的線程當中的。然而如果Client是在另外一個進程的話,被調用的方法則是執行在來自系統爲Server端維護的一個線程池當中的某個線程中(非UI Thread)。例如,既然Service的onBind()的方法可以被service進程的UI線程所調用執行,那麼onBind所返回的對象(Client端)所實現的方法則可以被線程池中的線程所調用執行。因爲一個service可以擁有多個client,那麼在同一時刻可以有不止一個線程可以佔用同一個IBinder的回調函數。所以IBinder的方法必須是線程安全的。

同樣的,一個content provider可以接受來自另外一個進程的數據請求。儘管ContentResolver與ContentProvider類隱藏了實現細節,但是ContentProvider所提供的query(),insert(),delete(),update()與getType()都是在content provider進程的線程池中被調用執行的,而不是進程的主線程中。因爲那些方法可能同時被多個線程所調用,所以他們都應該是線程安全的。

Interprocess Communication

Android提供了爲遠程過程調用(RPC)提供了一種進程間通信(IPC)的機制。調用發生在activity或者其他組件中,執行卻在另外一個進程,最後再把結果返回給調用者。這需要把調用的數據解析成操作系統能夠識別的格式,解碼,傳遞,再編碼返回。Android提供了IPC交互的實現細節,因此我們只需要專注於定義與實現RPC接口。

爲了執行IPC,你的程序必須通過bindService()方法綁定到service上,更多細節,請查看Services開發指南。


文章學習自http://developer.android.com/guide/components/processes-and-threads.html
轉載請註明出處,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章