Android Handler深度分析

 

       Handler在Android开发中的使用频率很高,在子线程中使用它可以实现更新UI的功能。为了更好的理解Handler机制,本文将对Handler进行一些深入的探讨,探讨主要针对几个问题来展开。至于Handler的基础原理和使用方法,请自行Google。

为什么Android要求子线程只能使用Handler更新UI?

       如果不使用Handler的方式更新UI,而是子线程直接修改UI,就会面临线程安全的问题。线程安全问题的解决方法,就是在进行操作的时候加锁。这么做有两个明显的弊端,一个是代码实现复杂并容易出错,另外就是加锁导致程序的运行效率会下降。因此,Android对UI的更新限制不能在子线程更新,而是发送到主线程,由主线程统一处理。

子线程更新UI的方式有哪些,它们有什么联系?

       常用的子线程更新UI的方式主要有以下几种:

Handler.post(Runnable)

Handler.sendMessage()

View.post(Runnable)

AsyncTask

Activity.runOnUiThread()

       这几种方式看起来差别很大,但实际上它们的原理都是相同的,最终都是通过Handler的sendMessage方法来发送消息,然后在UI线程中处理Handler的回调。只是Google帮我们对Handler机制进行了不同程度的封装以适应不同的应用场景。

       为了说明这个问题,我们先看一下View.post()方法。

 

    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }

       方法中调用了attachInfo.mHandler.post(action)去发送消息,其实就是调用了Handler的post()方法,继续看这个方法:

 

 

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

       post方法里使用的就是sendMessge()的方式发送消息。

 

       再来看一下runOnUiThread()方法,

 

    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

       首先判断是否是主线程,如果是就直接执行代码逻辑,如果不是,依然是调用了Handler 的post()方法。从这里可以看出,所有可以实现子线程更新UI的方法的原理都是一样的。

 

主线程怎么使用Handler给子线程发送消息?

       我们知道Handler是需要与一个Looper绑定的,主线程是默认启动了Looper的。Android程序的执行入口是在ActivityThread类的main方法中,我们从这个方法入手来查找。

 

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

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        AsyncTask.init();

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

        Looper.loop();

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

       代码显示,在ActivityThread的main()方法中创建了Looper对象并启动了Looper循环,主线程中创建的Handler默认都是与这个Looper绑定的。
       由于子线程默认是不会创建Looper对象的,如果将Handler与子线程绑定,就要在绑定前先调用Looper.prepare()和Looper.loop()方法启动Looper循环,然后才能通过Handler向其所关联的MessageQueue中发送消息,否则就会报如下异常:Can't create handler inside thread that has not calledLooper.prepare()。下面展示一段典型的实现代码。

 

 

public class MainActivity extends Activity {
	private LooperThread  looperThread;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_layout);
        looperThread = new LooperThread();
        looperThread.start();
	looperThread.getHandler().sendEmptyMessage(1);
    }
    
    
    class LooperThread extends Thread {
        private Handler mHandler;
        private final Object mLock = new Object();

        public void run() {
            Looper.prepare();
            synchronized (mLock) {
                mHandler = new Handler(){
                	@Override
                	public void handleMessage(Message msg) {
                		.....
                	}
                };
                mLock.notifyAll();
            }
            Looper.loop();
        }
        
        public Handler getHandler() {
            synchronized (mLock) {
                if (mHandler == null) {
                    try {
                        mLock.wait();
                    } catch (InterruptedException e) {
                    }
                }
                return mHandler;
            }
        }
        public void exit() {
            getHandler().post(new Runnable(){
                public void run() {
                    Looper.myLooper().quit();
                }});
        }
    }
}

 

不使用Handler真的不能更新UI吗?

       首先来看下面一段代码

 

       new Thread(new Runnable() {
            @Override
            public void run() {
                btn.setText("aaaaaaaaa");
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                btn.setText("cccccccc");
            }
        }).start();

 

       这段代码在Activity的onCreate回调中执行,btn是我们定义的一个Button对象。从执行结果看,第一段代码顺利执行且UI正确更新,却在执行第二段代码的时候发生了崩溃,这是为什么呢?

       来看一下崩溃栈信息

 

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6063)
at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:885)
at android.view.ViewGroup.invalidateChild(ViewGroup.java:4320)
at android.view.View.invalidate(View.java:10946)
at android.view.View.invalidate(View.java:10901)
at android.widget.TextView.checkForRelayout(TextView.java:6594)
at android.widget.TextView.setText(TextView.java:3820)
at android.widget.TextView.setText(TextView.java:3678)
at android.widget.TextView.setText(TextView.java:3653)
at com.enjoy.vicleedemo.service.ServiceUpdateActivity$3.run(ServiceUpdateActivity.java:121)
at java.lang.Thread.run(Thread.java:841)

       我想每个Android开发者都对这个异常不会陌生。根据堆栈信息,更新UI时会调用View的invalidate()方法,继而调用ViewRootImpl的invalidateChildInParent()方法,其中调用了checkThread(),其中会检查当前是否为主线程,否则抛出上面的那个异常。

 

 

public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);

        if (dirty == null) {
            invalidate();
            return null;
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }......}
    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

也就是说是在ViewRootImpl这个类中进行了线程的检查。只是在onCreate()中更新UI的时候,ViewRootImpl的对象还没有创建,因此不会进行这个检查,也就出现了上面例子中的现象。那么它是在什么时候创建的呢?ActivityThread类会调用handleResumeActivity方法将顶层视图DecorView添加到PhoneWindow中,我们就从handleResumeActivity开始看起。

 

 

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
            ..................
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                //得当当前Activity的WindowManagerImpl对象
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);

            .....................

代码中,ViewManager wm = a.getWindowManager()这个得到的是一个WindowManagerImpl类的对象,然后调用了WindowManagerImpl类的addView方法。查看addView方法,又调用了mGlobal的addView方法,mGlobal是WindowManagerGlobal类的对象。

    @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

然后看WindowManagerGlobal类的addView方法:

 

 

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ............
        ViewRootImpl root;
        ............
         root = new ViewRootImpl(view.getContext(), display);
        ...........
        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
          ...........
        }
    }

       终于看到了ViewRootImpl,它正式在这里创建的。也就是说,ViewRootImpl是在onResume执行之后创建的。这也就解释了为什么直接在Activity的onCreate中在子线程更新UI不会报错,而在延时一段时间后却出现崩溃。

       欢迎关注我的公众号一起交流学习

     

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值