认识UI
Thread(1/2)
介绍过Android主线程与子线程之沟通。所谓主线程通常是UI线程。Android的UI是单线程(Single-threaded)的。为了避免拖住GUI,一些较费时的对象应该交给独立的线程去执行。如果幕后的线程来执行UI对象,Android就会发出错误讯息
CalledFromWrongThreadException
例如下述范例:
//----- Looper_05范例 -----
package com.misoo.kx04;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import
android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ac01 extends
Activity implements OnClickListener {
private final int WC =
LinearLayout.LayoutParams.WRAP_CONTENT;
private final int FP =
LinearLayout.LayoutParams.FILL_PARENT;
public TextView tv;
private myThread t;
private Button btn, btn2;
private Handler h;
private Context ctx;
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
ctx
= this;
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
btn = new Button(this);
btn.setId(101);
btn.setBackgroundResource(R.drawable.heart);
btn.setText("test looper");
btn.setOnClickListener(this);
LinearLayout.LayoutParams param =
new LinearLayout.LayoutParams(100,50);
param.topMargin = 10;
layout.addView(btn, param);
btn2 = new Button(this);
btn2.setId(102);
btn2.setBackgroundResource(R.drawable.ok_blue);
btn2.setText("exit");
btn2.setOnClickListener(this);
layout.addView(btn2, param);
tv = new
TextView(this);
tv.setTextColor(Color.WHITE);
tv.setText("");
LinearLayout.LayoutParams param2 =
new LinearLayout.LayoutParams(FP, WC);
param2.topMargin = 10;
layout.addView(tv, param2);
setContentView(layout);
//------------------------
t = new myThread();
t.start();
}
public void onClick(View v) {
switch(v.getId()){
case 101:
String obj = "mainThread";
Message m = h.obtainMessage(1, 1, 1, obj);
h.sendMessage(m);
break;
case 102:
h.getLooper().quit();
finish();
break;
}
}
//------------------------------------------------
public class EventHandler
extends Handler {
public EventHandler(Looper looper) {
super(looper);
}
@Override
public
void handleMessage(Message msg) {
tv.setText((String)msg.obj);
}
}
//------------------------------------------------
class myThread extends Thread{
final boolean TEST_FLAG =
true;
public void run() {
Looper.prepare();
h = new Handler(){
public void handleMessage(Message msg) {
if( TEST_FLAG )
tv.setText("myThread
is running");
else
{
EventHandler
ha = new EventHandler(Looper.getMainLooper());
String obj = (String)msg.obj + ", myThread";
Message
m = ha.obtainMessage(1, 1, 1, obj);
ha.sendMessage(m);
}
}
};
Looper.loop();
}
}
}
由于指令:
final
boolean TEST_FLAG = true;
所以子线程会执行到指令:
tv.setText("myThread is running")
因为tv对象是主线程诞生的UI对象,如果子线程也去插手的话,Android程序就停止了,如下图:
解决方法之一是将指令改为:
final
boolean TEST_FLAG = false;
子线程就「不会」执行到指令:
tv.setText("myThread is running")
而是执行到:
EventHandler ha = new
EventHandler(Looper.getMainLooper());
String obj =
(String)msg.obj + ", myThread";
Message m =
ha.obtainMessage(1, 1, 1, obj);
ha.sendMessage(m);
就触发主线程去执行:
public class EventHandler extends Handler {
…………
public void handleMessage(Message msg) {
tv.setText((String)msg.obj);
}
}
本来就是主线程所诞生tv对象,现在由主线程来执行这个:
tv.setText((String)msg.obj);
指令,Android程序就顺利进行了,输出画面如下:
基本上,Android希望UI thread能够给予User做快速的反应。如果UI
thread花费太多时间做幕后的事情,超过5秒钟,Android就会给user如下的提示:
例如,将上述程序的onClick()函数修改如下:
public void onClick(View v) {
switch(v.getId()){
case 101:
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String obj = "mainThread";
Message m = h.obtainMessage(1, 1, 1, obj);
h.sendMessage(m);
break;
case 102:
h.getLooper().quit();
finish();
break;
}
}
UI thread执行到指令:Thread.sleep(8000);
就停下来。Android发现停太久了,就发给user一个提示:
UI
thread所执行的每一个函数,所花费的时间都应该越短越好。只要是较费时的工作,都应该交由子线程去执行。那么,UI thread
如何等待子线程做完其工作呢?常用方法是:诞生一个主线程的Handler对象,当做Listener去让子线程能将讯息push到主线程的Message
Queue里,以便触发主线程的handleMessage()函数,让主线程知道子线程的状态。
与UI
类别很类似的是IntentReceiver类别,要求其函数工作时间必须很短,而且每一个函数的执行时间是间断的,所以其对象是stateless。基于这样的要求,对于IntentReceiver而言,如果任务(Task)里含有费时的工作,不宜将诞生子线程来单任此费时之工作;而较常用的作法是:由IntentReceiver启动一个Service来担任之。
IntentReceiver最好不要启动一个Activity,以免此Activity被push到Activity
推迭(Stack)上,覆盖掉正在执行的Activity。此时适合用Notification
Manager,来作为IntentReceiver与User沟通的管道。
现在,来看看ProgressDialog与线程之运用,如下的典型ProgressDialog范例:
//----- Looper_06范例 -----
package com.misoo.pkcc;
import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import
android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ac01 extends
Activity implements OnClickListener{
private final int WC =
LinearLayout.LayoutParams.WRAP_CONTENT;
private final int FP =
LinearLayout.LayoutParams.FILL_PARENT;
private ProgressDialog progressDialog = null;
public TextView tv;
private Button btn, btn2;
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
LinearLayout
layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
btn = new Button(this);
btn.setId(101);
btn.setBackgroundResource(R.drawable.heart);
btn.setText("test looper");
btn.setOnClickListener(this);
LinearLayout.LayoutParams param =
new LinearLayout.LayoutParams(100,50);
param.topMargin = 10;
layout.addView(btn,
param);
btn2 = new Button(this);
btn2.setId(102);
btn2.setBackgroundResource(R.drawable.ok_blue);
btn2.setText("exit");
btn2.setOnClickListener(this);
layout.addView(btn2, param);
tv = new TextView(this);
tv.setTextColor(Color.WHITE);
tv.setText("");
LinearLayout.LayoutParams param2 =
new
LinearLayout.LayoutParams(FP, WC);
param2.topMargin = 10;
layout.addView(tv, param2);
setContentView(layout);
//------------------------
}
public void onClick(View v) {
switch(v.getId()){
case 101:
progressDialog = ProgressDialog.show(this,
"please wait…","Loading",true);
new Thread(){
public void run() {
try{
sleep(6000); //故意延迟
}
catch(Exception e){
e.printStackTrace();
}
progressDialog.dismiss();
}
}.start();
setTitle("mainThread...");
break;
case 102:
finish();
break;
}
}
}
指令:
new Thread(){
public void run() {
try{
sleep(6000); //故意延迟
}
catch(Exception e){
e.printStackTrace();
}
progressDialog.dismiss();
}
}.start();
就诞生一个子线程,等到子线程做完事情之后,就执行:progressDialog.dismiss();
而关闭ProgressDialog。其画面为:
此范例是由子线程来执行progressDialog.dismiss();指令。其相当于:
//----- Looper_06aa范例
-----
public class ac01 extends
Activity implements OnClickListener{
//………………(省略)
public void onClick(View v) {
switch(v.getId()){
case 101:
progressDialog = ProgressDialog.show(this,
"please wait…","Loading",true);
th1 = new myThread();
th1.start();
setTitle("mainThread....");
break;
case 102:
finish();
break;
}
}
//
---------------------------------------
class
myThread extends Thread {
@Override
public void run() {
try{
sleep(6000); //故意延迟
}
catch(Exception e)
{
e.printStackTrace();
}
progressDialog.dismiss();
}
}
}
上述程序又可写为:
//----- Looper_06bb范例
-----
public class ac01 extends
Activity
implements OnClickListener, Runnable{
//………………(省略)
public void onClick(View v) {
switch(v.getId()){
case 101:
progressDialog = ProgressDialog.show(this,
"please wait…","Loading",true);
th1 = new Thread(this);
th1.start();
setTitle("mainThread....");
break;
case 102:
finish();
break;
}
}
public void run() {
try{
Thread.sleep(6000);
//故意延迟
}
catch(Exception e)
{
e.printStackTrace();
}
progressDialog.dismiss();
}
}
如果想由主线程来执行progressDialog.dismiss();可藉由Message
Queue来传递讯息,如下范例:
//----- Looper_06cc范例
-----
package com.misoo.pkcc;
import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import
android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ac01 extends
Activity implements OnClickListener{
private final int WC =
LinearLayout.LayoutParams.WRAP_CONTENT;
private final int FP =
LinearLayout.LayoutParams.FILL_PARENT;
private ProgressDialog progressDialog = null;
private myThread th1;
public TextView tv;
private Button btn, btn2;
private EventHandler h;
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
LinearLayout
layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
btn = new Button(this);
btn.setId(101);
btn.setBackgroundResource(R.drawable.heart);
btn.setText("test looper");
btn.setOnClickListener(this);
LinearLayout.LayoutParams param =
new LinearLayout.LayoutParams(100,50);
param.topMargin
= 10;
layout.addView(btn, param);
btn2 = new Button(this);
btn2.setId(102);
btn2.setBackgroundResource(R.drawable.ok_blue);
btn2.setText("exit");
btn2.setOnClickListener(this);
layout.addView(btn2, param);
tv = new TextView(this);
tv.setTextColor(Color.WHITE);
tv.setText("");
LinearLayout.LayoutParams param2 =
new LinearLayout.LayoutParams(FP, WC);
param2.topMargin = 10;
layout.addView(tv, param2);
setContentView(layout);
//------------------------
}
public void onClick(View v) {
switch(v.getId()){
case 101:
progressDialog = ProgressDialog.show(this,
"please wait…","Loading",true);
h = new EventHandler(Looper.myLooper());
th1 = new myThread();
th1.start();
break;
case 102:
finish();
break;
}
}
//------------------------------------------------
public class
EventHandler extends Handler {
public EventHandler(Looper looper) {
super(looper);
}
@Override
public void
handleMessage(Message msg) {
progressDialog.dismiss();
setTitle((String)msg.obj);
}
}
//
---------------------------------------
class
myThread extends Thread {
@Override
public void run() {
try{
sleep(6000); //故意延迟
}
catch(Exception e)
{
e.printStackTrace();
}
String obj = "mainThread....";
Message m = h.obtainMessage(1, 1, 1, obj);
h.sendMessage(m);
}
}
}
一开始,画面如下:
接着画面变为:
以上介绍了UI线程及其子线程。除了这些主/子线程之沟通之外,不同的主线程之间也有沟通的机制,例如Activity类别的UI主线程与remote
Service主线程之间有如何沟通呢? 还有,Java类别与C++的*.so里各由不同的线程执行,它们又如何沟通呢?
|