小知识 安卓线程和ui

时间:2023-03-08 20:19:13

*:first-child {
margin-top: 0 !important;
}

body>*:last-child {
margin-bottom: 0 !important;
}

/* BLOCKS
=============================================================================*/

p, blockquote, ul, ol, dl, table, pre {
margin: 15px 0;
}

/* HEADERS
=============================================================================*/

h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
}

h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code {
font-size: inherit;
}

h1 {
font-size: 28px;
color: #000;
}

h2 {
font-size: 24px;
border-bottom: 1px solid #ccc;
color: #000;
}

h3 {
font-size: 18px;
}

h4 {
font-size: 16px;
}

h5 {
font-size: 14px;
}

h6 {
color: #777;
font-size: 14px;
}

body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child {
margin-top: 0;
padding-top: 0;
}

a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0;
}

h1+p, h2+p, h3+p, h4+p, h5+p, h6+p {
margin-top: 10px;
}

/* LINKS
=============================================================================*/

a {
color: #4183C4;
text-decoration: none;
}

a:hover {
text-decoration: underline;
}

/* LISTS
=============================================================================*/

ul, ol {
padding-left: 30px;
}

ul li > :first-child,
ol li > :first-child,
ul li ul:first-of-type,
ol li ol:first-of-type,
ul li ol:first-of-type,
ol li ul:first-of-type {
margin-top: 0px;
}

ul ul, ul ol, ol ol, ol ul {
margin-bottom: 0;
}

dl {
padding: 0;
}

dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px;
}

dl dt:first-child {
padding: 0;
}

dl dt>:first-child {
margin-top: 0px;
}

dl dt>:last-child {
margin-bottom: 0px;
}

dl dd {
margin: 0 0 15px;
padding: 0 15px;
}

dl dd>:first-child {
margin-top: 0px;
}

dl dd>:last-child {
margin-bottom: 0px;
}

/* CODE
=============================================================================*/

pre, code, tt {
font-size: 12px;
font-family: Consolas, "Liberation Mono", Courier, monospace;
}

code, tt {
margin: 0 0px;
padding: 0px 0px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px;
}

pre>code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent;
}

pre {
background-color: #f8f8f8;
border: 1px solid #ccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px;
}

pre code, pre tt {
background-color: transparent;
border: none;
}

