Android可不可以在子线程中更新UI?

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_34895720/article/details/97922066

其实在某些情况下是可以在子线程中更新UI的!

比如:在一个activity的xml文件中中随便写一个TextView文本控件,然后在Activity的onCreate方法中开启一个子线程并在该子线程的run方法中更新TextView文本控件,你会发现根本没有任何问题。

private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.threadcrashui_act);
    textView = (TextView) findViewById(R.id.textView);
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 更新TextView文本内容
            textView.setText("update TextView");
        }
    }).start();
}

但是如果你让子线程休眠2秒钟如下面代码:

private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.threadcrashui_act);
    textView = (TextView) findViewById(R.id.textView);
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
                //更新TextView文本内容
                textView.setText("update TextView");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

程序直接挂掉了,好吧,我们先去看看log日志,如图提示
在这里插入图片描述
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

这个异常是从android.view.ViewRootImpl的checkThread方法抛出的。

程序不允许在非UI线程中更新UI线程
这个ViewRootImpl我们可能在Eclipse或者Android Studio中我们无法查看,大家可以使用Source Insight 去查看源码:打开Source Insight 打开ViewRootImpl类,找到 invalidateChild这个方法

public void invalidateChild(View child, Rect dirty) {
  //关键的地方就是这个方法
    checkThread();
  //后边的全部省略
}
 void checkThread() {
    //在这里mThread表示的是主线程,程序作了判断,检查当前线程是不是主线程,如果不是就会抛出异常
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

看到上边方法中抛出的异常是不是感觉很熟悉,对,没错,就是我log中截出来的那句话!!那么我们现在懵逼了,为什么我们在不让子线程休眠的情况下去更新TextView文本可以,而让线程休眠两秒后就出抛异常呢?根本原因就是ViewRootImpl到底是在哪里被初始化的!ViewRootImpl是在onResume中初始化的,而我们开启的子线程是在onCreat方法中,这个时候程序没有去检测当前线程是不是主线程,所以没有抛异常!!下边我们去看ActivityThread源码,去找出原因!!

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
         if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            //ViewPrent实现类
            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;
          //问题的根源在这里,addView方法在这里对ViewRootImpl进行初始化,大家可以去看看ViewGroup的源码,找里边的addView方法,你会发现,最后又回到View的invalidate(true)方法;
                wm.addView(decor, l);
            }       
    }
}

子线程更新UI界面的2种方法
方式一:Handler和Message
① 实例化一个Handler并重写handlerMessage()方法

private Handler handler = newHandler() {
    public void handleMessage(Message msg) {
          // 处理消息
    super.handleMessage(msg);
    switch (msg.what) {
    case 1:
      button1.setText("点击安装");
      break;
    case 2:
      button1.setText("打开");
      break;
    }
    };
 };

② 在子线程中获取或创建消息,并使用handler对象发送。

//子线程一
             doInBackground1=new Runnable(){

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    //1.访问数据库或者互联网,不在UI进程,所以不卡
                    Message msg=new Message();
                    //对消息一个识别号,便于handler能够识别
                    msg.what=1;
                    handler.sendMessage(msg);
                    Log.i("dd","子线程一已发送消息给handler");
                }};

方式二:在子线程中直接调用Activity.runOnUiThread(Runnable action)方法

new Thread() {
public void run() {
//这儿是耗时操作,完成之后更新UI;
runOnUiThread(new Runnable(){

				@Override
				public void run() {
					//更新UI
					imageView.setImageBitmap(bitmap);
				}
				
			});
		}
	}.start();

如果在非上下文类中(Activity),可以通过传递上下文实现调用;


Activity activity = (Activity) imageView.getContext();
				activity.runOnUiThread(new Runnable() {
 
					@Override
					public void run() {
						imageView.setImageBitmap(bitmap);
					}
				});

扩展:
ViewRoot对应ViewViewRootImpl类,它是连接WindowManager和DecorView的纽带,view的三大流程(measure,layout,draw)均是通过ViewRoot来完成的,ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时创建ViewRootImpl对象并和DecorView建立联系。DecorView作为顶级的View一般情况下它内部会包含一个竖向的LinearLayout,这个LinearLayout里面有上下两个部分(具体情况和Android版本及主题有关),上面是标题栏,下面是内容栏。

猜你喜欢

转载自blog.csdn.net/qq_34895720/article/details/97922066