Android 入门第六讲03-Handler(学会Debug模式断点调试,Handler机制(线程问题分析,Handler的使用方法),Handler的原理(超详细))

1.本讲必备操作

1.准备所需代码

导入前一讲已经分析过,且本讲需要用到的代码
添加权限

  	 <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

application节点下添加

	android:usesCleartextTraffic="true"

在这里插入图片描述

activity.java代码

public class MainActivity extends AppCompatActivity {
    TextView textView1;
    TextView textView2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView1 = findViewById(R.id.textView);
        textView2 = findViewById(R.id.textView2);
//怎么创建线程
        Thread thread = new Thread() {//方法1
            @Override
            public void run() {
                super.run();
                try { // 子线程
                    Log.i("MainActivity", "start");
//服务器的地址
                    URL url = new URL("http://148.70.46.9/object3");
//和服务器建立连接
                    HttpURLConnection httpURLConnection = (HttpURLConnection)
                            url.openConnection();
//发送请求到服务器
                    httpURLConnection.setRequestMethod("GET");
// 服务器返回的数据 字节流 byte 字符流 char 文本
// 输入流 从磁盘(网络)到内存 输出内存到磁盘
                    InputStream inputStream = httpURLConnection.getInputStream(); //字节流
                    Reader reader = new InputStreamReader(inputStream);// 字符流
                    BufferedReader bufferedReader = new BufferedReader(reader); //缓存流
                    String result = "";
                    String temp;
                    while ((temp = bufferedReader.readLine()) != null) {
                        result += temp;
                    }
                    Log.i("MainActivity", "result:" + result);
// { "grade":"18级","classname":"中医学","students":["张三","李四","王五"] }
                    JSONObject jsonObject = new JSONObject(result);
                    final String grade = jsonObject.getString("grade");
                    final String classname = jsonObject.getString("classname");
                    Log.i("MainActivity", "grade:" + grade + " classname :" + classname);
                    JSONArray jsonArray = jsonObject.getJSONArray("students");
                    for (int i = 0; i < jsonArray.length(); i++) {
                        JSONObject jsonObject1 = jsonArray.getJSONObject(i);
                        String id = jsonObject1.getString("id");
                        int age = jsonObject1.getInt("age");
                        String name = jsonObject1.getString("name");
                        Log.i("MainActivity", "id:" + id + " age:" + age + " name :" + name);
                    }
// ViewRootImpl$CalledFromWrongThreadException 在一个错误的线程中
// 子线程 -网络访问只能在子线程
// ui操作 文本设置值 点击事件 有时候会问题 有时候不会有问题 -不能子线程
// 切到主线程
                    runOnUiThread(new Runnable() {

                        @Override
                        public void run() { // 主线程
                            textView1.setText("年级:" + grade);
                            textView2.setText("班级:" + classname);
                        }
                    });
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        thread.start();
    }
}

xml代码

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="88dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginLeft="110dp" />
    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="27dp"
        app:layout_constraintStart_toStartOf="@+id/textView"
        app:layout_constraintTop_toBottomOf="@+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>

运行结果
在这里插入图片描述

2.学会Debug模式断点调试

断点调试详细介绍推荐博客

1.点击Debug模式 运行调试

第一步,加断点(在开始和排查过程中都可以添加断点)
在这里插入图片描述

第二步,点击按钮运行调试(Debug模式)
在这里插入图片描述
在这里插入图片描述
模拟器会出现这样一个弹框(我们不用管)
在这里插入图片描述
来,一起操作吧
在这里插入图片描述

2.调试方法

视图介绍

在这里插入图片描述
这里主要介绍两个按钮功能

  1. Step Over
  2. Run to Cursor

在这里插入图片描述

1.Step Over(程序执行下一行)

功能:单步跳过,点击该按钮表示程序将执行下一行,如果该行是一句代码直接执行到下一行, 如果该行是一个方法,不会进入该方法,直接执行到下一行。

在这里插入图片描述

在这里插入图片描述

2.Run to Cursor(跳到下一个断点)

在这里插入图片描述
在这里插入图片描述

3.排查错误

我们在这行代码key中加3个问号,使key与服务器的不对应,模拟错误

在这里插入图片描述
我们点击Step Over按钮,会发现程序执行到这一行,就跳到异常处理了
在这里插入图片描述
并且报错,我们找到刚才跳到异常的那一行代码,并且结合报错原因,便可得知错误所在
在这里插入图片描述

最后讲下为什么我们要学习断点调试,因为当我们找不到错误所在时,通过断点调试可以帮助我们查看程序运行期间每一步各变量的值,断点调试的方法可以帮助我们加快排查错误的速度,提高我们的工作效率,是一名合格程序员必备的技能

2.Handler机制

1.线程问题分析

问题分析:在子线程中UI操作出现问题
在这里插入图片描述
再次运行,有时候会抛出这样一个异常(有时候会有问题 有时候不会有问题 带有一定的随机性)
在这里插入图片描述
// ViewRootImpl$CalledFromWrongThreadException 表示在一个错误的线程中
// 网络访问只能在子线程
// ui操作 (对于控件的操作-例如文本设置值 点击事件)-不能放在子线程
// 解决方法就是把ui操作放到主线程

耗时操作要放到子线程里面(例如网络访问),UI操作要放到主线程(例如文本设置)

在这里插入图片描述

那么问题来了,UI操作要放到主线程,不能放到子线程,按道理来说,UI操作放到子线程一定有问题,为什么结果却是有时候会有问题,有时候不会呢?
首先,我们来看一个方法,按住shift+ctrl+r 搜索这个文件
我们
找到这个方法
在这里插入图片描述
这个方法实际实际上就是帮我们检查线程的,但是这个方法是在OnResume()方法中调用的,所以当这个checkThread()检查线程的方法没有执行的时候,ui操作放到子线程就不会有问题,相反,就会出问题

就比方:
有时候网络请求耗时很长,调用ui操作的代码还没有执行,而检查线程的方法执行了,然后再执行调用ui操作的代码,ui操作放到子线程就会检查出有问题,
有时候网络请求花的时间很短,调用ui操作的代码已经执行了,但是检查线程的方法还没有执行,所以也就ui操作放到子线程就没有检查出有问题

所以我们不要抱着侥幸的心态,养成一个好习惯,把UI操作放到主线程,把耗时操作放到子线程

2.解决方法-切到主线程

方法一.我们之前讲到的直接写一个runOnUiThread方法来创建主线程,

 runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                         //对ui的操作 只能放到主线程
                            try {
                                
                            }catch (Exception e){
                                
                            }
                        }
                    });`

实际上这个runOnUiThread方法也是Handler的机制
在这里插入图片描述

方法二-用Handler来切到主线程

3.Handler的使用方法

第一步.创建Handler类
在这里插入图片描述
在这里插入图片描述

  Handler mHandler=new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {//重写handleMessage方法
            super.handleMessage(msg);
        }
    };

1.通过sendEmptyMessage实现主线程方法回调(不带参数)

把之前的创建的主线程代码注释,把对文本赋值的操作粘贴到handleMessage方法中,
并在子线程加一句代码

                    mHandler.sendEmptyMessage(1);

在这里插入图片描述
我们这里不带参数运行
在这里插入图片描述
代码

  Handler mHandler=new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {//重写handleMessage方法
            super.handleMessage(msg);
            textView1.setText("年级:" );
            textView2.setText("班级:" );
        }
    };

运行
在这里插入图片描述
如果去掉 mHandler.sendEmptyMessage(1); 不发消息通知的话
在这里插入图片描述

所以,也就是说
mHandler.sendEmptyMessage(1);在子线程中执行,主线程的handleMessage就会回调,我们也就可以肯定handleMessage方法中的代码一定会在主线程中执行,

所以这个mHandler.sendEmptyMessage(1);的目的是,在子线程中发送消息去通知主线程来进行UI操作

2.通过sendMessage实现主线程方法回调(带参数)

在这里插入图片描述
代码


                    Message message=new Message();
                    message.what=1;

                    Bundle bundle=new Bundle();
                    bundle.putString("grade",grade);//把所需要传的值放入这个 Bundle 当中
                    bundle.putString("classname",classname);
                    message.setData(bundle);//再把 bundle 放到message当中

                    mHandler.sendMessage(message);

再回到handleMessage方法接收消息,结果运行成功
在这里插入图片描述
代码

 Handler mHandler=new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {//重写handleMessage方法
            super.handleMessage(msg);
           String grade= msg.getData().getString("grade");
            String classname= msg.getData().getString("classname");
            textView1.setText("年级:"+ grade);
            textView2.setText("班级:" +classname);
        }
    };

4.Handler的原理(重要)

1.Handler原理 大概思路

在这里插入图片描述

2.Handler原理 详细讲解

  1. 首先,我们要搞清楚一个概念,在线程里面,有一个这样的结论,就是在不同线程之间,变量是可以相互共享的,不同的线程可以拿到同一个变量的引用
    在这里插入图片描述

2.小伙伴都知道,我们刚才分析的 Handler 是在主线程里面创建的,小伙伴注意,activity里面所有的成员变量,包括OnCreate方法里面的定义的变量都是在主线程,所以也就是说我们创建的 Handler 是在主线程的,在主线程里面可以用 Handler 进行各种操作,但是我们在子线程里面也用到了 Handler,再次证明了线程间 变量的引用是可以相互共享的结论
在这里插入图片描述
.因为我们创建了一个 Handler ,那么实际上 主线程和子线程是都可以拿到这个Handler 的引用,因为他就是在主线程里面创建的,那么子线程也可以对它进行操作

3.那我们在子线程对Handler 进行了一个什么操作呢?我们可以看到,我们实际上调用了一个方法-sendMessage方法,我们还创建了一个Message对象,这个Message对象里面有很多数据,比如说我们创建的这个Bundle类,可以帮助我们传递消息,还有一个what整型变量,也就是说这个 Message我们可以把他看成一个实体类,里面可以传递很多数据
在这里插入图片描述

5.当子线程调用sendMessage方法后,这个Message对象就被加到主线程里面去,加到一个叫MessageQueue里面去,MessageQueue是一个Message队列,MessageQueue里面会保存很多的Message ,所以每次发送一个Message到MessageQueue,就会把这个Message放到队列的队尾
在这里插入图片描述

6.我们知道队列知识一种数据结构,它里面的代码不会去执行,那么这个队列的代码是怎么样去执行的呢?实际上在主线程里面还有一个角色-Looper,
在这里插入图片描述
这里有一个loop()方法,然后在通过死循环先把队列中队首取出来,遵循先进先出的原则,
然后再一个一个取出Message,如果Message取完了,这个死循环就会在这里一直等着,来一个取一个
,为什么需要一个队列来存Message呢,因为如果某个时候有很多个UI操作同时进行,如果不是一个队列的话,那我们的应用程序就会卡死,因为执行不过来,如果是一个队列的话,就算有很多UI操作,我们的队列可以一个一个执行,就不会导致我们的应用程序界面被卡死
在这里插入图片描述

7.我们将这个Message取出来还进行了什么操作呢,再往下看Looper代码
在这里插入图片描述
也就是说,Message取出来以后,会去调用Handler的dispatchMessage方法,我们可以看到,Handler是有这个方法的
在这里插入图片描述
我们来看一下这个方法执行了什么操作
在这里插入图片描述
我们可以发现这里的dispatchMessage方法调用了handleMessage方法,所以也就相当于回调了handleMessage方法
在这里插入图片描述

3.Handler原理 图文解说

在这里插入图片描述

4.Handler原理 视频讲解

博主原声讲解-Handler原理(多多关照哈哈)

Handler不仅可以实现主线程和子线程的切换,也可以实现子线程与子线程之间的切换

案例实战-倒计时 发送消息

常见应用:例如 点击验证码发送以后需要间隔60秒才能再次发送

第一步,在xml文件当中添加一个按钮控件
在这里插入图片描述
第二步,把Button定义为全局变量 ,并且实例化Button,并且添加点击事件,然后再定义一个变量

在这里插入图片描述
第三步,子线程发送消息,然后主线通过判断time,再每延时1秒发送一次消息
在这里插入图片描述

第四步,记得注释原来子线程 发送的消息
在这里插入图片描述
运行
在这里插入图片描述

5.关于Handler总结

1. Handler主要用来实现线程和线程之间的数据交互
2. CalledOnWrongThreadException异常都用Handler 来解决

小伙伴们,今天的内容就讲到这里啦,已经凌晨两点了,再动用了超级多的工具以后,终于把这篇博客写完了,还给你们录了个视频,讲的不好,多多指教,我会继续努力,谢谢您的阅读,晚安,玛卡巴卡。
在这里插入图片描述
Andrioid 入门第六讲04-网络请求第三方框架-xUtils(原生HTTP网络访问的缺点,xUtils简介,使用方法(网络请求访问,注解(布局文件+控件+点击事件)加载网络图片))

猜你喜欢

转载自blog.csdn.net/qq_46526828/article/details/107456378
今日推荐