編輯:關於android開發
當你的app的組件啟動了,並且app中沒有其他的組件的時候,Android系統會自動為為這個應用啟動一個linux進程,其中有一個執行的線程。默認時,一個app的所有的組件運行在同一個進程的同一個線程,也就是主線程。如果一個組件啟動的時候,app已經有一個進程了,(因為可能這個應用中其他的組件已經運行了),那麼這個組件就在這個進程中被運行,而且使用同一個線程。但是你可以你的應用中的不同的組件在不同的進程中運行,而且可以給一個進程添加新的線程。
下面的教程講述Android應用中進程和線程是如何工作的。
進程
默認而言,同一個應用中的組件運行在同一個進程中,應用不應該改變這種設定。但是如果你需要控制某一個組件對應一個線程,那麼你可以在manifest文件中進行設定。
manifest文件給四大組件元素——,
元素也支持android:process屬性,可以為其中的元素都設定一個默認的屬性。
當內存不足的時候,或者用戶啟動了更加迫近使用的程序的時候,Android可能會在一些時刻關閉一些進程。而在被關閉進程中的組件,會按照順序被銷毀。當這個進程裡面的組件又有任務的時候,這個進程就會被重新創建。
Android系統會通過衡量進程相對於用戶的權重,來決定銷毀哪些的進程。比如銷毀一個含有一個很久都沒有顯示過的activity的進程,要比銷毀一個正在顯示的activity所在的進程好。進程是否被銷毀取決於在進程中運行的組件的狀態。用來判別是否結束線程規則將在下文討論。
進程的聲明周期 Process lifecycle
Android系統會盡可能長時間地維護一個進程,但是終究會為了更新更重要的進程來將舊的進程結束,以回收空間。系統根據進程中擁有的組件和組件的狀態來給進程設立優先級等級,每一個進程都在優先級等級當中。最低重要性的進程會最先被淘汰,然後是次不重要的,以此類推,直到系統回收了足夠的所需空間。
在重要性等級中有五個層次。下面按照重要性的順序來列舉這些等級。(最前面的是最重要的,最不能被殺死的)
1.前台進程
這是個用戶正在使用的進程。如果下面的情況成立就認為一個進程是前台進程。
- 其中包含一個正在和用戶交互的activity(activity的onResume方法被調用過)。
-包含一個與正在和用戶交互的activity綁定的service。
- 包含了一個運行在前台的service,也就是service調用過startForeground
- 包含一個正在執行這些聲明周期回調方法的service:onCreate,onStart,或者onDestroy。
- 包含了一個正在執行onReceive方法的BroadcastRecevier。
通常來講,只有一些的前台進程才會一直都存在。他們只是最後被殺死,如果內存很低他們就不能都運行。通常在這些時候設備已經進入了內存分頁狀態,所以需要保證殺掉一些進程的時候用戶交互的界面得以保留。
2.可視進程
這是一種雖然已經不包含任前台的組件,但是仍然影響用戶用戶的屏幕顯示的進程。如果下面任意的情況都屬於可視進程。
- 包含一個不在前台了的activity,但是仍然可以被用戶看到(調用了onPause方法)。比如一個前台的activity開啟了一個對話框,與此同時之前的那個activity仍然在後面可見。
- 包含一個綁定了可見或者前台activity的service。
可見進程的重要性很高,除非為了保護前台進程,否則不會回收可視進程。
3.服務進程
包含一個使用startService啟動的服務,並且服務不在上面兩種的級別裡面。盡管服務的進程的功用用戶不能直接可見,但是他們通常都和用戶關心的業務相關。比如說後台播放音樂以及從網絡下載數據。所以系統會盡量保證其運行,除非內存空間很低,為了保護前兩者等級。
4.後台進程
後台進程是包含現在用戶不可見的的activity的進程(調用過activity的onStop方法)。它們對用戶體驗沒有直接影響,系統可以在任何時候殺掉他們,給前台,可視,或者服務進程騰出空間。總是有很多的後台進程,所以系統會維護一個LRU(多久沒用了的列表least recent used)來使得最近剛剛用過的那些activity最後被殺死。如果一個activity正確地實現了他的生命周期方法,保存了當前的狀態,那麼殺死他不會對用戶體驗造成可以看見的影響。因為當用戶從新進入這些activity的時候,activity恢復了它的所有的可見狀態。關於存儲和恢復狀態,可以參見Activity文檔。
5.空進程
這是不包含任何的activity和組建的進程。保留這種進程唯一目的是用作緩存,來提升下此次其中組件啟動需要運行的時間。系統通過殺掉這種進程,來平衡整個系統的裡的,進程緩存和底層的內核的緩存。
Android系統按照他們包含組件的最高等級的情況來判斷優先級。例如如果一個進程包含了一個service和一個可視的activity,那麼就被判定為可視進程而非服務進程。
另外,一個進程可能因為被其他的進程所依賴而提升等級。一個被依賴進程的等級一定會比依賴它的進程等級高。例如A進程中的content provider正在為進程B的客戶端服務,或者進程A中的服務被B中的組件綁定了,那麼A的重要性就會比B高。(至少相同)。因為一個運行了service的進程優先級高於後台activity的優先級,所以與其啟動了一個進行長時間運行操作的activity(通過建立一個線程)不如啟動一個service更好。特別是這個操作要比activity的生存更長時間的時候。例如如果一個activity通過啟動一個service來用網路上傳圖片,那麼即使用戶離開了這個activity,圖片仍然在被上傳。使用service可以保證操作至少擁有service級別的優先級,無論activity發生了什麼。這也是為什麼broadcast應該使用service,而不是僅僅把費時操作放入一個線程中。
線程 Thread
當啟動一個應用的時候,為其創建一個執行線程,叫主線程。這個線程十分重要,因為它肩負著派發事件到合適的用戶交互的的組件上去的任務,包括繪制事件。她也是你的應用和Android UI工具箱(android.widget和android.view包中的組件)中的組件進行交互的線程。這樣而言,主線程有時候也叫作UI線程。(用戶交互線程)
系統不會為每一個組件創建分別的線程。 在同一個進程中的組件都是有UI線程實例化的,並且系統對每一個組件的調用都被派發到這個線程中。因此,響應系統調用的方法,比如說onKeyDown方法,用來匯報用戶動作的,或者聲明周期調用,都是在進程的UI線程中調用的。
例如,當用戶用戶點了一下屏幕上的按鈕,你的應用的UI線程就會把這個觸控事件派發給控件,反過來它會設定成按下的狀態,並且發送一個使之無效的請求給事件隊列(因為已經響應過了)。UI線程會把請求出列,並且向控件發出重繪自己的通知。
如果你的app要在響應用戶交互中運行高負荷,那麼如果你沒有合適地構造你的應用,單線程模型就會性能很差。特別是如果什麼事情都交給UI線程去做的話,網絡訪問或者數據庫查詢,就會使得整個UI都阻塞。當線程被阻塞的時候,就無法再派發事件,包括繪制事件。從用戶的角度來說,就是應用卡了。更差勁的是如果UI線程阻塞了大概5秒以上,就會彈出一個惱人的應用沒有響應ANR對話框。用戶會為此不開心,從而關掉甚至卸載你的應用。
另外,Android UI工具箱是線程不安全的。所以你不可以在你的工作線程中(也就是非UI線程)操作篡改UI——你只能也必須在UI線程中操作UI。如此僅有兩個Android單線程模型的規則:
1.不要阻塞UI線程。
2.不要在UI線程以外訪問訪問UI工具箱。
工作線程 (後台線程) Worker threads
基於上面討論的單線程模型的特性,保持你的應用UI的響應性,不阻塞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(); }這個看起來一開始工作的不錯,因為它創建了一個新的線程來處理網絡操作。但是它觸犯了規則裡面的第二條,即不要在非UI線程中操作UI。例子中是在工作線程中進行了UI操作,而不是在UI線程中。這會導致一個未定義的和出乎意料的結果,這會很難也很費時間來追蹤這種錯誤。
為了修整這種錯誤,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(); }現在這個構造就線程安全了:網絡操作在另外的線程中進行,並且ImageView的操作是在UI線程中。
然而隨著操作的復雜度的上升,這樣的代碼會變得很難維護。想要用工作線程進行更復雜的交互,你應該考慮在工作線程中使用Handler,來處理從UI線程中發過來的消息。可能最好的解決辦法通過擴展AsynTask類,它簡化了工作線程需要跟UI線程交互需要做的工作。
使用異步任務 Using AsyncTask
AsyncTask可以讓你在你的UI上進行異步的工作。它將阻塞的進程放在工作線程,並在UI線程中發布工作進展的情況。不需要你自己處理thread和handler。
你需要繼承AsyncTask並且實現doInBackgound()的回調方法,這個方法會在後台的線程池裡面運行。如果要一邊更新你的UI,你需要實現onPostExcecute方法,它會發送doInBackground()的結果,並且在UI線程中運行,所以你可以安全地更新你的UI。你可以在UI線程中調用execute()方法來啟動這個task。
我們依然用上面例子演示:
public void onClick(View v) { new DownloadImageTask().execute("http://example.com/image.png"); } private class DownloadImageTask extends AsyncTask{ /** The system calls this to perform work in a worker thread and * delivers it the parameters given to AsyncTask.execute() 系統通過調用這個方法來執行後台業務,並且接收從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() 系統調用這個方法來進行UI線程的UI操作,它接收從doInBackground中傳來的結果*/ protected void onPostExecute(Bitmap result) { mImageView.setImageBitmap(result); } }
你可以閱讀AsyncTask的文檔來全面的了解怎樣使用這個類,下面是一個快速一覽:
- 你可以使用泛型來制定傳入的參數類型,過程變量,和任務的結果返回類型。
- doInBackground會在工作線程自動執行。
- onPreExcecute(),onPostExecute()和onProgressUpdate()都是在UI線程中執行的。
-在doInBackground()中返回的參數被傳入到onPoastExecute()中。
- 你可以在doInBackground中的任何時候調用publishProgress()來在UI線程上執行onProgressUpdate()。
- 你可以在任何時候,任何線程中來取消任務。
警告:當你使用工作線程的時候可能會遇到一個問題。就是因為運行時適配變化導致的activity的重啟(比如說當用戶改變了屏幕的方向),這可能會銷毀掉你的工作線程。想知道怎樣在這種重啟中繼續保持你的任務,和怎樣在activity銷毀的時候合適地取消任務,請參見Shelves 例子的源代碼。
線程安全的方法 Thread-safe methods
有時候你寫的方法可能會被很多的線程調用,所以你的方法需要寫的線程安全。
對於遠程調用的方法而言,確實是這樣,比如說bound service中的方法。當調用運行在同一個進程中的IBinder中的方法時,方法會在調用者的線程中執行。當這個調用在另一個進程的時候,方法會在IBinder所在的進程中維護的線程池中選擇一個,在其中來執行方法(排除進程中的UI線程)。例如一個service的onBind方法可能被service進程中的UI線程調用,onBind返回對象(比如說實現了RPC方法的子類)中的方法就會從線程池中的一個線程中調用。因為一個service可能有很多個客戶端,多個在線程池中的方法都可以同時調用同一個IBinder中的方法。因此IBinder一定要構造地線程安全。
類似地,一個content provider可能同時接收其他進程中的數據請求。盡管ContentResolver和ContentProvider隱藏了跨進程的通信是怎樣管理的,ContentProvider中的響應請求的方法-query(),insert(),delete(),update(),getType-都是在content provider所在的進程的線程池的線程中調用的。因為這些方法可能被任意數量的線程同時調用,所以它們也一定要被定義為線程安全。
跨進程通信 Interprocess Communication
Android提供了一種跨進程通信機制(IPC),它使用遠程調用來實現(RPCs)。其中的方法從一個activity或者其他的應用組件中發起調用,在遠程(其他進程)中執行,並且可以返回給調用者結果。這就需要把調用的方法和其數據分解為一個操作系統可以理解的層面上,把它們從本地進程和地址空間傳送到遠程的進程和地址空間,在那裡重新組裝以後再重新激活。返回值同樣的方式傳回。Android系統提供了所有實現IPC傳送的代碼,所以你需要關注的就是定義和實現RPC編程接口。
要實現IPC,你的應用需要使用bindServie來綁定(bind)一個服務。更多的信息可以參見教程的Service文檔。
[實踐] Android5.1.1源碼,android5.1.1源碼前言 本文的方法要修改Android源碼。但只要將系統服務寫出來,也可以注入system_server
Android動態部署五:如何從插件apk中啟動Service 經過前面幾篇文章的分析,我們了解到了Google原生是如何拆分apk的,並且我們自己可以通過解析manif
融雲如何實現文件發送(高級進階) 干貨來啦~! 想在聊天中發 小視頻?gif 動圖? 發紅包? 發 自定義表情? 沒有問題!在融雲統統都可以實現! 以上不管是 小視頻
關於安卓APP的啟動界面,安卓APP啟動界面剛學安卓App開發的朋友們,可能會遇到一個問題,就是人家的App剛進入會有一個頁面出現一會兒後消失, 這個頁面