Android多线程

序言:线程和进程

1、进程

进程是指运行中的程序,比如我们使用QQ,就启动该进程分配内存空间。

进程是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。

2、线程

线程是由进程创建的,是进程的一个实体。

一个进程可以拥有多个线程,一个线程还可以创建它的子线程。

3、其他概念

单线程:同时允许执行一个线程

多线程:同一个时刻,可以执行多个线程。比如:QQ可以打开多个聊天窗口;一个迅雷进程,可以同时下载多个文件。

并发:同一个时刻,多个任务交替执行,造成“貌似同时”的错觉,简单的说,单核CPU实现的多任务就是并发。

并行:同一个时刻,多个任务同时执行。多核CPU可以同时执行。

也可能出现:“并行和并发”同时存在的情况。

进程(Process)是一个程序的一个运行实例,“一个程序运行可以做很多事”;而线程则是CPU调度的基本单位,可以理解为“程序执行过程中做的某一件事”。

我们知道,一个应用程序的主入口一般都是main函数,这基本上成了程序开发的一种规范——它是“一切事物的起源”。

而main()函数的工作也是前篇一律:

  • 初始化 (比如创建对象、申请资源等)
  • 进入死循环 (在循环中处理各种事件,直到进程退出)

这种模型是“以事件为驱动”的软件系统的必然结果,因此几乎存在于任何操作系统和编程语言中。

对于Android应用开发者而言,通常面对的都是Activity、Service等组件,并不需要特别关心进程是什么。

因而产生了一些误区,如部分开发者认为系统四大组件就是进程的载体。

正确的观点是:

(1)四大组件并不是程序(进程)的全部,只能算作进程的组成部分,只是程序的零件。

(2)应用程序启动后,将创建ActivityThread主线程。

(3)一个Activity应用启动后至少会有3个线程:主线程 + 两个Binder线程。

Android多线程

实现方式有很多种:

1、基础使用

  • 继承Thread类
  • 实现Runnable接口
  • Handler

2、复合使用

  • AsyncTask
  • HandlerThread
  • IntentService

3、高级使用

  • 线程池(ThreadPool)

一、基础使用

Android多线程实现的基础使用包括:

  • 继承Thread类
  • 实现Runnable接口
  • Handler

1、继承Thread类

应用场景:创建两个线程,实现两个不同的耗时任务。

应用说明:实现2个窗口同时售卖火车票,每个窗口各卖100张,但卖票速度不同:窗口1是1s/张,窗口2是3s/张。

具体实现:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:id="@+id/btnTicketByThread"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="两个窗口各售票100张" />

</LinearLayout>

