A. 在多线程中,子线程更新主线程ui有哪些方法及注意点
android
UI多线程Androidthread工作
在一个Android 程序开始运行的时候,会单独启动一个Process。默认的情况下,所有这个程序中的Activity或者Service(Service和 Activity只是Android提供的Components中的两种,除此之外还有Content Provider和Broadcast Receiver)都会跑在这个Process。
一个Android 程序默认情况下也只有一个Process,但一个Process下却可以有许多个Thread。在这么多Thread当中,有一个Thread,我们称之为UI Thread。UI Thread在Android程序运行的时候就被创建,是一个Process当中的主线程Main Thread,主要是负责控制UI界面的显示、更新和控件交互。在Android程序创建之初,一个Process呈现的是单线程模型,所有的任务都在一个线程中运行。因此,我们认为,UI Thread所执行的每一个函数,所花费的时间都应该是越短越好。而其他比较费时的工作(访问网络,下载数据,查询数据库等),都应该交由子线程去执行,以免阻塞主线程。
那么,UI Thread如何和其他Thread一起工作呢?常用方法是:
诞生一个主线程的Handler物件,当做Listener去让子线程能将讯息Push到主线程的Message Quene里,以便触发主线程的handlerMessage()函数,让主线程知道子线程的状态,并在主线程更新UI。
例如,在子线程的状态发生变化时,我们需要更新UI。如果在子线程中直接更新UI,通常会抛出下面的异常:11-07 13:33:04.393: ERROR/javaBinder(1029):android.view.ViewRoot$:Only the original thread that created a view hierarchy can touch its views.
意思是,无法在子线程中更新UI。为此,我们需要通过Handler物件,通知主线程Ui Thread来更新界面。
如下,首先创建一个Handler,来监听Message的事件:
private final int UPDATE_UI = 1;private Handler mHandler = new MainHandler();private class MainHandler extends Handler {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case UPDATE_UI: {Log.i("TTSDeamon", "UPDATE_UI");showTextView.setText(editText.getText().toString());ShowAnimation();break;}default:break;}}}
或者
private Handler mHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case UPDATE_UI: {Log.i("TTSDeamon", "UPDATE_UI");showTextView.setText(editText.getText().toString());ShowAnimation();break;}default:break;}}}
当子线程的状态发生变化,则在子线程中发出Message,通知更新UI。
mHandler.sendEmptyMessageDelayed(UPDATE_UI, 0);
在我们的程序中,很多Callback方法有时候并不是运行在主线程当中的,所以如果在Callback方法中更新UI失败,也可以采用上面的方法。
B. android里如何在子线程中如何更新主线程的控件
步骤:
1、创建Handler对象(此处创建于主线程中便于更新UI)。
2、构建Runnable对象,在Runnable中更新界面。
3、在子线程的run方法中向UI线程post,runnable对象来更新UI。
代码
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private Button button;
private TextView textview;
private final int SPLASH_DISPLAY_LENGHT = 1;
private static int flag = 0;
private static int count=0;
private int ab=1;
private boolean isrun = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button)findViewById(R.id.button1);
textview = (TextView)findViewById(R.id.textView1);
final Handler handler= new Handler();
final Runnable runnable = new Runnable() {
public void run() {
textview.setText(Integer.toString(ab));
}
};
final Thread t = new Thread(){
//public boolean isrun=true;
@Override
public void run() {
while(isrun)
{
handler.post(runnable); //加入到消息队列 这样没有启动新的线程,虽然没有报异常。但仍然阻塞ProgressDialog的显示
ab++;
try {
sleep(1000); //直接调用
} catch (InterruptedException e) {
return;
}
}
}
};
t.start();
button.setOnClickListener(new Button.OnClickListener()
{
@Override
public void onClick(View view)
{
isrun=false;
}
});
}
}
C. 为什么loop之后就可以子线程更新ui
我们常常听到这么一句话:更新UI要在UI线程(或者说主线程)中去更新,不要在子线程中更新UI,而Android官方也建议我们不要在非UI线程直接更新UI。
事实是不是如此呢,做一个实验:
更新之前:
代码:
package com.bourne.android_common.ServiceDemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.bourne.android_common.R;
public class ThreadActivity extends AppCompatActivity {
private Thread thread;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread);
textView = (TextView) findViewById(R.id.textView);
thread = new Thread(new Runnable() {
@Override
public void run() {
textView.setText("text text text");
}
});
thread.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
登录后复制
这里在Activity里面新建了一个子线程去更新UI,按理说会报错啊,可是执行结果是并没有报错,如图所示:
接下来让线程休眠一下:
package com.bourne.android_common.ServiceDemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.bourne.android_common.R;
public class ThreadActivity extends AppCompatActivity {
private Thread thread;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread);
textView = (TextView) findViewById(R.id.textView);
thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
textView.setText("text text text");
}
});
thread.start();
}
}
登录后复制
应用报错,抛出异常:
android.view.ViewRootImpl$: Only the original thread that created a view hierarchy can touch its views
只有创建View层次结构的线程才能修改View,我们在非UI主线程里面更新了View,所以会报错
因为在OnCreate里面睡眠了一下才报错,这是为什么呢?
Android通过检查我们当前的线程是否为UI线程从而抛出一个自定义的AndroidRuntimeException来提醒我们“Only the original thread that created a view hierarchy can touch its views”并强制终止程序运行,具体的实现在ViewRootImpl类的checkThread方法中:
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
// 省去海量代码…………………………
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new (
"Only the original thread that created a view hierarchy can touch its views.");
}
}
// 省去巨量代码……………………
}
登录后复制
这就是Android在4.0后对我们做出的一个限制。
其实造成这个现象的根本原因是:
还没有到执行checkThread方法去检查我们的当前线程那一步。”Android对UI事件的处理需要依赖于Message Queue,当一个Msg被压入MQ到处理这个过程并非立即的,它需要一段事件,我们在线程中通过Thread.sleep(200)在等,在等什么呢?在等ViewRootImpl的实例对象被创建。”
ViewRootImpl的实例对象是在OnResume中创建的啊!
看onResume方法的调度,其在ActivityThread中通过handleResumeActivity调度:
public final class ActivityThread {
// 省去海量代码…………………………
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
boolean reallyResume) {
unscheleGcIdler();
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
// 省去无关代码…………
final int forwardBit = isForward ?
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
}
}
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
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;
wm.addView(decor, l);
}
} else if (!willBeVisible) {
// 省去无关代码…………
r.hideForNow = true;
}
cleanUpPendingRemoveWindows(r);
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
if (r.newConfig != null) {
// 省去无关代码…………
performConfigurationChanged(r.activity, r.newConfig);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.newConfig));
r.newConfig = null;
}
// 省去无关代码…………
WindowManager.LayoutParams l = r.window.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
!= forwardBit) {
l.softInputMode = (l.softInputMode
& (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
| forwardBit;
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
wm.updateViewLayout(decor, l);
}
}
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
if (!r.onlyLocalRequest) {
r.nextIdle = mNewActivities;
mNewActivities = r;
// 省去无关代码…………
Looper.myQueue().addIdleHandler(new Idler());
}
r.onlyLocalRequest = false;
// 省去与ActivityManager的通信处理
} else {
// 省略异常发生时对Activity的处理逻辑
}
}
// 省去巨量代码……………………
}
登录后复制
handleResumeActivity方法逻辑相对要复杂一些,除了对当前显示Window的逻辑判断以及没创建的初始化等等工作外其在最终会调用Activity的makeVisible方法
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2 {
// 省去海量代码…………………………
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
// 省去巨量代码……………………
}
登录后复制
在makeVisible方法中逻辑相当简单,获取一个窗口管理器对象并将根视图DecorView添加到其中,addView的具体实现在WindowManagerGlobal中:
public final class WindowManagerGlobal {
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 省去很多代码
ViewRootImpl root;
// 省去一行代码
synchronized (mLock) {
// 省去无关代码
root = new ViewRootImpl(view.getContext(), display);
// 省去一行代码
// 省去一行代码
mRoots.add(root);
// 省去一行代码
}
// 省去部分代码
}
}
登录后复制
在addView生成了一个ViewRootImpl对象并将其保存在了mRoots数组中,每当我们addView一次,就会生成一个ViewRootImpl对象,其实看到这里我们还可以扩展一下问题一个APP是否可以拥有多个根视图呢?答案是肯定的,因为只要我调用了addView方法,我们传入的View参数就可以被认为是一个根视图,但是!在framework的默认实现中有且仅有一个根视图,那就是我们上面makeVisible方法中addView进去的DecorView,所以为什么我们可以说一个APP虽然可以有多个Activity,但是每个Activity只会有一个Window一个DecorView一个ViewRootImpl,看到这里很多童鞋依然会问,也就是说在onResume方法被执行后我们的ViewRootImpl才会被生成对吧,但是为什么下面的代码依然可以正确运行呢:
package com.bourne.android_common.ServiceDemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.bourne.android_common.R;
public class ThreadActivity extends AppCompatActivity {
private Thread thread;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread);
textView = (TextView) findViewById(R.id.textView);
thread = new Thread(new Runnable() {
@Override
public void run() {
textView.setText("text text text");
}
});
thread.start();
}
@Override
protected void onResume() {
super.onResume();
}
}
登录后复制
Activity.onResume前,ViewRootImpl实例没有建立,所以没有checkThread检查。但是使用了Thread.sleep(200)的时候,ViewRootImpl已经被创建完毕了,自然checkThread就起作用了,抛出异常顺理成章。
第一种做法中,虽然是在子线程中setText,但是这时候View还没画出来呢,所以并不会调用之后的invalidate,而相当于只是设置TextView的一个属性,不会invalidate,就没有后面的那些方法调用了,归根结底,就不会调用ViewRootImpl的checkThread,也就不会报错。而第二种方法,调用setText之后,就会引发后面的一系列的方法调用,VIew要刷新界面,ViewGroup要更新布局,计算子View的大小位置,到最后,ViewRootImpl就会checkThread,就崩了。
所以,严格上来说,第一种方法虽然在子线程了设置View属性,但是不能够归结到”更新View”的范畴,因为还没画出来呢,就没有所谓的更新。
当我们执行Thread.sleep时候,这时候onStart、onResume都执行了,子线程再调用setText的时候,就会崩溃。
那么说,在onStart()或者onResume()里面执行线程操作UI也是可以的:
@Override
protected void onStart() {
super.onStart();
thread = new Thread(new Runnable() {
@Override
public void run() {
textView.setText("text text text");
}
});
thread.start();
}
登录后复制
@Override
protected void onResume() {
super.onResume();
thread = new Thread(new Runnable() {
@Override
public void run() {
textView.setText("text text text");
}
});
thread.start();
}
登录后复制
注意的是:当你在来回切换界面的时候,onStart()和onResume()是会再执行一遍的,这时候程序就崩溃了!
1、能不能在非UI线程中更新UI呢?
答案:能、当然可以
2、View的运行和Activity的生命周期有什么必然联系吗?
答案:没有、或者隐晦地说没有必然联系
3、除了Handler外是否还有更简便的方式在非UI线程更新UI呢?
答案:有、而且还不少,Activity.runOnUiThread(Runnable)、View.Post(Runnable)、View.PostDelayed(Runnable,long)、AsyncTask、其内部实现原理都是向此View的线程的内部消息队列发送一个Message消息,并传送数据和处理方式,省去了自己再写一个专门的Handler去处理。
4、在子线程里面用Toast也会报错,加上Looper.prepare和Looper.loop就可以了,这里可以这样做吗?
答案当然是不可以。Toast和View本质上是不一样的,Toast在子线程报错,是因为Toast的显示需要添加到一个MessageQueue中,然后Looper取出来,发给Handler调用显示,子线程因为没有Looper,所以需要加上Looper.prepare和Looper.loop创建一个Looper,但是实质上,这还是在子线程调用,所以还是会报错的!
5、为什么Android要求只能在UI主线程中更改View呢
这就要说到Android的单线程模型了,因为如果支持多线程修改View的话,由此产生的线程同步和线程安全问题将是非常繁琐的,所以Android直接就定死了,View的操作必须在UI线程,从而简化了系统设计。
参考文章
为什么我们可以在非UI线程中更新UI
【Android开发经验】来来来,同学,咱们讨论一下“只能在UI主线程更新View”这件小事
线程
android
ui
女式凉鞋,时尚,优雅,透气,货到付款!
精选推荐
广告
可能是全网最简单透彻的安卓子线程更新 UI 解析
71阅读·0评论·0点赞
2019年4月24日
android 不能在子线程中更新ui的讨论和分析
1.5W阅读·7评论·14点赞
2016年1月26日
android.view.ViewRootImpl$: Only the original thread that created
140阅读·0评论·0点赞
2022年9月23日
Looper.prepare()和Looper.loop(),在子线程中更新UI
2520阅读·0评论·0点赞
2016年5月2日
Android为什么能在子线程中更新UI
305阅读·0评论·1点赞
2020年4月9日
android多线程中更新ui,Android 在子线程中更新UI
146阅读·0评论·0点赞
2021年6月3日
Android子线程更新UI就会Crash么
1781阅读·0评论·4点赞
2017年4月1日
为什么只能在主线程中操作UI?为什么子线程中setText不报错?
3970阅读·1评论·5点赞
2017年6月27日
Android 子线程更新TextView的text 不抛出异常原因 分析总结
1211阅读·0评论·3点赞
2019年7月10日
非主线程更新UI
210阅读·0评论·0点赞
2018年4月12日
非 UI 线程中更新 UI
215阅读·0评论·0点赞
2021年4月29日
【Android】 Handler——子线程更新UI
722阅读·1评论·4点赞
2019年12月29日
android-如何在子线程中更新ui
4019阅读·4评论·2点赞
2016年8月23日
SurfaceView
251阅读·0评论·0点赞
2019年3月8日
非UI线程中更新UI
373阅读·0评论·0点赞
2018年7月10日
QT非UI线程更新UI(跨线程更新UI)
275阅读·0评论·0点赞
2022年9月21日
Android开发之UI线程和非UI线程
1619阅读·0评论·1点赞
2020年4月5日
非UI线程可不可以更新UI(一)
1265阅读·0评论·2点赞
2016年2月29日
为什么我们可以在非UI线程中更新UI
2.8W阅读·56评论·35点赞
2015年2月3日
去首页
看看更多热门内容
D. android怎么更新UI
首先,android的UI刷新是在主线程(UI线程)中完成的。四大组件中,activity和service运行在主线程中。现在总结自己在项目中常用到的UI刷新方式。
第一,利用子线程发消息刷新UI。
子线程负责处理UI需要的数据,然后发消息到主线程来刷新UI。代码结构如下:
new Thread(new Runnable() {
@Override
public void run() {
Person person=new Person();
person.setName(mName.getText().toString().trim());
person.setPhone(mPhone.getText().toString().trim());
Log.i("person",person.toString());
DatabaseInfoFactory.getPersonDao(mContext).addPerson(person);
Looper.prepare();
Message msg=Message.obtain();
msg.what=0x123456;
handler.sendMessage(msg);
Looper.loop();
}
}).start();
主线程中:
private Handler mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
if(msg.what==0x123456||msg.what==0x123){
fillData();
setListener();
}
}
};
第二,利用异步任务更新UI。代码结构如下:
new AsyncTask<void,void,void>() {
@Override
protected void onPostExecute(Void result) {
if(mAdapter==null){
mAdapter=new LeaveInfoAdapter();
//设置数据适配器
mLVleaveInfos.setAdapter(mAdapter);
Log.i("测试", "异步任务显示后台获得数据库数据");
}
else {
mAdapter.notifyDataSetChanged();
}
super.onPostExecute(result);
}
@Override
protected Void doInBackground(Void... params) {
//获得要显示的数据
mleaveInfos=mLeaveInfosDao.findAll();
if (mleaveInfos==null) {
Toast.makeText(HomeActivity.this,"请假数据不存在或是已经清除!", 500).show();
}
Log.i("测试", "异步任务后台获得数据库数据"+mleaveInfos.size());
return null;
}
}.execute();</void,void,void>
第三,利用配置文件+activity的生命周期方法刷新UI。
E. android中如何实现UI的实时更新
1、在主线程中启动一个子线程
首先,我们需要在主线程中启动一个子线程,这个比较简单,直接在MainActivity的onCreate()方法中调用如下方法即可:
newThread(mRunnable).start();
2、在子线程中发送Message给Handler
在创建子线程时,我们使用了Runnable接口对象mRunnable。这里,只需要实现Runnable接口,并重写该接口的run()方法,在run()方法中实现每1秒发送一条Message给Handler即可。具体实现方法如下:
/*
*Function:实现run()方法,每1秒发送一条Message给Handler
*/
privateRunnablemRunnable=newRunnable(){
publicvoidrun(){
while(true){
try{
Thread.sleep(1000);
mHandler.sendMessage(mHandler.obtainMessage());
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
}
};
3、Handler接收Message通知
最后,我们创建一个Handler对象,用来接收Message通知。在收到Message通知后,完成刷新UI的操作即可。具体实现方法如下:
/*
*Function:实现handleMessage()方法,用于接收Message,刷新UI
*/
privateHandlermHandler=newHandler(){
publicvoidhandleMessage(Messagemsg){
super.handleMessage(msg);
refreshUI();
}
};
4、刷新UI
由以上的代码可以看出,刷新UI的操作,我们是放在refreshUI()方法中来完成的。refreshUI()方法的实现也很简单,调用HttpUtils工具类中的getInputStream()方法,获得图1所示Web工程的页面内容输入流,再将该输入流转化为字符串,放入TextView控件中进行显示即可。具体实现方法如下:
/*
*Function:刷新UI
*/
privatevoidrefreshUI(){
try{
InputStreaminputStream=HttpUtils.getInputStream();
StringresultData=HttpUtils.getResultData(inputStream);
mTextView.setText(resultData);
}catch(IOExceptione){
e.printStackTrace();
}
}
F. android studio子线程更新UI 问题
睡使用Thread.currentthread().sleep(1000);
发送不同的Message,每次都是一个,放到for循环内Message message = new Message();
G. Android 在子线程中更新UI的几种方法示例
请您慢慢看:
直接在UI线程中开启子线程来更新TextView显示的内容,运行程序我们会发现,如下错误:android.view.ViewRoot$: Only the original thread that created a view hierarchy can touch its views.翻译过来就是:只有创建这个控件的线程才能去更新该控件的内容。
所有的UI线程要去负责View的创建并且维护它,例如更新冒个TextView的显示,都必须在主线程中去做,我们不能直接在UI线程中去创建子线程,要利用消息机制:handler,如下就是handler的简单工作原理图:
既然android给我们提供了Handler机制来解决这样的问题,请看如下代码:
public class HandlerTestActivity extends Activity { private TextView tv; private static final int UPDATE = 0; private Handler handler = new Handler() { @Overridepublic void handleMessage(Message msg) { // TODO 接收消息并且去更新UI线程上的控件内容if (msg.what == UPDATE) { // Bundle b = msg.getData();// tv.setText(b.getString("num")); tv.setText(String.valueOf(msg.obj)); } super.handleMessage(msg); } }; /** Called when the activity is first created. */@Overridepublic void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); tv = (TextView) findViewById(R.id.tv); new Thread() { @Overridepublic void run() { // TODO 子线程中通过handler发送消息给handler接收,由handler去更新TextView的值try { for (int i = 0; i < 100; i++) { Thread.sleep(500); Message msg = new Message(); msg.what = UPDATE; // Bundle b = new Bundle();// b.putString("num", "更新后的值:" + i);// msg.setData(b); msg.obj = "更新后的值:" + i; handler.sendMessage(msg); } } catch (InterruptedException e) { e.printStackTrace(); } } }.start(); }}
我们就通过Handler机制来处理了子线程去更新UI线程控件问题,Andrid开发中要经常用到这种机制。