interView - handler相关

来源:互联网 发布:mac更新系统进度条不动 编辑:程序博客网 时间:2024/06/08 14:37

为什么要使用handler?

因为与UI相关的操作(例如创建UI,刷新UI,处理UI)都必须在主线程中进行。如果说在主线程进行耗时操作,就会被阻塞在当前状态,界面出现假死,此时就会忽略用户操作,这时如何用户的操作在5秒内没响应就会出现ANR异常,所以需要把耗时操作放在子线程里操作,让子线程处理耗时操作,主线程来响应用户的操作,但是子线程在执行完任务后又无法去操作UI,所以为了解决这个问题Handler就出现了,handler是在多线程之间使用的,用于线程间通信,通过在主线程中创建一个Hanlder对象,并且复写其HandlerMessage方法,在创建的工作线程中创建一个Message对象,设置其响应的属性,使用此Handler对象将这个Message对象发送到主线程的消息队列中,主线程的中的Looper对象会不断的监听消息队列中的内容,当监听到消息队列中有新的内容时,就会从消息队列中拿到消息,并且交给这个发送对象的Handler对象来进行处理,就是在HandlerMessage里处理消息来操作UI。


handler机制的原理?内部是如何实现的?

android启动应用程序时会替主线程创建一个消息队列,用来存放线程放入的消息,一个线程可以产生一个looper对象(默认情况下android中新诞生的线程是没有开启消息循环的,主线程除外,系统会自动为主线程创建looper对象,开启消息循环),由它来管理此线程里的MessageQueue(消息队列),

handler对象可以用来与looper沟通,以便push新消息到Message Queue里,或者接收Looper从Message Queue取出所送进来的消息。


消息处理机制?如何终止Looper?

1.获得消息  通过Message.obtain()获得消息,内部是从消息池获取消息,如果消息池没有消息,则创建一个消息对象返回。

2.发送消息  通过sendMessage()发送消息,内部是调用MessageQueue.enqueueMessage(Message msg,long when)方法,依据消息传入的时间入队。

3.取出消息  在Looper.loop()这个轮询消息的方法中,获取MessageQueue对象后,从中取出消息(Message msg = queue.next())。

4.分发消息  从消息队列中取出消息后,调用msg.target.dispatchMessage(msg);进行消息的分发。

5.处理消息  在handlerMessage方法中进行消息的处理。

6.回收消息  在消息使用完毕后,在Looper.loop()方法中调用msg.recycle(),将消息进行回收,即将消息的所有字段恢复为初始状态。

过程如下:


(1)获得消息

    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

(2) 发送消息

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

(3)取出消息(4)分发消息(6)回收消息

public static void loop() {
        Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        MessageQueue queue = me.mQueue;
        
        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        
        while (true) {
            Message msg = queue.next(); // might block
            if (msg != null) {
                if (msg.target == null) {
                    // No target is a magic identifier for the quit message.
                    return;
                }


                long wallStart = 0;
                long threadStart = 0;


                // This must be in a local variable, in case a UI event sets the logger
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                    wallStart = SystemClock.currentTimeMicro();
                    threadStart = SystemClock.currentThreadTimeMicro();
                }


                msg.target.dispatchMessage(msg);


                if (logging != null) {
                    long wallTime = SystemClock.currentTimeMicro() - wallStart;
                    long threadTime = SystemClock.currentThreadTimeMicro() - threadStart;


                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                    if (logging instanceof Profiler) {
                        ((Profiler) logging).profile(msg, wallStart, wallTime,
                                threadStart, threadTime);
                    }
                }


                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf(TAG, "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                }
                
                msg.recycle();
            }
        }
    }

Handler对象维护一个消息队列,有新的消息送来(sendMessage())的时候就把它放到队尾,之后排队到处理(handlerMessage)该消息的时候,由主线程的Handler对象来处理,整个过程是异步的。

Looper.loop() 让looper开始工作,从消息队列里取消息,处理消息,写在Looper.loop之后的代码不会被执行,这个函数内部是一个循环,当调用mHandler.getLoop().quit()后,Looper才会终止,其后的代码才能够执行。


如何在子线程中创建handler?

可以使用HandlerThread,HandlerThread本质就是一个Thread,它与普通Thread的差别在于它有个Looper的成员变量,这个Looper其实就是对消息队列以及队列处理逻辑的封装,简单说就是消息队列+消息循环。

代码如下:

public class HandlerThreadDemoActivity extends Activity {


private Handler childHandler;
private HandlerThread handlerThread;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv = new TextView(this);
tv.setText("HandlerThreadDemo");
setContentView(tv);
handlerThread = new HandlerThread("handler_thread");
handlerThread.start();
childHandler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
System.out.println("handleThread收到了消息");
}
super.handleMessage(msg);
}


};
Message msg = Message.obtain();
msg.what = 1;
childHandler.sendMessage(msg);
}


@Override
protected void onDestroy() {
super.onDestroy();
childHandler.getLooper().quit();
}


}

也可以直接创建Thread,再创建Looper来实现,

代码如下:

private Handler childHandler;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv = new TextView(this);
tv.setText("HandlerThreadDemo");
setContentView(tv);
new Thread(new MyThread()).start();

}

private class MyThread implements Runnable{


@Override
public void run() {
Looper.prepare();
childHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
System.out.println("handleThread收到了消息");
}
}

};
Message msg = Message.obtain();
msg.what = 1;
childHandler.sendMessage(msg);
Looper.loop();
}

}


@Override
protected void onDestroy() {
super.onDestroy();
childHandler.getLooper().quit();
}

hanlder.post()这个方法干嘛用的?

Activity类有一个runOnUiThread方法,此方法的参数是一个Runnable对象,在run方法中可以直接更新UI,它内部就是通过调用Handler.post(),那么Handler.post究竟干了些什么, ?

查看源码: public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

这里面有个关键的方法getPostMessage(r),它将Runnable转成一个Message,它内部到底干了什么?看一下它的源码:

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

这里面就是将Runnable转化成一个message,内部跟sendMessage一样都是通过发送消息来通知主线程更UI,只是post在代码上简化了操作,如果在一个类只有个单个消息处理,那么使用handler.post更加方便。也可以用handler.postDelayed来执行定时器的操作。


handler在Activity中的释放问题!

handler可能会处理延迟消息,这时如果Activity已经finish了,mHandler释放,执行消息则会导致程序崩溃,所以要在onDestroy中执行removeMessages()方法来清除消息。如果是子线程创建的looper还需要mHandler.getLooper().quit();


handler造成内存泄漏的分析与解决

什么是内存泄露?

Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。

android中使用Handler造成内存泄露的原因

Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        mImageView.setImageBitmap(mBitmap);
    }
}

上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

内存泄露的危害

只有一个,那就是虚拟机占用内存过高,导致OOM(内存溢出),程序出错。对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制,FC。

使用Handler导致内存泄露的解决方法

方法一:通过程序逻辑来进行保护。

1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。

2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。

方法二:将Handler声明为静态类。

静态类不持有外部类的对象,所以你的Activity可以随意被回收。代码如下:

static class MyHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        mImageView.setImageBitmap(mBitmap);
    }
}

但其实没这么简单。使用了以上代码之后,你会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference):

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();
        if (activity != null) {
            mImageView.setImageBitmap(mBitmap);
        }
    }
}

将代码改为以上形式之后,就算完成了。

延伸:什么是WeakReference?

WeakReference弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了。



0 0
原创粉丝点击