浅析Android中的Handler

一、快速了解Handler

作为一个初学Android的小白,在代码中看到Handler时难免会一头雾水,不知道Handler是什么、有什么作用。本章通过回答What、Why、How,来帮助读者快速了解Handler。

1.1 场景

先来看一下这么个简单场景:页面中有一个Button和一个TextView,点击Button后开始下载文件,下载完成后在TextView显示下载开始时间和下载完成时间。

简单实现

界面如下:

image.png

代码如下:

public class MainActivity extends AppCompatActivity {
    TextView mTvInfo;
    Button mBtnDownload;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTvInfo = findViewById(R.id.tv_info);
        mTvInfo.setText("点击下方按钮开始下载");
        mBtnDownload = findViewById(R.id.btn_download);
        mBtnDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String beginInfo = "下载开始 " + getCurrentTime();
                
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String finishInfo = "下载完成 " + getCurrentTime();
                mTvInfo.setText(beginInfo + "n" + finishInfo);
            }
        });
    }

    public String getCurrentTime() {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
        Date date = new Date();
        return dateFormat.format(date);
    }
}

上面这段代码是有问题的,按下按钮后,整个界面会卡住,无法操作。Android开发中,在主线程(UI线程)上休眠,会导致应用无响应(ANR)。

因此,在Android开发中,耗时操作(如网络请求、数据库操作、复杂计算等)应该在后台线程上执行,以避免阻塞主线程,这可以防止应用出现界面卡顿或ANR的问题。

将下载任务放在后台线程执行

既然这样,就在点击按钮后开启一个子线程执行下载任务吧~

public class MainActivity extends AppCompatActivity {
    TextView mTvInfo;
    Button mBtnDownload;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTvInfo = findViewById(R.id.tv_info);
        mTvInfo.setText("点击下方按钮开始下载");
        mBtnDownload = findViewById(R.id.btn_download);
        mBtnDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Thread downloadThread =  new Thread(){
                    @Override
                    public void run() {
                        String beginInfo = "下载开始 " + getCurrentTime();
                        
                        try {
                            Thread.sleep(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        String finishInfo = "下载完成 " + getCurrentTime();
                        mTvInfo.setText(beginInfo + "n" + finishInfo);
                    }
                };
                downloadThread.start();  
            }
        });
    }
}

这样就可以正常运行了吗?

很抱歉,5s过后,程序崩溃了

image.png

报错android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. ,这个错误信息表示你试图在一个非UI线程(也就是不是创建视图层次结构的原始线程)中直接操作UI组件。

在Android中,UI操作(如修改视图属性、添加/移除视图等)必须要在主线程(也称为UI线程)中进行。如果你在子线程中进行这些操作,就会抛出这个异常。

也就是说,在downloadThread里不能执行setText()修改文本框内容,必须回到主线程才行。所以,需要通过一种方式,实现在后台进行耗时操作,操作结束后回到主线程修改UI。

这时,Handler就呼之欲出了

1.2 What:Handler是什么

Handler 是 Android 中用于线程间通信的重要机制,用于主线程(UI 线程)和工作线程之间传递消息和执行任务。

Handler 允许开发者从后台线程发送消息到主线程,或者在主线程上延时执行某些任务。

Handler 通常与 MessageQueue 和 Looper 一起工作,形成一个完整的异步消息处理机制。

  • MessageQueue:存储待处理的消息队列。
  • Looper:从 MessageQueue 中取出消息并分发给对应的 Handler 处理。
  • Message:包含数据的对象,可以通过 Handler 发送并在目标 Handler 中处理。

1.3 Why:为什么要用Handler

在 Android 中,开发者不能直接从子线程中更新 UI,所有 UI 操作必须在主线程(UI线程)上执行。如果子线程需要更新 UI 或与主线程通信,Handler 提供了一个安全的方式将任务传递到主线程执行。同时,Handler 也可以延时执行任务或周期性执行任务。

  • 跨线程通信: 在多线程环境中,用于线程间的消息传递。
  • UI 更新: 从子线程传递任务到主线程,更新 UI。
  • 定时任务: 实现定时任务和延时任务。

