参考文章:
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 方法处理消息。