MainActivity.java

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "MainActivity";
    private Button btnTicketByThread;

    //步骤1:创建线程类,继承自Thread类
    //因为这里需要有两个操作:一个窗口卖票速度是1s/张,一个窗口是3s/张
    //所以需要创建两个Thread的子类
    //第一个Thread子类实现一个窗口卖票速度是1s/张
    private class MyThread1 extends Thread {
        private int ticket = 100; //一个窗口有100张票
        private String name; //窗口名, 也是线程的名字

        public MyThread1(String name) {
            this.name = name;
        }

        //在run方法里复写需要进行的操作:卖票速度是1s/张
        @Override
        public void run() {
            while (ticket > 0) {
                ticket--;
                Log.d(TAG, name + "卖掉了1张票,剩余票数为:" + ticket);
          try {
                    Thread.sleep(1000); //卖票速度是1s一张
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //第二个Thread子类实现一个窗口卖票速度是3s/张
    private class MyThread2 extends Thread {
        private int ticket = 100; //一个窗口有100张票
        private String name; //窗口名, 也即是线程的名字

        public MyThread2(String name) {
            this.name = name;
        }

        //在run方法里复写需要进行的操作:卖票速度是3s/张
        @Override
        public void run() {
            while (ticket > 0) {
                ticket--;
                Log.d(TAG, name + "卖掉了1张票,剩余票数为:" + ticket);
          try {
                    Thread.sleep(3000); //卖票速度是1s一张
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnTicketByThread = findViewById(R.id.btnTicketByThread);
        btnTicketByThread.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btnTicketByThread:
                //步骤2:创建线程类的实例
                //创建两个线程,模拟两个窗口卖票
                MyThread1 mt1 = new MyThread1("窗口1");
                MyThread2 mt2 = new MyThread2("窗口2");
                //步骤3:调用start()方法开启线程
                //启动两个线程,也即是窗口,开始卖票
                mt1.start();
                mt2.start();
                break;
        }
    }
}

运行结果:

由于卖票速度不同,所以窗口1卖3张时,窗口2才卖1张。

窗口1卖完了,后面只剩窗口2在卖票。

与“实现Runnable接口”对比

在Java中,继承Thread类和实现Runnable接口是实现多线程最常用的2种方法。

对比下这两种方法:

 

2、实现Runnable接口

特别注意:

  • Java中真正能创建新线程的只有Thread类对象。
  • 通过实现Runnable的方式,最终还是通过Thread类对象来创建线程。
  • 所以对于实现了Runnable接口的类,称为线程辅助类;Thread类才是真正的线程类。

应用场景:创建两个线程,实现一个耗时任务

应用说明:实现2个窗口同时卖火车票;两个窗口一共卖100张,卖票速度均为1s/张。

具体实现:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:id="@+id/btnTicketByThread"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="两个窗口各售票100张" />

    <Button
        android:id="@+id/btnTicketByRunnable"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="两个窗口共售票100张" />
</LinearLayout>

MainActivity.java

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "MainActivity";
    private Button btnTicketByThread, btnTicketByRunnable;

    //步骤1:创建线程类,继承自Thread类
    //因为这里需要有两个操作:一个窗口卖票速度是1s/张,一个窗口是3s/张
    //所以需要创建两个Thread的子类
    //第一个Thread子类实现一个窗口卖票速度是1s/张
    private class MyThread1 extends Thread {
        private int ticket = 100; //一个窗口有100张票
        private String name; //窗口名, 也是线程的名字

        public MyThread1(String name) {
            this.name = name;
        }

        //在run方法里复写需要进行的操作:卖票速度是1s/张
        @Override
        public void run() {
            while (ticket > 0) {
                ticket--;
                Log.d(TAG, name + "卖掉了1张票,剩余票数为:" + ticket);
                try {
                    Thread.sleep(1000); //卖票速度是1s一张
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //第二个Thread子类实现一个窗口卖票速度是3s/张
    private class MyThread2 extends Thread {
        private int ticket = 100; //一个窗口有100张票
        private String name; //窗口名, 也即是线程的名字

        public MyThread2(String name) {
            this.name = name;
        }

        //在run方法里复写需要进行的操作:卖票速度是3s/张
        @Override
        public void run() {
            while (ticket > 0) {
                ticket--;
                Log.d(TAG, name + "卖掉了1张票,剩余票数为:" + ticket);
                try {
                    Thread.sleep(3000); //卖票速度是1s一张
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //步骤1:创建线程类,实现Runnable接口
    private class MyThread3 implements Runnable {
        private int ticket = 100; //两个窗口一共要卖100张票
        //在run方法里复写需要进行的操作:卖票速度1s/张
        @Override
        public void run() {
            while (ticket > 0) {
                ticket--;
                Log.d(TAG, Thread.currentThread().getName() + "卖掉了1张票,剩余票数为:" + ticket);
                try {
                    Thread.sleep(1000); //卖票速度是1s一张
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnTicketByThread = findViewById(R.id.btnTicketByThread);
        btnTicketByThread.setOnClickListener(this);
        btnTicketByRunnable = findViewById(R.id.btnTicketByRunnable);
        btnTicketByRunnable.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btnTicketByThread:
                //步骤2:创建线程类的实例
                //创建两个线程,模拟两个窗口卖票
                MyThread1 mt1 = new MyThread1("窗口1");
                MyThread2 mt2 = new MyThread2("窗口2");
                //步骤3:调用start()方法开启线程
                //启动两个线程,也即是窗口,开始卖票
                mt1.start();
                mt2.start();
                break;
            case R.id.btnTicketByRunnable:
                //步骤2:创建线程类的实例
                //因为是两个窗口共卖100张票,即共用资源
                //所以只实例化一个实现了Runnable接口的类
                MyThread3 mt3 = new MyThread3();
                //因为要创建两个线程,模拟两个窗口卖票
                Thread mt31 = new Thread(mt3, "窗口1");
                Thread mt32 = new Thread(mt3, "窗口2");
                //步骤3:调用start()方法开启线程
                //启动两个线程,也即是窗口,开始卖票
                mt31.start();
                mt32.start();
                break;
        }
    }
}

运行结果:

观察运行结果,会发现,有时候会出现售票错误,窗口1和2同时买出去同一张票或者卖出去票的顺序不对。

 

这个问题是因为不同线程之间抢占cpu资源,线程醒过来的时间不同,造成了访问共享数据ticket(剩余票数)错误。

解决这个问题,我们需要对共享数据加锁,将共享数据的代码块锁起来,使在任意时刻时,只能有一个线程来修改数据,保证访问共享数据的操作原子性。

如何加锁呢?最简单的就是使用synchronized关键字。

synchronized (共享数据对象) {

}

由于原先定义的ticket(剩余票数)是int型,不是引用类型,不是对象,所以我们要将ticket(剩余票数)的数据类型改为Integer类型。

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "MainActivity";
    private Button btnTicketByThread, btnTicketByRunnable;

    //步骤1:创建线程类,继承自Thread类
    //因为这里需要有两个操作:一个窗口卖票速度是1s/张,一个窗口是3s/张
    //所以需要创建两个Thread的子类
    //第一个Thread子类实现一个窗口卖票速度是1s/张
    private class MyThread1 extends Thread {
        private int ticket = 100; //一个窗口有100张票
        private String name; //窗口名, 也是线程的名字

        public MyThread1(String name) {
            this.name = name;
        }

        //在run方法里复写需要进行的操作:卖票速度是1s/张
        @Override
        public void run() {
            while (ticket > 0) {
                ticket--;
                Log.d(TAG, name + "卖掉了1张票,剩余票数为:" + ticket);
                try {
                    Thread.sleep(1000); //卖票速度是1s一张
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //第二个Thread子类实现一个窗口卖票速度是3s/张
    private class MyThread2 extends Thread {
        private int ticket = 100; //一个窗口有100张票
        private String name; //窗口名, 也即是线程的名字

        public MyThread2(String name) {
            this.name = name;
        }

        //在run方法里复写需要进行的操作:卖票速度是3s/张
        @Override
        public void run() {
            while (ticket > 0) {
                ticket--;
                Log.d(TAG, name + "卖掉了1张票,剩余票数为:" + ticket);
                try {
                    Thread.sleep(3000); //卖票速度是1s一张
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //步骤1:创建线程类,实现Runnable接口
    private class MyThread3 implements Runnable {
//        private int ticket = 100; //两个窗口一共要卖100张票
        private Integer ticket = 100; //两个窗口一共要卖100张票

        //在run方法里复写需要进行的操作:卖票速度1s/张
        @Override
        public void run() {
            while (ticket > 0) {
                synchronized (ticket) {
                  ticket--;
                  Log.d(TAG, Thread.currentThread().getName() + "卖掉了1张票,剩余票数为:" + ticket);
                  try {
                      Thread.sleep(1000); //卖票速度是1s一张
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                }
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnTicketByThread = findViewById(R.id.btnTicketByThread);
        btnTicketByThread.setOnClickListener(this);
        btnTicketByRunnable = findViewById(R.id.btnTicketByRunnable);
        btnTicketByRunnable.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btnTicketByThread:
                //步骤2:创建线程类的实例
                //创建两个线程,模拟两个窗口卖票
                MyThread1 mt1 = new MyThread1("窗口1");
                MyThread2 mt2 = new MyThread2("窗口2");
                //步骤3:调用start()方法开启线程
                //启动两个线程,也即是窗口,开始卖票
                mt1.start();
                mt2.start();
                break;
            case R.id.btnTicketByRunnable:
                //步骤2:创建线程类的实例
                //因为是两个窗口共卖100张票,即共用资源
                //所以只实例化一个实现了Runnable接口的类
                MyThread3 mt3 = new MyThread3();
                //因为要创建两个线程,模拟两个窗口卖票
                Thread mt31 = new Thread(mt3, "窗口1");
                Thread mt32 = new Thread(mt3, "窗口2");
                //步骤3:调用start()方法开启线程
                //启动两个线程,也即是窗口,开始卖票
                mt31.start();
                mt32.start();
                break;
        }
    }
}

运行结果:

我们可以多运行几次,观察输出。这样就可以正确实现了两个窗口一起卖100张票的目的。

3、Handler

(1)作用

在多线程的应用场景中,将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理。

(2)意义

问:为什么要用Handler消息传递机制?

答:多个线程并发更新UI的同时保证线程安全。

(3)相关概念

关于 Handler 异步通信机制中的相关概念如下:

在下面的讲解中,我将直接使用英文名讲解,即 Handler、Message、Message Queue、Looper。

 (4)使用方式

Handler的使用方式因发送消息到消息队列的方式不同而不同。

共分为2种:

  • Handler.sendMessage()
  • Handler.post()

方式1:Handler.sendMessage()

在该使用方式中,又分为2种:新建Handler子类(内部类)、匿名 Handler子类,但本质相同,即继承了Handler类 和 创建了子类 。

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

public class HandlerActivity extends AppCompatActivity {
    /**
     * 方式1:新建Handler子类(内部类)
     */
    // 步骤1:在主线程中,自定义Handler子类(继承Handler类),并重写handleMessage()方法
//    class MyHandler extends Handler {
//        // 通过重写handlerMessage()从而确定更新UI的操作
//        @Override
//        public void handleMessage(@NonNull Message msg) {
//            super.handleMessage(msg);
//            switch (msg.what) {
//                case 1:
//                    // 需执行的UI操作
//                    break;
//                default:
//                    // 需执行的UI操作
//                    break;
//            }
//        }
//    }
    // 在主线程中,创建Handler实例
//    private Handler myHandler = new MyHandler();

    /**
     * 方式2:匿名内部类
     */
    // 步骤1:在主线程中,通过匿名内部类,创建Handler类对象
    private Handler myHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
          case 1:
                    // 需执行的UI操作
                    break;
                default:
                    // 需执行的UI操作
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        new Thread(new Runnable() { // 多线程可采用AsyncTask、继承Thread类、实现Runnable
            @Override
            public void run() {
                // 步骤2:创建所需的消息对象
                Message msg = Message.obtain(); // 实例化消息对象
                msg.what = 1; // 消息标识
                msg.obj = "OK"; // 消息内容存放

                // 步骤3:在子线程中,通过Handler发送消息到消息队列中
                myHandler.sendMessage(msg);
            }
        }).start(); // 步骤4:启动子线程(同时启动了Handler)
    }
}

方式2:Handler.post()

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class HandlerActivity extends AppCompatActivity {
    // 步骤1:在主线程中创建Handler实例
    private Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        // 步骤2:在子线程中,发送消息到消息队列中,并指定操作UI内容
        // 需传入1个Runnable对象
        new Thread() {
            @Override
            public void run() {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        // 需执行的UI操作
                    }
                });
            }
        }.start();
    }
}

(5)实例讲解

注:

  • 由于Handler的作用 = 将工作线程需操作UI的消息 传递 到主线程,使得主线程可根据工作线程的需求
  • 更新UI,从而避免线程操作不安全的问题 故下文的实例 = 1个简单 “更新UI操作” 的案例
  • 主布局文件相同 = 1个用于展示的TextView,具体如下:

activity_handler.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btnSendMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Handler.sendMessage()" />

    <Button
        android:id="@+id/btnPost"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Handler.post()" />

    <TextView
        android:id="@+id/tvMsg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="消息" />
</LinearLayout>

① 使用 Handler.sendMessage()

方式1:新建Handler子类(内部类)具体使用 

HandlerActivity.java

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "HandlerActivity";
    private Button btnSendMessage, btnPost;
    private TextView tvMsg;

    /**
     * 方式1:新建Handler子类(内部类)
     */
    // 步骤1:在主线程中,自定义Handler子类(继承Handler类),并重写handleMessage()方法
    class MyHandler extends Handler {
        // 通过重写handlerMessage()从而确定更新UI的操作
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            // 根据不同线程发送过来的消息,执行不同的UI操作
            // 根据 Message对象的what属性 标识不同的消息
            switch (msg.what) {
                case 1:
                    Log.d(TAG, "handleMessage: " + msg.obj);
                    tvMsg.setText("执行了线程1的UI操作");
                    break;
                case 2:
                    Log.d(TAG, "handleMessage: " + msg.obj);
                    tvMsg.setText("执行了线程2的UI操作");
                    break;
            }
        }
    }

    // 在主线程中,创建Handler实例
    private Handler myHandler = new MyHandler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        tvMsg = findViewById(R.id.tvMsg);
        btnSendMessage = findViewById(R.id.btnSendMessage);
        btnSendMessage.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btnSendMessage:
                Log.d(TAG, "onClick: btnSendMessage");
                // 创建第1个子线程
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(3000); // 间隔3s
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        // 步骤2:创建所需的消息对象
                        Message msg = Message.obtain();
                        msg.what = 1; // 消息标识
                        msg.obj = "A"; // 消息内存存放

                        // 步骤3:在工作线程中 通过Handler发送消息到消息队列中
                        myHandler.sendMessage(msg);
                    }
                }.start(); // 步骤4:启动子线程(同时启动了Handler)

                // 此处再创建第2个子线程
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(6000); // 间隔6s
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Message msg = Message.obtain();
                        msg.what = 2; // 消息标识
                        msg.obj = "B"; // 消息内存存放
                        myHandler.sendMessage(msg);
                    }
                }.start(); // 启动子线程(同时启动了Handler)
                break;
        }
    }
}

方式2:匿名内部类具体使用

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "HandlerActivity";
    private Button btnSendMessage, btnPost;
    private TextView tvMsg;

    /**
     * 方式2:匿名内部类
     */
    // 步骤1:在主线程中,通过匿名内部类,创建Handler类对象
    private Handler myHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            // 根据不同线程发送过来的消息,执行不同的UI操作
            // 根据 Message对象的what属性 标识不同的消息
            switch (msg.what) {
                case 1:
                    Log.d(TAG, "handleMessage: " + msg.obj);
                    tvMsg.setText("执行了线程1的UI操作");
                    break;
                case 2:
                    Log.d(TAG, "handleMessage: " + msg.obj);
                    tvMsg.setText("执行了线程2的UI操作");
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        tvMsg = findViewById(R.id.tvMsg);
        btnSendMessage = findViewById(R.id.btnSendMessage);
        btnSendMessage.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btnSendMessage:
                Log.d(TAG, "onClick: btnSendMessage");
                // 创建第1个子线程
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(3000); // 间隔3s
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        // 步骤2:创建所需的消息对象
                        Message msg = Message.obtain();
                        msg.what = 1; // 消息标识
                        msg.obj = "A"; // 消息内存存放

                        // 步骤3:在工作线程中 通过Handler发送消息到消息队列中
                        myHandler.sendMessage(msg);
                    }
                }.start(); // 步骤4:启动子线程(同时启动了Handler)

                // 此处再创建第2个子线程
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(6000); // 间隔6s
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Message msg = Message.obtain();
                        msg.what = 2; // 消息标识
                        msg.obj = "B"; // 消息内存存放
                        myHandler.sendMessage(msg);
                    }
                }.start(); // 启动子线程(同时启动了Handler)
                break;
        }
    }
}

② 使用 Handler.post()

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "HandlerActivity";
    private Button btnSendMessage, btnPost;
    private TextView tvMsg;

    // 步骤1:在主线程中创建Handler实例
    private Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        tvMsg = findViewById(R.id.tvMsg);
        btnSendMessage = findViewById(R.id.btnSendMessage);
        btnSendMessage.setOnClickListener(this);
        btnPost = findViewById(R.id.btnPost);
        btnPost.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btnSendMessage:
                // ......
                break;
            case R.id.btnPost:
                Log.d(TAG, "onClick: btnPost");
                // 步骤2:在子线程中,发送消息到消息队列中,并指定操作UI内容
                // 需传入1个Runnable对象
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(3000); // 间隔3s
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // 通过psot()发送,需传入1个Runnable对象
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                // 指定操作UI内容
                                Log.d(TAG, "run: 执行了线程1的UI操作");
                                tvMsg.setText("执行了线程1的UI操作");
                            }
                        });
                    }
                }.start(); // 步骤3:开启子线程(同时启动了Handler)

                // 此处再创建第2个子线程
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(6000); // 间隔6s
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                Log.d(TAG, "run: 执行了线程2的UI操作");
                                tvMsg.setText("执行了线程2的UI操作");
                            }
                        });
                    }
                }.start();
                break;
        }
    }
}