1.4 How:怎么使用Handler

创建 Handler 来处理消息

为主线创建一个handler,重写handlMessage方法,用于处理消息。程序执行耗时任务时,开启一个子线程,在任务执行完成后,子线程通过handler.sendMessage(message)将消息发回主线程的消息队列,消息最终会在主线程中被handlerMessage处理,可以实现修改UI。


Handler handler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        
        if (msg.what == 1) {
            
            textView.setText("Message Received");
        }
    }
};


new Thread(() -> {
    Message message = handler.obtainMessage();
    message.what = 1;
    handler.sendMessage(message); 
}).start();

使用 Runnable 发送任务

post() 方法直接接受一个 Runnable 对象,当 Runnable 被执行时,它会在主线程中运行。

使用 Handler 的 post 方法可以简化代码,直接将一个 Runnable 对象排队,等待主线程处理,不需要管理 Message 的创建和发送。


handler.post(new Runnable() {
    @Override
    public void run() {
        
        textView.setText("Task Executed");
    }
});

延时执行任务

postDelayed方法可以延时执行任务,例如打开APP时会在欢迎界面停留3s,然后进入登录界面,就可以在欢迎界面里使用postDelayd,3s后跳转到登录界面。


handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        
    }
}, 3000);

在后台线程中使用 Handler

如果你需要在后台线程中使用 Handler,你需要为该线程准备一个 Looper:


new Thread(new Runnable() {
    @Override
    public void run() {
        
        Looper.prepare();

        
        Handler backgroundHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                
                switch (msg.what) {
                    case 1:
                        String data = (String) msg.obj;
                        Log.d("BackgroundThread", "Received: " + data);
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        };

        
        Looper.loop();
    }
}).start();


Message message = Message.obtain();
message.what = 1; 
message.obj = "Data from Main Thread"; 
backgroundHandler.sendMessage(message);

1.5 回到开始的场景

通过以上的介绍,大家应该对Handler有了初步的了解。现在回到最开始的场景,我们使用Handler来实现这个Demo,代码如下:

public class MainActivity extends AppCompatActivity {
    TextView mTvInfo;
    Button mBtnDownload;
    Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new Handler();
        mTvInfo = findViewById(R.id.tv_info);
        mTvInfo.setText("点击下方按钮开始下载");
        mBtnDownload = findViewById(R.id.btn_download);
        mBtnDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Thread downloadThread =  new Thread(){
                    @Override
                    public void run() {
                        String beginInfo = "下载开始 " + getCurrentTime();
                        
                        try {
                            Thread.sleep(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        String finishInfo = "下载完成 " + getCurrentTime();
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                
                                mTvInfo.setText(beginInfo + "n" + finishInfo);
                            }
                        });

                    }
                };
                downloadThread.start();
            }
        });
    }
}

现在这个Demo就可以模拟在不阻塞主线程的情况下,执行耗时的下载任务了

二、继续深挖Handler

第一章中介绍了与Handler相关的另外几个概念:MessageQueue,Looper,Message。

通过下面这张图来形象地理解一下这几个类:

  • Message:货物,在子线程(主线程也行)中创建后,通过 Handler放置到传送带上。
  • MessageQueue:传送带,货物可以在上面存放和取出。
  • Looper:电机,驱动传动带前进,依次取出最前面的货物,分发给对应的 Handler 处理。

image.png

从图中可以看出,Handler是主线程和子线程之间的媒介,实现线程间的消息传递和任务执行。

2.1 Handler

首先对于Handler,我们需要探究两个问题:

  • Handler如何传递消息
  • Handler如何执行任务

先来看一下Handler的是怎么创建的


Handler的创建

大部分开发者使用的应该都是Handler的无参构造函数,而在Android 11中Handler的无参构造函数已经被标记为废弃的了,如下图所示,删除线表示废弃的方法。

image.png

Google 官方更推荐的做法是通过显式传入 Looper 对象来完成初始化,而非隐式使用当前线程关联的 Looper。Looper对象可以有两种方式:

  • Looper.getMainLooper()
  • Looper.myLooper()

