㈠ 這些關於 Handler 的知識點你都知道嗎
在安卓面試中,關於Handler的問題是必考的,那麼這些關於Handler的知識點你都知道嗎?讓我們逐一探索Handler的奧秘。
關於Handler的原理,大家都應該有所了解,一張圖就可以說明。
在子線程中使用Handler需要先執行兩個操作:Looper.prepare和Looper.loop。為何需要這兩個操作?因為Handler的構造函數會對Looper進行判斷,如果ThreadLocal獲取的Looper為空,就會報錯。那麼,Looper.prepare做了什麼?它創建了Looper並設置給ThreadLocal,每個Thread只能有一個Looper,否則會拋出異常。而Looper.loop則是開始讀取MessageQueue中的消息並執行。
Looper.loop實際上就是開始讀取MessageQueue中的消息。如果MessageQueue中沒有消息,Looper會做什麼?它會等待消息。那麼,它是如何等待的呢?通過Looper.loop方法,我們知道是MessageQueue.next()來獲取消息的,如果沒有消息,會阻塞在這里。具體來說,MessageQueue.next調用了native方法nativePollOnce。
在android 2.2及以前,確實使用java的wait/notify進行阻塞等待消息。但隨著版本更新,改為使用epoll機制,主要原因是需要處理native側的事件,僅使用Java的wait/notify不夠。具體改動見commit記錄。
一個線程對應一個Looper,一個Looper對應一個MessageQueue,多個Handler共享一個MessageQueue。
保證線程安全的方式是為MessageQueue加鎖。
Handler處理延遲消息的機制在post一個延遲消息時,會將uptimeMillis和delayMillis相加作為when參數進行順序排序。執行流程包括創建Message並設置參數,將其加入MessageQueue,等待執行。
View.post與Handler.post本質上都通過Handler進行消息處理。區別在於View.post最終也是通過Handler.post執行,其執行過程包含在ViewRootImpl的實現中。
內存泄漏問題與Handler息息相關,通常涉及內存管理與生命周期,如何排查與避免需要綜合考慮。
在非UI線程操作View時,確實存在限制。這主要是因為ViewRootImpl在主線程創建,檢查創建線程與當前線程是否一致,因此非主線程無法直接操作UI。
本文總結了Handler相關的關鍵知識點,希望對你的學習和面試有所幫助。更多資源和面試題整理在GitHub上,歡迎查閱。
㈡ Android之Looper使用
Looper是Android中的一個類,用於為線程提供消息循環。在Android中,主線程已經默認開啟了一個Looper,因此可以直接使用Handler來發送消息。但是對於其他線程,如果需要使用Handler來發送消息,就需要先創建一個Looper。
以下是使用Looper的步驟:
1. 在子線程中創建一個Looper對象,並調用Looper的prepare()方法和Looper的loop()方法,這樣就可以為該線程創建一個消息循環。
```java
public class MyThread extends Thread {
public Handler mHandler;
public void run() {
// 創建Looper對象
Looper.prepare();
// 創建Handler對象
mHandler = new Handler() {
public void handleMessage(Message msg) {
// 處理消息
}
};
// 進入消息循環
Looper.loop();
}
}
```
2. 在主線程或其他線程中,可以通過Handler向該線程發送消息。
```java
MyThread thread = new MyThread();
thread.start();
// 向子線程發送消息
thread.mHandler.sendEmptyMessage(1);
```
在使用完Looper之後,需要調用Looper的quit()方法來退出消息循環。
```java
Looper.myLooper().quit();
```
需要注意的是,Looper是一個輪詢消息隊列的無限循環,如果沒有消息需要處理,會一直阻塞在loop()方法處,因此需要謹慎使用,避免出現死循環或內存泄漏等問題。
㈢ android中looper的實現原理,為什麼調用looper.prepare就在當前線程關聯了一個lo
實際上:消息發送和計劃任務提交之後,它們都會進入某線程的消息隊列中,我們可以把這個線程稱之為目標線程。不論是主線程還是子線程都可以成為目標線程。上例中之所以在主線程中處理消息,是因為我們要更新UI,按照android中的規定我們必須由主線程更新UI。所以我們讓主線程成為了目標線程。
那麼如何控制讓某個線程成為目標線程呢?
這就引出了Looper的概念。Android系統中實現了消息循環機制,Android的消息循環是針對線程的,每個線程都可以有自己的消息隊列和消息循環。Android系統中的通過Looper幫助線程維護著一個消息隊列和消息循環。通過Looper.myLooper()得到當前線程的Looper對象,通過Looper.getMainLooper()得到當前進程的主線程的Looper對象。
前面提到每個線程都可以有自己的消息隊列和消息循環,然而我們自己創建的線程默認是沒有消息隊列和消息循環的(及Looper),要想讓一個線程具有消息處理機制我們應該在線程中先調用Looper.prepare()來創建一個Looper對象,然後調用Looper.loop()進入消息循環。如上面的源碼所示。
當我們用Handler的構造方法創建Handler對象時,指定handler對象與哪個具有消息處理機制的線程(具有Looper的線程)相關聯,這個線程就成了目標線程,可以接受消息和計劃任務了。Handler中的構造方法如下:
[java] view
plainprint?
public Handler() {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = null;
}
public Handler(Looper looper) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = null;
}
public Handler() {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = null;
}
public Handler(Looper looper) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = null;
}
在上述的計時器的例子中,之所以可以在主線程中處理消息而我們自己並沒有調用Looper.prepare()等方法,是因為Android系統在Activity啟動時為其創建一個消息隊列和消息循環,當我們用無參的Handler構造方法創建對象時又用了當前線程的Looper對象,及將handler與主線程中的Looper對象進行了關聯。
android中是使用Looper機制來完成消息循環的,但每次創建線程時都先初始化Looper比較麻煩,因此Android為我們提供了一個HandlerThread類,他封裝了Looper對象,是我們不用關心Looper的開啟和釋放問題。
不管是主線程還是其他線程只要有Looper的線程,別的線程就可以向這個線程的消息隊列中發送消息和任務。
我們使用HandlerThread類代替上一篇文章中的子線程,並用HandlerThread類中的Looper對象構造Handler,則接受消息的目標線程就不是主線程了,而是HandlerThread線程。代碼如下:
[java] view
plainprint?
public class clockActivity extends Activity {
/** Called when the activity is first created. */
private String TAG="clockActivity";
private Button endButton;
private TextView textView;
private int timer=0;
private boolean isRunning=true;
private Handler handler;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
endButton=(Button)findViewById(R.id.endBtn);
textView=(TextView)findViewById(R.id.textview);
endButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
isRunning=false;
}
});
HandlerThread thread=new HandlerThread("myThread");
handler=new Handler(thread.getLooper());//與HandlerThread中的Looper對象關聯
thread.start();
Runnable r=new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
if(isRunning){
textView.setText("走了"+timer+"秒");
timer++;
handler.postDelayed(this, 1000);//提交任務r,延時1秒執行
}
}
};
handler.postDelayed(r, 1000);
}
}
public class clockActivity extends Activity {
/** Called when the activity is first created. */
private String TAG="clockActivity";
private Button endButton;
private TextView textView;
private int timer=0;
private boolean isRunning=true;
private Handler handler;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
endButton=(Button)findViewById(R.id.endBtn);
textView=(TextView)findViewById(R.id.textview);
endButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
isRunning=false;
}
});
HandlerThread thread=new HandlerThread("myThread");
handler=new Handler(thread.getLooper());//與HandlerThread中的Looper對象關聯
thread.start();
Runnable r=new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
if(isRunning){
textView.setText("走了"+timer+"秒");
timer++;
handler.postDelayed(this, 1000);//提交任務r,延時1秒執行
}
}
};
handler.postDelayed(r, 1000);
}
}
此時處理任務會在handlerThread線程中完成。當然這個例子會出線異常:依然是因為在非主線程中更新了UI。這樣做只是為了大家能夠理解這種機制。
深入理解Android消息處理機制對於應用程序開發非常重要,也可以讓我們對線程同步有更加深刻的認識,希望這篇文章可以對朋友們有所幫助。