这个问题有点意思,估计大家都会很明确的说在Activity中setTitle()操作,toast显示,AlertDialog的显示肯定都要放置在UI主线程中操作。
其实我也是这么觉得的,但是感觉Activity的setTitle和AlertDialog,Toast还是有区别的。
下面我做了一个测试:
package com.example.demo_handlertest;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity {
private Context mContext=this;
private final static String TAG="HandlerTest";
private Handler myHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d(TAG, "handler out threadid:"+Thread.currentThread().getId());
switch(msg.what){
case 1:
setTitle("1myHandlerMessage");
break;
case 2:
setTitle("2timerTaskMessage");
break;
case 3:
setTitle("3myHandlerMessage");
break;
}
}
};
Timer timer = new Timer();
TimerTask task = new TimerTask(){
public void run() {
Message message = new Message();
message.what = 2;
myHandler.sendMessage(message);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//操作A:2秒钟后,将设置title为timerTaskMessage,is ok
timer.schedule(task, 1000*2);
Log.d(TAG, "oncreate threadid:"+Thread.currentThread().getId());
Button but=(Button) this.findViewById(R.id.but);
but.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//操作B:直接设置新title,is ok.
setTitle("4 but is clicked");
new Thread(){
public Handler mHandler;
public AlertDialog dlg;
@Override
public void run(){
Log.d(TAG, "onclick threadid:"+Thread.currentThread().getId());
//操作E:在非UI主线程直接更新title,is wrong.
//setTitle("error");
SystemClock.sleep(1000*5);
//操作C:通知myHandler去更新title
myHandler.sendEmptyMessage(3);
SystemClock.sleep(1000*5);
Looper.prepare();
Toast.makeText(getApplicationContext(), "test1", Toast.LENGTH_LONG).show();
Log.d(TAG, "handler in looper threadid:"+Thread.currentThread().getId());
dlg=new AlertDialog.Builder(mContext).setTitle("提示").setCancelable(false)
.setMessage("程序崩溃了...").setNeutralButton("我知道了", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dlg.dismiss();
}
})
.create();
dlg.show();
mHandler = new Handler() { //如果没用使用Looper.getMainLooper(),里面是不可以写setTitle方法的
public void handleMessage(Message msg) {
// 处理收到的消息
}
};
mHandler.post(new Runnable(){
@Override
public void run() {
//操作F:更新title,is error
//setTitle("5mHanlder message");
Log.d(TAG, "1handler post threadid:"+Thread.currentThread().getId());
Toast.makeText(getApplicationContext(), "test2", Toast.LENGTH_LONG).show();
}
});
Looper.loop();
}
}.start();
}
});
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/but"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="update" />
</RelativeLayout>
这个测试结果的log如下:
这个测试说明什么呢?
其实说明了2点:
1、从日志中看threadid:1为UI主线程,从oncreate启动的;threadid:8824是在but点击之后启动的一个线程。
2、操作E和操作F被我注释掉了,setTitle会报错的,因为在非主UI线程;操作A,B,C都是在UI主线程中更新的Title这个好理解,
Looper.prepare();
和Looper.loop()之间操作的Toast和AlertDialog为何可以正常显示,然而setTtile却不可以呢?
这个问题大家可以先思考一下,下面我把代码再修改一下。
package com.example.demo_handlertest;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity {
private Context mContext=this;
private final static String TAG="HandlerTest";
private Handler myHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d(TAG, "handler out threadid:"+Thread.currentThread().getId());
switch(msg.what){
case 1:
setTitle("1myHandlerMessage");
break;
case 2:
setTitle("2timerTaskMessage");
break;
case 3:
setTitle("3myHandlerMessage");
break;
}
}
};
Timer timer = new Timer();
TimerTask task = new TimerTask(){
public void run() {
Message message = new Message();
message.what = 2;
myHandler.sendMessage(message);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//操作A:2秒钟后,将设置title为timerTaskMessage,is ok
timer.schedule(task, 1000*2);
Log.d(TAG, "oncreate threadid:"+Thread.currentThread().getId());
Button but=(Button) this.findViewById(R.id.but);
but.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//操作B:直接设置新title,is ok.
setTitle("4 but is clicked");
new Thread(){
public Handler mHandler;
public AlertDialog dlg;
@Override
public void run(){
Log.d(TAG, "onclick threadid:"+Thread.currentThread().getId());
//操作E:在非UI主线程直接更新title,is wrong.
//setTitle("error");
SystemClock.sleep(1000*5);
//操作C:通知myHandler去更新title
myHandler.sendEmptyMessage(3);
SystemClock.sleep(1000*5);
Looper.prepare();
Toast.makeText(getApplicationContext(), "test1", Toast.LENGTH_LONG).show();
Log.d(TAG, "handler in looper threadid:"+Thread.currentThread().getId());
dlg=new AlertDialog.Builder(mContext).setTitle("提示").setCancelable(false)
.setMessage("程序崩溃了...").setNeutralButton("我知道了", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dlg.dismiss();
}
})
.create();
dlg.show();
mHandler = new Handler(Looper.getMainLooper()) { //如果没用使用Looper.getMainLooper(),里面是不可以写setTitle方法的
public void handleMessage(Message msg) {
// 处理收到的消息
}
};
mHandler.post(new Runnable(){
@Override
public void run() {
//操作F:更新title,is right
setTitle("5mHanlder message");
Log.d(TAG, "1handler post threadid:"+Thread.currentThread().getId());
Toast.makeText(getApplicationContext(), "test2", Toast.LENGTH_LONG).show();
}
});
Looper.loop();
}
}.start();
}
});
}
}
logcat的日志如下:
其实这2个代码中只是把mHandler = new Handler(Looper.getMainLooper()),这个是变化之处,我们知道加上Looper.getMainLooper(),handler.post(runnable)其实就是在主线程中运行了。只有这样子 操作F才会正常执行,但是为何无论哪种方式toast和AlertDialog都可以创建呢?
通过这个例子,大家有何想法可以与我交流。
其实,AsyncTask我也做了同样的测试,结果跟上述一样:
public class MyAsyncTask extends AsyncTask<Integer, Integer, String> {
AlertDialog dlg;
/**
* 这里的Integer参数对应AsyncTask中的第一个参数
* 这里的String返回值对应AsyncTask的第三个参数
* 该方法并不运行在UI线程当中,主要用于异步操作,所有在该方法中不能对UI当中的空间进行设置和修改
* 但是可以调用publishProgress方法触发onProgressUpdate对UI进行操作
*/
@Override
protected String doInBackground(Integer... params) {
Looper.prepare();
Handler mHandler = new Handler(Looper.getMainLooper()) { //如果没用使用Looper.getMainLooper(),里面是不可以写setTitle方法的;而且只能执行onPreExecute方法就卡住了
public void handleMessage(Message msg) {
// 处理收到的消息
}
};
mHandler.post(new Runnable(){
@Override
public void run() {
//操作G:更新title,is error
setTitle("doInBackground");
Log.d(TAG, "doInBackground threadid:"+Thread.currentThread().getId()+",name:"+Thread.currentThread().getName());
Toast.makeText(getApplicationContext(), "doInBackground", Toast.LENGTH_LONG).show();
dlg=new AlertDialog.Builder(mContext).setTitle("doInBackground").setCancelable(false)
.setMessage("我在UI主线程吗").setNeutralButton("我知道了", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dlg.dismiss();
}
})
.create();
dlg.show();
}
});
Looper.loop();
//Toast.makeText(getApplicationContext(), "doInBackground", Toast.LENGTH_LONG).show();
Log.d(TAG, "Thread name:"+Thread.currentThread().getName()+",1doInBackground");
Looper.loop();
Log.d(TAG, "Thread name:"+Thread.currentThread().getName()+",2doInBackground");
return "";
}
/**
* 这里的String参数对应AsyncTask中的第三个参数(也就是接收doInBackground的返回值)
* 在doInBackground方法执行结束之后在运行,并且运行在UI线程当中 可以对UI空间进行设置
*/
@Override
protected void onPostExecute(String result) {
setTitle("onPostExecute");
Toast.makeText(getApplicationContext(), "onPostExecute", Toast.LENGTH_LONG).show();
Log.d(TAG, "Thread name:"+Thread.currentThread().getName()+",onPostExecute");
}
//该方法运行在UI线程当中,并且运行在UI线程当中 可以对UI空间进行设置
@Override
protected void onPreExecute() {
setTitle("onPreExecute");
//textView.setText("开始执行异步线程");
Toast.makeText(getApplicationContext(), "onPreExecute", Toast.LENGTH_LONG).show();
Log.d(TAG, "Thread name:"+Thread.currentThread().getName()+",onPreExecute");
}
/**
* 这里的Intege参数对应AsyncTask中的第二个参数
* 在doInBackground方法当中,,每次调用publishProgress方法都会触发onProgressUpdate执行
* onProgressUpdate是在UI线程中执行,所有可以对UI空间进行操作
*/
@Override
protected void onProgressUpdate(Integer... values) {
setTitle("onProgressUpdate");
Toast.makeText(getApplicationContext(), "onProgressUpdate", Toast.LENGTH_LONG).show();
Log.d(TAG, "Thread name:"+Thread.currentThread().getName()+",onProgressUpdate");
}
}