导航:首页 > 操作系统 > android使用looper

android使用looper

发布时间:2025-05-25 09:13:54

‘壹’ 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中的一个类,用于为线程提供消息循环。在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消息处理机制对于应用程序开发非常重要,也可以让我们对线程同步有更加深刻的认识,希望这篇文章可以对朋友们有所帮助。

阅读全文

与android使用looper相关的资料

热点内容
查看硬盘空间linux 浏览:387
mc的服务器如何备战 浏览:946
网络编程具体做什么 浏览:863
面包车app和快递哪个好 浏览:484
android卸载app命令 浏览:277
方块方舟如何跨私人服务器 浏览:218
编译器打不开了 浏览:410
腾讯云服务器已登录 浏览:178
笔记本文件夹不能加密 浏览:218
单片机程序执行时间计算公式 浏览:926
keil5编译显示找不到头文件 浏览:166
android保存图片到相册不显示图片 浏览:330
照片在文件夹怎么发抖音 浏览:900
什么app可以交房产税 浏览:913
单片机9脚排阻 浏览:554
陈意云张昱第3版编译原理 浏览:535
台湾最可爱程序员 浏览:470
区分加密ic卡 浏览:922
可编程控制器实验指导书 浏览:515
服务器老是崩服怎么办 浏览:353