二、复合使用

Android多线程实现的复合使用包括:

  • AsyncTask
  • HandlerThread
  • IntentService

称为”复合使用“的主要原因是:这3种方式的本质原理都是Android多线程基础实现(继承Thread类、实现Runnable接口、Handler)的组合实现。

1、AsyncTask

  • 一个Android 已封装好的轻量级异步类
  • 属于抽象类,即使用时需实现子类

AsyncTask的本质:AsyncTask的实现原理 = 线程池 + Handler

其中:线程池用于线程调度、复用 和 执行任务;Handler 用于异步通信。

使用步骤
AsyncTask的使用步骤有3个:

  • 创建 AsyncTask 子类,并根据需求实现核心方法
  • 创建 AsyncTask子类的实例对象(即 任务实例)
  • 手动调用execute()从而执行异步线程任务
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    /**
     * 步骤1:创建AsyncTask子类
     * 注:
     * a. 继承AsyncTask类
     * b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替
     * c. 根据需求,在AsyncTask子类内实现核心方法
     */
    private class MyTask extends AsyncTask<Params, Progress, Result> {
        // 方法1:onPreExecute()
        // 作用:执行子线程任务前的操作
        // 注:根据需求重写
        @Override
        protected void onPreExecute() {
            // 下载的准备工作
        }

        // 方法2:doInBackground()
        // 作用:接收输入参数、执行任务中的耗时操作、返回线程任务执行的结果
        // 注:必须重写,从而定义子线程任务
        @Override
        protected Result doInBackground(Params... params) {
            // 子线程中的,要异步执行完成的任务

            // 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()
            publishProgress(count);
            return null;
        }

        // 方法3:onProgressUpdate()
        // 作用:在主线程,显示线程任务执行的进度
        // 注:根据需求重写
        @Override
        protected void onProgressUpdate(Progress... progresses) {
            // 下载过程中,要更新进度的方法
        }

        // 方法4:onPostExecute()
        // 作用:接收子线程任务执行结果、将执行结果显示到UI组件
        // 注:必须重写,从而更新UI操作
        @Override
        protected void onPostExecute(Result result) {
            // UI操作,下载完成后,子线程给主线程提交结果的方法
        }

        // 方法5:onCancelled()
        // 作用:将异步任务设置为”取消“状态
        @Override
        protected void onCancelled() {
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        /**
         * 步骤2:创建AsyncTask子类的实例对象(即 任务实例)
         * 注:AsyncTask子类的实例必须在UI线程中创建
         */
        MyTask mTask = new MyTask();

        /**
         * 步骤3:手动调用execute(Params... params) 从而执行异步线程任务
         * 注:
         *    a. 必须在UI线程中调用
         *    b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常
         *    c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute()
         *    d. 不能手动调用上述方法
         */
        mTask.execute();
    }
}

