Android面试----Handler

1. Handler的介绍及原理

1.1Handler的介绍

Handler是Android异步消息处理线程相关的概念,其他相关的概念还有Looper、Message等。
异步消息处理线程启动后会进入一个无限循环体中,每循环一次,就从其内部的消息队列中取出一个消息,然后回调对应的消息处理函数,执行完成一个消息后,继续循环。若消息队列为空,线程会阻塞等待。
其实Looper负责的就是创建一个MessageQueue,然后进入一个无限循环体不断从该MessageQueue中读取消息,而消息的创建者就是一个或多个Handler 。

1.1.1 Looper

Looper主要作用:
1、 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
2、 loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。
好了,我们的异步消息处理线程已经有了消息队列(MessageQueue),也有了在无限循环体中取出消息的哥们,只需要Handler创建消息就可以了。

1.1.2 整个流程

1、首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。

2、Looper.loop()会让当前线程进入一个无限循环,不端从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。

3、Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue相关联。

4、Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。

5、在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。

参考:Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系

好了,总结完成,大家可能还会问,那么在Activity中,我们并没有显示的调用Looper.prepare()和Looper.loop()方法,为啥Handler可以成功创建呢,这是因为在Activity的启动代码中,已经在当前UI线程调用了Looper.prepare()和Looper.loop()方法。

1.1.3 为什么在Android中主线程创建了Looper,不断loop循环,而不会造成ANR(Application Not Responding 应用无响应)?

对于这个问题,首先我们来理解一下什么原因会造成ANR。

1.当前事件没有得到机会处理(有可能是因为主线程正常处理其他的事件,而不能及时处理当前的事件)。

2.当前事件正在处理了,但是没有及时处理完成。

我们知道了造成了ANR原因之后,再来分析一下Looper.loop()会使线程阻塞,为什么又不会造成ANR呢?

通过源码可以发现,在主线程中当loop()方法没有轮询到消息的时候。会报出一个thrownewRuntimeException(“Main thread loop unexpectedly exited”)异常,接着主线程就直接退出了。所以在这里我们知道了Looper.loop()消息循环的必要性,一旦退出消息循环,那么你的应用也就退出了。

其实,Android应用是由事件驱动的。looper.loop()不断的轮询接收事件,handler不断的处理事件。每一个点击触摸事件,或者Activity的生命周期的创建,都是运行在looper.loop()控制之下,简单的来说就是,这些都是属于一个事件消息,然后由looper.loop()轮询到,接着做相应的操作。也就是说,这些这些事件都在looper.loop()循环内执行的。这些事件消息的处理,可能会卡顿,造成了looper.loop()循环的阻塞,而不是looper.loop()阻塞了这些事件。

举个例子:假如现在轮询到了onResume这个消息,这个时候, Activity应该执行onResume方法。假如这个时候我们在onResume方法里面执行耗时操作。同时又进行点击事件,那么点击事件就不会得到及时的处理。这样的话,就会造成卡顿,然后ANR了。

小结:Looper.loop()当没有轮询到事件的时候,整个应用程序就退出了,而我们的消息事件都是在Looper.loop()循环内,如果循环内没有得到及时的处理事件,就会造成了ANR.

啰嗦一下:我们可以这样理解,Anroid应用程序的运行,就像车子在行走一样,如果轮子不转了,那么车子也停了,也就是说应用程序也就退出了。当车子一下子遇到很多个石头可能会开的比较慢,甚至卡死了。也就类似于当事件没有得到处理,那么就会造成ANR,卡死。

作者:大熊啊啊啊
链接:https://www.jianshu.com/p/d7f2e2495339

2. Handler的使用

2.1 传递消息Message

//2种创建消息方法
//1.通过handler实例获取
Handler handler = new Handler();
Message message = handler.obtainMessage();

//2.通过Message获取
Message message = Message.obtain();

//源码中第一种获取方式其实也是内部调用了第二种:
public final Message obtainMessage(){
    return Message.obtain(this);
}

不建议直接new Message,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存。

//传递的数据
Bundle bundle = new Bundle();
bundle.putString("msg" , "传递我这个消息");
//发送数据
Message message = Message.obtain();
message.setData(Bundle);    //message.obj = bundle 传值也行
message.what = 0x11;
handler.sendMessage(message);


//数据的接收
final Handler handler = new Handler(){
	@Override
	public void handleMessage(Message msg){
		super.handleMessage(msg);
		if(msg.what == 0x11){
			Bundle bundle = msg.getData();
			String data = bundle.getString("msg");
		}
	}
};

2.2 子线程通知主线程更新ui

//创建handler
final Handler handler = new Handler(){
	@Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (msg.what == 0x11) {
                    //更新ui
                          ......
                }
            }
};

