多线程可以让你同时异步执行多种任务,是各种编程语言里很重要的一个概念。合理的采用多线程可以让你的 App 拥有更好的运行性能,但是如果使用不当可能会让你的程序非常混乱,出现很多令人费解且难以定位的问题。
当用户打开一个 App 时,Android 系统会创建一个 Linux 进程,同时在进程中创建一个执行线程,我们称之为“主线程”,因为 Andfoid 规定只能在主线程更新 UI,所以又叫“UI线程”。
系统在创建主线程的时候帮我们创建好了一套消息处理机制,包含了第 38 节提到的 Handler、Looper、MessageQueue 等模块,主线程就利用这一套消息机制来实现 Actvity、Fragment 的生命周期回调以及和其他 App 之间的通信。所有需要在 UI 线程执行的任务都要首先被 push 到任务队列中,然后等待主线程 Looper 来轮询。如果我们将所有的任务都放到主线程的任务队列,那么可能需要等很久才能执行到,所以一个比较好的选择就是将耗时任务单独放到一个子线程中,这样就可以独享一个 MessageQuene,并且不再占用主线程的资源。
接下来我们来看看具体怎么使用 Android 多线程。
Java 虚拟机支持多线程并发编程,并发意味着同时执行多个任务。在 Android 中常见的多线程常见就是在子线程执行耗时操作,然后将结果通过线程间通信传递给主线程,主线程仅仅拿到结果进行 UI 的刷新。
我们有两种方式进行线程的创建
Thread
实现一个线程类:class TestThread extends Thread {
@Override
public void run() {
Log.d("Threading", "继承 Thread 的线程:"+Thread.currentThread().getName());
}
}
Runnable
接口class TestRunnable implements Runnable {
@Override
public void run() {
Log.d("Runable", "实现 Runable 的线程>"+Thread.currentThread().getName());
}
}
无论是哪种方式,都需要在类中实现一个无参的run()
方法,然后将线程的实际执行任务放在run()
方法中,在要用多线程的类中需要创建出一个Runnable
接口实例。
对于第一种创建方式,直接创建 TestThread 实例调用start()
即可:
new TestThread().start()
而对于第二种方式,在创建 Thread 的同时传入 Runnable 接口实例,然后调用start()
:
new Thread(new TestRunnable()).start()
调用了 Thread 对象的 start()
之后,run()
方法就会在我们的子线程中执行了。
和 Activity 一样,Thread 在执行过程中也有自己的生命周期,一共有 5 种状态:
start()
,等待 CPU 分配时间片各个生命周期的切换如下图:
本节创建两个耗时子线程,在线程的开始和结束分别打上日志,然后观察两个线程任务是同时执行,还是需要等待其中一个线程的耗时任务执行结束才能执行第二个。
MainActivity 代码很简单,在里面创建两个线程,为了方便演示我们用“继承自 Thread”和“实现 Runnable”两种方式来创建两个线程:
package com.emercy.myapplication;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.util.Random;
import static android.Manifest.permission.RECORD_AUDIO;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
public class MainActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new Thread() {
@Override
public void run() {
Log.d("ThreadTest", "Thread1 start");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d("ThreadTest", "Thread1 end");
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
Log.d("ThreadTest", "Thread2 start");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d("ThreadTest", "Thread2 end");
}
}).start();
}
private void task() {
for (int i = 0; i < 10; i++) {
Log.d("Thread", Thread.currentThread().getName() + " 当前i = " + i);
}
}
}
在两个线程中通过sleep()
来模拟 500 毫秒的耗时任务,在任务的开始和结束都打上日志,观察结果如下:
可以看到首先会同时开启两个子线程,然后分别同时执行 500 毫秒的任务,在执行结束打上结束的 Log,可以证明两个 Thread 是同时执行的。
本节学习了一个能让你的 App 并发高效执行任务的方式,多线程可以帮助你提升 App 的整体性能,但用之不当可能会造成一定的资源浪费,所以一定要谨记本节所提到的注意事项。然后按照步骤去创建、运行子线程,了解线程执行的生命周期,让程序更好的为用户服务。
0/1000