Android-進程和線程

當一個應用(application)組件首次啓動時,Android系統會爲這個應用啓動一個新的進程(process),併爲之開啓一個單獨的線程(thread);如果一個應用組件啓動時它所再的應用已經創建了一個進程,則它直接運行在這個進程中。默認情況下,一個應用所以的組件(Activity/Service/BroadCastReceiver/ContentProvider)都運行在一個同一個進程的同一個線程中(main thread)。當然,也可以指定你的應用組件運行在單獨的進程中,你也可以在你的進程中創建多個線程。


進程


默認情況下,應用程序的所有組件都運行在相同的進程中,通常情況下,我們也不需要去改變它們。當然,如你需要,你可以去通過修改 manifest 文件的配置,爲你的應用組件指定特定的進程

 manifest 文件中四大組件節點<activity><service><receiver>, 和 <provider>—都支持 android:process這個屬性來指定當前組件特有的進程。於是可能應用組件之間可能就會出現如下關係:


  • 幾個應用橫向組件運行在單獨的進程中
  • 同一個應用的不同組件運行在相同的進程中(共享進程)
  • 不同應用的組件運行在同一個進程中。(當然這個需要兩個應用共享 Linux user id 並且提供相同的簽名認證)

在某些情況下 Android 會關閉的進程,例如,在 low memory 情況下,其他優先級更高的應用進程(例如電話)起來需要更多內存服務用戶時,系統會殺掉那些不重要的進程,同時,依附在這個進程中的應用組件將會被銷燬釋放。當然,用戶再次喚醒它的時候進程會重新啓動。

Android 決定關掉哪個進程,時通過權衡進程對用戶的重要性來判斷的,如何權衡進程對用戶的重要性?


進程的生命週期

Android 系統會儘可能的讓應用進程存活的越久,但是當系統內存有限,最終不得不殺掉舊進程釋放內存來給那些新的對用戶更重要的進程。系統如何決定“誰去誰留”?系統會根據運行在進程中的應用組件及應用組件的狀態來劃分重要性的等級。一共劃分了五個等級:

  • 前臺進程(Foreground process)

 當前正在被用戶請求服務的進程。滿足以下條件:

*包含一個 Activity 正在和用戶交互(在 Resume 狀態下)

*包含一個綁定到 Activity 的 Service ,這個Activity 正在和用戶交互。

*包含一個前臺Service(Service 調用了startForeground()方法)

*包含一個正在執行 onReceive() 方法的 BroadcastReceiver

總之,在給定的時間內,前臺進程不多,它們優先級最高,是最後被列爲被殺對象的。


  • 可見進程(Visible process)

一個進程不包含前臺組件(Foreground components),但包含能被用戶看的見的組件(可見不可交互)。如下幾種條件:

*包含一個 Activity,這個 Activity 不坑交互但是可見(處於 onPause()狀態),例如 Activity 打開一個半透明的 Dialog

*包含一個 Service 這個 Service 綁定到一個可見但不可交互的 Activity 。

  • Service 進程

一個進程包含一個 Service ,例如調用了 startService ()方法的,但是不包含上面兩種更高優先級的組件。例如在後臺播放的音樂,或在後臺下載數據的 service。


  • 後臺進程

一個進程包含 Activity ,但是 Activity 不再對用戶可見了。例如一個應用,這個應用沒有上面提到的幾種組件,這個應用被按了home 鍵,Activity 處於 onStop()狀態。


  • 空進程

一個進程不存在活動的應用組件。系統爲什麼會有這樣的進程?主要是爲了緩存,以便下次啓動一個應用的時候直接使用它,從而提高啓動速度。系統進程會殺掉一些這樣的進程以保存進程資源緩存和 Linuxe 內涵緩存資源的平衡。

因爲一個運行着 service 的進程優先級比一個運行着後臺 activity 的優先級高,這就是爲什麼要使用一個 service 來處理一個耗時的操作會比簡單開啓一個工作線程來處理要好的原因。也是爲啥在廣播中開啓線程來處理操作比不上啓動一個 service 來操作更保險的原因。

線程(Threads)


當一個應用啓動,系統就爲這個應用啓動了一個線程,這個線程叫做“main” 。這個線程非常重要,因爲它負責着整個應用的事件分發及組件的繪製。和 UI 組件交互的操作都是在這個線程中完成的,因此這個線程有叫做 UI 線程(UI Thread)。