image.png

具体细节放在后面介绍Looper时展开说明,这里我们只要知道传入Looper对象的目的是:
将Handler与执行任务的线程绑定

结合本章开始的那幅图,可以形象地理解为要将Handler与一条传送带绑定,指定Handler处理这条传动带上依次传送的货物。即mQueue = looper.mQueue;。


@UnsupportedAppUsage
final Looper mLooper;
final MessageQueue mQueue;
@UnsupportedAppUsage
final Callback mCallback;
final boolean mAsynchronous;
@UnsupportedAppUsage
IMessenger mMessenger;


@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

初步了解了Handler的创建,接下来看一下Handler如何发送消息。


Handler如何发送消息

还是看一下本章开始的示意图,在子线程生产了一个货物,需要投递到对应的传送带上,这个就是发送消息的过程。

Handler中发送消息的方法有很多个,大部分最终调用的都是sendMessageAtTime()方法。uptimeMillis即消息具体要执行的时间戳,如果该时间戳比当前时间大,那么就意味着要执行的是延迟任务。sendMessageAtTime()会调用enqueueMessage()将消息投递到消息队列里(也就是将货物放到传送带上),注意下面代码的第16行msg.target = this;,target指向了Handler对象自己,也就是说,Handler自己投递的消息,最终要由自己来处理。

 public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}



private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    
    return queue.enqueueMessage(msg, uptimeMillis);
}

一般发送消息主要会用两个方法:

二者的区别是,sendMessage()发送消息需要显式创建Message对象;post()发送消息不需要显式创建Message对象,但是需要实现任务的执行逻辑。


new Thread(new Runnable() { 
    @Override 
    public void run() { 
        
        downloadFile(); 
        
        Message message = handler.obtainMessage(1); 
        handler.sendMessage(message); 
        } 
    }).start();
    
    

new Thread(new Runnable() {
    @Override
    public void run() {
        
        downloadFile();
        
        handler.post(new Runnable() {
            @Override
            public void run() {
                
                textView.setText("下载完成");
            }
        });
    }
}).start();

那么,Hanlder如何处理消息?


Handle如何执行任务

传动带上的货物被取出时,如果货物自带了说明书,就按照说明书进行处理,否则就按照提前制定的规则,根据货物类型进行相应的处理

消息队列里的消息会通过Handler的dispatchMessage()方法进行分发,进行相应的处理:

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}


public void handleMessage(@NonNull Message msg) {
}
  • 如果该 Message 是通过 post(Runnable)等方法进行发送的,那么就直接回调该 Runnable 对象
  • 如果在初始化 Handler 时传入了 Callback 对象,则优先交由其处理,如果 Callback 的 handleMessage 方法返回了 true,则结束流程
  • 调用 Handler 的handleMessage 方法来处理消息,外部通过重写该方法来定义业务逻辑

一、 通过Runnable发送的消息,实际上也会被封装成Message,最后执行在Runnable中重写的run()方法。

public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

二、 Handler中有一个Callback对象,它的注释 “Callback interface you can use when instantiating a Handler to avoid having to implement your own subclass of Handler”的意思是当你创建一个 Handler 实例时,你可以使用回调接口(callback interface)来避免自己实现 Handler 的子类。


public interface Callback {
    
    boolean handleMessage(@NonNull Message msg);
}

Handler 类有一个构造函数,允许传入一个 Callback 接口的实例。这个接口只有一个方法 handleMessage,你需要实现这个方法来处理消息。代码示例如下:

public class MyActivity extends AppCompatActivity {

    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        
        mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                
                switch (msg.what) {
                    case 1:
                        String data = (String) msg.obj;
                        Log.d("MyActivity", "Received: " + data);
                        break;
                    default:
                        return false; 
                }
                return true; 
            }
        });
    }

    
}

三、 在创建 Handler 时,需要重写 handleMessage() 方法,通过 sendMessage() 发送消息时,会指定消息的类型 msg.what,在 handleMessage() 里就根据不同的消息类型,执行不同的处理:

