您的位置:首页 > 移动开发 > Android开发

Android Handler 原理初探

2016-08-09 19:36 465 查看
前言

经典使用方法
代码写法

子线程更新UI
在更新UI时为什么要用到子线程都有哪些常用的更新UI的方法
ViewpostRunnable

ActivityrunOnUIThreadRunnable

子线程如何将状态更新到UI
UI 线程

子线程

前言

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android ui