系統不會爲每個組件創建獨立的線程。所以同一個進程裏面的所有組件都運行在 UI 線程中。 系統對這些組件的調用、分發都是從 UI 線程開始的。所以,UI 組件的事件回調 (例如 onKeyDown()等事件)都是在 UI 線程中發生的。

例如,當用戶點擊一個 button ,點擊事件從 UI 線程分發(dispatch)到這個 button,button 接收到這會開始設置他的 pressed state ,然後發送一個刷新請求到事件列表,UI 線程從事件列表中取出並通知 button 重會。

由於 UI 的單線程模式,你不能再 UI 線程中處理密集耗時的工作,這將會導致 UI 阻塞,影響用戶體驗,甚至導致 “application not response”(ANR).對於 UI 線程,要遵循下面簡單的兩條規則:

1  避免 UI 線程阻塞。

2 避免從其他線程訪問 UI 組件。


  • 工作線程(Worker thread)


由於上面我們討論的 UI 線程不能阻塞,不能做耗時操作,所以我們把這樣的操作放在新的線程中來完成,我們稱這樣的線程爲工作線程。

例如,下面代碼演示從網絡下載一張圖片並顯示在  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();
}
首先,我們看到,開啓工作線程,下載是沒問題的,滿足上面說的第一條原則,但是再看代碼 
mImageView.setImageBitmap(b);
這裏,在工作線程中對 ImageView 進程操作,這裏違背來第二條原則,這會引起意想不到的錯誤或異常。我們可通過以下幾種方式來修正這個問題:

例如使用View.post(Runnable)來修改這個問題。

Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
For example, you can fix the above code by using the View.post(Runnable) method:

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


現在這個實現符合了線程安全要求,網絡操作在工作線程中處理和對 UI 的刷新彼此分離。然而,這樣會使代碼變的很複雜,不好維護。對於工作線程和 UI 交互比較複雜的可以考慮使用 handler 來處理。或許  AsyncTask 也是極其不錯的方案。

  • 使用 AsyncTask

AsyncTask 實現 UI 的異步交互。AsyncTask 內部存在一組工作線程(線程池管理),用於處理耗時(阻塞)操作,並且會將操作的結果發佈到 UI 線程中進行處理。對與線程的管理及和 UI 交互的細節不用你自己去處理,使用簡單方便。

實現 AsyncTask 必須實現 doInBackGround()  方法和 onPostExecute()方法。在doInBackground()方法中處理阻塞操作,在onPostExecute()方法中處理 UI 刷新。我們需要在 UI 線程中調用 AsyncTask 的 execute()方法來開始工作。

例如:

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 阻塞線程安全的解決方案的代碼變的簡單了。更多關於 AsyncTask 的使用細節,點擊  AsyncTask  。

  • 線程安全方法(Thread-safe methods)

在以下解決方案中,你的方法實現可能會被多個線程調用,所以這些方法必須是線程安全的。

這裏重要提及的事關於被遠程調用的方法,例如,一個 bound service。通過 IBinder 實現的方法調用來自運行這個 IBinder 的進程,方法執行發生着調用者的線程。然而,當方法調用來自另一個進程時,情況就不一樣了。方法的執行將發生在一個線程中,這個線程來自一個線程池,這個線程池由運行者這個Binder服務的進程維持。簡單的說就是當 boud service (通過 IBinder 通信) 提供的事一個本地服務,服務方法的執行方式在本進程中,並且能能夠明確的知道調用發生在哪個線程裏。如果 bound service(通過 IBinder 通信)是一個遠程服務,那麼如果調用來自另一個進程則調用發生在一個不確定的線程中(啓用線程池來處理遠程請求),所以這種情況下必須保證服務方法是線程安全的。圖解:

類似的,像 contentProvider 處理來自另一個進程的請求,也是一種遠程服務調用。其實 ContentResolver 和 ContentProvider 已經幫我們封裝了進程之間的交互細節。contentProvider 不可能只服務於一個客戶端進程,甚至可能同時服務於多個客戶端的請求。所以contentProvider 的 insert()、delete()、update()和 getType()等這樣的方法都是在一個線程池的線程中執行的。所以這些方法的實現也要確保是線程安全的。

發佈了40 篇原創文章 · 獲贊 64 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章