java—在android中将后台线程的结果与ui线程进行通信的正确方法

iklwldmw  于 2021-07-06  发布在  Java
关注(0)|答案(3)|浏览(308)

这是我最困惑的主题之一。所以我的问题是,当这个结束的时候,什么是正确的方式来传递后台线程的结果呢?。
想象一下我想更新一些 TextView 我刚下载了一些信息。当我需要执行后台任务时,我可以使用以下三种方法:

异步任务

很容易使用,这个有 onPostExecute() 方法将结果直接返回到uithread,以便我可以使用回调接口或执行任何我想要的操作。我喜欢这门课,但不太赞成。

线程池执行器

这就是我在需要执行后台任务时实际使用的方法,这就是我的问题所在,也就是我必须将结果交给uithread的时候。我已经告诉自己 Looper 以及 Handler 课程和关于 mainLooper .
所以,当我需要返回一些结果时,我使用 runOnUiThread() 正如我所读到的,只要 Looper 然后发布我的 Runnable 去排队。
好吧,这是工作,我可以与主线程通信,但我发现它真的很难看,我相信有一个更优雅的方式来做它比填充我的所有代码“ runOnUiThread() “方法。另外,如果后台任务需要太多时间,可能用户已经更改了 Activity 或者 Fragment 当里面的代码 runOnUiThread() 会导致什么 Exceptions (我知道使用 LiveData 以及 MVVM 模式可以解决最后一个问题,但是我在一个遗留项目中工作,我不能重构所有的代码,所以我在使用clasical activity mvc模式)
那么,还有别的方法吗?你能举个例子吗?我真的找了很多,但什么也没找到。。。

协同程序

我实际上在一个遗留项目中工作,我必须使用java,所以不能使用kotlin coroutines ,但我发现它们很容易使用,而且功能强大。
任何帮助都将不胜感激!

rryofs0p

rryofs0p1#

从一开始(api1),android线程间的通信方式就一直是处理器。事实上 AsyncTask 只是线程池的 Package 器,它使用 Handler 同样为了与主线程通信,您可以 checkout 源代码并创建自己的 Package 器。 Handler 是非常低级的原语,我不会说使用 Handler 是丑陋的,但它肯定需要一些多线程编程的知识,使代码更冗长。正如您所提到的,会出现很多问题,比如任务完成时用户界面可能会消失,您必须自己处理。低级原语总是这样。
当您在寻找信誉良好的源代码时,下面是关于这个问题的正式文档—用纯java将结果从后台线程传递到主线程。
所以不幸的是没有其他更好的官方推荐的方法。当然,有很多像rxjava这样的java库构建在相同的原语之上,但提供了更高级别的抽象。

jv4diomz

jv4diomz2#

我个人使用asynctask的方式如下:
在我的活动或片段中设置broadcastreceiver
使用您选择的执行器,使用对象[]中的任何所需参数调用asynctask。
一旦asynctask完成了数据或结果的绑定,就发送一个包含此绑定的localbroadcast。
在我的片段或活动中,接收广播并处理结果。我从来没有对这个方法有过任何问题,我确实理解有些人对asynctask避而远之,但对于大多数目的和我所遇到的一切,这是一个简单而可靠的方法。

vktxenjb

vktxenjb3#

背景
在android中,当应用程序启动时,系统会为应用程序创建一个执行线程,称为主线程(也称为ui线程)。google介绍了主线程及其负责人,如下所示。
主线程有一个非常简单的设计:它唯一的任务是从线程安全的工作队列中获取并执行工作块,直到它的应用程序终止。这个框架从不同的地方生成这些工作块。这些位置包括与生命周期信息、用户事件(如输入)或来自其他应用程序和进程的事件相关联的回调。此外,app可以自己显式地将块排队,而无需使用框架。
应用程序执行的几乎所有代码块都与事件回调相关联,例如输入、布局膨胀或绘图。当某个事件触发时,事件发生的线程将事件从自身推送到主线程的消息队列中。然后主线程可以为事件提供服务。
当动画或屏幕更新发生时,系统尝试每16毫秒左右执行一个工作块(负责绘制屏幕),以便以每秒60帧的速度平滑渲染。为了使系统达到这个目标,ui/视图层次结构必须在主线程上更新。但是,当主线程的消息队列包含的任务太多或太长,主线程无法足够快地完成更新时,应用程序应该将此工作移动到工作线程。如果主线程不能在16ms内完成工作块的执行,用户可能会观察到挂接、滞后或ui对输入缺乏响应。如果主线程阻塞大约5秒钟,系统将显示应用程序无响应(anr)对话框,允许用户直接关闭应用程序。
要更新视图,必须在主线程上执行,如果尝试在后台线程中更新,系统将抛出 CalledFromWrongThreadException .
如何从后台线程更新主线程上的视图?
主线程有一个循环器和一个分配给它的消息队列。要更新视图,我们需要创建一个任务,然后将其放入messagequeue。为此,android提供了处理程序api,允许我们将任务发送到主线程的messagequeue,以便稍后执行。

