中国最全IT社区平台 联系我们 | 收藏本站
阿里云优惠2阿里云优惠1
您当前位置:首页 > php开源 > 综合技术 > 子线程不能更新UI线程总结

子线程不能更新UI线程总结

来源:程序员人生   发布时间:2017-05-06 14:50:49 阅读次数:414次

子线程不能更新UI线程总结

  • 子线程整的不能更新UI线程吗
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

有时候大家做项目的时候偶尔会碰到这个毛病。不用说大家都知道是子线程更新主线程(UI)线程的问题,一样大家也会给出相对应的解法:使用handle+Thread方法通过发送Message进行更新UI线程。
eg:

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                iv_img.setBackground(ContextCompat.getDrawable(MainActivity.this,R.drawable.ic_launcher));
                btn_commit.setText("iiiiiiiiiiiiiiiiiiiii  ");
                Toast.makeText(MainActivity.this,"阿斯蒂芬噶是的发送到",Toast.LENGTH_LONG).show();
            }
        }).start();

此时就会出现1下毛病:
这里写图片描述
此时我们都知道最简单的1种解决方式就是:

        new Thread(new Runnable() {
            @Override
            public void run() {
                Message msg = handler.obtainMessage();
                msg.what = 100;
                msg.obj = "发送消息";
                handler.sendMessage(msg);
            }
        }).start();
    }

接下来我们就来探究1下:子线程和UI线程之间更新问题。
首先我们要知道:

  • android利用程序遵守的是依照单线程模式的原则
    这是由于:Android UI操作其实不是线程安全的并且这些操作必须在UI线程中履行。
    android的UI线程就是主线程,当做粗第1次启动的时候 Android会同时启动1个对应的主线程(Main Thread),主线程主要负责处理与UI相干的事件,如:用户的按键事件,用户接触屏幕的事件和屏幕绘图事件,并把相干的事件分发到对应的组件进行处理。所以主线程通常又被叫做UI线程。这个和Java的运行机制是不同的。
    andorid在UI中进行绘图和处理事件启动1个监听的作用 ,此时就必须要在UI中时时刻刻的进行相利用户的点击事件和UI操作事件。异步操作和耗时操作需要另外气1个线程,不然UI线程在5s内未响利用户的操作,系统就会弹出弹出对话框停止程序终止进程的提示。

我们想看看报错的这行代码在ViewRootImpl.java:

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

mThread是1个线程,如果改线程是当前的线程的时候,则继续向下走,不会抛出异常。大家可以看到,我在onCreate方法,只有1个线程,肯定是当前的线程,那为何会报错呢?我们接着往下看。
接下来我们看看checkThread()在那几处用到了,其中有

  @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
        .......
        invalidateRectOnScreen(dirty);
        return null;
    }
 @Override
    public void requestFitSystemWindows() {
        checkThread();
        mApplyInsetsRequested = true;
        scheduleTraversals();
    }
   @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

从报错的点 at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:848)我们可以看到是848行是invalidateChildInParent方法里调用的。我们进1步与发现invalidateChildInParent又在invalidateChild()里调用,在View中那个地方调用了呢?我们进1步向下跟进。

    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
            ......    
            final AttachInfo ai = mAttachInfo;
            final ViewParent p = mParent;
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
                p.invalidateChild(this, damage);
            }
            ......            
    }

而invalidateInternal方法在invalidate()方法中是这样的

    void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom -  mTop, invalidateCache, true);
    }
到这里1目了然了。原来View中再重新回执的时候刷新方法里调用了ViewRootImpl的checkThre(),刷新是检查该线程是否是当前的线程,即主线程。
我们根据源码可以知道invalidate()–>checkThread()是1步1步去调用的。
android.view.View.setBackground()
android.view.View.setBackground()—->setBackgroundDrawable()–> invalidate()–>invalidateInternal()–>invalidateChild()–>checkThread()–>invalidateRectOnScreen()

现在我们清楚了,严格的来讲原来子线程是不能刷新UI线程的。
解决方式:
第1种方式:

 new Thread(new Runnable() {
            @Override
            public void run() {
                Message msg = handler.obtainMessage();
                msg.what = 100;
                msg.obj = "发送消息";
                handler.sendMessage(msg);
            }
        }).start();
    }

第2中方式:

         runOnUiThread(new Runnable() {
             @Override
             public void run() {
                 try {
                     Thread.sleep(200);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 iv_img.setBackground(ContextCompat.getDrawable(MainActivity.this,R.drawable.ic_launcher));
             }
         });

第3种方式: 利用AsyncTask方法。
以上都是进程间通讯的几种方式。这里我不再做过量的描写。
如果您觉我的文章对您有所帮助,
QQ交换群 :232203809,欢迎入群
这里写图片描述
微信公众号:终端研发部
(欢迎关注学习和交换)

生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠
程序员人生
------分隔线----------------------------
分享到:
------分隔线----------------------------