1. 线程的同步和异步
线程是程序执行流的最小单元, Android 每个运行的应用程序可能包含多个线程.
Andorid 系统中默认只有一个主线程, 也叫 UI 线程, 因此 View 绘制只能在这个线程内进行, 即修改界面的操作只能在主线程中执行.
所以如果阻塞了 (某些操作使这个线程在此处运行了 N 秒) 这个线程, 这期间 View 绘制将不能进行, UI 就会卡. 所以要极力避免在 UI 线程进行耗时操作.
如果主线程中做一些耗时操作, 阻塞了主线程. 阻塞时间超过 5 秒(或 6 秒), 就会报 ANR 异常(Application Not Response, 应用程序无响应). 开发中要极力避免 ANR 异常.
网络请求是一个典型耗时操作.
对应 Android4.0 以上版本, Google 更加在意 UI 界面运行的流畅性, 强制要求访问网络的操作不允许在主线程中执行, 只能在子线程中进行, 在主线程请求网络时, 会报如下错误:
Android.os.NetworkOnMainThreadException
常见的卡顿
解决方式就是把连接网络的操作放在子线程中:
网络操作放到子线程
看日志不是很方便, 可以将结果显示在 TextView 中.
textview
MainActivity
运行结果:
虽然程序不会报错, 但原则上不允许在子线程中修改界面的.
由于在 onCreate 方法中, 程序还没来得及校验, 因此给了我们一个可以在子线程修改 UI 的假象.
把上面的操作放在 Button 中体会一下:
Button
点击事件
上面的执行就报错了:
CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views.
如果子线程修改了 UI, 系统会验证当前线程是不是主线程, 如果不是主线程, 就会终止运行.
问题:
如果子线程不能修改主线程的 UI, 那么子线程需要修改 UI 时该怎么办?
解决方式:
使用 Handler 实现子线程与主线程之间的通信. 在子线程进行耗时操作, 完成后通过 Handler 将更新 UI 的操作发送到主线程执行. 这就叫异步.
- public class MainActivity extends AppCompatActivity {
- TextView textView;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- textView = findViewById(R.id.textview);
- }
- // 在主线程 new 的 Handler, 就会在主线程进行后续处理
- private Handler handler = new Handler();
- public void click(View v){
- // 创建子线程并通过 start 开启线程
- new Thread(new Runnable() {
- @Override
- public void run() {
- final String result = NetUtils.get("http://likedev.applinzi.com");
- Log.i("MainActivity",result);
- handler.post(new Runnable() {
- @Override
- public void run() {
- // 在 UI 线程更新 UI
- textView.setText(result);
- }
- });
- }
- }).start();
- }
- }
Handler 还有另外一种写法, 实现效果和上面一样:
- // 在主线程 new 的 Handler, 就会在主线程进行后续处理
- @SuppressLint("HandlerLeak")
- private Handler handler = new Handler(){
- // 方式 2 接收数据
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- // 处理消息
- String result = (String)msg.obj;
- textView.setText(result);
- }
- };
- public void click(View v){
- // 创建子线程并通过 start 开启线程
- new Thread(new Runnable() {
- @Override
- public void run() {
- final String result = NetUtils.get("http://likedev.applinzi.com");
- // 方式 2
- Message message = new Message();
- message.obj = result; // 设置数据
- // 发送消息
- handler.sendMessage(message);
- }
- }).start();
- }
Message 是线程之间传递的消息, 它可以在内部传递消息, 主要用于不同线程之间的交换数据. message.obj 可以传递一个 Object 对象, setData()可以设置传递的数据, message.what 是一个 int 类型, 一般用来标记消息类型, arg1 和 arg2 两个 int 类型参数可以传递少量数据.
使用 Handler 的 sendMessage()方法, 而发出的消息经过一系列辗转处理后, 最终会传递到 Handler 的 handleMessage()方法中.
在 Activity 环境中, 可以通过 Activity 中的 runOnUiThread 方法简写上面的代码, 代码显得更简洁:
- public void click(View v){
- // 方式 3
- new Thread(new Runnable() {
- @Override
- public void run() {
- final String result = NetUtils.get("http://likedev.applinzi.com");
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- textView.setText(result);
- }
- });
- }
- }).start();
- }
下面是 runOnUiThread 方法的源码, 这个方法也是通过封装 Handler 实现的, 原理是一样的:
runOnUiThread
来源: http://www.jianshu.com/p/023607804577