Processes and Threads
Processes and Threads Android Developer page
在 Android 系統(tǒng)中,默認情況下一個應用的所有組件都運行在一個相同的進程(process)和線程(thread)中谆沃,這個線程叫做主線程(main thread)锌介。當一個應用存在已經運行的組件(component)時匣屡,再啟動新的組件也拜,默認情況下新的 component 將會在相同的進程和線程中運行.
當然侣集,我們也可以安排不同的 component 運行在單獨的 processes 中镐牺,也可以為 process 添加額外的 thread.
Processes
默認情況下旗唁,同一個 app 的所以 components 都運行在相同的 process 中祷嘶。對于大部分應用,我們這樣做就足夠了焙矛,如果我們需要控制某個特定的 component 所屬的進程邓梅,那么我們可以在 manifest file 中來更改。
在 manifest 中怎爵,每一個 component 元素,包括 <activity>
, <service>
, <receiver>
和 <provider>
故痊,都支持屬性 android:process
指定這個 component 應該運行在哪個進程中顶瞳。不同應用的組件可以通過設置這個屬性來運行在同意個進程中,這樣可以使不同的應用共享相同的 Linux user ID愕秫,使用相同的證書慨菱。
<application>
元素同樣支持 android:process
屬性,用來指定所有 components 的默認值戴甩。
Threads
當應用啟動后符喝,系統(tǒng)創(chuàng)建一個叫做 main 的線程。這個線程非常重要甜孤,因為它控制著將包括繪制事件在內的事件(events)發(fā)送到合適的交互控件中协饲。這個線程通常是應用和 Android UI toolkit(components from the android.widget
and android.view
package) 進行交互的進程,因此 the main thread 也有時被稱作 the UI thread缴川。當然茉稠,在一些特殊情況下,一個應用的主進程可能也不是其 UI 進程把夸。
系統(tǒng)并不會為每個控件的實例創(chuàng)建單獨的線程而线,所有的控件都會在 UI 線程中實例化,對于每一個控件的系統(tǒng)調用也都是通過這個線程。通常膀篮,相應系統(tǒng)調用的方法都會運行在 UI 線程中嘹狞。
比如,當用戶在屏幕上觸摸一個 Button 的位置時誓竿,應用的 UI 進程將 touch event 發(fā)送到相應控件磅网,反過來,相應的控件設置其為按下的狀態(tài)筷屡,向事件隊列發(fā)送一個取消的請求涧偷。UI 進程取消請求并且通知控件應當重新繪制。
當所有工作都在 UI 線程中速蕊,需要長時間的那些操作嫂丙,比如網絡訪問和數據庫查詢,將會阻塞整個 UI 線程规哲。當線程被阻塞,事件將不會被派發(fā)诽表,包括繪制的事件唉锌。從用戶的角度看,應用表現為卡頓竿奏。更糟的是袄简,如果 UI 線程被阻塞超過一段時間(目前大概是 5 秒),用戶將被提示 ANR 對話 “application not responding”泛啸。用戶可能會選擇停止應用或者不愉快的卸載應用绿语。
注意:
Android UI toolkit 不是線程安全的,所以候址,嚴禁在工作線程處理 UI 工作吕粹,所有的 UI 操作都必須在 UI 線程中完成。
- Do not block the UI thread
- Do not access the Android UI toolkit from outside the UI thread
Work threads
因為單一線程模式下有諸多問題岗仑,你不能阻塞 UI 線程匹耕,如果你有一些非及時的操作,你就必須確保這些操作運行在單獨的線程中(被稱為后臺線程或工作線程荠雕,"background" or "worker" threads)稳其。
仍然要切記,不能在非 UI 線程中更新 UI炸卑。Android 提供了幾種從其他線程訪問 UI 線程的方法既鞠。
- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
- View.postDelayed(Runnable, long)
下面的代碼使用
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
// a potentially time consuming task
final Bitmap bitmap =
processBitMap("image.png");
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(bitmap);
}
});
}
}).start();
}
Using AsyncTask
AsyncTask
用來方便的處理多線程的問題。AsyncTask
適合于短期的一次性任務盖文。并不能完美完成所有異步任務嘱蛋。
使用 AsyncTask,需要繼承 AsyncTask 并且實現 doInBackground()
方法,來完成 background 工作浑槽。
實現 onPostExecute()
方法來處理 doInBackground()
的結果蒋失,這個函數在 UI 線程中運行,所以可以安全的更新 UI桐玻。
通過在 UI 線程中調用 execute()
方法來運行這段異步任務篙挽。
The 4 steps:
-
onPreExecute()
, 在 UI 線程中執(zhí)行,用來設置任務镊靴,例如铣卡,用來顯示進度條。 -
doInBackground(Params...)
, 在 background 線程中執(zhí)行偏竟,在onPreExecute()
執(zhí)行完成后被調用煮落。 -
onProgressUpdate(Progress...)
, 在 UI 線程中執(zhí)行,可以在doInBackground()
方法中通過publishProgress(Progress...)
調用踊谋。用來更新 UI蝉仇,例如更新進度條。 -
onPostExecute(Result)
, 在 UI 線程中執(zhí)行殖蚕,會獲得doInBackground
方法所返回的結果轿衔。
AsyncTask's generic types:
-
Params
, the type of the parameters sent to the task upon execution. -
Progress
, the type of the progress units published during the background computation. -
Result
, the type of the result of the background computation.
Not all types are always used by an asynchronous task. To mark a type as unused, simply use the type Void
.
Threading rules
- The AsyncTask class must be loaded on the UI thread.
- The Task instance must be created on the UI thread.
-
execute(Params...)
must be invoked on the UI thread. - Do not call
onPreExecute()
,onPostExecute(Result)
,doInBackground(Params...)
,onProgressUpdate(Progress...)
manually. - The task can be executed only once.
Limitations of ASyncTask
假設有這樣的一個場景,當你的應用啟動初始的 Activity, 然后啟動了 AsyncTask睦疫,進行了 Http 請求操作害驹,在操作完成之前,旋轉手機蛤育,為了正確顯示宛官,系統(tǒng)會重新繪制我們的 Activity, 理想的操作應該是銷毀舊的活動,將原先舊活動上的 AsyncTask 轉移到新的 Activity 上執(zhí)行瓦糕。但是 AsyncTask 是怎么運行的呢底洗?在新創(chuàng)建 Activity 時,會新創(chuàng)建一個 AsyncTask 并且進行 HTTP 請求刻坊。系統(tǒng)想要銷毀舊的 Activity 但是由于舊的 AsyncTask 還沒有完成所以無法銷毀枷恕,但是舊的 AsyncTask 已經沒有意義了,因為其執(zhí)行獲取的數據我們不會顯示在屏幕上谭胚,是無意義的操作徐块。這樣造成了巨大的資源浪費。
Others
- AsyncTask: Helps get work on/off the UI thread.
- HandlerThread: Dedicated thread for API callbacks.
- ThreadPool: Running lots of parallel work.
- IntentService: Helps get intents off the UI thread.
Loaders
Loader 可以在 Activity 或 Fragment 中異步加載數據灾而。 Loader 的特征有:
- Loader 運行在單獨的線程中胡控,不會阻塞 UI 線程。
- Loader 簡化了線程管理旁趟,通過相應事件發(fā)生時的回調函數昼激。
- Loader 在設置改變時保持并緩存結果,以免重復請求。
- Loader 可以實現 observer 去監(jiān)視數據源的變化橙困。比如瞧掺,
CursorLoader
可以自動注冊一個ContentObserver
,當數據變化時凡傅,觸發(fā)重新加載事件辟狈。
Loader 的三個相關類:
- LoaderManager
- LoaderManager.LoaderCallbacks
- Loader
在 Android 應用中如何使用 Loaders
Start a Loader
LoaderManager
管理一個或多個 Loader 實例,在一個 Activity 或 Fragment 中夏跷,只能有一個 LoadManager哼转。
通常我們在一個 activity 的 onCreate()
方法或者一個 fragment 的 onActivityCreated()
方法中初始化一個 Loader。通過以下的方式:
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
initLoader()
的參數介紹:
- 一個唯一區(qū)分 Loader 的 ID槽华。
- 額外的參數壹蔓。
-
LoaderManager.LoaderCallbacks
的實現。LoaderManager
會調用他的回調函數來報告 loader 的相關事件猫态。在這個例子中佣蓉,我們在當前類中實現了這個接口,所以我們使用this
懂鸵。
initLoader()
保證了一個 loader 被初始化并且被激活. 這個方法的調用有兩種情況:
- 如果所指定的 ID 已經存在, 那么最后一次創(chuàng)建的 Loader 將會被重用.
- 如果所指定的 ID 不存在,那么
initLoader()
將會出發(fā)LoaderManager.LoaderCallbacks
中的方法onCreateLoader
來初始化 Loader.
initLoader()
返回一個 Loader 實例但是我們不用去獲取它, LoaderManager
將會自動管理它的生命周期. 所以, 我們很少直接與 Loader 進行交互,我們通常通過 LoaderManager.LoaderCallbacks
中的方法來交互.
Restarting a Loader
有時候, 我們想要拋棄掉舊的數據重新開始一個新的 Loader. 這時, 我們可以使用 restartLoader()
.
public boolean onQueryTextChanged(String newText) {
// Called when the action bar search text has changed. Update
// the search filter, and restart the loader to do a new query
// with this filter.
mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
getLoaderManager().restartLoader(0, null, this);
return true;
}
Using the LoaderManager Callbacks
LoaderManager.LoaderCallbacks
是回調接口,可以使客戶同 LoaderManager
進行交互.
LoaderManager.LoaderCallbacks
包含以下方法:
-
onCreateLoader()
: 在新建 Loader 對象的時候調用,返回一個 Loader 對象. -
onLoadFinished()
: 在 load 任務完成時調用. -
onLoaderReset()
: 當前 loader 被重置, 其數據不再有效時調用, 我們應當在此時釋放相關資源.
@Override
public Loader<List<Earthquake>> onCreateLoader(int id, Bundle args) {
Log.d(TAG, "TEST: onCreateLoader() method is called");
return new EarthquakeLoader(this, URL_ADDRESS);
}
@Override
public void onLoadFinished(Loader<List<Earthquake>> loader, List<Earthquake> data) {
// this means that we fetched new data from the server.
// 1. we clear the old data
// 2. we set new data, the data may be empty
// 3. make the spinner invisible
// 4. set empty text view
Log.d(TAG, "TEST: onLoadFinished() method is called");
mAdapter.clear();
if (data != null && !data.isEmpty()) {
mAdapter.addAll(data);
}
mLoadingSpinnner.setVisibility(View.INVISIBLE);
mEmptyStateTextView.setText(R.string.no_earthquakes);
}
@Override
public void onLoaderReset(Loader<List<Earthquake>> loader) {
// the data we hold is out of data, we clear the data.
Log.d(TAG, "TEST: OnLoaderReset() method is called");
mAdapter.clear();
}
AsyncTaskLoader
我們上面介紹了關于 Loader 的管理, 我們下面介紹一個具體的 Loader, AsyncTaskLoader.
AsyncTaskLoader
使用 AsyncTask
來執(zhí)行工作. 當我們使用 AsyncTaskLoader 時, 我們需要繼承他并且至少重寫方法 loadInBackground
, 這個方法是用來執(zhí)行用戶的任務的.
概括來說偏螺,Loader有兩大生命周期: active 和 inactive,即活動狀態(tài)和非活動狀態(tài)匆光。這一點在LoaderManager的源碼中體現的很直觀: LoaderManager內部有兩個數組mLoaders和mInactiveLoaders,其中mLoaders存儲著所有處于active狀態(tài)的Loader酿联,mInactiveLoaders存儲著所有處于inactive狀態(tài)的Loader终息。
細分來說,Loader的active狀態(tài)又分為 started 狀態(tài)和stopped 狀態(tài)贞让,即啟動狀態(tài)和停止狀態(tài)周崭,注意,stopped 狀態(tài)也是 active 生命周期的喳张。Loader的inactive狀態(tài)又分為 abandoned 狀態(tài)和 reseted 狀態(tài)续镇,即廢棄狀態(tài)和重置狀態(tài),其中 abandoned 狀態(tài)表明 Loader 現在已經廢棄了销部,過段時間也會被重置摸航,reseted 狀態(tài)表明該 Loader 完全被銷毀重用了。Loader活躍度從高到低依次為 started -> stopped -> abandoned -> reseted舅桩,這也對應著 Loader 的四個生命周期酱虎,Loader 處于 started 狀態(tài)時最活躍, Loader 處于 reseted 狀態(tài)時完全不活躍擂涛。實際上读串,處于 stopped 狀態(tài)的 Loader 也有可能再次轉變?yōu)?started 狀態(tài),所以我們要稍將上面 started 到 stopped 之間單向箭頭改為雙向箭頭,即Loader準確的生命周期應該是 started <-> stopped -> abandoned -> reseted恢暖。
啟動任務流程:
- 如果沒有我們的對象, 那么
LoaderManager
調用onCreateLoader()
, 我們在onCreateLoader
會調用我們需要的 Loader 的constructor, 返回相應的 Loader 對象. - 如果有了 Loader 對象, 系統(tǒng)會調用
startLoading()
方法, 進入 started 狀態(tài).startLoading()
方法中, 會調用onStartLoading()
方法. 我們可以重寫onStartLoading()
方法來實現我們的邏輯. - 我們可以在重寫的
onStartLoading
方法中調用forceLoad()
方法, 這是一種調用forceLoad()
的方法, 總之, 這個方法我們必須調用, 不然后臺不會執(zhí)行. Loader.forceLoad( ) -> AsyncTaskLoader.onForceLoad( ), 在 AsyncTaskLoader 的onForceLoad()
方法中, 進行后臺線程的運行, 執(zhí)行loadInBackground()
中的內容. - 在異步線程執(zhí)行完后, 會調用
onLoadFinished()
中的內容, 可以在這里更新 UI. - 在合適的時機, LoaderManager 會調用
stopLoading()
方法, 進入 stopped 狀態(tài). 在stopLoading()
方法中, 會調用onStopLoading()
方法, 我們可以重寫該方法來實現我們的邏輯. - 在需要清除數據的時候調用
onLoaderReset
,reset()
,onReset()
方法.
About UI
Empty state of ListView
有時候, 我們可能沒有獲取到的數據, 比如在郵箱 app 中, 我們的收信箱是空的, 或者我們的通訊錄一開始處于空的狀態(tài). 那么我們如果讓列表處于空白狀態(tài), 對用戶不是一個好的交互狀態(tài), Android 的開發(fā)者們?yōu)槲覀兛紤]到了這一點, 所以提供了方便的解決方案.
- 第一步, 我們需要在 ListView 的父視圖中, 添加一個子視圖, 就是我們想要在 ListView 空白的時候, 所顯示的內容.
- 然后, 我們在
onCreate()
中通過ListView.setEmptyView()
來設置空狀態(tài)的顯示內容. - 需要注意的是, 我們可以看到有很多應用, 在剛啟動的時候, 空白頁面會一閃而過, 這是因為, 我們應用獲取數據是異步獲取, 那么, 剛啟動應用的時候并沒有獲取數據, 就會默認顯示空白屏, 那么, 我們可以通過設置空白屏的可見性 (Visibility), 啟動時先將其設為不可見, 在異步進程中, 獲取數據完成的回調函數中, 我們再將其設為可見.
ProgressBar
在加載數據的過程中, 我們最好顯示應用的進度, 以給用戶以好的體驗. 我們可以使用 ProgressBar
來實現. 有兩種基本的進度條:
![](https://lh3.googleusercontent.com/-dFZA5Tq6SFo/WZWk6Y1VW_I/AAAAAAAAGqw/cQHMTtHZef8vCurqWbPrvBxwnL3eOpgEQCHMYCw/I/15029590572367.jpg)
Network state
Include Permission
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
檢查網絡狀態(tài):
// 檢測網絡狀態(tài)
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
boolean isConnected = networkInfo != null && networkInfo.isConnectedOrConnecting();