同步和异步的差异
同步:在发生某件事后什么也不做,直到该事件完成后,再继续进行
异步:在某件事发生后,可以在等待他完成的时候去处理其他事件,等到该事件发生完成后,再回过头来处理它。
异步操作的核心思想就是将耗时操作放到子线程中执行,避免阻塞主线程,从而保持界面的流畅性。
异步实现方法
Thread + Looper + handler
Android 提供了 Handler 机制来进行线程之间的通信,我们可以使用 Android 最基础的异步方式:Thread + Looper + handler 来进行异步任务
Handler mHandler = newHandler(){
@Override
publicvoid handleMessage(Message msg){
if(msg.what == 1){
textView.setText("Task Done!!");
}
}
};
mRunnable = new Runnable() {
@Override
publicvoid run() {
SystemClock.sleep(1000); // 耗时处理
mHandler.sendEmptyMessage(1);
}
};
private void startTask(){
new Thread(mRunnable).start();
}
优点:
- 操作简单,无学习成本。
缺点:
- 代码规范性较差,不易维护。
- 每次操作都会开启一个匿名线程,系统开销较大。
AsyncTask
AsyncTask是较为轻量级的异步类,封装了 FutureTask 的线程池、ArrayDeque 和 Handler 进行调度。AsyncTask 主要用于后台与界面持续交互。
当我们定义一个类来继承 AsyncTask 这个类的时候,我们需要为其指定3个泛型参数:
AsyncTask <Params, Progress, Result>
- Params: 这个泛型指定的是我们传递给异步任务执行时的参数的类型。
- Progress: 这个泛型指定的是我们的异步任务在执行的时候将执行的进度返回给UI线程的参数的类型。
- Result: 这个泛型指定的异步任务执行完后返回给UI线程的结果的类型。
- 我们在定义一个类继承 AsyncTask 类的时候,必须要指定好这三个泛型的类型,如果都不指定的话,则都将其写成 void。
使用 AsyncTask 的注意事项
- AsyncTask 的实例必须在 UI Thread 中创建。
- 只能在 UI 线程中调用 AsyncTask 的 execute 方法。
- AsyncTask 被重写的四个方法是系统自动调用的,不应手动调用。
- 每个 AsyncTask 只能被执行一次,多次执行会引发异常。
- AsyncTask 的四个方法,只有 doInBackground 方法是运行在其他线程中,其他三个方法都运行在 UI 线程中,也就说其他三个方法都可以进行 UI 的更新操作。
- AsyncTask 默认是串行执行,如果需要并行执行,使用接口 executeOnExecutor 方法。
优点:
-
结构清晰,使用简单,适合后台任务的交互。
-
异步线程的优先级已经被默认设置成了:THREAD_PRIORITY_BACKGROUND,不会与 UI 线程抢占资源。
缺点:
-
结构略复杂,代码较多。
-
每个 AsyncTask 只能被执行一次,多次调用会发生异常。
-
AsyncTask 在整个 Android 系统中维护一个线程池,有可能被其他进程的任务抢占而降低效率。
线程池
利用 Executors 的静态方法 newCachedThreadPool()、newFixedThreadPool()、newSingleThreadExecutor() 及重载形式实例化 ExecutorService 接口即得到线程池对象。
- 动态线程池 newCachedThreadPool():根据需求创建新线程的,需求多时,创建的就多,需求少时,JVM 自己会慢慢的释放掉多余的线程。
- 固定数量的线程池 newFixedThreadPool():内部有个任务阻塞队列,假设线程池里有2个线程,提交了4个任务,那么后两个任务就放在任务阻塞队列了,即使前2个任务 sleep 或者堵塞了,也不会执行后两个任务,除非前2个任务有执行完的。
- 单线程 newSingleThreadExecutor():单线程的线程池,这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去。
例子
下面代码中,新建了一个固定数量为4的线程池
@Database(entities = {AccountDataItem.class, AccountData.class},version = 6,exportSchema = false)
public abstract class AppRoomDataBase extends RoomDatabase {
private static volatile AppRoomDataBase INSTANCE;
public abstract AccountDao accountDao();
public abstract AccountListDao accountListDao();
public static final ExecutorService databaseWriteExecutor = Executors.newFixedThreadPool(4);
// 单例模式
public static AppRoomDataBase getDataBase(Context context){
if (INSTANCE == null) {
synchronized (AppRoomDataBase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.getApplicationContext(),
AppRoomDataBase.class,
"记账数据库"
)
.fallbackToDestructiveMigration()
.build();
}
}
}
return INSTANCE;
}
}
public void deleteAllAccountItem() {
appRoomDataBase.databaseWriteExecutor.execute(() -> accountDao.deleteAll());
}
优点:
- 线程的创建和销毁由线程池来维护,实现了线程的复用,从而减少了线程创建和销毁的开销。
- 适合执行大量异步任务,提高性能。
- 灵活性高,可以自由控制线程数量。
- 扩展性好,可以根据实际需要进行扩展。
缺点:
- 代码略显复杂。
- 线程池本身对系统资源有一定消耗。
- 当线程数过多时,线程之间的切换成本会有很大开销,从而使性能严重下降。
- 每个线程都会耗费至少 1040KB 内存,线程池的线程数量需要控制在一定范围内。
- 线程的优先级具有继承性,如果在 UI 线程中创建线程池,线程的默认优先级会和 UI 线程相同,从而对 UI 线程使用资源进行抢占。