androidlooper
❶ Android-Looper
Looper.loop是一個死循環,拿不到需要處理的Message就會阻塞,那在UI線程中為什麼不會導致ANR?
首先我們來看造成ANR的原因:
1.當前的事件沒有機會得到處理(即主線程正在處理前一個事件,沒有及時的完成或者looper被某種原因阻塞住了)
2.當前的事件正在處理,但沒有及時完成
我們再來看一下APP的入口ActivityThread的main方法:
顯而易見的,如果main方法中沒有looper進行死循環,那麼主線程一運行完畢就會退出,會導致直接崩潰,還玩什麼!
現在我們知道了消息循環的必要性,那為什麼這個死循環不會造成ANR異常呢?
我們知道Android 的是由事件驅動的,looper.loop() 不斷地接收事件、處理事件,每一個點擊觸摸或者說Activity的生命周期都是運行在 Looper的控制之下,如果它停止了,應用也就停止了。只能是某一個消息或者說對消息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它,這也就是我們為什麼不能在UI線程中處理耗時操作的原因。
主線程Looper從消息隊列讀取消息,當讀完所有消息時,主線程阻塞。子線程往消息隊列發送消息,喚醒主線程,主線程被喚醒只是為了讀取消息,當消息讀取完畢,再次睡眠。因此loop的循環並不會對CPU性能有過多的消耗。
初始化當前線程和Looper,這樣可以在實際開始啟動循環(loop())之前創建一個Handler並且關聯一個looper。確保在先調用這個方法,然後調用loop()方法,並且通過調用quit()結束。
這裡面的入參boolean表示Looper是否允許退出,true就表示允許退出,對於false則表示Looper不允許退出。
初始化當前當前線程的looper。並且標記為一個程序的主Looper。由Android環境來創建應用程序的主Looper。因此這個方法不能由咱們來調用。另請參閱prepare()
這里的sThreadLocal.get()是和prepare(boolean)方法裡面的sThreadLocal.set(new Looper(quitAllowed));一一對應的。而在prepareMainLooper()方法裡面。
退出循環
將終止(loop()方法)而不處理消息隊列中的任何更多消息。在調用quit()後,任何嘗試去發送消息都是失敗的。例如Handler.sendMessage(Message)方法將返回false。因為循環終止之後一些message可能會被無法傳遞,所以這個方法是不安全的。可以考慮使用quitSafely()方法來確保所有的工作有序地完成。
安全退出循環
調用quitSafely()方法會使循環結束,只要消息隊列中已經被傳遞的所有消息都將被處理。然而,在循環結束之前,將來不會提交處理延遲消息。
調用退出後,所有嘗試去發送消息都將失敗。就像調用Handler.sendMessage(Message)將返回false。
❷ Android如何保證一個線程最多隻能有一個Looper
Looper的構造方法為private,所以不能直接使用其構造方法創建。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}1234
要想在當前線程創建Looper,需使用Looper的prepare方法,Looper.prepare()。
如果現在要我們來實現Looper.prepare()這個方法,我們該怎麼做?我們知道,Android中一個線程最多隻能有一個Looper,若在已有Looper的線程中調用Looper.prepare()會拋出RuntimeException(「Only one Looper may be created per thread」)。面對這樣的需求,我們可能會考慮使用一個HashMap,其中Key為線程ID,Value為與線程關聯的Looper,再加上一些同步機制,實現Looper.prepare()這個方法,代碼如下:
❸ android looper 怎麼理解
Android
中
Looper
和
handler
的聯系及處理
最近一直在看
Android
內核的代碼。把
Looper
及
Handler
這一節先分享一下:
Handle
主要起一個代理中介的作用,具體對象要向底層發請求,得通過
Handler
,
Handler
把這個請求放進對應的
MessageQueue
(消息隊列)中,而
MessageQueue
是由
Looper
來維護的。
Looper
中有一個循環,循環讀取
MessageQueue
中的請求,發給相應的底層處理(這個過程有點復雜)。當底層
有信息返回時,
也會先放進
MessageQueue
中,
然後對應的
Looper
遍歷,
交給對
應的
Handler
處理。
Handler
找出對應的回調函數或其他已注冊的相關方法,讓
其進行處理,最終在界面有一個反應。
以下是我寫的一個
demo
,
主要是如何關聯
Looper
、
Handler
、
以及對應的線程
(
MessageQueue
是
Looper
的一個成員)。
1.
測試方法入口:
testLooper1.java
package com.linquan.test.loop1;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
public class testLooper1 extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Log.d(MyConstant.TAG, "main start...");
new MyThread().start();
Message msg = new Message();
msg.what = MyConstant.CONSTANT3;
while(MyThread.h == null){
Log.d(MyConstant.TAG, "handler is not init ! please wait a
monment....");
try {
Thread.sleep(300);//
由於這里是測試,
所以加上此行代碼,
避免出錯。
實
際中多為用戶觸發事件,此時
handle
應該已經初始化了。
} catch (InterruptedException e) {
e.printStackTrace();
❹ 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消息處理機制對於應用程序開發非常重要,也可以讓我們對線程同步有更加深刻的認識,希望這篇文章可以對朋友們有所幫助。
❺ Android 系統運行機制 【Looper】【Choreographer】篇
目錄:
1 MessageQueue next()
2 Vsync
3 Choreographer doFrame
4 input
系統是一個無限循環的模型, Android也不例外,進程被創建後就陷入了無限循環的狀態
系統運行最重要的兩個概念:輸入,輸出。
Android 中輸入 輸出 的往復循環都是在 looper 中消息機制驅動下完成的
looper 的循環中, messageQueue next 取消息進行處理, 處理輸入事件, 進行輸出, 完成和用戶交互
應用生命周期內會不斷 產生 message 到 messageQueue 中, 有: java層 也有 native層
其中最核心的方法就是 messageQueue 的 next 方法, 其中會先處理 java 層消息, 當 java 層沒有消息時候, 會執行 nativePollOnce 來處理 native 的消息 以及監聽 fd 各種事件
從硬體來看, 屏幕不會一直刷新, 屏幕的刷新只需要符合人眼的視覺停留機制
24Hz , 連續刷新每一幀, 人眼就會認為畫面是流暢的
所以我們只需要配合上這個頻率, 在需要更新 UI 的時候執行繪制操作
如何以這個頻率進行繪制每一幀: Android 的方案是 Vsync 信號驅動。
Vsync 信號的頻率就是 24Hz , 也就是每隔 16.6667 ms 發送一次 Vsync 信號提示系統合成一幀。
監聽屏幕刷新來發送 Vsync 信號的能力,應用層 是做不到的, 系統是通過 jni 回調到 Choreographer 中的 Vsync 監聽, 將這個重要信號從 native 傳遞到 java 層。
總體來說 輸入事件獲取 Vsync信號獲取 都是先由 native 捕獲事件 然後 jni 到 java 層實現業務邏輯
執行的是 messageQueue 中的關鍵方法: next
next 主要的邏輯分為: java 部分 和 native 部分
java 上主要是取java層的 messageQueue msg 執行, 無 msg 就 idleHandler
java層 無 msg 會執行 native 的 pollOnce@Looper
native looper 中 fd 監聽封裝為 requestQueue, epoll_wait 將 fd 中的事件和對應 request 封裝為 response 處理, 處理的時候會調用 fd 對應的 callback 的 handleEvent
native 層 pollOnce 主要做的事情是:
vsync 信號,輸入事件, 都是通過這樣的機制完成的。
epoll_wait 機制 拿到的 event , 都在 response pollOnce pollInner 處理了
這里的 dispatchVsync 從 native 回到 java 層
native:
java:
收到 Vsync 信號後, Choreographer 執行 doFrame
應用層重要的工作幾乎都在 doFrame 中
首先看下 doFrame 執行了什麼:
UI 線程的核心工作就在這幾個方法中:
上述執行 callback 的過程就對應了圖片中 依次處理 input animation traversal 這幾個關鍵過程
執行的周期是 16.6ms, 實際可能因為一些 delay 造成一些延遲、丟幀
input 事件的整體邏輯和 vsync 類似
native handleEvent ,在 NativeInputEventReceiver 中處理事件, 區分不同事件會通過 JNI
走到 java 層,WindowInputEventReceiver 然後進行分發消費
native :
java:
input事件的處理流程:
輸入event deliverInputEvent
deliver的 input 事件會來到 InputStage
InputStage 是一個責任鏈, 會分發消費這些 InputEvent
下面以滑動一下 recyclerView 為例子, 整體邏輯如下:
vsync 信號到來, 執行 doFrame,執行到 input 階段
touchEvent 消費, recyclerView layout 一些 ViewHolder
scroll 中 fill 結束,會執行 一個 recyclerView viewProperty 變化, 觸發了invalidate
invalidate 會走硬體加速, 一直到達 ViewRootImpl , 從而將 Traversal 的 callback post choreographer執行到 traversal 階段就會執行
ViewRootImpl 執行 performTraversal , 會根據目前是否需要重新layout , 然後執行layout, draw 等流程
整個 input 到 traversal 結束,硬體繪制後, sync 任務到 GPU , 然後合成一幀。
交給 SurfaceFlinger 來顯示。
SurfaceFlinger 是系統進程, 每一個應用進程是一個 client 端, 通過 IPC 機制,client 將圖像顯示工作交給 SurfaceFlinger
launch 一個 app:
❻ android裡面所說的looper是什麼意思啊
Looper即:有消息循環的線程。
在Android里線程分為有消息循環的線程和沒有消息循環的線程,有消息循環的線程一般都會有一個Looper,這個事android的新概念。主線程(UI線程)就是一個消息循環的線程。針對這種消息循環的機制,引入一個新的機制Handle,有消息循環,就要往消息循環里 面發送相應的消息,自定義消息一般都會有對應的處理,消息的發送和清除,消息的處理,把這些都封裝在Handle裡面,注意Handle只是針對那些有Looper的線程,不管是UI線程還是子線程,只要有Looper,就可以往消息隊列裡面添加東西,並做相應的處理。