Handler.post解惑

今日修改bug拿到一份代码,其中网络请求方面由于功能很简单,就没有使用网络请求框架,ok,那按照我的想法,开启一个线程拿个数据,拿完发送handler更新UI即可了,但是代码中并不是这么写的,而是通过mNetHandler.post一个包含网络请求的runnable。mNetHandler的来源是这样的。

1
2
3
HandlerThread handlerThread = new HandlerThread("NET");
handlerThread.start();
mNetHandler = new Handler(handlerThread.getLooper());

而当我询问原作者原因时,解释是用mNetHandler来管理这些Runnable,在view结束时,

mNetHandler.removeCallback

移除这些runnable,解决掉在view,或者说activity中开启线程,而当view或者activity结束时 线程仍然存活的问题。

但是目前存在一个问题,removeCallback并不是立即停止该线程,而是移除掉还未执行的callback,正在执行的是无法立即结束的。此问题我写了demo,证实的确是正在运行的程序无论通过
mNetHandler.getLooper().quit();

handlerThread.quitSafely();

都无法停止该线程,我们来看一下我的demo

功能很简单,activity1点击button进activity2,activity2一进去就开启线程干事情,然后点击finsh按钮,结束当前activity,回到activity1,无论哪种模式,activity开的那个线程都能存活。

  • Activity 1的代码 没什么东西 就是跳转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MainActivity extends AppCompatActivity {
private Button button;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.my_text);
button = (Button) findViewById(R.id.button);
textView.setText("第一个界面");
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.i("iii"," 跳转去activity2");
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);
}
});
}
}
  • Activity2的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* Created by xiamin on 11/19/16.
*/
public class SecondActivity extends AppCompatActivity {
private Button button;
private TextView textView;
private Handler mNetHandler;
HandlerThread handlerThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i("iii","进入activity2");
textView = (TextView) findViewById(R.id.my_text);
button = (Button) findViewById(R.id.button);
textView.setText("第2个界面");
button.setText("finish()");
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
handlerThread = new HandlerThread("NET");
handlerThread.start();
mNetHandler = new Handler(handlerThread.getLooper());
mNetHandler.post(mRunnable);
}
private static int count = 0;
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
while (true) {
Log.i("iii"," count = " + count++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
@Override
protected void onDestroy() {
Log.i("iii","activity2 onDestroy() ");
mNetHandler.removeCallbacksAndMessages(null);
mNetHandler.getLooper().quit();
handlerThread.quitSafely();
super.onDestroy();
}
}

打印为,我们可以见到runnable里在继续打印

1
2
3
4
5
6
7
8
9
10
11
11-19 17:05:15.930 6280-6280/? I/iii: 跳转去activity2
11-19 17:05:15.960 6280-6280/? I/iii: 进入activity2
11-19 17:05:15.960 6280-6321/? I/iii: count = 0
11-19 17:05:16.960 6280-6321/? I/iii: count = 1
11-19 17:05:17.960 6280-6321/? I/iii: count = 2
11-19 17:05:18.960 6280-6321/? I/iii: count = 3
11-19 17:05:19.250 6280-6280/? I/iii: activity2 onDestroy()
11-19 17:05:19.960 6280-6321/? I/iii: count = 4
11-19 17:05:20.960 6280-6321/? I/iii: count = 5
11-19 17:05:21.960 6280-6321/? I/iii: count = 6
11-19 17:05:22.960 6280-6321/? I/iii: count = 7

可见,使用handlerThread只充当了一个线程的单行执行器的作用,并没有能够起到所谓的控制runnable执行的作用。事实上,什么都不能控制runnable执行的。

  • 这个事情提醒我们几点:

1.activity结束后线程还能存活,所以我们要记得结束它或者知道要管理好他们

2.使用handlerThread.post runnable 只是让handlerThread充当了线程的顺序执行器

  • handler回顾

Android程序员都知道不能在UI线程执行耗时的操作,Android引入handler就是为了解决这个问题,当然实现异步更新UI不仅仅只有这一种方法,还有AsyncTask也可以实现。

Android有一个 Handler类,使用该类可以对运行在不同线程中的多个任务进行排队,并使用Message和Runnable对象安排这些任务。在javadoc中,对Handler是这样解释的:Handler可以发送和处理消息对象或Runnable对象,这些消息对象和Runnable对象与一个线程相关联。每个Handler的实例都关联了一个线程和线程的消息队列。当创建了一个Handler对象时,一个线程或消息队列同时也被创建,该Handler对象将发送和处理这些消息或Runnable对象。

a、如果new一个无参构造函数的Handler对象,那么这个Handler将自动与当前运行线程相关联,也就是说这个Handler将与当前运行的线程使用同一个消息队列,并且可以处理该队列中的消息。
我做过这样一个实验,在主用户界面中创建一个带有无参构造函数的Handler对象,该Handler对象向消息队列推送一个Runnable对象,在Runnable对象的run函数中打印当前线程Id,主用户界面线程ID和Runnable线程ID均为1。

b、如果new一个带参构造函数的Handler对象,那么这个Handler对象将与参数所表示的Looper相关联。注意:此时线程类应该是一个特殊类HandlerThread类,一个Looper类的Thread类,它继承自Thread类。

c、如果需要Handler对象去处理消息,那么就要重载Handler类的handleMessage函数。


谢谢大家阅读,如有帮助,来个喜欢或者关注吧!


本文作者:Anderson/Jerey_Jobs

简书地址:Anderson大码渣

github地址:Jerey_Jobs