开发Android这么久了,总会听到有人说:主线程不能访问网络,子线程不能更新UI。Android的主线程的确不能长时间阻塞,但是子线程为什么不能更新UI呢?今天把这些东西整理,顺便在子线程更新UI。
首先写了一个handler在子线程更新主线程UI,在子线程做了一个耗时操作:从网络下载了一个图片并利用handler发送到handleMessage()的回调中,并更新到主线程的bitmap。图片显示成功,没有问题。接下来在子线程中更新onCreate()中实例化的textview,报错:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.只有创建了这个view的thread才能操纵这个view。Android加载view有两种方式,一是setContentView,二是inflater.inflate()。所以第三步,在子线程中用WindowManager.add()展示了view,run()代码如下:
LayoutInflater layoutInflater = LayoutInflater.from(getApplicationContext());
View view = layoutInflater.inflate(R.layout.test, null);
TextView textView = ((TextView) view.findViewById(R.id.test_tv));
textView.setText("十年一剑");
textView.setTextColor(getResources().getColor(R.color.colorAccent));
WindowManager windowManager = getWindowManager();
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.height = 600;
layoutParams.width = 400;
layoutParams.flags = 2;
layoutParams.format = 1;
windowManager.addView(view, layoutParams);
报错: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare().看他这意思,还需要looper。加上后,果然子线程内view显示出来了。尴尬的是,这个WindowManager.LayoutParams的flags和format等属性还没掌握。而且在代码前加上sleep方法view仍可以显示。那么,在onCreate()中的子线程中更新主线程UI呢?
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
new Thread() {
@Override
public void run() {
super.run();
textview.setText("heun3540");
}
}.start();
}
没有问题,子线程更新了主线程的UI,没有报错。但在加了一句Thread.sleep(1000)后,出现了android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.此外在点击事件中new Thread()更新主线程UI,点击后可以看到textview的文本已经改变,随后应用崩掉。
对以上几个例子总结一下:利用handler可以从子线程发送消息到主线程达到更新UI的目的;在子线程里可以更新在子线程中加载的view(需要looper);在主线程的onCreate()中创建的子线程也可以更新主线程的UI,前提是不做其他耗时操作;除外,在onCreate()的子线程做耗时后更新UI报错;子线程没有looper想更新自己的UI也报错;点击事件(可以看做耗时事件)中的子线程更新主线程UI报错。由此可以看出,持有view的线程都可以更改自己的view,主线程默认looper不需要手动添加。一般的更新其他线程的UI需要handler即线程间的通信但handler只是线程间传递数据,更新操作还是要rootview来完成。那为什么在onCreate()的子线程更新主线程UI没有报错呢?而稍一耗时就报错了呢?必然是因为更新UI快于异常线程检测以至UI更新已经完了可能ViewRootImpl才刚刚初始化完成,但这样是不安全的,大家都不推荐这种方式。
妈的,以为下午换办公室,直接电源断了,一上午写的全没了,日。
我们都非常熟悉的生命周期,为什么按照onCreate,onStart这样的顺序执行的呢,可能是在源码中某个方法确定了调用顺序,也可能跟堆栈,硬件交互,全局变量有关。
关于Java中的接口回调,在Android的点击事件和网络请求等有大量的应用。比如项目中有大量重复的工作需要抽取到工具类中,但具体的业务逻辑灵活多变,后者业务后期的维护升级这些都要求代码无法在工具类中一步到位。这时就可以用回调解决,封装的时候调用接口的抽象方法,在业务实现的地方添加相应的代码。那么关于接口的理解,就不再仅限于初学时的定义了。
接口的出现,一是解决了Java类只能继承一个父类而导致父类过于庞杂,抽象程度不高的问题,二是对于日后代码维护十分的方便。比如说一个项目里有动物,鸟,人,猴子,海豚,飞机这些东西,动物作为抽象类,具体的动物作为实现类,动物有吃,睡觉,走抽象方法,鸟是啄,人是用碗吃。至于鸟和飞机,他们都可以飞,飞就可以设计成接口。
再举个例子,鸟,喜鹊,鸵鸟,孔雀,飞机,战斗机。在这个例子中,鸟是需要抽象的,那么到底抽象成接口还是抽象类呢?假如是接口的话,走,叫,吃这些可以有,可是鸵鸟不会飞,只有孔雀可以开屏,那就需要再单独设计两个接口,一个包含fly(),一个包含开屏(),三个接口分别选择实现,而飞机只要实现飞这个接口就可以了。假如鸟抽象成抽象类,同样走吃叫设计为抽象方法,剩下的一样,这样看起来接口和抽象类对于鸟来说没太大区别。这个倒很好解释,因为鸟的几个方法都设计成了抽象方法,假如吃这个方法已经定下来了,显然抽象类更合适。
此外,按照java的规则,一切皆对象,而类是对象的抽象。那么孔雀是鸟,所以鸟也应该设计成类更合适。套用一句大神的话:类定义了是不是;接口定义了有没有。孔雀继承了鸟,那他就是鸟的一种,而飞机实现了飞,则是具备了飞的能力。需要提到一点是:飞设计成接口,是因为这个行为与吃,叫不是同一类行为,假若飞和吃放在一起,那飞机岂不是得会吃才行。所以也可以看出来,两种不同属性的行为是不能写到一起的。
而Handler更新主线程的UI也是在主线程中进行的,只不过通过handler对象将子线程等耗时操作中得到的数据利用message传到了主线程。关于handler的原理,老生常谈。温故而知新。今天试着解释一下相关的源码,6.0以上的。
Looper是final修饰,不可继承。
Class used to run a message loop for a thread.
Threads by default do not have a message loop associated with them; to create one ,call #prepare in the thread that is to run the loop,and then #loop to have it process messages until the loop is stopped.
Most interaction with a message loop is through the #Handler class.
Looper类的解释告诉我们两个主要方法,prepare和loop。主线程不需要显示调用Looper的两个方法。但在子线程中,则需要显式调用。几个全局变量:
private static final String TAG = "Looper"; // sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class final MessageQueue mQueue;
final Thread mThread;
ThreadLocal在这里理解为将Looper对象与当前线程绑定,在同一个线程作用域内可见,是一个Java工具类。一个静态的looper引用,一个messageQueue引用,一个线程引用。
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
prepare方法中传入的布尔值最终传给了MessageQueue的构造方法中,它代表了 True if the message queue can be quit。prepare方法得到了looper对象并且looper在实例化的时候同时获取到当前线程的引用,还会实例化一个成员变量MessageQueue。
Looper中的构造方法是私有的:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper的loop方法,首先会拿到looper和消息队列实例,接着在无限循环中调用queue.next()取出队列的消息,交给msg.target.dispatchMessage(msg)处理。这其中消息的发送正是由handler.sendMessageAtTime()来做。
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity(); for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
} // This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
} final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
} if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
} // Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
} msg.recycleUnchecked();
}
}
在第6行,调用myLooper方法返回了ThreadLocal保存的looper对象:
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
第十行将looper实例化时的messageQueue传给了新的引用MessageQueue,十七到二十行是无限循环,queue.next()取出消息。msg.target.dispatchMessage(msg)处理消息,msg.recycleUnchecked()回收资源。到此,消息队列和轮询已经建立,下面应该是发送消息了。那就来看一下Handler的代码:
/**
* A Handler allows you to send and process {@link Message} and Runnable Handler对象允许发送处理和一个线程的消息队列相关联的message和runnable对象。
* objects associated with a thread's {@link MessageQueue}. Each Handler 每一个Handler实例都与一个单独的线程和它的消息队列关联。
* instance is associated with a single thread and that thread's message 当创建一个Handler对象时,它就与创建它的线程和线程的消息队列绑定了。
* queue. When you create a new Handler, it is bound to the thread /
* message queue of the thread that is creating it -- from that point on, 至此,它就会分发消息和runnable对象到绑定的消息队列,并且在它们从消息队列取出时执行。
* it will deliver messages and runnables to that message queue and execute
* them as they come out of the message queue.
*
* <p>There are two main uses for a Handler: (1) to schedule messages and handler主要有两个用处:一是安排messages和runnable对象在未来的某一刻执行;
* runnables to be executed as some point in the future; and (2) to enqueue 二是为在其他线程执行的动作排队(此处翻译不当)
* an action to be performed on a different thread than your own.
*
* <p>Scheduling messages is accomplished with the 借助post和send系列方法,调度消息得到完成。
* {@link #post}, {@link #postAtTime(Runnable, long)},
* {@link #postDelayed}, {@link #sendEmptyMessage},
* {@link #sendMessage}, {@link #sendMessageAtTime}, and
* {@link #sendMessageDelayed} methods. The <em>post</em> versions allow post允许当Runnable对象被接收且将要被messagequeue调用时为它们排队;
* you to enqueue Runnable objects to be called by the message queue when sendMessage允许为一个包含了数据集且将会被handler的handleMessage方法(需要自己重写)处理的消息对象排队。
* they are received; the <em>sendMessage</em> versions allow you to enqueue
* a {@link Message} object containing a bundle of data that will be
* processed by the Handler's {@link #handleMessage} method (requiring that
* you implement a subclass of Handler).
*
* <p>When posting or sending to a Handler, you can either 当用post或者send向handler发消息时,可以在消息队列就绪时立即处理也可以指定延迟做延时处理。后者需要实现超时等时间行为。
* allow the item to be processed as soon as the message queue is ready
* to do so, or specify a delay before it gets processed or absolute time for
* it to be processed. The latter two allow you to implement timeouts,
* ticks, and other timing-based behavior.
*
* <p>When a
* process is created for your application, its main thread is dedicated to 当应用中的进程创建时,主线程致力于运行消息队列。队列着重于顶层的应用组件如活动,广播接收者等和任何这些组件创建的window。
* running a message queue that takes care of managing the top-level 可以创建子线程并且通过handler与主线程通信。和以前一样,是靠调用post或者sendMessage来实现,当然,是在子线程中调用。
* application objects (activities, broadcast receivers, etc) and any windows 发出的Runnable对象或者消息就会调度到handler的消息队列中并在恰当时处理。
* they create. You can create your own threads, and communicate back with
* the main application thread through a Handler. This is done by calling
* the same <em>post</em> or <em>sendMessage</em> methods as before, but from
* your new thread. The given Runnable or Message will then be scheduled
* in the Handler's message queue and processed when appropriate.
*/
说来惭愧,这一段类注释翻译花了好长时间,好歹六级也过了好多年了。从类的注释中得知,handler主要用send和post发送消息,在重写的handleMessage方法处理消息。
所有的send方法底层都是通过sendMessageAtTime实现的,其中在sendMessageDelayed方法中调用sendMessageAtTime时传入了SystemClock.uptimeMills():
/**
* Enqueue a message into the message queue after all pending messages
* before (current time + delayMillis). You will receive it in
* {@link #handleMessage}, in the thread attached to this handler.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
} /**
* Enqueue a message into the message queue after all pending messages
* before the absolute time (in milliseconds) <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* Time spent in deep sleep will add an additional delay to execution.
* You will receive it in {@link #handleMessage}, in the thread attached
* to this handler.
*
* @param uptimeMillis The absolute time at which the message should be
* delivered, using the
* {@link android.os.SystemClock#uptimeMillis} time-base.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
} private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
handler的enqueueMessage方法中将handler对象赋给了msg的target属性,接着调用了MessageQueue的enqueueMessage方法。MessageQueue也是final类,算是这几个类中比较native的,很多都是与底层交互的方法。在它的enqueueMessage方法中将message压入消息队列,接着loop()方法中msg.target.dispatchMessage(msg),上文已经提到了。Message也是final类。所以最后是调用handler的dispatchMessage方法:
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
} /**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
所以看到消息最后的处理正是在我们实例化handler时覆写的HandlerMessage方法,至此,消息发送处理机制走完了。
那么总结一下:消息机制的过程是,Looper.prepare()实例化looper对象和消息队列,handler实例化获得上一步的looper对象和消息队列的引用,handler.sendMessageAtTime()发送消息到消息队列(这其中包括了给message的target赋值,将message压入到消息队列),Looper.loop()轮询队列取出消息交给message.target.dispatchMessage()处理,实质上是调用了我们自己重写的handleMessage()。而Android为我们做了大量的封装工作。开发人员只需要构造message并发送,自定义消息处理逻辑就可以了。
在研究源码时,首先看类注释,接着明确自己的需求,再去找关键方法,千万莫要在庞杂的代码中迷失。
在探寻源码的过程中,发现了下一次博客的内容,就是WindowManager.LayoutParams,SystemClock,ThreadLocal,AtomicInteger。
水往低处流,人往高处走。
关于Handler的理解,子线程不能更新UI的纠正和回调的思考的更多相关文章
-
使用Handler在子线程中更新UI
Android规定仅仅能在主线程中更新UI.假设在子线程中更新UI 的话会提演示样例如以下错误:Only the original thread that created a view hierach ...
-
android 不能在子线程中更新ui的讨论和分析
问题描写叙述 做过android开发基本都遇见过 ViewRootImpl$CalledFromWrongThreadException,上网一查,得到结果基本都是仅仅能在主线程中更改 ui.子线程要 ...
-
Android多线程之(一)View.post()源码分析——在子线程中更新UI
提起View.post(),相信不少童鞋一点都不陌生,它用得最多的有两个功能,使用简便而且实用: 1)在子线程中更新UI.从子线程中切换到主线程更新UI,不需要额外new一个Handler实例来实现. ...
-
Android在子线程中更新UI(二)
MainActivity如下: package cc.testui2; import android.os.Bundle; import android.view.View; import andro ...
-
Android在子线程中更新UI(一)
MainActivity如下: package cc.testui1; import android.os.Bundle; import android.os.Handler; import andr ...
-
如何在子线程中更新UI
一:报错情况 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that creat ...
-
Android开发UI之在子线程中更新UI
转自第一行代码-Android Android是不允许在子线程中进行UI操作的.在子线程中去执行耗时操作,然后根据任务的执行结果来更新相应的UI控件,需要用到Android提供的异步消息处理机制. 代 ...
-
C#子线程中更新ui
本文实例总结了C#子线程更新UI控件的方法,对于桌面应用程序设计的UI界面控制来说非常有实用价值.分享给大家供大家参考之用.具体分析如下: 一般在winform C/S程序中经常会在子线程中更新控件的 ...
-
Android 在子线程中更新UI
今天在做练习时,在一个新开启的线程中调用“Toast.makeText(MainActivity.this, "登陆成功",Toast.LENGTH_SHORT).show();” ...
随机推荐
-
关于CSS中对IE条件注释的问题
一.通用区分方式:IE6.IE7能识别*,标准浏览器(如FF)不能识别*:IE6能识别*,但不能识别 !important:IE7能识别*,也能识别 !important:IE8能识别\0,不能识别* ...
-
django migration使用指南
转自: https://docs.djangoproject.com/en/1.8/topics/migrations/
-
第五章:最后一步准备,1.8的Json模型、状态描述机制详解
<基于1.8 Forge的Minecraft mod制作经验分享> 1.8的所有纹理材质都需要一个Json来对其描述,这一块感觉是各大神的教程里面涉及最少最浅的,我就斗胆在这分享下我研究了 ...
-
linux中的strings命令简介2
摘自:http://blog.csdn.net/stpeace/article/details/46641069 linux中的strings命令简介 之前我们聊过linux strings的用法和用 ...
-
ubuntu server 11.10 安装 oracle 10g XE
1.将配置的Oracle源服务器的公钥添加在本地 apt 系统的密钥库中: wget http://oss.oracle.com/el4/RPM-GPG-KEY-oraclesudo apt-key ...
-
HTML超文本
1.HTML链接 2.HTML表格 3.HTML图像 4.HTML列表 5.HTML块 6.HTML布局 7.HTML表单 1.HTML链接 (1)给文字及图片添加超链接 < html> ...
-
视频流GPU解码在ffempg的实现(一)-基本概念
这段时间在实现Gpu的视频流解码,遇到了很多的问题. 得到了阿里视频处理专家蔡鼎老师以及英伟达开发季光老师的指导,在这里表示感谢! 基本命令(linux下) 1.查看物理显卡 lspci | grep ...
-
duilib消息类型
//定义所有消息类型 ////////////////////////////////////////////////////////////////////////// #define DUI_MS ...
-
PLSQL实现分页查询
--集合实现游标查询 CREATE OR REPLACE PACKAGE emppkg IS TYPE t_record IS RECORD( rn INT, empno emp.empno%TYPE ...
-
asp.net 仿微信端菜单设置
第一步:添加引用文件 <link rel="stylesheet" href="~/assets/css/bootstrap.min.css"> & ...