
本文旨在解决android应用中,自定义线程内多个周期性后台任务相互阻塞的问题。通过分析`asynctask`在此场景下的局限性,并提出一种直接创建独立线程执行耗时操作的解决方案,确保各任务并行运行,维持精确的调度时序,避免ui线程阻塞,提升应用响应性。
在Android应用开发中,经常需要执行一些周期性的后台任务,例如数据同步、状态检查或图片上传等。这些任务通常不应阻塞用户界面(UI)线程,以保证应用的流畅性和响应性。开发者往往会选择创建独立的线程来处理这些耗时操作。然而,不恰当的线程管理方式可能导致任务之间相互干扰,无法按预期精确调度。
考虑一个场景:在一个自定义线程中,需要每10秒执行一次数据处理任务(doWork1()),每100秒执行一次图片上传任务(imgUpload()),并每秒检查GPS状态。开发者可能尝试使用Thread.sleep()在自定义线程中进行计时和调度,并利用AsyncTask来执行具体的耗时操作,代码结构可能如下:
public class MyService { // 假设这是一个Service或某个管理类
private static final String TAG = "MyService";
private int cntUpdate; // doWork1的计时器
private int cntUpload; // imgUpload的计时器
private int cntLosingGps; // GPS丢失的计时器
private Thread mySchedulerThread;
public void startTasks() {
if (mySchedulerThread == null || !mySchedulerThread.isAlive()) {
mySchedulerThread = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
Thread.sleep(1000); // 每秒检查一次
// 任务1: doWork1,每10秒执行
cntUpdate++;
if (cntUpdate >= 10) {
doWork1(); // 调用AsyncTask
cntUpdate = 0;
}
// 任务2: imgUpload,每100秒执行
cntUpload++;
if (cntUpload >= 100) {
imgUpload(); // 调用AsyncTask
cntUpload = 0;
}
// 任务3: GPS状态检查,每秒检查,如果连续丢失500秒则执行doSomething
if (isGpsLosing()) {
cntLosingGps++;
if (cntLosingGps >= 500) {
doSomething();
cntLosingGps = 0;
}
} else {
cntLosingGps = 0; // GPS恢复则重置计时
}
}
} catch (InterruptedException e) {
Log.d(TAG, "调度线程被中断: " + e.getMessage());
Thread.currentThread().interrupt(); // 重新设置中断标志
}
});
mySchedulerThread.start();
}
}
public void stopTasks() {
if (mySchedulerThread != null) {
mySchedulerThread.interrupt();
mySchedulerThread = null;
}
}
// 假设这些是耗时操作,原先使用AsyncTask实现
private void doWork1() {
// new AsyncWork1().execute(); // 原始实现
Log.d(TAG, "doWork1 触发");
// 实际的数据库操作或计算
// databaseWork();
}
private void imgUpload() {
// new UpLoadImg().execute(); // 原始实现
Log.d(TAG, "imgUpload 触发");
// 实际的图片上传操作
// sendImgtoServer();
}
private boolean isGpsLosing() {
// 模拟GPS状态检查
return System.currentTimeMillis() % 20000 < 5000; // 模拟每20秒有5秒丢失
}
private void doSomething() {
Log.d(TAG, "GPS丢失超过500秒,执行doSomething");
// 执行一些处理
}
// 假设的耗时方法
private void databaseWork() {
try {
Log.d(TAG, "执行数据库操作...");
Thread.sleep(2000); // 模拟耗时2秒
Log.d(TAG, "数据库操作完成。");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void sendImgtoServer() {
try {
Log.d(TAG, "上传图片到服务器...");
Thread.sleep(30000); // 模拟耗时30秒
Log.d(TAG, "图片上传完成。");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}在上述实现中,观察到的问题是:当imgUpload()任务被触发并执行时,doWork1()的调度会变得不规律。具体表现为imgUpload()执行期间,doWork1()停止按10秒间隔触发,但在imgUpload()完成后,doWork1()会连续快速触发多次(例如1-2秒内多次),然后才恢复正常的10秒间隔。这表明尽管AsyncTask的doInBackground方法在后台线程池中执行,但AsyncTask.execute()的调用本身,或者AsyncTask的内部机制,对mySchedulerThread的循环造成了某种程度的阻塞或延迟,导致其无法精确地按时调度后续任务。
AsyncTask是Android提供的一个轻量级异步操作工具,它旨在简化UI线程和后台线程之间的通信。然而,它有几个关键特性需要注意:
为了确保mySchedulerThread能够精确地维持其1秒的调度循环,最直接有效的方法是彻底解耦耗时操作。这意味着,当需要执行databaseWork()或sendImgtoServer()这类长时间运行的任务时,不应依赖AsyncTask,而是直接为每个耗时操作创建一个全新的、独立的Thread。这样,mySchedulerThread在触发这些操作时,仅仅是启动了一个新线程,这个过程是极其轻量且非阻塞的,从而保证了其自身的计时和调度不受任何干扰。
改进后的doWork1()和imgUpload()方法:
public class MyService {
// ... (其他成员变量和startTasks/stopTasks方法不变)
private void doWork1() {
// 直接在新线程中执行数据库操作
new Thread(() -> {
databaseWork();
}).start();
}
private void imgUpload() {
// 直接在新线程中执行图片上传
new Thread(() -> {
sendImgtoServer();
}).start();
}
// ... (databaseWork(), sendImgtoServer(), isGpsLosing(), doSomething() 方法不变)
}代码解析:
结合上述改进,MyService类的完整实现将确保所有周期性任务都能按预期精确调度:
import android.util.Log;
public class MyService { // 假设这是一个Service或某个管理类
private static final String TAG = "MyService";
private int cntUpdate; // doWork1的计时器
private int cntUpload; // imgUpload的计时器
private int cntLosingGps; // GPS丢失的计时器
private Thread mySchedulerThread;
public void startTasks() {
if (mySchedulerThread == null || !mySchedulerThread.isAlive()) {
cntUpdate = 0; // 初始化计时器
cntUpload = 0;
cntLosingGps = 0;
mySchedulerThread = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
Thread.sleep(1000); // 每秒检查一次
// 任务1: doWork1,每10秒执行
cntUpdate++;
if (cntUpdate >= 10) {
doWork1(); // 调用改进后的方法
cntUpdate = 0;
}
// 任务2: imgUpload,每100秒执行
cntUpload++;
if (cntUpload >= 100) {
imgUpload(); // 调用改进后的方法
cntUpload = 0;
}
// 任务3: GPS状态检查,每秒检查,如果连续丢失500秒则执行doSomething
if (isGpsLosing()) {
cntLosingGps++;
if (cntLosingGps >= 500) {
doSomething();
cntLosingGps = 0;
}
} else {
cntLosingGps = 0; // GPS恢复则重置计时
}
}
} catch (InterruptedException e) {
Log.d(TAG, "调度线程被中断: " + e.getMessage());
Thread.currentThread().interrupt(); // 重新设置中断标志
}
}, "MySchedulerThread"); // 给线程命名,方便调试
mySchedulerThread.start();
Log.d(TAG, "调度线程已启动。");
}
}
public void stopTasks() {
if (mySchedulerThread != null) {
mySchedulerThread.interrupt();
try {
mySchedulerThread.join(2000); // 等待线程结束,最多2秒
} catch (InterruptedException e) {
Log.w(TAG, "停止线程时被中断: " + e.getMessage());
Thread.currentThread().interrupt();
}
mySchedulerThread = null;
Log.d(TAG, "调度线程已停止。");
}
}
// 改进后的doWork1方法
private void doWork1() {
new Thread(() -> {
Log.d(TAG, "doWork1 触发,开始执行数据库操作...");
databaseWork(); // 实际的数据库操作或计算
Log.d(TAG, "doWork1 任务完成。");
}, "DatabaseWorkThread").start(); // 给线程命名
}
// 改进后的imgUpload方法
private void imgUpload() {
new Thread(() -> {
Log.d(TAG, "imgUpload 触发,开始上传图片到服务器...");
sendImgtoServer(); // 实际的图片上传操作
Log.d(TAG, "imgUpload 任务完成。");
}, "ImageUploadThread").start(); // 给线程命名
}
// 模拟GPS状态检查
private boolean isGpsLosing() {
// 模拟每20秒有5秒丢失GPS信号
return (System.currentTimeMillis() / 1000) % 20 < 5;
}
private void doSomething() {
Log.d(TAG, "GPS丢失超过500秒,执行doSomething。");
// 执行一些处理
}
// 假设的耗时方法
private void databaseWork() {
try {
// Log.d(TAG, "执行数据库操作..."); // 已移至doWork1方法中
Thread.sleep(2000); // 模拟耗时2秒
// Log.d(TAG, "数据库操作完成。"); // 已移至doWork1方法中
} catch (InterruptedException e) {
Log.e(TAG, "数据库操作被中断: " + e.getMessage());
Thread.currentThread().interrupt();
}
}
private void sendImgtoServer() {
try {
// Log.d(TAG, "上传图片到服务器..."); // 已移至imgUpload方法中
Thread.sleep(30000); // 模拟耗时30秒
// Log.d(TAG, "图片上传完成。"); // 已移至imgUpload方法中
} catch (InterruptedException e) {
Log.e(TAG, "图片上传被中断: " + e.getMessage());
Thread.currentThread().interrupt();
}
}
}// 示例:在后台线程中更新UI
new Thread(() -> {
// 执行耗时操作...
String result = "数据已处理";
// 切换到UI线程更新界面
new Handler(Looper.getMainLooper()).post(() -> {
// myTextView.setText(result);
Log.d(TAG, "UI已更新: " + result);
});
}).start();ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
// 执行任务
Log.d(TAG, "ScheduledExecutorService: 每5秒执行一次。");
}, 0, 5, TimeUnit.SECONDS);
// 停止调度
// scheduler.shutdown();Handler handler = new Handler(Looper.getMainLooper()); // 在主线程调度
Runnable periodicTask = new Runnable() {
@Override
public void run() {
// 执行任务
Log.d(TAG, "Handler.postDelayed: 每3秒执行一次。");
handler.postDelayed(this, 3000); // 再次调度自己
}
};
handler.postDelayed(periodicTask, 3000);
// 停止调度
// handler.removeCallbacks(periodicTask);在Android中实现精确的周期性后台任务调度,关键在于确保调度线程不被任何耗时操作阻塞。当遇到AsyncTask导致调度
以上就是Android中实现非阻塞周期性后台任务的正确姿势的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号