Handler

1. Handler 的作用

在Android为了保障线程安全,规定只能由主线程来更新UI信息。而在实际开发中,会经常遇到多个子线程都去操作UI信息的情况,那么就会导致UI线程不安全。这时,我们就需要借助 Handler 作为媒介,让 Handler 通知主线程按顺序一个个去更新UI,避免UI线程不安全。

那么,子线程要更新UI的信息时,我们就需要将要更新的消息传递到 UI主线程中,再由主线程完成更新,从而实现工作线程对UI的更新处理,最终完成异步消息的处理(如图1所示)。
在这里插入图片描述

2. Handler 相关概念解释

主要涉及的有:处理器(Handler)、消息(Message)、消息队列(Message Queue)、循环器(Looper)。
主线程mainThread:当应用第一次启动时就会开启一条主线程。作用:处理UI相关的事件。
子线程:人为手动开启的。作用:执行耗时的操作(如,网络请求等)。
Message:线程间通讯的数据单元(即Handler接受/处理的对象)。作用:存储需要操作的信息
Message Queue:一种数据结构(队:先进先出)。作用:存储Handler发来的消息(Message)
Handler:主线程与子线程的通讯媒介;◆ 线程消息的处理者。作用::◆ 添加消息(Message)到消息队列(Message Queue);
◆ 处理由循环器(Looper)分配过来的消息(Message)。
Looper:消息队列(Message Queue)与 Handler的通讯媒介。作用: ◆ 消息获取:循环取出消息队列(Message Queue)中的消息(Message);
◆ 消息分发:将取出的消息(Message)发送给对应的处理者(Handler)。

3. Handler 有哪些发送消息的方法

sendMessage(Message msg) 
sendMessageDelayed(Message msg, long uptimeMillis)
post(Runnable r)
postDelayed(Runnable r, long uptimeMillis)
sendMessageAtTime(Message msg,long when)

MessageQueue 中的 Message 是有序的吗?排序的依据是什么

Queue 都是有序的,Set 才是无序的。排序的依据是 Messag.when 字段,表示一个相对时间,该值是由 MessageQueue.enqueueMessage(Message, Long) 方法设置的。
在这里插入图片描述

Message#when 是指的是什么

Message#when 是一个时间,用于表示 Message 期望被分发的时间,该值是 SystemClock#uptimeMillis() 与 delayMillis 之和。
在这里插入图片描述
SystemClock#uptimeMillis() 是一个表示当前时间的一个相对时间,它代表的是 自系统启动开始从0开始的到调用该方法时相差的毫秒数 。
在这里插入图片描述

子线程中可以创建 Handler 对象吗?

不可以在子线程中直接调用 Handler 的无参构造方法,因为 Handler 在创建时必须要绑定一个 Looper 对象,有两种方法绑定
1先调用 Looper.prepare() 在当前线程初始化一个 Looper

Looper.prepare();
Handler handler = new Handler();
// ....
// 这一步可别可少了
Looper.loop();

2通过构造方法传入一个 Looper

Looper looper = .....;
Handler handler = new Handler(looper);

Handler 是如何与 Looper 关联的

上个问题应该告知了其中一种情况:通过构造方法传参。
还有一种是我们直接调用无参构造方法时会有一个自动绑定过程

// Handler.java:192
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());
        }
    }

    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 = callback;
    mAsynchronous = async;
}

Looper 是如何与 Thread 关联的

Looper 与 Thread 之间是通过 ThreadLocal 关联的,这个可以看 Looper#prepare() 方法

// Looper.java:93
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));
}

Looper 中有一个 ThreadLocal 类型的 sThreadLocal静态字段,Looper通过它的 get 和 set 方法来赋值和取值。
由于 ThreadLocal是与线程绑定的,所以我们只要把 Looper 与 ThreadLocal 绑定了,那 Looper 和 Thread 也就关联上了

Handler 有哪些构造方法

无参、单Looper、单Callback、Looper和Handler,共4种。

