Android Handler的使用

Posted 胡刚2021

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Handler的使用相关的知识,希望对你有一定的参考价值。

参考文章:
Android Handler用法解析
Android:为什么子线程不能更新UI

因为 android 的UI更新是在主线程中做的,如果在主线程做一些耗时操作,比如进行网络请求,弱网环境下就极为容易阻塞住UI更新,画面看起来就像是被卡住了。
从Android4.0开始,Android不允许再主线程中进行网络请求,不允许在子线程中进行UI更新。
既然不允许在主线程进行网络请求,为什么还不允许在子线程更新UI呢?
假如允许多线程更新UI,但是访问UI是没有加锁的,一旦多线程抢占了资源,那么界面将会乱套更新了,体验效果就不言而喻了。而且,加了锁会让性能降低,所以在Android中规定必须在主线程更新UI。
那是不是在子线程更新UI就一定不行呢?
答案是:在onCreate方法中,如果你调用子线程不做太多耗时操作的话,是可以在onCreate方法中调用子线程去更新UI的。
下面的代码,在子线程更新UI就不会报错

public class MainActivity extends AppCompatActivity 
    private TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = findViewById(R.id.text);
        new Thread(new Runnable() 
            @Override
            public void run() 
                mTextView.setText("测试是否报出异常");
            
        ).start();
    

原因是ViewRootIml的checkThread方法会检查当前线程是否是主线程,不是的话会报错,而ViewRootImpl对象是在onResume方法回调之后才创建,所以上面的代码并不会报错。

@Override
public void requestLayout() 
    if (!mHandlingLayoutInLayoutRequest) 
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    


void checkThread() 
    if (mThread != Thread.currentThread()) 
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    

当我们在子线程做了一些耗时操作后,要更新UI时,我们需要通知给主线程,这个通知使用的工具就是Handler。
我们需要在主线程中定义Handler
如果写成下面这种写法,就会容易产生内存泄漏


public class MainActivity extends AppCompatActivity 
    private ImagerView iv;
    private Handler mHandler = new Handler()
        @Override
        public void handleMessage(Message msg) 
            switch (msg.what)
                case value:
                iv.setImageResoure(...)
                break;
            
        
    ;

原因是,当Activity finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致 Activity泄露。
因此我们需要写成下面这种形式

private static class MyHandler extends Handler 
     private final WeakReference<MainActivity> mTarget;

     public MyHandler(MainActivity activity) 
         mTarget = new WeakReference<MainActivity>(activity);
     

     @Override
     public void handleMessage(@NonNull Message msg) 
         super.handleMessage(msg);
         if (msg.what == 0) 
             Log.e("myhandler", "change textview");
             // 当没有任何强引用到widget对象时使用get时突然返回null
             MainActivity ma = mTarget.get();
             ma.textView.setText("hahah");
         
     
 

我们需要将 MyHandler 定义成 static ,因为静态内部类不会引用外部类对象。而且,当我们要使用外部类对象的时候需要将其定义成 WeakReference ,这样一旦外部类销毁的时候使用weakWidget.get()就可以得到真实的Widget对象,因为弱引用不能阻挡垃圾回收器对其回收,你会发现当没有任何强引用到MainActivity对象时使用get时返回null。
然后在主线程创建一个类型为MyHandler的私有属性:

private Handler myHandler= new MyHandler(this);

创建一个线程,在子线程中向主线程发送消息

new Thread(new Runnable() 
    @Override
    public void run() 
        myHandler.sendEmptyMessage(0);
    
).start();

还有一种情况是:如果主线程想通知子线程一些消息,那么就需要使用下面的代码:
先在主线程声明Handler

private Handler threadHandler;

然后创建一个线程

HandlerThread handlerThread = new HandlerThread("test Handler");
handlerThread.start();

然后,真正创建在主线程声明的 Handler 对象

threadHandler = new Handler(handlerThread.getLooper()) 
    @Override
    public void handleMessage(Message msg) 
        super.handleMessage(msg);
        if (msg.what==2) 
            Toast.makeText(MainActivity.this, "主线程:", Toast.LENGTH_SHORT).show();
        
    
;

最后,我们需要在主线程调用

threadHandler.sendEmptyMessage(2);

这样 Handler 中的 handleMessage 方法处理消息。

以上是关于Android Handler的使用的主要内容,如果未能解决你的问题,请参考以下文章

[Android]View的Handler机制

android中的handler使用之四

Android Handler使用

Android--Handler的使用方法:在子线程中更新界面

Android 为啥使用Handler

Android Handler处理机制 ( 三 ) ——Handler,Message,Looper,MessageQueue