Android编程示例之——UI主线程以及多线程

前几天看到一个帖子,有位朋友列出了一段代码1:

/********************代码1:在非UI线程里面操作UI*****************************/

class myThread extends Thread {
private Handler myHandler;
public void run() {
Looper myLooper, mainLooper;
Looper.prepare();
myLooper = Looper.myLooper();
mainLooper = Looper.getMainLooper();
String obj;
if (myLooper == null) {
Log.d("ALOOP", "myLooper == null " + Thread.currentThread().getId());
} else {
myHandler = new Handler(myLooper) {//DebugPoint 2
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
switch (msg.what) {
case MSG_TIMER2:
String obj1 = "MSG_TIMER MainThread";
Log.d("ALOOP",
"Print1 myHandler::handleMessage begin thread id= "
+ Thread.currentThread().getId());
vText.setText(obj1);
Log.d("ALOOP",
"Print2 myHandler::handleMessage end thread id= "
+ Thread.currentThread().getId());
break;
}
}
};
myHandler.sendEmptyMessage(MSG_TIMER2);
}
Looper.loop();
};
}

显然,这段代码是在在非UI线程里面操作UI。Android 的UI元素不是线程安全的。也就是说,不应该在非UI线程里面操作UI元素。

但是这位朋友很困惑的是,这段代码确实运行正常。而且运行了若干次依然正常。

其实多线程编程,有时候你运行若干次,结果正确,并不表明你的逻辑就是对的。

那为什么这里运行多次一直正常呢?根据作者的以往的调试经验,这类代码通常会在运行的时候报

CalledFromWrongThreadException: “Only the original thread that created a view hierarchy can touch its views.” 。可是这段代码我运行多次,确实没有发现这个异常出来。

为了刨根究底, 我们不妨跟着vText.setText代码往下看,看看这段代码的执行路径:

TextView::setText

View::invalidate

ViewRoot::invalidateChild

作者在View::invalidate里面加入了打印信息,发现在myHandler.handleMessage函数的打印信息“Print1”和“Print1”之间,

判断条件mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)是不成立的,也就是说并没有调用p.invalidateChild(this, r)。

public void invalidate() {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
}
if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
final ViewParent p = mParent;
final AttachInfo ai = mAttachInfo;
if (p != null && ai != null) {
final Rect r = ai.mTmpInvalRect;
r.set(0, 0, mRight - mLeft, mBottom - mTop);
// Don't call invalidate -- we don't want to internally scroll
// our own bounds
p.invalidateChild(this, r);
}
}
}

p.invalidateChild的调用路径是:

View::invalidateChild
ViewGroup::invalidateChild
...(ViewGroup::invalidateChild若干)
ViewGroup::invalidateChild
ViewRoot::invalidateChild
ViewRoot::checkThread //这个地方会抛出CalledFromWrongThreadException

所以既然在“Print1”和“Print1”之间没有调用p.invalidateChild,那自然就不会抛出CalledFromWrongThreadException的异常。

从实验结果来看,这里调用的 TextView::setText,仅仅在于修改了TextView::mText的成员变量。但是并未对TextView发起一次刷新。也就是说,并没有完成对textView的修改,修改的仅仅是他的模型(数据)。可是做过实验的朋友会发现TextView的文本在这句话之后界面确实会显示“MSG_TIMER MainThread” 。这个界面(视图)的显示其实不是TextView::setText的结果,而是Activity::setContentView的结果。

这部分代码的实际运行路径分为两条线,UI修改线和textView数据修改线。

UI修改线程:

Activity::setContentView

ViewRoot::invalidateChild

textView数据修改线程:

TextView::setText

View::invalidate(并未实际修改UI)

ViewRoot::invalidateChild(因为状态不对,没有进入)

这两条线是并行进行的。UI修改线程会将mPrivateFlags 置为DRAWN | HAS_BOUNDS。但是“textView数据修改线程”的执行路径很短,所以他很可能在mPrivateFlags被UI修改线程修改之前到达View::invalidate,从而导致判断的失败,代码也并没有进入ViewRoot::invalidateChild。

所以,如果我们在代码vText.setText(obj1);之前加上一个延时,让textView数据修改线程稍微等待UI修改线程一会会,系统基本上会一直报异常CalledFromWrongThreadException:

try {//DebugPoint 3

Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

vText.setText(obj1);

综上,这个代码本身是有问题的。之所以没有报异常,是UI线程和数据修改线程在时序上错过了。

要根本上解决这个问题,就要把数据修改线程的Looper修改为UI主线程的Looper:

//myHandler = new Handler(mainLooper) { //DebugPoint 1

完整代码如下:

/*TempTestActivity.java*/

package com.xuxing;


import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;


public class TempTestActivity extends Activity {
private final int MSG_TIMER2 = 14;


private TextView vText = null;
myThread t;


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
vText = (TextView) findViewById(R.id.tv01);
//t.myHandler.sendEmptyMessage(MSG_TIMER2);
//vText.setText("adnb");
t = new myThread();
t.start();
}
class myThread extends Thread {
private Handler myHandler;


public void run() {
//Looper myLooper,
Looper mainLooper;
//Looper.prepare();
//myLooper = Looper.myLooper();
mainLooper = Looper.getMainLooper();
String obj;
//if (myLooper == null) {
Log.d("ALOOP", "myLooper == null " + Thread.currentThread().getId());
//} else {
myHandler = new Handler(mainLooper) { //DebugPoint 1
//myHandler = new Handler(myLooper) {//DebugPoint 2
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
switch (msg.what) {
case MSG_TIMER2:
String obj1 = "MSG_TIMER MainThread";
Log.d("ALOOP",
"myHandler::handleMessage begin thread id= "
+ Thread.currentThread().getId());
/*
try { //DebugPoint 3
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
*/
vText.setText(obj1);
//vText.setVisibility(View.VISIBLE);
//vText.invalidate();
Log.d("ALOOP",
"myHandler::handleMessage end thread id= "
+ Thread.currentThread().getId());
break;
}
}
};
myHandler.sendEmptyMessage(MSG_TIMER2);
//}
//Looper.loop();
};
}
}

主要Layout文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/LinearLayoutTestInval"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/tv01"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
</LinearLayout>



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值