美文网首页
子线程不能更新UI?

子线程不能更新UI?

作者: 卡路fly | 来源:发表于2020-05-22 02:02 被阅读0次

看这里看这里:Android中子线程真的不能更新UI吗

只能在UI线程访问UI原因

Android的UI访问是没有加锁的,这样在多个线程访问UI是不安全的。所以Android中规定只能在UI线程中访问UI。

onCreate的子线程中延迟200ms执行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:6581)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:924)

ViewRootImpl是ViewRoot的实现类。

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

mThread是主线程,在应用程序启动的时候,就已经被初始化了。

ViewRootImpl的checkThread方法会检查当前是哪个线程访问的UI,如果不是主线程就会抛出异常

Only the original thread that created a view hierarchy can touch its views
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:924)

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();  // 检查当前线程
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

scheduleTraversals()方法

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

注意到postCallback方法的的第二个参数传入了很像是一个后台任务。

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

// 跟进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的绘制过程就是从这个performTraversals方法开始的。每一次访问了UI,Android都会重新绘制View。

当访问UI时,ViewRootImpl会调用checkThread方法去检查当前访问UI的线程是哪个,如果不是UI线程则会抛出异常。

执行onCreate方法的那个时候ViewRootImpl还没创建,无法去检查当前线程。

ViewRootImpl创建时机

在ActivityThread中,handleResumeActivity方法

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
    ActivityClientRecord r = performResumeActivity(token, clearHide);

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

        //代码省略

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

      //代码省略    
}

内部调用了performResumeActivity方法

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) {
    //代码省略
            r.activity.performResume();

    //代码省略

    return r;
}

r.activity.performResume()这行代码,跟进 performResume方法

final void performResume() {
    performRestart();

    mFragments.execPendingActions();

    mLastNonConfigurationInstances = null;

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

    //代码省略

}

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());
            }
        }
    }
}

activity.onResume()。这也证实了,performResumeActivity方法确实是回调onResume方法的入口。

我们看回来handleResumeActivity方法,执行完performResumeActivity方法回调了onResume方法后,
会来到这一块代码:

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

activity调用了makeVisible方法

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

往WindowManager中添加DecorView,那现在应该关注的就是WindowManager的addView方法了。而WindowManager是一个接口来的,我们应该找到WindowManager的实现类才行,而WindowManager的实现类是WindowManagerImpl。
找到了WindowManagerImpl的addView方法

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {

    //代码省略  


    ViewRootImpl root;
    View panelParentView = null;

    //代码省略

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

        view.setLayoutParams(wparams);

        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);
    } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        synchronized (mLock) {
            final int index = findViewLocked(view, false);
            if (index >= 0) {
                removeViewLocked(index, true);
            }
        }
        throw e;
    }
}

ViewRootImpl是在WindowManagerGlobal的addView方法中创建的。

ViewRootImpl的创建在onResume方法回调之后。

相关文章

  • 如何做到在子线程更新 UI?

    一般来讲,子线程是不能更新 UI 的,如果在子线程更新 UI,会报错。 但在某种情况下直接开启线程更新 UI 是不...

  • 子线程更新UI的方法

    子线程中不能直接更新UI,如果直接更新的话会发生崩溃所以要在主线程中更新UI,总计三种回到主线程更新UI的方式 1...

  • Android多线程

    1.沿用java的子线程创建 2.在子线程中不能更新UI,那么在Android中更新UI的方法 runOnUiTh...

  • Android Handler框架最全分析

    Handler的作用 Handler的作用:在子线程更新UI或者处理延时任务 一般来说:我们不能在子线程更新UI(...

  • 封装工具类无法使用runOnUiThread解决办法

    由于Android中不能在子线程中更新ui,所以平时在子线程中需要更新ui时可以使用Android提供的RunOn...

  • Android中为什么不能在子线程中更新UI

    点击查看 原因: 这个只解释了如果在子线程更新UI为什么会抛异常;真正不能再自在子线程更新UI的原因是:UI控件非...

  • iOS多线程之3.NSThread的线程间通信

      我们把一些耗时操作放在子线程,例如下载图片,但是下载完毕我们不能在子线程更新UI,因为只有主线程才可以更新UI...

  • iOS子线程操作UI

    首先声明一点:子线程里面是可以更新UI的。 之所以说子线程不能操作UI是因为UIKit不是线程安全的。UI操作涉及...

  • 子线程不能更新UI?

    看这里看这里:Android中子线程真的不能更新UI吗 只能在UI线程访问UI原因 Android的UI访问是没有...

  • 从顶层设计的角度对Android ANR机制的一些思考

    “不能在子线程中更新UI”“主线程不能做耗时操作” 这些话被我们奉为圭臬,但有多少人想过为什么不能在子线程中更新U...

网友评论

      本文标题:子线程不能更新UI?

      本文链接:https://www.haomeiwen.com/subject/kchfahtx.html