public Handler() {
    this(null, false);
}
public Handler(Callback callback) {
    this(callback, false);
}
public Handler(Looper looper) {
    this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}

还有一个 boolean 的 async, 不过这个不是开放API,即使不知道个人觉得完全没有问题。

looper为什么调用的是Handler的dispatchMessage方法

看一下dispatchMessage方法

// Handler.java:97
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

从上面的代码不难看出有两个原因:

当msg.callback != null时会执行 handleCallback(msg),这表示这个 msg 对象是通过 handler#postAtTime(Runnable, long) 相关方法发送的,所以 msg.what和 msg.obj 都是零值,不会交给Handler#handleMessage方法。
从上一个问题你应该看到了Handler可以接受一个 Callback 参数,类似于 View 里的 OnTouchListener ,会先把事件交给 Callback#handleMessage(Message) 处理,如果返回 false 时该消息才会交给 Handler#handleMessage(Message)方法。

在子线程中如何获取当前线程的 Looper

Looper.myLooper()

内部原理就是同过上面提到的 sThreadLocal#get() 来获取 Looper

// Looper.java:203
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

如何在任意线程获取主线程的 Looper

Looper.getMainLooper()

这个在我们开发 library 时特别有用,毕竟你不知道别人在调用使用你的库时会在哪个线程初始化,所以我们在创建 Handler 时每次都通过指定主线程的 Looper 的方式保证库的正常运行。

如何判断当前线程是不是主线程

方法一:

Looper.myLooper() == Looper.getMainLooper()

方法二:

Looper.getMainLooper().getThread() == Thread.currentThread()

方法三: 方法二的简化版


Looper.getMainLooper().isCurrentThread()

Looper.loop() 会退出吗?

不会自动退出,但是我们可以通过 Looper#quit() 或者 Looper#quitSafely() 让它退出。

两个方法都是调用了 MessageQueue#quit(boolean) 方法,当 MessageQueue#next() 方法发现已经调用过 MessageQueue#quit(boolean) 时会 return null 结束当前调用,否则的话即使 MessageQueue 已经是空的了也会阻塞等待。

MessageQueue#next 在没有消息的时候会阻塞,如何恢复?

当其他线程调用 MessageQueue#enqueueMessage 时会唤醒 MessageQueue,这个方法会被 Handler#sendMessage、Handler#post 等一系列发送消息的方法调用。

boolean enqueueMessage(Message msg, long when) {
    // 略
    synchronized (this) {
        // 略
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // 略
        }
        if (needWake) {
            nativeWake(mPtr); // 唤醒
        }
    }
    return true;
}

Looper.loop() 方法是一个死循环为什么不会阻塞APP

public static void main(String[] args){
    while(true){
        // do work in while
    }
    doSomeThingOutWhile();
}

对于从整个main方法来看,while(true) 确实阻塞了 doSomeThingOutWhile() 这个方法的执行,对于这样看,好像确实是卡住了,因为我们在 doSomeThingOutWhile 方法中想要做的事没法做了,但是如果我们把我们要做的事情通过队列放到 while 里面去做,那么是不是你就不会觉得卡了,你想要做的事情都完成了,虽然有个死循环但并不影响你想要做什么,而Android中 Looper.loop() 就是这样的原理,因为所有让我们会觉得卡住的都被放到 MessageQueue 里,然后通过Looper取出并交给 Handler执行了。

PS:不仅仅是Android,几乎所有和UI操作的都有一个类似Android Handler机制的事件循环处理机制

Handler造成的内存泄露

Handler是由系统提供的异步消息处理的一种常用的方式,一般情况下不会导致内存泄漏,至于其为什么可能会导致内存泄漏,这里的内存泄漏常常指的是泄露了Acitivity等组件

public class HandlerTestActivity extends Activity{
    public Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
             
    }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
	}
}

这个写法有什么问题呢?这个问题就在于该Handler的实例采用了内部类的写法,在java中,非静态的内部类和匿名内部类都会隐式的持有一个外部类的引用,造成外部类在使用完成后不能被系统回收内存,从而造成内存泄漏。

