前言
Android 開發過程中,遇到了冗長的耗時的操作,亦或是爲了使得代碼結構更加清晰,或者是要動態的更新UI。一言不合就上Handler,這裏不討論java編程時的一些多線程模型,只探討一下Android中提供給開發者使用的Handler。
經典使用方法
關於Handler/Looper/Message之間的關係,在上一篇博文 Android Handler機制初探 中有較爲詳細的描述。這裏打算說人話,對照代碼來解釋一遍。
代碼寫法
new Handler對象
Handler myHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_WHAT:
//do something
break;
}
super.handleMessage(msg);
}
};
自定義Thread
class myThread implements Runnable {
public void run() {
while (!Thread.currentThread().isInterrupted()) {
Message message = new Message();
message.what = MESSAGE_WHAT;
myHandler.sendMessage(message);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
通過上面兩步,通常情況下,我們就可以正常的使用Handler了。但,這不是本文的目的;不是告訴你用法就行了,再說了這些東西Google一下立馬就出來很多,說不定比我的還好。打算在深入一下,到底爲什麼可以這樣做!
子線程更新UI
使用Handler作爲子線程更新UI可能是Handler用法中最爲常用、經典的了。但是問題是爲什麼要這麼做?爲什麼是我?其他人呢(其他方式)?
在更新UI時爲什麼要用到子線程,都有哪些常用的更新UI的方法
如果不在UI線程中更新,而新開啓一個線程處理邏輯,然後在子線程中更新UI線程;會面臨線程安全問題,再說了,Android壓根兒就不讓你這麼高。吧所有的操作都放在UI線程中到不是不可能,但當遇到高耗時的操作時,你能等得了?程序會崩潰的呀。。所以,還是Handler大法好。常見的更新UI的方法有如下幾種
Handler.post(Runnable)
Handler.sendMessage()
View.post(Runnable)
AsyncTask
Activity.runOnUiThread()
會用到的文件
//frameworks/base/core/java/android/app/ActivityThread.java
//frameworks/base/core/java/android/app/Activity.java
//frameworks/base/core/java/android/os/Looper.java
//frameworks/base/core/java/android/os/Handler.java
//frameworks/base/core/java/android/os/HandlerThread.java
//frameworks/base/core/java/android/os/Message.java
//frameworks/base/core/java/android/view/View.java
//frameworks/base/core/java/android/view/ViewRootImpl.java
//frameworks/base/core/java/android/view/WindowManagerGlobal.java
View.post(Runnable)
有關其他的方式,這兒不多說啦。主要看下View.post(Runnable)方法
/**
* <p>Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.</p>
*
* @param action The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*
* @see #postDelayed
* @see #removeCallbacks
*/
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}
看不論是attachInfo.mHandler.post(action)還是ViewRootImpl.getRunQueue().post(action)最後都調用的是Handler.post getRunQueue()最終的實現
//Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
Activity.runOnUIThread(Runnable)
如果是主線程,直接更新;如果不是,使用Handler更新。
/**
* Runs the specified action on the UI thread. If the current thread is the UI
* thread, then the action is executed immediately. If the current thread is
* not the UI thread, the action is posted to the event queue of the UI thread.
*
* @param action the action to run on the UI thread
*/
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
子線程如何將狀態更新到UI
通過上一篇文章 Android Handler機制初探,我們知道Handler和Looper之間是1對1的關係。問題是,我們在自定義的代碼中實現了Handler,可是Looper在哪兒?這裏區分一下UI線程(主線程)、非UI線程
UI 線程
這種ActivityThread.java中的main函數。看到了Looper.prepareMainLooper();和Looper.loop()了吧!不多說
public static void main(String[] args) {
//......
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
子線程
子線程基本上可以看做是自定義的線程;這就屬於千變萬化的了,你不能在指望ActivityThread幫你幹活了,此時就要自己動手豐衣足食。怎麼搞呢!當然是參照線程的東西了(ActivityThread中的實現)。
第一步: prepare()
第二部: loop()
爲啥不是prepareMainLooper 而是 prepare() The main looper for your application is created by the Android environment, so you should never need to call this function yourself
典型代碼如下所示:
package cp.com.clarify;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
//import android.support.v7.app.AlertDialog;
import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends /*AppCompatActivity*/ Activity{
private final static String TAG="MainActivity";
private TestThread mTestThread;
Intent intent;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intent = new Intent(getApplicationContext(), Main2Activity.class);
mTestThread = new TestThread();
mTestThread.start();
mTestThread.getHandler().sendEmptyMessage(1);
final Button mBtn = (Button)findViewById(R.id.button2);
new Thread(new Runnable() {
@Override
public void run() {
Log.e(TAG,""+this);
Log.e(TAG,"-----------------> set toolbar to RED <------------------------");
mBtn.setBackgroundColor(Color.RED);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);//注意這裏啊,1000000點傷害。。
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG,"-----------------> set toolbal to Green <------------------------");
Log.e(TAG,""+this);
mBtn.setBackgroundColor(Color.GREEN);
}
}).start();
}
class TestThread extends Thread {
private Handler mHandler;
private final Object mLock = new Object();
public void run() {
Looper.prepare();
synchronized (mLock) {
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//.....
switch(msg.what){
case 1:
Log.d(TAG,"================");
break;
default:
break;
}
}
};
mLock.notifyAll();
}
Looper.loop();
}
public Handler getHandler() {
synchronized (mLock) {
if (mHandler == null) {
try {
mLock.wait();
} catch (InterruptedException e) {
}
}
return mHandler;
}
}
public void exit() {
getHandler().post(new Runnable(){
public void run() {
Looper.myLooper().quit();
}});
}
}
@Override
protected void onResume() {
Log.e(TAG,"-----------------> onResume <------------------------");
super.onResume();
}
@Override
protected void onStop() {
Log.e(TAG,"-----------------> onStop <------------------------");
super.onStop();
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
Log.e(TAG,"-----------------> onPostCreate <------------------------");
super.onPostCreate(savedInstanceState);
}
@Override
protected void onPostResume() {
Log.e(TAG,"-----------------> onPostResume <------------------------");
super.onPostResume();
}
@Override
protected void onDestroy() {
Log.e(TAG,"-----------------> onDestroy <------------------------");
super.onDestroy();
}
@Override
protected void onPause() {
Log.e(TAG,"-----------------> onPause <------------------------");
super.onPause();
}
@Override
protected void onRestart() {
Log.e(TAG,"-----------------> onRestart <------------------------");
super.onRestart();
}
}
最後的運行結果顯示:Button的顏色是紅色的;這說明:子線程是可以更新UI的,但是過了5s之後程序掛了,這好像有說明子線程是不能更新UI的,出現瞭如下報錯;到底是要鬧哪樣???
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6556)
at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:942)
at android.view.ViewGroup.invalidateChild(ViewGroup.java:5084)
at android.view.View.invalidateInternal(View.java:12724)
at android.view.View.invalidate(View.java:12660)
at android.view.View.invalidateDrawable(View.java:16805)
at android.widget.TextView.invalidateDrawable(TextView.java:5408)
at android.graphics.drawable.Drawable.invalidateSelf(Drawable.java:385)
at android.graphics.drawable.ColorDrawable.setColor(ColorDrawable.java:136)
at android.view.View.setBackgroundColor(View.java:17196)
at cp.com.clarify.MainActivity$3.run(MainActivity.java:76)
at java.lang.Thread.run(Thread.java:818)
分析一下這份log我們發現,異常是在ViewRootImpl.java的checkThread中拋出的;是不是有點兒詭異?第一個Thread竟然沒有checkThread,第二個Thread竟然checkTread?來來跟着我的思路,這是不是說明,第一個Thread的時候壓根兒就沒有ViewRootImpl,到了第二個Thread(mBtn.setBackgroundColor(Color.GREEN);)的時候ViewRootImpl給創建了!O(∩_∩)O哈哈~機智如我!!
搜了一下這方面的資料。發現還真是這樣的。。
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume{
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
// TODO Push resumeArgs into the activity for consideration
if (r != null) {
final Activity a = r.activity;
if (localLOGV) Slog.v(
TAG, "Resume " + r + " started activity: " +
a.mStartedActivity + ", hideForNow: " + r.hideForNow
+ ", finished: " + a.mFinished);
// If the window hasn't yet been added to the window manager,
// and this guy didn't finish itself or start another activity,
// then go ahead and add the window.
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//這裏。。這裏 start
ViewManager wm = a.getWindowManager();
//這裏。。這裏 end
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);//這裏。。這裏。。
}
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
} else if (!willBeVisible) {
if (localLOGV) Slog.v(
TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
看下addView的實現
WindowManagerGlobal.java
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
好了。。
至於 View, Windows, WindowManager, WindowManagerImpl, View,DecorView, ViewGroup, View,ViewRoot,ViewGroup,ViewRoot,WindowManagerGlobal DecorView之間的關係,抽時間在分析。
可以參考這篇文章,很不錯的。。
http://www.cnblogs.com/samchen2009/p/3364327.html