这个问题有点意思,估计大家都会很明确的说在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>
这个测试说明什么呢?
其实说明了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(); } }); } }
其实这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"); } }