首页 热点资讯 义务教育 高等教育 出国留学 考研考公
您的当前位置:首页正文

Handler探究

2024-12-15 来源:花图问答

Handler相关概念

什么Handler

  • Handler允许您发送和处理与线程的MessageQueue关联的Message和Runnable对象。
  • 每个Handler实例都与一个线程和该线程的消息队列相关联。
  • 当你创建一个新的Handler时,它被绑定到正在创建它的线程的线程消息队列。
  • 从那时起,它将message和runnable传递给该消息队列并在它们出现时执行。

Handler有两个主要用途:

  1. 在未来的某个时刻发送message和执行runnables;
  2. 2.将一个action放入消息队列,而不是你自己的线程。

post和sendMessage

  • 和消息调度相关的方法post,postAtTime,postDelayed,sendEmptyMessage,sendMessage,sendMessageAtTime,sendMessageDelayed。
    post是将Runnable对象放入message queue去调用; sendMessage是将一个包含数据的Message对象放入message queue,最后交给重写了handleMessage的Handler处理。

  • 使用post和sendMessage,可以在message queue就绪时处理或者延时处理。延时处理可以做一些超时、倒计时和其他时间相关的行为。

原理

当为您的应用程序创建进程时,其主线程运行了一个消息队列,负责管理顶级应用程序对象(activity,broadcastReceiver等)和他们创建的window。您可以创建自己的线程,并通过Handler与主线程进行通信。这是通过像以前一样调用相同的post或sendMessage方法来完成的,但是来自你的新线程。然后,将在Handler的消息队列中调度给定的Runnable或Message ,并在适当时进行处理。

构造方法干了啥

public Handler(Callback callback, boolean async) {
    //潜在内存泄漏判断
    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());
        }
    }
    获取Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    //mLooper消息队列赋值
    mQueue = mLooper.mQueue;
    //自定义回调实现
    mCallback = callback;
    //是否同步,默认同步
    mAsynchronous = async;
}

问题:

1.为什么Looper.myLooper()可能为空?

/**
 * 返回和当前线程关联的Looper。如果没有关联,返回null
 */
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

looper顶部的注释有写到:

  • Looper是给线程循环消息用的。
  • 线程默认是没有Looper和他们关联的,在线程中调用prepare创建一个与线程关联的looper执行消息循环,调用loop处理消息,直到循环停止。

所以在新线程需要这么用:

class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        Looper.prepare();

        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };

        Looper.loop();
    }
}

2.为什么主线程就可以直接创建Handler,主线程什么时候创建了与之关联的Looper ?

我们可以看到looper有个prepareMainLooper方法

/**
 * Initialize the current thread as a looper, marking it as an
 * application's main looper. The main looper for your application
 * is created by the Android environment, so you should never need
 * to call this function yourself.  See also: {@link #prepare()}
 */
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        //如果sMainLooper已经创建,再手动创建就报错啦
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
} 

注释中说这个main looper是android环境创建的,我们看看是哪里调用的

    public static void main(String[] args) {
    //......................

    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
    
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

可以看到是在ActivityThread的main方法中调用了Looper.prepareMainLooper()和Looper.loop(),
而这个main方法就是应用程序的入口,即应用程序创建时就给主线程创建了main looper,所以我们就可以直接在activity创建handler。

3.发送消息时,new message和obtainMessage有什么区别

看下源码

/**
 * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
 * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
 *  If you don't want that facility, just call Message.obtain() instead.
 */
public final Message obtainMessage()
{
    return Message.obtain(this);
}

从全局message pool中返回一个message。比创建分配新实例更加高效,因为避免了创建过多实例。返回的message会将handler设置给这个实例(Message.target == this)。如果您不想要该设置,就调用Message.obtain()。

public static Message obtain(Handler h) {
    Message m = obtain();
    m.target = h;//这里把handler赋值给message的target

    return m;
}

实际调用还是在Message

/**
 * Return a new Message instance from the global pool. Allows us to
 * avoid allocating new objects in many cases.
 */
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

可以判断出sPool是一个链表结构,sPool本身就是Message。

我们来看看message的成员

public final class Message implements Parcelable {
    // sometimes we store linked lists of these things
    /*package*/ Message next;

    private static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;

    private static final int MAX_POOL_SIZE = 50;
}

可以看出sPool就是一个全局的链表结构的消息池,next记录链表中的下一个元素,sPoolSize记录链表长度,MAX_POOL_SIZE表示链表的最大长度为50。

void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

message对象回收时,会将对象的属性置空,小于50会放入sPool中,否则交给gc处理。

4.为什么Android要设计只能通过Handler机制更新UI?

最根本的目的就是解决多线程并发问题。
假设如果在一个Activity当中,有多个线程去更新UI,并且都没有加锁机制,那么会产生什么样的问题?(更新界面错乱)如果对更新UI的操作都进行加锁处理,又会产生什么样的问题?(性能下降)
基于对以上目的的考虑,android给我们提供了一套更新UI的机制,我们只需遵循这样的机制,无需考虑多线程问题。

5.为什么handler会带来内存泄漏?如何避免?

原因

  • 被延时处理的 message 持有 Handler 的引用,Handler 持有对 Activity 的引用,形成了message – handler – activity 这样一条引用链,导致 Activity 的泄露

如何避免

  • onDestory的时候移除message和runnable

    @Override
    protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
    }

  • 静态内部类+弱引用

    public static class MyHandler extends Handler {
    WeakReference<Activity> mActivityReference;

      MyHandler(Activity activity) {
          mActivityReference= new WeakReference<Activity>(activity);
      }
    
      @Override
      public void handleMessage(Message msg) {
          final Activity activity = mActivityReference.get();
      }
    

    }

6.什么是ThreadLocal

  • ThreadLocal用于实现在不同的线程中存储线程私有数据的类。
  • 在多线程的环境中,当多个线程需要对某个变量进行频繁操作,同时各个线程间不需要同步,此时,各个子线程只需要对存储在当前线程中的变量的拷贝进行操作即可,程序的运行效率会很高,即所谓的空间换时间。
  • ThreadLocal实现线程本地存储的原理:
    • 在当前线程中调用get方法时,通过ThreadLocal的initialValue方法创建当前线程的一个本地数据拷贝,将此拷贝添加到当前线程本地数据的table数组当中;
    • 或者在调用set方法时,将当前线程的本地数据存储到当前线程的table数组中.
    • 当前线程通过调用ThreadLocal对象的get方法即得到当前线程本地数据对象。
显示全文