Processes and Threads

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 線程中完成。

  1. Do not block the UI thread
  2. 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:

  1. Params, the type of the parameters sent to the task upon execution.
  2. Progress, the type of the progress units published during the background computation.
  3. 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有兩大生命周期: activeinactive,即活動狀態(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),所以我們要稍將上面 startedstopped 之間單向箭頭改為雙向箭頭,即Loader準確的生命周期應該是 started <-> stopped -> abandoned -> reseted恢暖。

啟動任務流程:

  1. 如果沒有我們的對象, 那么 LoaderManager 調用 onCreateLoader(), 我們在 onCreateLoader 會調用我們需要的 Loader 的constructor, 返回相應的 Loader 對象.
  2. 如果有了 Loader 對象, 系統(tǒng)會調用 startLoading() 方法, 進入 started 狀態(tài). startLoading() 方法中, 會調用 onStartLoading() 方法. 我們可以重寫 onStartLoading() 方法來實現我們的邏輯.
  3. 我們可以在重寫的 onStartLoading 方法中調用 forceLoad() 方法, 這是一種調用 forceLoad() 的方法, 總之, 這個方法我們必須調用, 不然后臺不會執(zhí)行. Loader.forceLoad( ) -> AsyncTaskLoader.onForceLoad( ), 在 AsyncTaskLoader 的 onForceLoad() 方法中, 進行后臺線程的運行, 執(zhí)行 loadInBackground() 中的內容.
  4. 在異步線程執(zhí)行完后, 會調用 onLoadFinished() 中的內容, 可以在這里更新 UI.
  5. 在合適的時機, LoaderManager 會調用 stopLoading() 方法, 進入 stopped 狀態(tài). 在 stopLoading() 方法中, 會調用 onStopLoading() 方法, 我們可以重寫該方法來實現我們的邏輯.
  6. 在需要清除數據的時候調用 onLoaderReset, reset(), onReset() 方法.

About UI

Empty state of ListView

有時候, 我們可能沒有獲取到的數據, 比如在郵箱 app 中, 我們的收信箱是空的, 或者我們的通訊錄一開始處于空的狀態(tài). 那么我們如果讓列表處于空白狀態(tài), 對用戶不是一個好的交互狀態(tài), Android 的開發(fā)者們?yōu)槲覀兛紤]到了這一點, 所以提供了方便的解決方案.

  1. 第一步, 我們需要在 ListView 的父視圖中, 添加一個子視圖, 就是我們想要在 ListView 空白的時候, 所顯示的內容.
  2. 然后, 我們在 onCreate() 中通過 ListView.setEmptyView() 來設置空狀態(tài)的顯示內容.
  3. 需要注意的是, 我們可以看到有很多應用, 在剛啟動的時候, 空白頁面會一閃而過, 這是因為, 我們應用獲取數據是異步獲取, 那么, 剛啟動應用的時候并沒有獲取數據, 就會默認顯示空白屏, 那么, 我們可以通過設置空白屏的可見性 (Visibility), 啟動時先將其設為不可見, 在異步進程中, 獲取數據完成的回調函數中, 我們再將其設為可見.

ProgressBar

在加載數據的過程中, 我們最好顯示應用的進度, 以給用戶以好的體驗. 我們可以使用 ProgressBar 來實現. 有兩種基本的進度條:

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();
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末排监,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子杰捂,更是在濱河造成了極大的恐慌舆床,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件琼娘,死亡現場離奇詭異峭弟,居然都是意外死亡,警方通過查閱死者的電腦和手機脱拼,發(fā)現死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門瞒瘸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人熄浓,你說我怎么就攤上這事情臭。” “怎么了赌蔑?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵俯在,是天一觀的道長。 經常有香客問我娃惯,道長跷乐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任趾浅,我火速辦了婚禮愕提,結果婚禮上,老公的妹妹穿的比我還像新娘皿哨。我一直安慰自己浅侨,他們只是感情好,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布证膨。 她就那樣靜靜地躺著如输,像睡著了一般。 火紅的嫁衣襯著肌膚如雪央勒。 梳的紋絲不亂的頭發(fā)上不见,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天,我揣著相機與錄音订歪,去河邊找鬼脖祈。 笑死,一個胖子當著我的面吹牛刷晋,可吹牛的內容都是我干的盖高。 我是一名探鬼主播慎陵,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼喻奥!你這毒婦竟也來了席纽?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤撞蚕,失蹤者是張志新(化名)和其女友劉穎润梯,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體甥厦,經...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡纺铭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了刀疙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舶赔。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖谦秧,靈堂內的尸體忽然破棺而出竟纳,到底是詐尸還是另有隱情,我是刑警寧澤疚鲤,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布锥累,位于F島的核電站,受9級特大地震影響集歇,放射性物質發(fā)生泄漏桶略。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一诲宇、第九天 我趴在偏房一處隱蔽的房頂上張望删性。 院中可真熱鬧,春花似錦焕窝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至溯泣,卻和暖如春虐秋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背垃沦。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工客给, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人肢簿。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓靶剑,卻偏偏與公主長得像蜻拨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子桩引,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,152評論 25 707
  • java 接口的意義-百度 規(guī)范缎讼、擴展、回調 抽象類的意義-樂視 為其子類提供一個公共的類型封裝子類中得重復內容定...
    交流電1582閱讀 2,231評論 0 11
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理坑匠,服務發(fā)現血崭,斷路器,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • 1 背景## 在Android中任何耗時的操作都不能放在UI主線程中厘灼,所以耗時的操作都需要使用異步實現夹纫。同樣的,在...
    我是昵稱閱讀 1,219評論 0 3
  • 閉上眼设凹,就憶起過去的瑣事舰讹,雖然它們已成為封塵的記憶,但我仍想將它們撫凈围来,細細品味當時的想法跺涤,嘲笑那時的我的懵懂〖嗤福總...
    琴斷弦奈何閱讀 233評論 0 2