handler是什么:
handler是Android给我们提供用于更新UI的一套机制,也是一套消息处理的机制,我们可以发送消息,也可以通过它处理消息。
为什么要用handler:
Android在设计的时候,就封装了一套消息创建,传递,处理机制,如果不遵循这样的机制就没办法更新UI信息,就会抛出异常信息。
handler的用法:
第一种:主线程中创建Handler对象,子线程中调用handler的post(Runnable runnable)方法更新UI。举例演示这种用法,2s后更新MainActivity中TextView的显示内容。
MainActivity的代码:
public class MainActivity extends AppCompatActivity { private TextView mTextView; private Handler mHandler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.text_view); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); mHandler.post(new Runnable() { @Override public void run() { mTextView.setText("hello, I am here"); } }); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }第二种:主线程中创建Handler对象;创建Runnable类,在Runnable类中的run方法中更新UI,调用handler的postDelayed(Runnable runnable,long delayMilis)方法指定具体的延时时间更新UI操作;在主线程中调用handler.postDelayed(Runnable runnable,long delayMilis); 举例说明每隔2s中更新MainActivity中的ImageView
public class MainActivity extends AppCompatActivity { private TextView mTextView; private ImageView mImageView; private Handler mHandler = new Handler(); private int index; private int[] images = {R.drawable.image1, R.drawable.image2, R.drawable.image3}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.text_view); mImageView = (ImageView) findViewById(R.id.image_view); mHandler.postDelayed(mMyRunnable,2000); } private MyRunnable mMyRunnable = new MyRunnable(); class MyRunnable implements Runnable{ @Override public void run() { index ++; index = index %3; mImageView.setBackgroundResource(images[index]); mHandler.postDelayed(mMyRunnable,2000); } } }第三种:主线程中创建Handler对象,并重写它的handleMessage方法,在handleMessage方法中根据子线程中发送过来的message信息更新UI;子线程创建Message对象,调用Messeng的参数封装要传递的信息,使用Handler的sendMessage()方法或者message的sendToTarget()方法发送消息。messsage的obj属性时传递一个对象,arg1和arg2是传递一个int型的参数,以第一个更新textview的内容作为演示:
public class MainActivity extends AppCompatActivity { private TextView mTextView; private ImageView mImageView; private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); mTextView.setText(""+msg.obj); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.text_view); mImageView = (ImageView) findViewById(R.id.image_view); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); Message message = mHandler.obtainMessage(); Human human = new Human(); human.name = "Peter"; human.age = 18; message.obj = human; mHandler.sendMessage(message); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } class Human{ private int age; private String name; @Override public String toString() { return "name: "+name +" age: "+age; } } }第四种:Handler中message的终止操作,把消息从Handler中移除。调用Handler的removeCallbacks(Runnable r)方法或者它的重载方法,以button的点击事件作为触发消息移除的操作动作,在第二种的基础上进行添加,代码如下:
public class MainActivity extends AppCompatActivity { private TextView mTextView; private ImageView mImageView; private Button mButton; private Handler mHandler = new Handler(); private int index; private int[] images = {R.drawable.image1, R.drawable.image2, R.drawable.image3}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.text_view); mImageView = (ImageView) findViewById(R.id.image_view); mButton = (Button) findViewById(R.id.btu); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mHandler.removeCallbacks(mMyRunnable); } }); mHandler.postDelayed(mMyRunnable,2000); } private MyRunnable mMyRunnable = new MyRunnable(); class MyRunnable implements Runnable{ @Override public void run() { index ++; index = index %3; mImageView.setBackgroundResource(images[index]); mHandler.postDelayed(mMyRunnable,2000); } } }第五种:对Handler发送的消息进行截获,根据是否截获成功进行相应的处理。在创建Handler对象时,调用Handler的带参构造(Callback c)如下:
public class MainActivity extends AppCompatActivity { private TextView mTextView; private ImageView mImageView; private Button mButton; private Handler mHandler = new Handler(new Handler.Callback() { //在下面的返回值为boolean类型的handleMessage方法中进行截获,根据是否截获成功返回true或者false, //若返回为false则表示没有截获成功,下面的void handleMessage方法会继续执行;若返回为true则表示截获成功, //就只会调用boolean handleMessage方法中的逻辑,而不会执行void handleMessage方法中的逻辑 @Override public boolean handleMessage(Message msg) { //...... Toast.makeText(MainActivity.this, "1", Toast.LENGTH_SHORT).show(); return false; } }){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); //....... Toast.makeText(MainActivity.this, "2", Toast.LENGTH_SHORT).show(); } };
android为什么要设计只能通过Handler机制更新UI
最根本的目的就是解决多线程并发问题。
假如在一个activity当中,有多个线程去更新UI,并且都没有加锁机制,那么会产生什么样子的问题?
更新界面错乱
如果对更新ui的操作都进行加锁处理的话又会出现什么问题?
性能下降
处于对以上目的问题的考虑,Android给我们提供了一套更新ui的机制,我们只需遵循这样的机制就可以了,不用关心多线程问题,所有的更新ui的操作,都是在主线程的消息队列当中去轮询处理的
Handler的原理
Handler负责发送消息,Looper负责接收handler发送的消息,并直接将消息回传给Handler自己,MessageQueue就是一个存储消息的容器
如何实现一个与线程相关的handler
public class SecondActivity extends AppCompatActivity { private static final String TAG = "SecondActivity"; class MyThread extends Thread{ public Handler mHandler; @Override public void run() { Looper.prepare(); mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.e(TAG, "currentThread: "+Thread.currentThread()); //E/SecondActivity: currentThread: Thread[Thread-136,5,main] } }; Looper.loop(); } } private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.e(TAG, "handleMessage: currentThread"+Thread.currentThread() ); //E/SecondActivity: handleMessage: currentThreadThread[main,5,main] } }; private MyThread mThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); mThread = new MyThread(); mThread.start(); try { mThread.sleep(1000); mThread.mHandler.sendEmptyMessage(1); } catch (InterruptedException e) { e.printStackTrace(); } mHandler.sendEmptyMessage(1); } }HandlerThread解决多线程的并发问题,多线程并发导致的空指针问题
public class ThridActivity extends AppCompatActivity { private static final String TAG = "ThridActivity"; class MyThread extends Thread{ public Handler mHandler; @Override public void run() { Looper.prepare(); mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.e(TAG, "currentThread: "+Thread.currentThread()); //E/SecondActivity: currentThread: Thread[Thread-136,5,main] } }; Looper.loop(); } } private MyThread mThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thrid); mThread = new MyThread(); mThread.start(); //构建Handler时调用了子线程中的Looper,引发并发问题的空指针现象 Handler handler = new Handler(mThread.mHandler.getLooper()){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.e(TAG, "handleMessage: currentThread" + Thread.currentThread() ); } }; handler.sendEmptyMessage(1); } }报错如下:
com.example.sun.handlerdemo E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.sun.handlerdemo, PID: 2057
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.sun.handlerdemo/com.example.sun.handlerdemo.ThridActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'android.os.Looper android.os.Handler.getLooper()' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
at android.app.ActivityThread.-wrap11(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.os.Looper android.os.Handler.getLooper()' on a null object reference
at com.example.sun.handlerdemo.ThridActivity.onCreate(ThridActivity.java:40)
at android.app.Activity.performCreate(Activity.java:6237)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
at android.app.ActivityThread.-wrap11(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
改用HandlerThread
public class ThridActivity extends AppCompatActivity { private static final String TAG = "ThridActivity"; private Handler mHandler; private HandlerThread mThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thrid); mThread = new HandlerThread("handler Thread"); mThread.start(); Handler handler = new Handler(mThread.getLooper()){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.e(TAG, "handleMessage: currentThread: " + Thread.currentThread() ); // E/ThridActivity: handleMessage: currentThread: Thread[handler Thread,5,main] } }; handler.sendEmptyMessage(1); } }Android中更新UI的几种方式:
public class FiveActivity extends AppCompatActivity { public static final int UPDATE_UI = 1; private TextView mTextView; private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case UPDATE_UI: mTextView.setText("ok"); break; default: break; } } }; //更新ui的方式一 handler.post(Runnable r) private void updateUI_1(){ mHandler.post(new Runnable() { @Override public void run() { mTextView.setText("ok"); } }); } //更新ui的方式二 handler.sendMessage(message m) private void updateUI_2(){ Message message = new Message(); message.what = UPDATE_UI; mHandler.sendMessage(message); } //更新ui的方式三 runOnUIThread(Runnable r) private void updateUI_3(){ runOnUiThread(new Runnable() { @Override public void run() { mTextView.setText("ok"); } }); } //更新ui的方式四 view.post(Runnable r) private void updateUI_4(){ mTextView.post(new Runnable() { @Override public void run() { mTextView.setText("ok"); } }); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_five); mTextView = (TextView) findViewById(R.id.textView); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); //updateUI_1(); // updateUI_2(); // updateUI_3(); updateUI_4(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }Handler使用过程中的两种常见的异常情况
第一种:在子线程中进行UI操作,报错如下:
E/AndroidRuntime: FATAL EXCEPTION: Thread-175
Process: com.example.sun.handlerdemo, PID: 4454
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.requestLayout(ViewRootImpl.java:907)
...
第二种:在子线程中创建Handler时,并没有为这个Handler指定Looper
E/AndroidRuntime: FATAL EXCEPTION: Thread-179
Process: com.example.sun.handlerdemo, PID: 4529
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)
at com.example.sun.handlerdemo.FiveActivity$1.run(FiveActivity.java:72)
at java.lang.Thread.run(Thread.java:818)