new Thread(new Runable(){
	@Override
            public void run() {
                //FIXME 这里直接更新ui是不行的,只能在主线程中更新ui
                //还有其他更新ui方式,runOnUiThread()等          
                message.what = 0x11;
                message.setData(数据)  //或message.obj = 数据     
                handler.sendMessage(message);  
            
}).start();

2.3 常用api

//消息
Message message = Message.obtain();

Handler handler = new Handler();
//发送消息
handler.sendMessage(message);
//延时1s发送消息
handler.sendMessageDelayed(message , 1000);   //1000ms = 1s
//发送代表及的消息(内部创建了message,并设置msg.what = 0x1)
handler.sendEmptyMessage(0x1);
//延时1s发送代表及的消息
handler.sendEmptyMessageDelayed(0x1 , 1000);
//延时1秒发送消息(第二个参数为:相对系统开机时间的绝对时间,SystemClock.uptimeMillis()是当前开机时间)
handler.sendMessageAtTime(message , SystemClock.uptimeMillis() + 1000);

//避免内存泄露的方法:
//移除标记为0x1的消息
handler.removeMessages(0x1);
//移除回调的消息
handler.removeCallbacks(Runnable);
//移除回调和所有message
handler.removeCallbacksAndMessages(null);

3. Handler.sendMessage() 和 Handler.post()的区别和联系

  1. post和sendMessage功能其实差不多,post其实也是通过sendMessage来实现的,都是发送消息到Handler所在的线程的消息队列中
  2. post的用法更方便,经常会post一个Runnable,处理的代码直接写在Runnable的run方法中,其实就是将这个Runnable发送到Handler所在线程(一般是主线程)的消息队列中。sendMessage方法主线程处理方法一般则是写在handleMessage()中。

3.1 post()的使用

final Handler handler = new Handler();

new Thread(new Runnable(){
	@Override
    public void run() {
        final String response = get(url);
        handler.post(new Runnable() {
                    @Override
                    public void run() {
                        //doSomeThing
                        //若handler创建在主线程中,可以写更新ui的方法
                    }
                });
   }
}).start();

3.2 sendMessage()的使用

sendMessage()的使用方法可看上面。

3.3 post()源码分析

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

可以看到,post()其实是调用了sendMessageDelayed()方法,而且延时时间为0。在getPostMessage中,得到了一个Message对象,然后将我们创建的Runable对象作为callback属性,赋值给了此message。

4. 在子线程中使用Handler

默认情况下,ActivityThread类为我们创建的了主线程的Looper和消息队列,所以当你创建Handler之后发送消息的时候,消息的轮询和handle都是在ui线程进行的。这种情况属于子线程给主线程发消息,通知主线程更新ui…等,那么反过来,怎么才能让主线程给子线程发消息,通知子线程做一些耗时逻辑??

Android的消息机制遵循三个步骤:

1  创建当前线程的Looper

2  创建当前线程的Handler

3  调用当前线程Looper对象的loop方法

特意强调了“当前线程”。是的之前我们学习的很多都是Android未我们做好了的,譬如:创建主线程的Looper、主线程的消息队列…就连我们使用的handler也是主线程的。那么如果我想创建非主线程的Handler并且发送消息、处理消息,依然遵循上面的三步走:

public class ChildThreadHandlerActivity extends Activity {
    private MyThread childThread;

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

        childThread = new MyThread();
        childThread.start();

        Handler childHandler = new Handler(childThread.childLooper){//这样之后,childHandler和childLooper就关联起来了。
            public void handleMessage(Message msg) {
                
            };
        };
    }

    private class MyThread extends Thread{
        public Looper childLooper;

        @Override
        public void run() {
            Looper.prepare();//创建与当前线程相关的Looper
            childLooper = Looper.myLooper();//获取当前线程的Looper对象
            Looper.loop();//调用此方法,消息才会循环处理
        }
    }
}

但是这样去创建子线程的Handler可能会出现一定问题:looper指针为空,原因是当我们start子线程后,虽然子线程的run方法得到执行,但与此同时,主线程中的代码依然会向下执行,造成空指针的原因是当我们new Handler(childHandler.childLooper)的时候,run方法中的Looper对象还没初始化。

这时候我们可以使用Android给我们提供的HandlerThread类:

4.1 HandlerThread类:

public class HandlerThreadActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        TextView textView = (TextView) findViewById(R.id.tv);
        textView.setText("HandlerThreadActivity.class");

        HandlerThread handlerThread = new HandlerThread("HandlerThread");
        handlerThread.start();

        Handler mHandler = new Handler(handlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.d("HandlerThreadActivity.class","uiThread2------"+Thread.currentThread());//子线程
            }
        };

        Log.d("HandlerThreadActivity.class","uiThread1------"+Thread.currentThread());//主线程
        mHandler.sendEmptyMessage(1);
    }
}

创建HandlerThread对象的时候,有个参数,是指定线程名字的。上面的代码不管运行多少次都不会奔溃。并且这种方法创建的handler的handleMessage方法运行在子线程中。所以我们可以在这里处理一些耗时的逻辑。到此我们完成了主线程给子线程发通知,在子线程做耗时逻辑的操作。

