如何理解線程
在操作系統(tǒng)中如捅,線程是操作系統(tǒng)調(diào)度的最小單元,同時線程又是一種受限的系統(tǒng)資源样傍,即線程不可能無限制的產(chǎn)生邮丰,并且線程的創(chuàng)建和銷毀都會有相應(yīng)的開銷,當(dāng)系統(tǒng)中存在大量的線程時铭乾,系統(tǒng)會通過時間片輪轉(zhuǎn)的方式調(diào)度每個線程剪廉,在這么多線程中有一個被稱為主線程,主線程是指進(jìn)程所擁有的線程炕檩,在JAVA中默認(rèn)情況下一個進(jìn)程只有一個線程斗蒋,這個線程就是主線程。主線程主要處理界面交互相關(guān)的邏輯笛质,因?yàn)橛脩綦S時會和界面發(fā)生交互泉沾,因此主線程在任何時候都必須有比較高的響應(yīng)速度,否則就會產(chǎn)生一種界面卡頓的感覺妇押。為了保持較高的響應(yīng)速度跷究,這就要求主線程中不能執(zhí)行耗時的任務(wù),這個時候子線程就派上用場了敲霍。子線程也叫工作線程俊马,除了主線程以外的線程都是子線程。
Android中的線程
Android沿用了JAVA的線程模型肩杈,其中的線程也分為主線程和子線程柴我,其中主線程又叫UI線程。在Android系統(tǒng)中扩然,在默認(rèn)情況下艘儒,一個應(yīng)用程序內(nèi)的各個組件(如Activity、BroadcastReceiver、Service)都會在同一個進(jìn)程(Process)里執(zhí)行界睁,且由此進(jìn)程的主線程負(fù)責(zé)執(zhí)行觉增。如果有特別指定(通過android:process),也可以讓特定組件在不同的進(jìn)程中運(yùn)行翻斟。無論組件在哪一個進(jìn)程中運(yùn)行抑片,默認(rèn)情況下,他們都由此進(jìn)程的主線程負(fù)責(zé)執(zhí)行杨赤。主線程既要處理Activity組件的UI事件敞斋,又要處理Service后臺服務(wù)工作,通常會忙不過來疾牲。為了解決此問題植捎,主線程可以創(chuàng)建多個子線程來處理后臺服務(wù)工作,而本身專心處理UI畫面的事件阳柔。子線程的任務(wù)則是執(zhí)行耗時任務(wù)焰枢,比如網(wǎng)絡(luò)請求,I/O操作等舌剂。從Android4.0開始系統(tǒng)要求網(wǎng)絡(luò)訪問必須在子線程中進(jìn)行济锄,否則網(wǎng)絡(luò)訪問將會失敗并拋出NetWorkOnMainThreadException這個異常,這樣做是為了避免主線程由于被耗時操作阻塞從而出現(xiàn)ANR現(xiàn)象霍转。
為什么會出現(xiàn)ANR
Android希望UI線程能根據(jù)用戶的要求做出快速響應(yīng)荐绝,如果UI線程花太多時間處理后臺的工作,當(dāng)UI事件發(fā)生時避消,讓用戶等待時間超過5秒而未處理低滩,Android系統(tǒng)就會給用戶顯示ANR提示信息。主線程除了處理UI事件之外岩喷,還要處理Broadcast消息恕沫。所以在BroadcastReceiver的onReceive()函數(shù)中,不宜占用太長的時間纱意,否則導(dǎo)致主線程無法處理其它的Broadcast消息或UI事件婶溯。如果占用時間超過10秒,Android系統(tǒng)就會給用戶顯示ANR提示信息偷霉。解決辦法自然還是解放UI主線程迄委,將耗時操作交給子線程,避免阻塞腾它。
Android中也有main()方法
剛接觸Android的開發(fā)者可能會因?yàn)檎也坏絁ava程序的執(zhí)行入口main()方法而覺得疑惑跑筝,其實(shí)Android中當(dāng)然是也有main()方法的(如下)死讹,它被包裝在源碼中的ActivityThread類里瞒滴。ActivityThread為應(yīng)用程序的主線程類,所有的Apk程序都有且僅有一個ActivityThread類,程序的入口為該類中的static main()方法妓忍,ActivityThread所在的線程即為UI線程或主線程虏两。Activity從main()方法開始執(zhí)行,調(diào)用prepareMain()為UI線程創(chuàng)建一個消息隊(duì)列(MessageQueue)世剖。然后創(chuàng)建一個ActivityThread對象定罢,在ActivityThread的初始化代碼中會創(chuàng)建一個H(Handler)對象和一個ApplicationThread(Binder)對象。其中Binder負(fù)責(zé)接收遠(yuǎn)程AmS的IPC調(diào)用旁瘫,接收到調(diào)用后祖凫,則通過Hander把消息發(fā)送到消息隊(duì)列,UI主線程會異步地從消息隊(duì)列中取出消息并執(zhí)行相應(yīng)操作酬凳,比如start,pause,stop等惠况。接著UI主線程調(diào)用Looper.loop()方法進(jìn)入消息循環(huán)體,進(jìn)入后就會不斷地從消息隊(duì)列中讀取并處理消息宁仔。
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:實(shí)現(xiàn)Runnable接口
public class MyRunnable implements Runnable{
@Override
public void run(){
//TODOAuto-generatedmethodstub
}
}
new MyThread().start();
Android APK程序中都有哪些線程稠屠?
通過debug,我們可以捕獲當(dāng)前應(yīng)用程序中的線程(如下圖)翎苫,其中藍(lán)色選中部分即為當(dāng)前應(yīng)用程序的主線程权埠,當(dāng)前程序中還運(yùn)行了三個Binder,每個Binder對象都對應(yīng)一個線程煎谍,這些Binder線程主要負(fù)責(zé)接收Linux Binder驅(qū)動發(fā)送的IPC調(diào)用攘蔽。除此以外還有Java中的守護(hù)線程和垃圾回收線程堆裁剪守護(hù)進(jìn)程等在運(yùn)行。
程序中自定義Thread和UI線程的區(qū)別是什么呐粘?
自定義Thread和UI線程的區(qū)別在于秩彤,UI線程是從ActivityThread運(yùn)行的,在該類中的main()方法中事哭,已經(jīng)使用Looper.prepareMainLooper()為該線程添加了Looper對象漫雷,即已經(jīng)為該線程創(chuàng)建了消息隊(duì)列(MessageQueue),因此鳍咱,程序員才可以在Activity中定義Hander對象(因?yàn)槁暶鱄ander對象時降盹,所在的線程必須已經(jīng)創(chuàng)建了MessageQueue)。而普通的自定義Thread是一個裸線程谤辜,因此蓄坏,不能直接在Thread中定義Hander對象,從使用場景的角度講丑念,即不能直接給Thread對象發(fā)消息涡戳,但卻可以給UI線程發(fā)消息。
子線程為什么不能更新UI
因?yàn)閁I訪問是沒有加鎖的脯倚,在多個線程中訪問UI是不安全的渔彰,如果有多個子線程都去更新UI嵌屎,會導(dǎo)致界面不斷改變而混亂不堪。所以最好的解決辦法就是只有一個線程有更新UI的權(quán)限恍涂,所以這個時候就只能有一個線程振臂高呼:放開那女孩宝惰,讓我來!那么最合適的人選只能是主線程再沧。
子線程也可以更新UI
SurfaceView是 android 里唯一一個可以在子線程更新的控件尼夺。SurfaceView可以在主線程之外的線程中向屏幕繪圖。這樣可以避免畫圖任務(wù)繁重的時候造成主線程阻塞炒瘸,從而提高了程序的反應(yīng)速度淤堵。當(dāng)需要快速,主動地更新View的UI,或者當(dāng)前渲染代碼阻塞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();
}
}
這是為什么呢乘陪?一個應(yīng)用程序中有一個主線程和若干個子線程,而線程的檢查工作是由ViewRoot完成的雕擂。ViewRoot是什么呢啡邑?可以簡單的理解為Window和View之前的橋梁或者紐帶。而ViewRoot的創(chuàng)建是在onResume()之后才完成的井赌,也就是說在onResume()之前谤逼,系統(tǒng)本身是無法區(qū)分當(dāng)前線程到底是主線程還是子線程,而上面的代碼中UI的更新操作在onCreate()中完成仇穗,先于onResume()流部,所以上述的子線程才有機(jī)會越俎代庖。
子線程如何與主線程通信
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(來自網(wǎng)絡(luò)的文字);
}
});
}
});
}
});
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(來自網(wǎng)絡(luò)的文字);
}
});
}
});
}
});
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(來自網(wǎng)絡(luò)的文字);
}
}, 10);
}
});
}
});
4耘子、Handler(子線程調(diào)用Handler的
handle.sendMessage(msg);
Handler handle = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mTextView.setText(來自網(wǎng)絡(luò)的文字);
}
};
class MyThread extends Thread {
@Override
public void run() {
// 耗時操作
loadNetWork();
Message msg = new Message();
handle.sendMessage(msg);
super.run();
}
}
5果漾、AsyncTask
主線程調(diào)用:
aTask ak = new aTask();
ak.execute();
AsyncTask
private class aTask extends AsyncTask {
//后臺線程執(zhí)行時
@Override
protected Object doInBackground(Object... params) {
// 耗時操作
return loadNetWork();
}
//后臺線程執(zhí)行結(jié)束后的操作,其中參數(shù)result為doInBackground返回的結(jié)果
@Override
protected void onPostExecute(Object result) {
super.onPostExecute(result);
mTextView.setText(result);
}
}
總結(jié)
最后來個總結(jié)谷誓,Android中的線程延續(xù)了JAVA的設(shè)計(jì)模型绒障,默認(rèn)一個應(yīng)用程序只有一個主線程,主線程的開啟是在Activity的main()方法捍歪。主線程實(shí)際上是一個死循環(huán)户辱,不斷的循環(huán)處理系統(tǒng)以及其他子線程發(fā)來的消息鸵钝。主線程的綁定是在DecorView初始化的時候,也就是生命周期的onResume()之后焕妙。主線程主要處理UI操作蒋伦,和Broadcast相關(guān)消息弓摘,主線程如果長時間無法響應(yīng)焚鹊,將出現(xiàn)ANR,為了避免ANR韧献,耗時操作一般都開啟子線程處理末患。子線程處理完再發(fā)消息通知主線程來改變UI。
本文首發(fā):CSDN锤窑,次發(fā):簡書
相關(guān)閱讀:
我是一個線程
線程璧针、多線程與線程池總結(jié)