返回 登录
0

难道子线程真的不能更新UI吗?能或不能,那又是为什么呢?

  1. 初学android一段时间,经常我们会听到说不能在子线程更新UI,不能在主线程执行耗时操作,但是谁知道这到底是为什么呢?

  2. 难道子线程一点UI都不能操作吗?若在子线程操作UI会出现什么问题呢?

  3. 还有android(java)为什么要有handler这种机制呢?

========================================================================

  下来我们以一个settext方法为例直入主题,抛弃长篇大论的源码片段,摘出一小段源码,争取做到简单易懂,让君恍然大悟。
  
  通过activity的启动和布局创建过程(如果嫌麻烦的话可以稍微了解一下就OK),我们知道activity的创建需要新建一个ViewRootImpl对象,那么看看ViewRootImpl的构造函数就很必要了:

/**avtivity的线程*/
public ViewRootImpl(Context context, Display display) {
    mThread = Thread.currentThread();
    ...
}

在初始化一个ViewRootImpl函数的时候,会调用native方法,获取到该线程对象mThread(currentThread是指什么线程呢,先带着疑问往下看),接着setText函数会调用到requestLayout方法(TextView绘制出来之后,调用setText才会去调用requestLayout方法,没有绘制出来之前,在子线程中调用setText是不会抛出Exception):

/**settext的线程*/
public void requestLayout() {
    .....
    checkThread();
    .....
}
void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

可以看到,若此处的mThread不指向当前线程,则抛出异常!

所以“不能在子线程中更新ui”这个问题很清楚了吧,不管startActivity函数调用在什么线程,ActivityThread是运行在主线程中的:

/**
 * This manages the execution of the main thread in an
 * application process, scheduling and executing activities,
 * broadcasts, and other operations on it as the activity
 * manager requests.
 */
public final class ActivityThread {
}

所以ViewRootImpl对象的创建也是在主线程中,所以一个Activity的对应ViewRootImpl对象中的mThread一定是代表主线程!所以下面的Thread.currentThread()若不是主线程,是不是就异常了呢?

if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
 [为什么android会设计成只有创建viewRootImpl的原始线程才能更改ui呢?这就要说到Android的单线程模型了,因为如果支持多线程修改View的话,由此产生的线程同步和线程安全问题将是非常繁琐的,所以Android直接就定死了,View的操作必须在UI线程,从而简化了系统设计。](http://blog.csdn.net/self_study/article/details/50548894)

那么handler的出现是不是就显得理所应当了呢,简单粗暴,就是为了解决子线程更新UI而出现的。



你以为到这儿就完了吗? NO!NO! NO!
往下看:

有一次我在子线程中settext竟然没报错,搞什么! 难道子线程中可以更新UI? 一度让我怀疑那个线程是主线程了。

终于,找到了原因:

其实 ViewRootImpl 是在Activity 生命周期执行到 onResume()时产生的。
看下activity的生命周期 , onCreate()–>onStart()–>onResume(),并且在主线程开始的时候,子线程也开始了。

从onresume()开始追溯:

我们知道整个activity创建是在ActivityThread里面创建的,那我们就去看看,我们找到了一个handleResumeActivity()的方法,在onResume()的时候会回调这个方法:

final void handleResumeActivity(IBinder token,
                ViewManager wm = a.getWindowManager();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ...
                if (a.mVisibleFromClient) {
                    wm.addView(decor, l);
                }
    }

这里的decor其实就是整个Activity的根布局(Layout),然后最后调用addView( decor,l)方法。
我们去ViewManager 看看 addView()的方法,而 WindowManager是一个接口,实际上就是调用了WindowManagerImpl里的addView 方法,那我们就瞧一眼。

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

addView()中又继续调用 mGlobal.addView(),然后再去Global去找父类addview()方法:

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

看见了没,root = new ViewRootImpl(view.getContext(), display)
足以说明ViewRootImpl对象是在onresume()方法这儿创建的,也就是说:

  • 在onResume()方法之前更新UI,mThread指向子线程,此时判断等于,不抛异常。

  • 在onResume()方法之后更新UI,ViewRootImpl对象被创建,mThread指向主线程,此时判断不等于,抛出异常。

可见,子线程是不能更新UI的,但是因为执行顺序或者速度等一些方面的原因,允许在很短很短的时间内更新UI,可是这个时间谁也说不清楚,因此说明子线程更新UI是线程不安全的!!!!当然,也可以看出UI控件也是线程不安全的。

然后呢,再加上一点:
主线程不能做耗时操作,因为若耗时操作长达5s或以上,就会引发ANR问题,因此也拒绝在主线程中进行耗时操作。

号外:(因此,handler的出现很显然就是为了子线程向主线程发送UI请求而出现的~~出现的根本目的就是上面所说。)

读到这儿,我希望读者能有一种恍然大悟的感觉,若没有…………咳咳,那我再练练吧
blog:http://blog.csdn.net/u012534831/article/details/52056072

评论