浅析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 通常与 MessageQueueLooper 一起工作,形成一个完整的异步消息处理机制。

  • 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 被执行时,它会在主线程中运行。

使用 Handlerpost 方法可以简化代码,直接将一个 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相关的另外几个概念:MessageQueueLooperMessage

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

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

image.png

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

2.1 Handler

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

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

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


Handler的创建

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

image.png

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

  • 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如何执行任务

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

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

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,则结束流程
  • 调用 HandlerhandleMessage 方法来处理消息,外部通过重写该方法来定义业务逻辑

一、 通过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 中的 extrasBundle 是一个映射,允许你传递更复杂的数据集合。
  • 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 () 方法,但它会自动设置 Messagetarget 属性为调用这个方法的 Handler,确保消息能够由正确的 Handler 处理。

2.3 MessageQueue

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

数据结构

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

使用方式

消息入队

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

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 中的消息会不断被取出进行处理。HandlerhandleMessage(Message msg)方法会被自动调用,传入从消息队列中取出的 Message 对象。在这个方法中,可以根据消息的whatarg1arg2obj等属性来判断消息的类型,并执行相应的操作。

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 对象都有一个 MessageQueueLooper就像是电机,在 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

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