kbd {
-moz-border-bottom-colors: none;
-moz-border-left-colors: none;
-moz-border-right-colors: none;
-moz-border-top-colors: none;
background-color: #DDDDDD;
background-image: linear-gradient(#F1F1F1, #DDDDDD);
background-repeat: repeat-x;
border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD;
border-image: none;
border-radius: 2px 2px 2px 2px;
border-style: solid;
border-width: 1px;
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
line-height: 10px;
padding: 1px 4px;
}

/* QUOTES
=============================================================================*/

blockquote {
border-left: 4px solid #DDD;
padding: 0 15px;
color: #777;
}

blockquote>:first-child {
margin-top: 0px;
}

blockquote>:last-child {
margin-bottom: 0px;
}

/* HORIZONTAL RULES
=============================================================================*/

hr {
clear: both;
margin: 15px 0;
height: 0px;
overflow: hidden;
border: none;
background: transparent;
border-bottom: 4px solid #ddd;
padding: 0;
}

/* TABLES
=============================================================================*/

table th {
font-weight: bold;
}

table th, table td {
border: 1px solid #ccc;
padding: 6px 13px;
}

table tr {
border-top: 1px solid #ccc;
background-color: #fff;
}

table tr:nth-child(2n) {
background-color: #f8f8f8;
}

/* IMAGES
=============================================================================*/

img {
max-width: 100%
}
-->

多线程环境下的ui修改

  开发过程中,经常需要开启新的线程,并且在其它线程中改变ui线程的ui对象的状态。Android设计出于性能考虑,ui对象为非线程安全的,然后让ui对象仅能在主线程——也就是ui线程中被修改,以此来保证ui对象的线程安全。以下引出一些跨线程修改ui对象的情形,以及可能的实现方式。

1. 定时更新ui

一些类似定时更新ui的代码,如动画控制。

1.1 多线程定时更改ui

具体就是新启动(不让ui线程sleep而卡住)一个线程去计时,之后定时来通知ui修改。

1.1.1 新启动线程定时执行任务

  • Timer + TimerTask
  • 新启动线程:run方法中:while(true) + Thread.Sleep/SystemClock.Sleep

  本质上都是一个新线程在背后计时。由于使用一个新的非ui线程执行计时,需要在时间到达后去通知ui修改。出于性能考虑,安卓的ui控件不是线程安全的,然后谷歌设计只让ui线程(主线程)能够直接修改ui控件,其它非ui线程不能来达到ui的线程安全。

1.1.2 非ui线程更新ui控件的方式

  • runOnUiThread
  • Handler
  • View.postDelay

  runOnUiThread从名字上可以看出就是专门供其它线程更改ui使用的。而handler用于不同线程之间的消息传递,可以让线程T1在希望的时刻去通知T2执行某些特定操作。这当然也完全能满足[非ui线程定时通知ui线程更改ui控件状态] 的目的。

1.2 Handler定时更新ui

如果只是为了完成“定时执行”,那么不开启一个新线程,使用handler.sendEmptyMessageDelayed(1,2000)也可以。

在开始定时任务执行的地方调用:
handler.sendEmptyMessage(); 主线程中的handler重写handleMessage:
final int UPDATE_ANIM = 1001;
boolean isAnimRun;
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_ANIM:
if(isAnimRun){
//更新ui的代码,代码中根据情况改变isAnimRun标志。
handler.sendEmptyMessageDelayed(UPDATE_ANIM, 2000);
}
break;
}
}
};

  可见,开启新的线程一般是执行一些非ui但耗时的操作,比如网络获取新闻数据。而定时任务这样的事情Handler也可以搞定。

2.Handler的跨线程通信

2.1原理简述

为了让其它线程发消息通知当前线程执行一些任务,当前线程线程可以这样做:

  • 当前线程执行Looper.prepare方法,这会产生一个MessageQueue和一个Looper实例。两个实例会被保存到ThreadLocal中,prepare只能执行一次,以保证每个线程仅有唯一的Looper和MessageQueue。
>> MessageQueue用来接收其它线程丢进来的Message,使用先进先出的时间顺序维护所有消息。
>> Looper管理MessageQueue,主要就是不断的(loop)从中取走Message,然后发送给把此Message的target去处理,
target即为发送此Message的handler。
  • 当前线程可以创建一或多个Handler对象。Handler对象创建时会记录当前线程的MessageQueue和Looper实例作为自己的成员变量。。所以,handler在哪个线程中创建,就和哪个线程的Looper、MessageQueue对应起来。

  • 其它线程可以根据需要创建Message对象,调用handler的sendMessage,此实例方法会设置Message的target为方法的handler实例。 然后Message被发送到handler记录的MessageQueue中。

  • 当前线程执行Looper.loop方法,进入一个死循环。执行方式就是一个生产者消费者模式。消费者就是Looper,loop方法一旦执行,就去MessageQueue中取消息,没有消息时会阻塞,取到消息就把Message交给Message.target对应的handler去处理。生产者就是其它线程,其它线程创建Message并使用当前线程的handler执行sendMessage方法(会设置方法的实例对象handler为Message的target)来发送消息到handler字段记录的队列中。

  通过以上Looper、MessageQueue、Handler的合作,每一个线程都通过Handler来让其它线程根据需要通知自己执行一些操作。 Handler可以实现不同线程之间的通信,默认主线程已经提供好了Looper和MessageQueue,所以按需要自己写个handler对象,就可以在新开启的其它线程中使用handler来让主线程执行操作,常见的就是更新ui控件。 每个Message对象同时设置了when属性,这样可以做到消息通知的立即执行和延迟执行。

2.2 让自己的线程开始接收消息