Handler handler = new Handler(Looper.getMainLooper()) { 
    @Override 
    public void handleMessage(Message msg) { 
        
        if (msg.what == MESSAGE_TYPE_1) { 
            
        } else if(msg.what == MESSAGE_TYPE_2){
            
        }
    } 
};

2.2 Message

Message 是用来在不同线程之间传递信息的载体,下面介绍一下基本属性和创建方式。

基本属性

Message 对象是用来在线程之间传递数据的载体。一个 Message 对象包含了一些基本的属性,这些属性定义了消息的内容、目标处理器以及其他一些元数据。以下是 Message 类的一些基本属性:

  • what:
    • 类型:int
    • 描述:这个属性通常用来标识消息的类型或者命令。在 Handler 的 handleMessage 方法中,你可以通过检查 msg.what 的值来决定如何处理不同的消息。
    • 示例:假设在一个应用中,有下载图片和下载文件两种异步任务,当任务完成后通过 Handler 发送消息。可以定义what值,如public static final int DOWNLOAD_IMAGE_COMPLETE = 1;和public static final int DOWNLOAD_FILE_COMPLETE = 2;,在handleMessage方法中就可以根据what的值来判断是图片下载完成还是文件下载完成,进而进行相应的 UI 更新操作。
  • arg1 和 arg2:
    • 类型:int
    • 描述:这两个属性可以用来传递整型数据。它们通常用于传递简单的参数或者状态码。
    • 示例:在一个图片加载的场景中,arg1可以用来表示图片的加载进度(0 – 100),arg2可以表示图片的版本号或者当前加载的图片在一组图片中的序号。
  • obj:
    • 类型:Object
    • 描述:这个属性可以用来传递任何类型的对象。它允许你将任意数据传递给 Handler,但要注意,传递的对象不应包含对当前线程上下文的引用,以避免潜在的内存泄漏。
    • 示例:如果从网络上下载了一个包含用户信息(姓名、年龄、性别等)的User对象,当下载完成后,可以将这个User对象设置为obj属性,然后通过 Handler 发送消息到 UI 线程,在 UI 线程的handleMessage方法中就可以获取这个User对象并进行显示
  • target:
    • 类型:Handler
    • 描述:这个属性定义了处理这个消息的 Handler 实例。当一个消息被发送时,target 指定了消息应该被哪个 Handler 的 handleMessage 方法处理。
  • callback:
    • 类型:Runnable
    • 描述:这个属性允许你指定一个 Runnable 对象,该对象将在消息处理时被执行。如果 callback 不为空,Handler 在处理消息时会优先调用 callback.run() 而不是 handleMessage 方法。
  • data:
    • 类型:Bundle
    • 描述:这个属性可以用来传递一组键值对,类似于 Intent 中的 extras。Bundle 是一个映射,允许你传递更复杂的数据集合。
  • replyTo:
    • 类型:Messenger
    • 描述:这个属性用于跨进程通信(IPC)时指定回复的 Messenger。在进程间传递消息时,replyTo 用来指定接收回复消息的 Handler。
  • when:
    • 类型:long
    • 描述:这个字段用于记录消息的发送时间或计划执行时间(取决于消息的发送方式)。Handler 机制会按照消息的when属性值来对消息进行排序和调度。消息队列中的消息会按照时间戳的先后顺序被取出并处理。如果一个消息的when时间戳比当前时间晚,那么它会在到达指定时间后才被处理。
  • next:
    • 类型:Message
    • 描述:在Message的内部实现中,next字段用于将多个Message对象链接成一个链表,以支持消息对象的复用和缓存。这个字段对开发者来说是透明的,不需要在常规的消息处理中直接操作。

创建方式

在 Android 的 Handler 机制中,Message 有以下几种常见的创建方式:

使用无参构造函数直接创建

这是最直接也是最简单的方式,通过Message类的无参构造函数来创建一个新的 Message 对象。这种方式每次调用都会在内存中创建一个新的 Message 实例,如果频繁使用,可能会导致内存压力增大。

