Android中的線程
本文轉自:http://www.jianshu.com/p/d59b3cce2b54
如何理解線程
在操作系統中,線程是操作系統調度的最小單元,同時線程又是一種受限的系統資源,即線程不可能無限制的產生,並且線程的創建和銷毀都會有相應的開銷,當系統中存在大量的線程時,系統會通過時間片輪轉的方式調度每個線程,在這麽多線程中有一個被稱為主線程,主線程是指進程所擁有的線程,在JAVA中默認情況下一個進程只有一個線程,這個線程就是主線程。主線程主要處理界面交互相關的邏輯,因為用戶隨時會和界面發生交互,因此主線程在任何時候都必須有比較高的響應速度,否則就會產生一種界面卡頓的感覺。為了保持較高的響應速度,這就要求主線程中不能執行耗時的任務,這個時候子線程就派上用場了。子線程也叫工作線程,除了主線程以外的線程都是子線程。
Android中的線程
Android沿用了JAVA的線程模型,其中的線程也分為主線程和子線程,其中主線程又叫UI線程。在Android系統中,在默認情況下,一個應用程序內的各個組件(如Activity、BroadcastReceiver、Service)都會在同一個進程(Process)裏執行,且由此進程的主線程負責執行。如果有特別指定(通過android:process),也可以讓特定組件在不同的進程中運行。無論組件在哪一個進程中運行,默認情況下,他們都由此進程的主線程負責執行。主線程既要處理Activity組件的UI事件,又要處理Service後臺服務工作,通常會忙不過來。為了解決此問題,主線程可以創建多個子線程來處理後臺服務工作,而本身專心處理UI畫面的事件。子線程的任務則是執行耗時任務,比如網絡請求,I/O操作等。從Android4.0開始系統要求網絡訪問必須在子線程中進行,否則網絡訪問將會失敗並拋出NetWorkOnMainThreadException這個異常,這樣做是為了避免主線程由於被耗時操作阻塞從而出現ANR現象。
為什麽會出現ANR
Android希望UI線程能根據用戶的要求做出快速響應,如果UI線程花太多時間處理後臺的工作,當UI事件發生時,讓用戶等待時間超過5秒而未處理,Android系統就會給用戶顯示ANR提示信息。主線程除了處理UI事件之外,還要處理Broadcast消息。所以在BroadcastReceiver的onReceive()函數中,不宜占用太長的時間,否則導致主線程無法處理其它的Broadcast消息或UI事件。如果占用時間超過10秒,Android系統就會給用戶顯示ANR提示信息。解決辦法自然還是解放UI主線程,將耗時操作交給子線程,避免阻塞。
Android中也有main()方法
剛接觸Android的開發者可能會因為找不到Java程序的執行入口main()方法而覺得疑惑,其實Android中當然是也有main()方法的(如下),它被包裝在源碼中的ActivityThread類裏。ActivityThread為應用程序的主線程類,所有的Apk程序都有且僅有一個ActivityThread類,程序的入口為該類中的static main()方法,ActivityThread所在的線程即為UI線程或主線程。Activity從main()方法開始執行,調用prepareMain()為UI線程創建一個消息隊列(MessageQueue)。然後創建一個ActivityThread對象,在ActivityThread的初始化代碼中會創建一個H(Handler)對象和一個ApplicationThread(Binder)對象。其中Binder負責接收遠程AmS的IPC調用,接收到調用後,則通過Hander把消息發送到消息隊列,UI主線程會異步地從消息隊列中取出消息並執行相應操作,比如start,pause,stop等。接著UI主線程調用Looper.loop()方法進入消息循環體,進入後就會不斷地從消息隊列中讀取並處理消息。
public static final void main(String[] args) {
SamplingProfilerIntegration.start();
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
if (sMainThreadHandler == null) {
sMainThreadHandler = new Handler();
}
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
if (Process.supportsProcesses()) {
throw new RuntimeException("Main thread loop unexpectedly exited");
}
thread.detach();
String name = (thread.mInitialApplication != null)
? thread.mInitialApplication.getPackageName()
: "<unknown>";
Slog.i(TAG, "Main thread of " + name + " is now exiting");
}
}
Android中的子線程
Android中開啟一個子線程無非還是這兩種方法
1:繼承Thread類
public class MyThread extends Thread {
public void run(){
}
}
newMyThread().start();
2:實現Runnable接口
public class MyRunnable implements Runnable{
@Override
public void run(){
//TODOAuto-generatedmethodstub
}
}
new MyThread().start();
Android APK程序中都有哪些線程?
通過debug,我們可以捕獲當前應用程序中的線程(如下圖),其中藍色選中部分即為當前應用程序的主線程,當前程序中還運行了三個Binder,每個Binder對象都對應一個線程,這些Binder線程主要負責接收Linux Binder驅動發送的IPC調用。除此以外還有Java中的守護線程和垃圾回收線程堆裁剪守護進程等在運行。
Paste_Image.png
程序中自定義Thread和UI線程的區別是什麽?
自定義Thread和UI線程的區別在於,UI線程是從ActivityThread運行的,在該類中的main()方法中,已經使用Looper.prepareMainLooper()為該線程添加了Looper對象,即已經為該線程創建了消息隊列(MessageQueue),因此,程序員才可以在Activity中定義Hander對象(因為聲明Hander對象時,所在的線程必須已經創建了MessageQueue)。而普通的自定義Thread是一個裸線程,因此,不能直接在Thread中定義Hander對象,從使用場景的角度講,即不能直接給Thread對象發消息,但卻可以給UI線程發消息。
子線程為什麽不能更新UI
因為UI訪問是沒有加鎖的,在多個線程中訪問UI是不安全的,如果有多個子線程都去更新UI,會導致界面不斷改變而混亂不堪。所以最好的解決辦法就是只有一個線程有更新UI的權限,所以這個時候就只能有一個線程振臂高呼:放開那女孩,讓我來!那麽最合適的人選只能是主線程。
子線程也可以更新UI
SurfaceView是 android 裏唯一一個可以在子線程更新的控件。SurfaceView可以在主線程之外的線程中向屏幕繪圖。這樣可以避免畫圖任務繁重的時候造成主線程阻塞,從而提高了程序的反應速度。當需要快速,主動地更新View的UI,或者當前渲染代碼阻塞GUI線程的時間過長的時候,SurfaceView就是解決上述問題的最佳選擇。
子線程可以更新除SurfaceView以外的UI
子線程更新UI?沒錯,不信下面的代碼跑一遍試試,並不會報錯,而且正確顯示。
public class MainActivity extends AppCompatActivity {
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView=(TextView)findViewById(R.id.textView);
new Thread(new Runnable() {
@Override
public void run() {
mTextView.setText("Child Thread");
}
}).start();
}
}
這是為什麽呢?一個應用程序中有一個主線程和若幹個子線程,而線程的檢查工作是由ViewRoot完成的。ViewRoot是什麽呢?可以簡單的理解為Window和View之前的橋梁或者紐帶。而ViewRoot的創建是在onResume()之後才完成的,也就是說在onResume()之前,系統本身是無法區分當前線程到底是主線程還是子線程,而上面的代碼中UI的更新操作在onCreate()中完成,先於onResume(),所以上述的子線程才有機會越俎代庖。
子線程如何與主線程通信
1、Activity.runOnUiThread(Runnable)
mHandle.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
// 耗時操作
loadNetWork();
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
mTextView.setText(來自網絡的文字);
}
});
}
});
}
});
2、 View.post(Runnable)
mHandle.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
// 耗時操作
loadNetWork();
mTextView.post(new Runnable() {
@Override
public void run() {
mTextView.setText(來自網絡的文字);
}
});
}
});
}
});
3、View.postDelayed(Runnable,long)
mHandle.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
// 耗時操作
loadNetWork();
mTextView.postDelayed(new Runnable() {
@Override
public void run() {
mTextView.setText(來自網絡的文字);
}
}, 10);
}
});
}
});
4、Handler(子線程調用Handler的
handle.sendMessage(msg);
Handler handle = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mTextView.setText(來自網絡的文字);
}
};
class MyThread extends Thread {
@Override
public void run() {
// 耗時操作
loadNetWork();
Message msg = new Message();
handle.sendMessage(msg);
super.run();
}
}
5、AsyncTask
主線程調用:
aTask ak = new aTask();
ak.execute();
AsyncTask
private class aTask extends AsyncTask {
//後臺線程執行時
@Override
protected Object doInBackground(Object... params) {
// 耗時操作
return loadNetWork();
}
//後臺線程執行結束後的操作,其中參數result為doInBackground返回的結果
@Override
protected void onPostExecute(Object result) {
super.onPostExecute(result);
mTextView.setText(result);
}
}
總結
最後來個總結,Android中的線程延續了JAVA的設計模型,默認一個應用程序只有一個主線程,主線程的開啟是在Activity的main()方法。主線程實際上是一個死循環,不斷的循環處理系統以及其他子線程發來的消息。主線程的綁定是在DecorView初始化的時候,也就是生命周期的onResume()之後。主線程主要處理UI操作,和Broadcast相關消息,主線程如果長時間無法響應,將出現ANR,為了避免ANR,耗時操作一般都開啟子線程處理。子線程處理完再發消息通知主線程來改變UI。
Android中的線程