生命周期较短的组件引用了生命周期较长的组件。Handler就是一种典型的示例,以上面的代码举例。HandlerTestActivity可能会被泄漏,也就是该组件没有用了,比如调用了finish()后,垃圾回收器却迟迟没有回收该Activity。原因出在该实例的handler内部类引用了它,而该handler实例可能被MessageQueue引用着。其实正常情况下是不会内存泄漏的,除非handler队列等待太久,想要解决handler导致的内存泄漏,你就需要理解泄漏的原因,(handler持有activity,然后message持有handler,然后MessgQuene持有message),理解了这样持有链,那么handler持有activity可以通过弱引用或静态内部类等方式解决,而removeCallbacksAndMessages则是清除后面的引用,其实如果你的handler中没有什么耗时操作,任务完成了handler就会释放Activity的引用就不会导致内存泄漏了;或者你的handler中是一个2秒的操作,在Activity退出的2s后,释放了Activity的引用,这种情况属于短时间内存泄漏,它也会释放的,所以也可以不管,但是如果你的handler中是死循环啥的的话,就会导致内存泄漏了。

解决Handler造成的内存泄露:

1.保证Activity被finish()时该线程的消息队列没有这个Activity的handler内部类的引用。这个场景是及其常见的,因为handler经常被用来发延时消息。一个补救的办法就是在该类需要回收的时候,手动地把消息队列中的消息清空:mHandler.removeCallbacksAndMessages(null);

2.要么让这个handler不持有Activity等外部组件实例,让该Handler成为静态内部类。(静态内部类是不持有外部类的实例的,因而也就调用不了外部的实例方法了)

3.在2方法的基础上,为了能调用外部的实例方法,传递一个外部的弱引用进来)

public class HandlerTestActivity extends Activity {
private static class MyHandler extends Handler {
private final WeakReference<HandlerTestActivity> mActivity;
public MyHandler(HandlerTestActivity activity) {
  mActivity = new WeakReference<HandlerTestActivity>(activity);
}

@Override
public void handleMessage(Message msg) {
  ShanActivity activity = mActivity.get();
  if (activity != null) {
     //do Something
  }
}
}

4.将Handler放到抽取出来放入一个单独的顶层类文件中。

/**
 * 实现回调弱引用的Handler
 * 防止由于内部持有导致的内存泄露
 * 传入的Callback不能使用匿名实现的变量,必须与使用这个Handle的对象的生命周期一 
 * 致否则会被立即释放掉了
 */
public class WeakRefHandler extends Handler {
    private WeakReference<Callback> mWeakReference;
    
    public WeakRefHandler(Callback callback) {
        mWeakReference = new WeakReference<Handler.Callback>(callback);
    }
    
    public WeakRefHandler(Callback callback, Looper looper) {
        super(looper);
        mWeakReference = new WeakReference<Handler.Callback>(callback);
    }
    
    @Override
    public void handleMessage(Message msg) {
        if (mWeakReference != null && mWeakReference.get() != null) {
            Callback callback = mWeakReference.get();
            callback.handleMessage(msg);
        }
    }
}

检测内存泄漏的工具:LeakCancry/li:k,Can,krai/

使用方法:在AndroidStudio中添加依赖

 dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
 }

在Application中初始化并单例,就可以监听Activity中内存泄漏情况

 @Override
    public void onCreate() {
        super.onCreate();
        refWatcher=LeakCanary.install(this);
    }

    //增加监测内存泄漏的Leak
    public static RefWatcher getRefWatcher(Context context) {
        AppContext application = (AppContext) context.getApplicationContext();
        return application.refWatcher;
    }

    private RefWatcher refWatcher;

在Fragment增加监听内存泄漏

public class BaseFragment extends Fragment {
    @Override
    public void onDestroy() {
        super.onDestroy();
        //增加OOM监听
        RefWatcher refWatcher= AppContext.getRefWatcher(getActivity());
        refWatcher.watch(this);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值