// Create a handler that associated with Looper of the main thread
Handler mainHandler = new Handler(Looper.getMainLooper());

// Send a task to the MessageQueue of the main thread
mainHandler.post(new Runnable() {
    @Override
    public void run() {
        // Code will be executed on the main thread
    }
});

为了帮助开发者方便地从后台线程与主线程进行通信,android提供了几种方法:
activity.runonuithread(可运行)
view.post(可运行)
view.postdelayed(可运行,长)
在幕后,他们使用处理程序api来完成他们的工作。
回到你的问题上来
异步任务
这个类被设计成线程和处理程序的助手类。它负责:
创建线程或线程池以在后台执行任务
创建一个与主线程关联的处理程序,将任务发送到主线程的messagequeue。
它已从api级别30弃用
线程池执行器
在java中创建和处理线程有时很困难,如果开发人员不能正确地处理它,可能会导致很多错误。java提供了threadpoolexecutor来更有效地创建和管理线程。
此api不提供任何更新ui的方法。
Kotlin公司
coroutines是android上异步编程的解决方案,可以简化异步执行的代码。但它只对Kotlin有用。
所以我的问题是,当这个结束的时候,什么是正确的方式来传递后台线程的结果呢?。
1.使用处理程序或基于处理程序构建的机制
1.1. 如果线程与活动/片段绑定:
activity.runonuithread(可运行)
1.2. 如果线程有对视图的引用,例如适配器类。
view.post(可运行)
view.postdelayed(可运行,长)
1.3. 如果线程没有绑定到任何ui元素,那么就自己创建一个处理程序。

Handler mainHandler = new Handler(Looper.getMainLooper);

注意:使用处理程序的一个好处是可以使用它在线程之间进行两种方式的通信。这意味着从后台线程可以将任务发送到主线程的messagequeue,从主线程可以将任务发送到后台的messagequeue。
2.使用广播接收机
此api旨在允许android应用程序发送和接收来自android系统、应用程序内的其他应用程序或组件(活动、服务等)的广播消息,类似于publish-subscribe design partern。
因为broadcastreceiver.onreceive(context,intent)方法在默认情况下是在主线程内调用的。因此,您可以使用它来更新主线程上的ui。例如。
从后台线程发送数据。

// Send result from a background thread to the main thread
Intent intent = new Intent("ACTION_UPDATE_TEXT_VIEW");
intent.putExtra("text", "This is a test from a background thread");
getApplicationContext().sendBroadcast(intent);

从活动/片段接收数据

// Create a broadcast to receive message from the background thread
private BroadcastReceiver updateTextViewReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String text = intent.getStringExtra("text");
        myTextView.setText(text);
    }
};

@Override
protected void onStart() {
    super.onStart();
    // Start receiving the message
    registerReceiver(updateTextViewReceiver, new IntentFilter("ACTION_UPDATE_TEXT_VIEW"));
}

@Override
protected void onStop() {
    // Stop receving the message
    unregisterReceiver(updateTextViewReceiver);
    super.onStop();
}

这种方法通常用于android应用程序之间或android应用程序与系统之间的通信。实际上,您可以使用它在android应用程序中的组件之间进行通信,例如(活动、片段、服务、线程等),但它需要大量的代码。
如果您想要一个类似的解决方案,但代码较少,易于使用,那么您可以使用以下方法。
3.使用eventbus
eventbus是android和java的发布/订阅事件总线。如果要执行在主线程上运行的方法,只需用 @Subscribe(threadMode = ThreadMode.MAIN) 注解。

// Step 1. Define events
public class UpdateViewEvent {
    private String text;

    public UpdateViewEvent(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }
}

// Step 2. Prepare subscriber, usually inside activity/fragment
@Subscribe(threadMode = ThreadMode.MAIN)  
public void onMessageEvent(MessageEvent event) {
    myTextView.setText = event.getText();
};

// Step 3. Register subscriber
@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

// Step 4. Unregister subscriber
@Override
public void onStop() {
    super.onStop();
    EventBus.getDefault().unregister(this);
}

// Step 5. Post events from a background thread
UpdateViewEvent event = new UpdateViewEvent("new name");
EventBus.getDefault().post(event);

当用户可以看到活动/片段(他们正在与你的应用程序交互)时,如果你想更新视图,这很有用。

相关问题