UI線程
當系統啟動的時候,就會建立一個主線程(Main Thread),然後這個主線程向UI元件分發事件,主線程和UI的元件進行互動,故稱UI線程。
線程安全
Android的UI線程是不安全的。引用一下,百度百科的解釋
線程安全就是多線程通路時,采用了加鎖機制,當一個線程通路該類的某個資料時,進行保護,其他線程不能進行通路直到該線程讀取完,其他線程才可使用。不會出現資料不一緻或者資料污染。 線程不安全就是不提供資料通路保護,有可能出現多個線程先後更改資料造成所得到的資料是髒資料
from 百度百科
既然這樣,Google給我們提供了更新ui界面的Handler類。
Handler
一個處理異步消息的類,建立一個子線程發送消息至主線程,在主線程更新相應的UI界面。比如在子線程進行長時間的網絡操作,然後更新UI界面上的TextView。
使用的方法有兩種,
1.post(runnable)&2.sendMessage(message)
先示範一下,所謂不能直接在UI線程更新TextView的操作。在XML布局檔案裡建立一個TextView。
package com.example.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.widget.Button;
import android.widget.TextView;
/**
* Created by Does on 2017/7/20.
*/
public class HandlerExample extends Activity {
private TextView tv_content;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_content= (TextView) findViewById(R.id.tv_content);
new Thread(){
@Override
public void run() {
try {
Thread.sleep();
tv_content.setText("更新UI");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
寫好之後,運作之後,
Exception:隻有建立了視圖層次結構的原始線程才能觸及它的視圖
為了解決這種問題,我們使用Handler。
post(runnable)?
這裡模拟一個簡單的下載下傳功能。XML檔案裡面添加兩個元件,一個Button和一個Textview
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView tv_content;
private Button btn_onclick;
private Handler handler=new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_content= (TextView) findViewById(R.id.tv_content);
btn_onclick= (Button) findViewById(R.id.btn_onclick);
btn_onclick.setOnClickListener(this);
}
@Override
public void onClick(View view) {
DownLoadThread downLoadThread=new DownLoadThread();
downLoadThread.start();
}
class DownLoadThread extends Thread{
@Override
public void run() {
try {
System.out.println("檔案正在下載下傳...");
Thread.sleep();
System.out.println("檔案下載下傳成功");
Runnable runnable=new Runnable() {
@Override
public void run() {
System.out.println("目前線程的id是: "+Thread.currentThread().getId());
MainActivity.this.tv_content.setText("textview已經改變");
}
};
handler.post(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
運作效果如下,點選Button之前
點選Button之後
sendMessage(message)
public class HandlerExample extends Activity {
private TextView tv_content;
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case :
tv_content.setText("UI變化了");
break;
default:
break;
}
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_content= (TextView) findViewById(R.id.tv_content);
new Thread(){
@Override
public void run() {
try {
Thread.sleep();
Message message=new Message();
message.what=;
handler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
效果和上面的是一樣的。
這個明顯和上一個有些不同,多了幾樣的東西。Message(),sendMessage(),handleMessage()
首先要子線程中,建立一個Handler的一個對象handler,然後執行handler.sendMessage(message),
handler攜帶message的一個對象,message.what=1,給它一個辨別,随便取。在handleMessage()方法裡,就會接受到傳過去的值,進而在handleMessage裡進行UI的更新操作。
Handler運作機制
接下來我們說說内部機制的運作。主要有用到Looper,Handler,MessageQueu(消息隊列).從源碼入手,
當我們手機一啟動的時候,系統預設主線程會先調用prepareMainLooper()方法。先執行prepare(false)
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
...
sMainLooper = myLooper();
}
}
接着建立一個Looper對象,将ThreadLocal設定為線程安全的對象。
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));
}
接着sThreadLocal向Looper類裡傳過去quitAllowed,并在Looper()構造器裡建立了一個MessageQueue的對象mQueue。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}
最後,再調用prepareMainLooper()中的myLooper(),取出線程安全的Looper對象。
public static Looper myLooper() {
return sThreadLocal.get();
}
這是系統幫我們做的事情!!
接着我們需要做一些事情更新UI的操作,我們在MainActivity方法中建立了一個Handler對象,
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
handler取出了系統為我們建立的Looper對象,并取出系統建立的mQueue對象。此時我們建立的handler就持有了系統剛建立的Looper對象和MessageQueue對象。
**
發送消息
**
當我們發送消息的時候,sendMessage
sendMessage(Message msg)
sendMessageDelayed(Message msg, long delayMillis)
public final boolean sendMessageAtFrontOfQueue(Message msg) {
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, );
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
首先是取出Looper對象中MessageQueue在enqueueMessage()方法中,handler攜帶我們要發送的Message,然後放入消息隊列MessageQueue中
boolean enqueueMessage(Message msg, long when) {
...
boolean needWake;
synchronized (this) {
...
msg.when = when;
Message p = mMessages;
if (p == null || when == || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
}
...
return true;
}
循環取出
接着Looper調用loop()方法
public static void loop() {
final Looper me = myLooper();
...
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next();
...
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
msg.target.dispatchMessage(msg);
...
final long newIdent = Binder.clearCallingIdentity();
...
msg.recycle();
}
}
myLooper()取出目前Looper對象
me.mQueue拿到目前的MessageQueue對象
queue.next();取出下一個消息
如果消息存在 則調用消息的msg.target.dispatchMessage(msg);
處理消息
這就是我們重寫的方法,
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}