实例讲解

(1) 实例说明

点击按钮 则 开启线程执行线程任务

显示后台加载进度

加载完毕后更新UI组件

期间若点击取消按钮,则取消加载

(2) 具体实现

activity_asynctask.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context=".AsyncTaskActivity">

    <Button
        android:id="@+id/btnStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始加载" />

    <TextView
        android:id="@+id/tvMessage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="还没开始加载!" />

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:max="100"
        android:progress="0" />

    <Button
        android:id="@+id/btnCancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="取消" />
</LinearLayout>

AsyncTaskActivity.java

package com.sdbi.threadtest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class AsyncTaskActivity extends AppCompatActivity {
    // 线程变量
    private MyTask mTask;
    // 主布局中的UI组件
    private Button btnStart, btnCancel; // 加载、取消按钮
    private TextView tvMessage; // 更新的UI组件
    private ProgressBar progressBar; // 进度条

    /**
     * 步骤1:创建AsyncTask子类
     * 注:
     * a. 继承AsyncTask类
     * b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替
     * 此处指定为:输入参数 = String类型、执行进度 = Integer类型、执行结果 = String类型
     * c. 根据需求,在AsyncTask子类内实现核心方法
     */
    private class MyTask extends AsyncTask<String, Integer, String> {
        // 方法1:onPreExecute()
        // 作用:执行 线程任务前的操作
        @Override
        protected void onPreExecute() {
            tvMessage.setText("加载中...");
            // 执行前显示提示
        }

        // 方法2:doInBackground()
        // 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果
        // 此处通过计算从而模拟“加载进度”的情况
        @Override
        protected String doInBackground(String... params) {
            try {
                int count = 0;
                int length = 1;
                while (count < 99) {
                    count += length;
                    // 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()
                    publishProgress(count);
                    // 模拟耗时任务
                    Thread.sleep(50);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }

        // 方法3:onProgressUpdate()
        // 作用:在主线程 显示线程任务执行的进度
        @Override
        protected void onProgressUpdate(Integer... progresses) {
            progressBar.setProgress(progresses[0]);
            tvMessage.setText("loading..." + progresses[0] + "%");
        }

        // 方法4:onPostExecute()
        // 作用:接收线程任务执行结果、将执行结果显示到UI组件
        @Override
        protected void onPostExecute(String result) {
            // 执行完毕后,则更新UI
            tvMessage.setText("加载完毕");
        }

        // 方法5:onCancelled()
        // 作用:将异步任务设置为:取消状态
        @Override
        protected void onCancelled() {
            tvMessage.setText("已取消");
            progressBar.setProgress(0);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_asynctask);

        btnStart = findViewById(R.id.btnStart);
        btnCancel = findViewById(R.id.btnCancel);
        tvMessage = findViewById(R.id.tvMessage);
        progressBar = findViewById(R.id.progressBar);

        // 加载按钮按按下时,则启动AsyncTask
        // 任务完成后更新TextView的文本
        btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                /**
                 * 步骤2:创建AsyncTask子类的实例对象(即 任务实例)
                 * 注:AsyncTask子类的实例必须在UI线程中创建
                 */
                mTask = new MyTask();
                /**
                 * 步骤3:手动调用execute(Params... params) 从而执行异步线程任务
                 * 注:
                 *    a. 必须在UI线程中调用
                 *    b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常
                 *    c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute()
                 *    d. 不能手动调用上述方法
                 */
                mTask.execute();
            }
        });
        btnCancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 取消一个正在执行的任务,onCancelled方法将会被调用
                mTask.cancel(true);
            }
        });
    }
}

运行结果:

核心方法:

异步任务类的三个泛型参数  与  四个回调方法参数或返回值的关系:

 

方法执行顺序如下:

 

 

 

 

2、HandlerThread

HandlerThread的本质:继承Thread类 & 封装Handler类

HandlerThread的使用步骤分为5步:

 

 

 

3、IntentService

Android里的一个封装类,继承四大组件之一的Service

处理异步请求  和 实现多线程

使用场景
线程任务 需 按顺序、在后台执行。

使用步骤

  • 步骤1:定义 IntentService的子类,需复写onHandleIntent()方法
  • 步骤2:在Manifest.xml中注册服务
  • 步骤3:在Activity中开启Service服务

 

 

三、 高级使用

Android多线程的高级使用主要是线程池(ThreadPool)。

posted @ 2022-09-18 15:16  熊猫Panda先生  阅读(433)  评论(0编辑  收藏  举报