Android线程需要学习什么东西?
先说答案:
什么是主线程,和怎么避免在主线程中执行耗时操作
1. 主线程(UI线程)理解主线程是如何工作的,以及为什么不应该在主线程中执行耗时操作,从而避免应用界面失去响应(ANR:Application Not Responding)。
事件循环:主线程运行着一个事件循环,通常称为消息循环(Message Loop)或事件分发循环(Event Dispatch Loop)。它负责从事件队列中逐个取出事件并处理它们。这些事件可以是用户的交互动作,也可以是系统的绘制请求等。
UI更新:所有的UI更新都必须在主线程中进行,因为Android的UI工具包不是线程安全的。如果尝试从非UI线程更新UI,程序会抛出异常。
响应性:要保持应用的响应性,主线程必须能够快速地处理事件。如果主线程被耗时的任务阻塞,它将无法及时处理其他事件,包括UI更新,导致界面卡顿或者触摸无响应。
ANR的产生:如果主线程在特定时间内(如5秒内)无法响应用户输入事件或者广播接收器(BroadcastReceiver),系统将会弹出“应用程序未响应”(ANR)的对话框。发生这种情况时,用户可以选择等待或者强制关闭应用。
为什么不应该在主线程中执行耗时操作:
阻塞UI渲染:所有的UI渲染动作都是在主线程中完成的,如果主线程被耗时操作阻塞,UI渲染就会停滞,界面就无法更新,用户体验大打折扣。
引发ANR:如上所述,如果主线程被长时间占用,无法处理输入事件或者其他关键操作,系统最终会显示ANR对话框。
违反设计原则:Android的设计原则之一是保持应用的响应性,因此所有的耗时操作都应该在后台线程中进行。
2.怎么避免在主线程中执行耗时操作:
使用Handler和Looper:
通过在后台线程中处理耗时逻辑,然后使用Handler将结果回传到主线程进行UI更新。
消息(Message):Handler可以发送和处理包含数据的Message对象。
可运行对象(Runnable):Handler也可以排队执行Runnable对象,即代码块。
消息队列(Message Queue):Handler关联的Looper拥有一个消息队列,它负责保持所有的消息(Message)和可运行对象(Runnable),直到它们被分发。
Looper: 每个Handler都需要绑定到一个线程的Looper。Looper是一个用于循环提取和分发消息队列中的消息的类。在Android中,主线程默认会有一个Looper。
主线程(UI线程)有一个默认的Looper,负责循环的从message queue中取出message或者runnable对象,取到handle中由handle处理,message/runnable是由handle发送的
handle和线程绑定 每个线程只有一个looper UI不用创建 其他线程需要创建和开启循环
handle可以sendmessage 或者post runnable
然后message或者runnable会加入messagequeue中 然后由looper由加入顺序一个一个提取到handle
// 创建一个子线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// 执行耗时操作
// 创建一个消息
Message message = Message.obtain();
message.what = 1; // 给消息设置一个整型值标识
message.obj = "任务完成"; // 可以放置任何需要传递的数据
// 发送消息到主线程的Handler
mainHandler.sendMessage(message);
}
});
// 主线程的Handler
Handler mainHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
if (msg.what == 1) {
// 在这里可以安全地更新UI
String result = (String) msg.obj;
textView.setText(result);
}
}
};
// 启动子线程
thread.start();
使用AsyncTask:
尽管已经被弃用,但AsyncTask仍是理解异步编程的一个好例子,它允许你在后台线程中执行操作,然后在主线程中更新UI。
使用Service:
对于不需要与用户互动的长时间运行的操作,可以使用服务(Service)。
使用IntentService(已弃用):
对于需要顺序执行的任务,可以使用IntentService,它会创建一个工作线程,并且所有任务都在该线程中逐个执行。
使用线程池:
通过ThreadPoolExecutor和Executors类创建线程池,管理后台任务的执行。
线程复用:线程池通过复用已经创建的线程来执行任务,避免了频繁创建和销毁线程的开销。
任务队列:线程池通常使用一个阻塞队列来存放待执行的任务。当所有的线程都忙于处理任务时,新提交的任务会被放入队列中等待,直到有线程变为可用。
动态线程管理:对于某些类型的线程池(如缓存线程池),线程池可以根据需要动态地调整线程的数量,增加新线程或者减少空闲线程。
核心线程和最大线程:线程池通常有两个关键参数:核心线程数和最大线程数。核心线程数是线程池希望保持活跃的线程数量,最大线程数是线程池允许创建的最大线程数量。
线程保活时间:对于超出核心线程数的线程,线程池会设置一个空闲保活时间。如果线程在这段时间内没有执行任务,则可能会被线程池回收。
任务调度和执行顺序:线程池不保证任务的执行顺序,除非是使用了特定类型的队列(如LinkedBlockingQueue)或者特定的线程池(如newSingleThreadExecutor)。一般情况下,任务的执行顺序取决于其在队列中的排列顺序以及线程池的调度策略。
线程池状态控制:线程池提供了方法来控制其状态,例如启动、关闭和终止。
使用协程(Kotlin):
如果你使用Kotlin编程,协程提供了一种更加强大和简洁的方式来处理异步任务和并发。
使用LiveData和ViewModel:
这些架构组件可帮助你以生命周期感知的方式管理UI相关数据和任务。
使用WorkManager:
对于需要确保执行,即使应用退出或设备重启也要完成的任务,可以使用WorkManager来安排这些任务。
在并发编程中,线程池是一种基于池化技术的线程使用模式,目的是减少在创建和销毁线程上的开销。Java提供了多种类型的线程池,主要通过`java.util.concurrent.ExecutorService`接口和它的实现类进行管理,主要类型包括:
1. **CachedThreadPool**
- 缓存线程池可以创建一个可根据需要创建新线程的线程池,但会在之前构造的线程可用时重用这些线程。
- 如果线程池当前没有可用的线程,缓存线程池就会创建新的线程。
- 如果线程池中的线程在60秒内没有被使用,那么它们可能会被终止并从缓存中移除。
2. **FixedThreadPool**
- 固定大小线程池可以重新使用固定数量的线程操作。
- 固定线程池的核心和最大线程数是相同的。
- 固定线程池使用的是无界队列来存放待执行的任务。
3. **SingleThreadExecutor**
- 单线程化的线程池有且只有一个工作线程执行任务,所有任务保证按照任务队列的顺序(FIFO, LIFO, 优先级)执行。
4. **ScheduledThreadPool**
- 调度线程池可以延迟或定时地执行任务。
- 调度线程池的核心线程数是固定的,而非核心线程数(如果需要)可以是无限的。
- 任务可以周期性重复执行。
5. **WorkStealingPool**
- 这是Java 8引入的基于Fork/Join框架,它使用了工作窃取算法来提高线程间的工作效率。
- 线程会尝试查找和执行其他线程创建的未执行的子任务。
6. **SingleThreadScheduledExecutor**
- 这是ScheduledThreadPool的特例,它保证所有定时任务都在同一个线程中执行。
每种类型的线程池都有它自己的优势和用途。例如,CachedThreadPool适用于有大量短暂异步任务的场景,FixedThreadPool适用于负载比较重的服务器,而SingleThreadExecutor则适用于需要保证任务顺序执行的场景。
使用线程池的时候,重要的是要根据具体的应用场景选择最适合的线程池类型,并合理配置线程池的参数,以此来达到最优的性能表现。
网友评论