其它线程默认Looper和MessageQueue是没有准备好的,可以在run方法里通过以下几步配置好:

  • 调用Looper.prepare()方法,这会建立Looper实例和对应的MessageQueue。

  • 设计好自己的Handler对象,主要是处理消息,之后供外界用它来向MessageQueue发送消息。

  • 最后调用Looper.loop()方法开启Looper对消息队列的监测。表示可以让其它线程来发送通知了。

2.3 Toast.show的调用

本质上对ui控件的修改,最终都是ui线程执行的。比如我们的线程里需要设置某个TextView的Text属性,那么只能是使用ui线程的handler去发送消息给ui线程去执行。或者使用runOnUiThread这样的简便方法。

一个特殊的例子就是Toast.show,它仅要求当前线程Looper和MessageQueue准备好即可:
void run(){
Looper.prepare();
Toast.makeText(DemoActivity.this,"Wow......",0).show();
Looper.loop();
}

3.多线程更新ListView

另一个常见“跨线程改变ui”的例子就是网络数据加载,比如加载新闻列表到ListView,启动新的线程是为了避免主线程阻塞而卡ui。相比启动一个线程去达到计时器的目的,使用非ui线程去执行耗时操作等就划算得多了。一般的套路是:

  • 界面上需要新的数据时,启动一个线程去从网络或本地获取一批数据,通常是分页获得一个合理的数据集合。界面上显示进度条,并且使得一部分界面不可交互。

  • 获取数据完毕后,调用adapter的notifyDataSetChanged()它是一个ui操作,需要使用“非ui线程执行ui操作”的技巧去完成。

  ListAdapter的要点就是:复用屏幕不可见的View对象,并且使用ViewHolder来避免findViewById的开销。

4.AsyncTask

AsyncTask是围绕Thread和Handler构建的一个简单包裹类,可以完成一些后台执行任务后更新UI的操作,api中指出操作不宜过长——a few seconds at most,如果是更为耗时的任务,就需要自己使用java.util.concurrent 包下的诸如Executor, ThreadPoolExecutor 和 FutureTask等类。

4.1 基本要点

  • new AsyncTask<Prams,Progress,Result>

  • onPreExecute中执行一些耗时操作的预备动作,可以是ui操作,如显示进度条。

  • doInBackground中执行耗时任务,调用publishProgress来更新进度。

  • onPostExecute中使用结果数据,更新ui,如dismiss掉进度条。

  • 应该在ui线程中创建AsyncTask的实例,并调用其execute方法。

4.2 原理简述

AsyncTask拥有一个静态的Handler成员:

private static final InternalHandler sHandler = new InternalHandler();

  InternalHandler继承自Handler,它接收处理两个消息:

public void handleMessage(Message msg) {
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}

  可以知道,更新进度结束2个回调方法的执行是涉及到ui操作的,因此必须确保sHandler对象是属于ui线程的: The AsyncTask class must be loaded on the UI thread. This is done automatically as of JELLY_BEAN.

AsyncTask的构造方法使用创建好了一个Callable和一个FutureTask来实现线程的创建。

api要求AsyncTask的创建和execute方法的调用必须在ui线程中执行,实际上重点是execute方法,它里面调用了onPreExecute()方法,此方法会涉及ui操作,而且没有使用handler机制,就必须被在ui线程中执行。execute只能执行一次,我们通常会写new MyAsyncTask().execute() 这样的代码,所以为了确保在ui线程中执行execute,我们最好是在ui线程中执行AsyncTask的创建——当然了,在非ui线程中创建AsyncTask实例通常也没多大意义。

  • 第1个关键点就是InternalHandler,它保证updateProgress和postExecute在ui线程中执行。sHandler是类级别的,它结合AsyncTaskResult完成了在UI线程中调用指定AsyncTask的updateProgress和postExecute:
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
result.mTask.finish(result.mData[0]);
result.mTask.onProgressUpdate(result.mData);
  • 第2个关键点就线程创建部分:Executor,最终是使用ThreadPoolExecutor完成对Future的启动执行:
//在executeOnExecutor中
exec.execute(mFuture);