使用 Message.obtain () 方法

这是官方推荐的方式,因为它可以复用之前已经创建但不再使用的 Message 对象,从而减少内存分配和垃圾回收的开销。Message.obtain()方法及其重载版本会从一个全局的 Message 缓存池中获取一个可用的 Message 对象,如果缓存池为空,则新创建一个 Message 对象。

在obtain()方法的内部实现中,它首先会检查一个静态的 Message 对象链表(缓存池)是否非空。如果非空,它会从链表的头部取出一个 Message 对象,将其 next 指针置为null,表示该对象已被取出使用,并将缓存池中的对象数量减一。如果链表为空,则直接通过new Message()创建一个新的 Message 对象。

使用 Handler.obtainMessage () 方法

通过关联的 Handler 来获取一个 Message 对象。这个方法实际上也是调用了 Message.obtain () 方法,但它会自动设置 Message 的 target 属性为调用这个方法的 Handler,确保消息能够由正确的 Handler 处理。

2.3 MessageQueue

MessageQueue就像一个传送带,Message通过传送带存放和取出。

数据结构

MessageQueue 本质上是一个按照时间顺序排列的单链表结构。每个节点代表一个 Message 对象。消息按照它们的when属性值(即执行时间戳)进行排序,时间戳小的消息排在前面,时间戳大的消息排在后面。这样可以保证 Handler 在从消息队列中取出消息时,总是先取出时间戳最小的、也就是应该最先被处理的消息。

使用方式

消息入队

当一个新的 Message 被创建并准备发送到特定的 Handler 时,实际上是将这个 Message 添加到与之关联的 Handler 的 MessageQueue 中。这个过程通常是通过调用Handler.sendMessage(Message msg)方法来实现的。在这个方法内部,会将消息的target属性设置为当前的 Handler,并将消息按照时间顺序插入到消息队列中,调用的是 MessageQueue 的 enqueueMessage()方法:

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
        ···
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        
        boolean needWake;
        
        
        if (p == null || when == 0 || when < p.when) {
            
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            
            
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            
            Message prev;
            
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    
                    
                    needWake = false;
                }
            }
            msg.next = p; 
            prev.next = msg;
        }

        
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

消息出队

MessageQueue 中的消息会不断被取出进行处理。Handler 的handleMessage(Message msg)方法会被自动调用,传入从消息队列中取出的 Message 对象。在这个方法中,可以根据消息的what、arg1、arg2、obj等属性来判断消息的类型,并执行相应的操作。

MessageQueue 中消息的不断取出是通过next()实现的。

@UnsupportedAppUsage
Message next() {
    ···
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {  
                
                
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    
                    
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                
                nextPollTimeoutMillis = -1;
            }
            ···
        }
        ···
    }
        ···
}

其中,nextPollTimeoutMillis表示阻塞的时间,-1表示无限时间,直到有事件发生为止,0表示不阻塞。当消息队列为空时,Looper.loop() 会阻塞当前线程,但这种阻塞是可中断的,并且不会导致线程永久挂起。一旦有新的消息到达或 Looper.quit() 被调用,Looper.loop() 会立即返回,从而使线程继续执行。

消息屏障

Android 系统为了保证某些高优先级的 Message(异步消息) 能够被尽快执行,采用了一种消息屏障(Barrier)机制。其大致流程是:先发送一个屏障消息到 MessageQueue 中,当 MessageQueue 遍历到该屏障消息时,就会判断当前队列中是否存在异步消息,有的话则先跳过同步消息(开发者主动发送的都属于同步消息),优先执行异步消息。这种机制就会使得在异步消息被执行完之前,同步消息都不会得到处理。MessageQueue 在获取队头消息的时候,如果判断到队头消息的 target 对象为 null 的话,就知道该 Message 属于屏障消息,那么就会再向后遍历找到第一条异步消息优先进行处理,每次调用 next() 方法时都会重复此步骤知道所有异步消息均被处理完。

