×

为什么不能再子线程修改UI?

96
酸菜xwdz
2017.01.09 11:45* 字数 893

如果在子线程中修改UI报错如下:

报错

如果问题搞清楚了这三大原则基本上就ok了;

  • why
  • what
  • how

Why?

为什么会报错,报错的方法是checkThread,requestLayout 方法都在ViewRootimpl 类里面,
也就是说一旦ViewRootImpl 这个类被实例化,则就会调用requestLaoyout 方法来检查,requestLayout 方法如下:

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
   void checkThread() {
    //报错如下
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

报错的原因是因为咱们没有在主线程里面去修改UI,chechThread 方法会检查当前线程是不是主线程

How

正确修改方法如下:

  Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
          //当子线程获取到数据之后使用Handler 来修改UI
          // 当然如果这样写会存在内存泄露  这里不做讨论
          if(msg.waht == 0x123){
                textView.setText("主线程修改UI");
           }

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.supportRequestWindowFeature(Window.FEATURE_ACTION_BAR);
        setContentView(R.layout.aty_xxx);

       new Thread(new Runnable() {
            @Override
            public void run() {
                  //获取数据
                 //成功之后使用handler 发送what
                Message msg = Message.obtain();
                msg.what = 0x123;
                msg.obj = datas;
                hanlder.sendMessage(msg)
            }
        }).start();
    }

以上就是在主线程修改UI的标准写法。

What

请观察以下代码

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.supportRequestWindowFeature(Window.FEATURE_ACTION_BAR);
        setContentView(R.layout.aty_xxx);

    new Thread(new Runnable() {
            @Override
            public void run() {
                textView.setText("我在onCreate方法 一开始的时候就修改UI你拿我怎的?");
            }
        }).start();
    }
}

以上代码会不会执行成功呢?
如果你是从文章的开始看到这里来的话也许您会说肯定不能运行啊,不能再子线程里面修改UI啊;
很遗憾,程序会正常运行,(截图我就不上了,有兴趣探究的同学可以试试)而模拟器上也会显示textView 几个大字;那到底这是为什么呢?

这里我们回到之前的为什么会报错,咱们进入源码看一看:、

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
   void checkThread() {
    //报错如下
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

前面说到了如果实例化了ViewRootImpl 这个类则会检查当前线程是不是主线程,那么直接杀到在哪里实例化ViewRootImpl 这个类的地方不就水落石出了嘛,别急,咱们一步一步来:
可以看到requestLayout 调用了checkThread ,和scheduleTraversals() 方法,前者呢 是抛出错误的地方那后者呢?

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
         // 省略
        }
    }

这里postCallback 方法 第一个为一个常数,第三个是null,第二个则是一跟线程,代码如下;

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

 void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

performTraversals() 这个方法则是View进行绘制的开始; 每一次访问UI,即UI则会重绘;

回过头来我们再来找找在哪里进行实例化 ViewRootimpl 的;

 final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;

        // TODO Push resumeArgs into the activity for consideration
       // 执行resume activity 
        ActivityClientRecord r = performResumeActivity(token, clearHide);

        if (r != null) {
            final Activity a = r.activity;

            if (localLOGV) Slog.v(
                TAG, "Resume " + r + " started activity: " +
                a.mStartedActivity + ", hideForNow: " + r.hideForNow
                + ", finished: " + a.mFinished);
}

可以看到上面执行了resumeActivity 方法 ; 方法如下:

public final ActivityClientRecord performResumeActivity(IBinder token,
            boolean clearHide) {
        ActivityClientRecord r = mActivities.get(token);
        if (localLOGV) Slog.v(TAG, "Performing resume of " + r
                + " finished=" + r.activity.mFinished);
        if (r != null && !r.activity.mFinished) {
            if (clearHide) {
                r.hideForNow = false;
                r.activity.mStartedActivity = false;
            }
               // 省略
                r.activity.performResume();
              //省略
}

这里可以看到回调了activity.performResume() 方法 方法如下:

Activity ===>performResume()

 final void performResume() {
        performRestart();

        mFragments.execPendingActions();

        mLastNonConfigurationInstances = null;

        mCalled = false;
        // mResumed is set by the instrumentation
        mInstrumentation.callActivityOnResume(this);

        if (!mCalled) {
            throw new SuperNotCalledException(
                "Activity " + mComponent.toShortString() +
                " did not call through to super.onResume()");
        }
}

mInstrumentation 是用来辅助Activity完成启动Activity的过程

Instrumentation===>callActivityOnResume()

   public void callActivityOnResume(Activity activity) {
        activity.mResumed = true;
        activity.onResume();
        
        if (mActivityMonitors != null) {
            synchronized (mSync) {
                final int N = mActivityMonitors.size();
                for (int i=0; i<N; i++) {
                    final ActivityMonitor am = mActivityMonitors.get(i);
                    am.match(activity, activity, activity.getIntent());
                }
            }
        }
    }

可是看到真正执行onResume在这里 也就是说执行到这里 activity 的生命周期经历如下
onCreate ----- onStart ------onResume;

而performResumeActivity 里面执行完回调activity.onResume() 方法 有如下代码:

               r.activity.mVisibleFromServer = true;
                mNumVisibleActivities++;
                if (r.activity.mVisibleFromClient) {
                    r.activity.makeVisible();
                }

进入makeVisible 如下:

Activity===>callActivityOnResume()

  void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

ViewManager 是一个抽象类,而mDecor 是一个View ,我们找到ViewManager 实现类如下:

WindowManagerGlobal===>addView()

 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

          //省略

        ViewRootImpl root;
        View panelParentView = null;

         //省略

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        //省略
    }

进入到addView 方法内部终于找到了实例化ViewRootImpl 的地方;

这也就是解释了为什么在oncreate 生命周期刚开始的时候在子线程修改UI会报错呢,因此此时的ViewRootImpl 还没有被实例化;

 ActivityClientRecord r = performResumeActivity(token, clearHide);

要知道ViewRootImpl 实例化是在执行完handleResumeActivity 方法里面以上代码之后哦,而performResumeActivity 是回调activity.onResume的,他们的流程是这样的

QQ图片20170109113641.png

可是ViewRootImpl 实例化最后的过程是在activity -> makeVisible 方法里面在里面调用了
在WindowManagerGlobal (ViewManager 的实现类)--->addView 真正实行了ViewRootImpl 的实例化

有时候看源码真的很枯燥,但是某一个把自己看过的源码给串起来的感觉真的特别好(比如笔者之后有了解了activity的启动过程);

参考文章:

http://blog.csdn.net/xyh269/article/details/52728861
http://blog.csdn.net/singwhatiwanna/article/details/18154335

about Me

Android
Web note ad 1