回调函数是否在主线程(UI线程)上发生?

18

有很多Android SDK API可以注册回调处理程序。以MediaPlayer为例,可以设置onCompletionListener回调函数。这些回调将从主(UI)线程调用吗?如果答案是“取决于情况”,那么我正在寻找一些常规规则,以确定哪些回调将从主线程调用而不是从其他线程调用。SDK文档似乎没有明确说明。(也许我错过了。)

这似乎很重要,因为如果我能保证主线程的回调,那么我可以跳过某些数据同步,在代码中不同位置共享的数据上。如果出于无知而被迫持悲观态度,那么我必须编写额外的同步块代码,并担心死锁、数据完整性和降低性能。

7个回答

24

如果有疑问,您可以使用 Log.i("TAG", Thread.currentThread().getName()); 然后查看结果 :)


3
为什么你期望这个实验的结果可以在未来的操作系统版本中重复?你将如何设计实验以引发可能需要在另一个线程进行调用的情况?你需要多少结果才能开始认为这可能是可推广的? - James Moore
@JamesMoore 你是什么意思? - Mateen Ulhaq

13

如果您创建一个通过AIDL公开的远程服务,则Android将在某些其他线程上调用您的代码 -- 这些AIDL方法将在Binder线程而不是主应用程序线程上调用。

但是,这只是个例外。正如其他人所指出的那样,绝大多数情况下都是在主应用程序线程上调用这些方法。


1
我尝试在我的服务中运行异步操作(使用RxAndroid),并使用AndroidSchedulers.mainThread()调用为此操作提供的客户端回调。在调试时,我可以确认回调在主应用程序线程中被调用。然而,在调用回调之后,在回调内部运行的代码(在客户端)不在主应用程序线程中。尽管如此,是否有可能保证我的服务客户端,他们的回调代码将在主应用程序线程中运行,因此他们不需要处理它? - Eido95
@Eido95:我建议你在Stack Overflow上提出一个单独的问题,你可以用[mcve]详细解释你的问题。 - CommonsWare

4

根据我的经验,这些回调函数总是在非UI线程上返回。你尝试过使用Activity.runOnUiThread()来确保你的代码在UI线程上运行吗?虽然这会导致性能下降,因为代码需要更长时间运行,但你可以避免一些与线程同步相关的常见问题。


我遇到了类似的问题。我的数据源类(定义在单独的模块中)通过回调(从DataSource中的AsyncTask调用)返回响应,但我没有从发起调用的地方收到任何调用。使用了使用 Looper.getMainLooper() 创建的处理程序,但问题仍然存在。 - Sahil Patel

3
一般来说,回调函数将在事件所在的线程上发生。如果您在非 UI 线程上注册回调并开始播放某些内容,则回调将在非 UI 线程上发生。但是,Android 不会自己在后台创建新线程。
所有与 UI 相关的事件都必须在 UI 线程上发生,因此您可以保证单击处理程序回调等将在 UI 线程上发生。
正如 Aaron C 指出的那样,您可以使用 Activity.runOnUiThread 强制在 UI 线程上执行操作。
此外,AsyncTask 在进行快速后台工作时非常有用,其中需要保证某些完成步骤在 UI 线程上进行。
编辑:来自评论的示例。
public void MyWorker {
   private OnCompleteListener onCompleteListener;

   public void setOnCompleteListener(OnCompleteListener onCompleteListener) {
     this.onCompleteListener = onCompleteListener;
   }

   public void doWork() {
     // do lots of work here
     onCompleteListener.onComplete();
   }
}

// somewhere in my Activity

public void onCreate() {
   final MyWorker worker = new MyWorker();
   worker.setOnCompleteListener(new OnCompleteListener() { ... });

   new Thread(new Runnable() {
       public void run() {
          worker.doWork();
       }
   }).start();
}

在这个例子中,onComplete“回调”将在非UI线程中运行。线程将在onComplete完成后退出。

我不明白你描述的非UI工作案例。似乎非UI线程不会像UI线程一样运行分派循环。也就是说,UI线程中有一些代码调用UI事件、视图渲染、注册回调的方法。但如果我只是创建一个Runnable扩展类的线程,我认为JVM只是启动线程并退出,没有Android SDK钩子将回调放在同一个线程上。也许你描述的是其他事情。 - Erik Hermansen
“回调”不一定涉及分派循环。假设您有一个执行复杂工作的类。它具有setOnCompleteListener函数和start函数。在某个非UI线程上,您为其提供了一个OnCompleteListener,然后调用start。它会离开并完成其工作,完成后将调用registeredOnCompleteListener.onComplete()。这将导致侦听器中的onComplete函数从对象正在运行的线程中被调用。 - Cheryl Simon
只是为了正确理解您的示例:在线程1上调用start()。在线程2中,在run()方法内部调用setOnCompleteListener()。onComplete方法将在线程1还是线程2中执行? - Erik Hermansen
不,没有“run”。抱歉,我认为您将我的随机示例与Java Runnable混淆了。在我的示例中,所有内容都在一个线程上执行,只是它不是主UI线程。实际上唯一的要点是没有涉及调度。您给类提供了一个侦听器的实现。当它感觉到需要时,它会调用侦听器上的方法。这些方法调用将在任何正在运行的线程上发生。 - Cheryl Simon
我认为,除非涉及调度机制,否则回调必须在与初始线程不同的单独线程上执行。为了具体化,假设我将setOnCompleteListener()和start调用放在扩展Runnable类中。在这种情况下,onCompleteListener()在哪个线程中被调用?它不能是在run()方法内部执行的线程,因为该线程将在run()退出后关闭。 - Erik Hermansen
显示剩余2条评论

1
另一个例外是在使用WebView时。当JavaScript调用Java函数时,此调用不会发生在主线程中。

0

在我处理位置API时,出现了类似的情况。正如我们所知,我们为位置服务提供回调,以便我们在获取位置后得到通知。因此,在这个回调中执行一些需要很长时间的操作,比如使用地理编码API。

之前我认为,这个回调会从其他线程中被调用,因此不会在主UI线程上执行。但是我发现事实并非如此。

因此,无论我在这个监听器中编写什么代码,都将在主线程上执行。

现在我不明白的是,他们是如何做到这一点的,是通过使用一个标准函数 runOnUIThread() 吗?


0

前几天我遇到了关于任务的问题。你可以查看this链接

默认情况下,附加到线程的监听器在应用程序主(UI)线程上运行。当附加监听器时,您还可以指定用于调度监听器的执行器。

// Create a new ThreadPoolExecutor with 2 threads for each processor on the
// device and a 60 second keep-alive time.
int numCores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(numCores * 2, numCores *2,
        60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

task.addOnCompleteListener(executor, new OnCompleteListener<AuthResult>() {
    @Override
    public void onComplete(@NonNull Task<AuthResult> task) {
        // ...
    }
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接