我们可以通过调用 Message 对象的 setAsynchronous(boolean async) 方法来主动发送异步消息。而如果想要主动发送屏障消息的话,就需要通过反射来调用 MessageQueue 的 postSyncBarrier() 方法了,该方法被系统隐藏起来了

屏障消息的作用就是可以确保高优先级的异步消息可以优先被处理,ViewRootImpl 就通过该机制来使得 View 的绘制流程可以优先被处理

2.4 Looper

每个 Handler 都需要绑定一个 Looper 对象,每一个 Looper 对象都有一个 MessageQueue。Looper就像是电机,在 Looper 中开启循环,就可以驱动传送带 MessageQueue 传动,不断取出最前面的 Message 执行相应任务。

Looper初始化

Looper如何与MessageQueue绑定

创建Looper对象时,会为其创建一个 MessageQueue 对象,

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

Looper对象何时创建

可以看到Looper的构造函数是私有的,在 Looper.prepare() 里调用

static final ThreadLocal sThreadLocal = new ThreadLocal();

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));  
}

其中,ThreadLocal 的作用是为每一个线程设置自己的 Looper 对象,起到线程隔离的作用,每个线程只能读取和修改自己的 Looper 对象。

prepare()何时执行

主线程和子线程在 Looper 的使用上有些区别。在子线程中,我们需要手动调用 Looper.prepare(),为子线程绑定一个 Looper 对象:

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                
            }
        };
        Looper.loop();
    }
}).start();

主线程中的 Looper.prepare() 是由系统自动调用的,通常在应用启动时,在进入 ActivityThread.main() 方法后,系统会为主线程创建 Looper 并启动消息循环:

public static void main(String[] args) {
    ···
    Looper.prepareMainLooper();
    ···
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

可以看到第3行 Looper.prepareMainLooper(),这个就是初始化主线程 Looper 的方法,我们再点进去看这个方法:

private static Looper sMainLooper;

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

其中,sMainLooper 表示专门用来表示主线程的 Looper,是一个静态对象,可以通过 getMainLooper获取,作为 Handler有参构造函数的入参,将 Hanlder 与主线程绑定。

Looper循环

loop()开启消息循环

prepare() 和 loop() 是成对出现的。loop() 会开启消息循环,不断从消息队列中取出消息进行分发:

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    
    ......
    
    for (;;) {
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}

private static boolean loopOnce(final Looper me, final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); 
    if (msg == null) {
        
        return false;
    }
    
    try {
        msg.target.dispatchMessage(msg);
        ...
    }
    ...
    msg.recycleUnchecked();

    return true;
}

第17行me.mQueue.next()会不断取出消息队列中的消息,通过第24行msg.target.dispatchMessage(msg) 进行分发。

Looper退出

  • quit() 方法
    • 功能:quit()方法用于直接退出Looper的消息循环。当调用此方法时,Looper会立即停止处理消息队列中的消息,并尝试退出循环。
    • 行为:调用quit()后,Looper会检查消息队列,如果队列为空或者Looper正在等待新消息(即处于阻塞状态),则Looper会立即退出循环。如果队列中还有未处理的消息,这些消息将被丢弃,Looper不会等待它们处理完毕。
    • 使用场景:适用于那些不再需要处理任何消息,且可以立即安全退出的场景。
  • quitSafely() 方法
    • 功能:quitSafely()方法用于安全地退出Looper的消息循环。与quit()不同,quitSafely()会等待消息队列中所有已存在的消息都被处理完毕后,再退出循环。
    • 行为:调用quitSafely()后,Looper会继续处理消息队列中的消息,直到队列为空。一旦所有消息都被处理,Looper就会退出循环。
    • 使用场景:适用于那些需要确保所有已发送的消息都被处理完毕,再安全退出的场景。
public final class Looper {
    ...
    public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }
    ...
}


public final class MessageQueue {
    ...
    
    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            
            nativeWake(mPtr);
        }
    }

    ...
}

三、参考资料

[1] 一文读懂Handler机制

[2] 并发编程 · 基础篇(中) · 三大分析法分析Handler

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/22507,转载请注明出处。
赏 0

评论0

显示验证码
没有账号?注册  忘记密码?