4.1.1为什么使用HandlerThread类不会造成Looper空指针异常

我们去看看源码

public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

HandlerThread类的getLooper方法如上,我们看到当我们获取当前线程Looper对象的时候,会先判断当前线程是否存活,然后还要判断Looper对象是否为空,都满足之后才会返回给我Looper对象,否则处于等待状态。既然有等待,那就有唤醒的时候,我们发现HandlerThread的run方法中,有如下代码

@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

说明HandlerThread类start的时候,Looper对象就初始化了,并唤醒之前等待的,所以HandlerThread很好的避免了之前空指针的产生。
所以以后要想创建非主线程的Handler时,我们可以使用HandlerThread。

  • 在主线程中创建HandlerThread并使用由HandlerThread提供的Looper对象去创建Handler,并重写handleMessage方法。
  • 主线程和其他子线程可以通过这个Handler对象来发送Message给HandlerThread,并且处理Message是在HandlerThread中的(所以能执行耗时操作,但不能更新ui)。

5. Handler使用避免内存泄漏

5.1 内存泄露和内存溢出的概念

内存泄漏:(memory leak)是指程序中已经动态分配的堆内存由于某种原因导致未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果

内存溢出:(out of memory)通俗理解就是内存不够,存储的数据超出了指定控件的大小,这时数据就会越界。容易造成程序异常,严重的,攻击者可以以此获取程序的控制权。

5.2 handler如何使用会产生内存泄漏?


public class MainActivity extends AppCompatActivity {
 
    final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
                ......
        }
    };
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //activity被执行时,被延迟的这个消息存于主线程消息队列中1分钟,
        //此消息包含handler引用,而handler由匿名内部类创建,持有activity引用(如果不持有,怎么更改ui或其他逻辑?)
        //所以activity便不能正常销毁,从而泄露(普通handler内部类也同理)
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                ......
            }
        }, 1000 * 60);
    }
}

其实就是,如果我们通过匿名内部类或者普通内部类的方法创建Handler实例的时候,Handler会持有当前Activity的引用,可能造成当前Activity不能正常销毁。

5.3 如何避免handler的内存泄漏

解决方法:

  • 将内部类或匿名内部类改为静态内部类,因为静态内部类不持有外部类的引用。

但由于Handler不再持有外部类引用,导致程成不允许你在Handler中操作Activity中的对象。

  • 所以,我们还需要在Handler中增加一个对Activity的弱引用(使用弱引用的好处在于:activity一旦被置为null,他就会被立刻回收)。上面持有的引用属于强引用,强引用的特点就是当当前类被回收的时候,如果它被强引用所持有,那么当前类是不会被回收的。所以我们改成弱引用持有当前类对象,这样在GC回收时会忽略掉弱引用,即就算有弱引用指向某对象,该对象也会在被GC检查到时回收掉。
public class MainActivity extends AppCompatActivity {
 
    //创建静态内部类
    private static class MyHandler extends Handler{
        //持有弱引用MainActivity,GC回收时会被回收掉.
        private final WeakReference<MainActivity> mAct;
        public MyHandler(MainActivity mainActivity){
            mAct =new WeakReference<MainActivity>(mainActivity);
        }
        @Override
        public void handleMessage(Message msg) {
            MainActivity mainAct=mAct.get();
            super.handleMessage(msg);
            if(mainAct!=null){
                //执行业务逻辑
            }
        }
    }
    private static final Runnable myRunnable = new Runnable() {
        @Override
        public void run() {
            //执行我们的业务逻辑
        }
    };
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyHandler myHandler=new MyHandler(this);
        //延迟5分钟后发送
        myHandler.postDelayed(myRunnable, 1000 * 60 * 5);
    }
}

当然,我们也可以直接抽取成一个基类,将来创建Handler的时候继承这个基类就好(避免重复代码)

public abstract class WeakHandler<T> extends Handler {

    protected WeakReference<T> reference;

    //创建子线程Handler使用的构造器
    public WeakHandler(Looper looper, T reference) {
        super(looper);
        this.reference = new WeakReference<>(reference);
    }

    //创建主线程Handler使用的构造器
    public WeakHandler(T reference) {
        this.reference = new WeakReference<>(reference);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        T t = reference.get();
        if (t == null)
            return;
        handleMessage(t, msg);
    }

    protected abstract void handleMessage(T t, Message message);

}

上述代码,我们使用了泛型,这个泛型就是我们之前说的当前类,同时提供了两种构造器,这样不管我们是创建主线程还是非主线程Handler对象时,都不会造成内存泄漏了。

参考:
https://www.cnblogs.com/lang-yu/p/6228832.html

https://blog.csdn.net/qq_37321098/article/details/81535449?biz_id=102&utm_term=Handler&utm_medium=distribute.pc_search_result.none-task-blog-2blogsobaiduweb~default-0-81535449&spm=1018.2118.3001.4187

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值