【问题标题】:How can I make some class, running in the Main Thread, await concurrently for other class to reach some specific state?如何让某个类在主线程中运行,同时等待其他类达到某个特定状态?
【发布时间】:2019-02-25 18:49:26
【问题描述】:

我很难理解如何创建一些在主线程中声明和实例化的 AsyncTask 子级,以等待 Service 子级实例达到某个特定状态。

这里的代码示例是服务的相关部分;此代码执行预期的操作:接收 JSON 响应并保存它。

public class MyService extends Service {
    private boolean received = false;
    private string url = "http://someserver.mine/get-data-in-json-format";

    // [...]
    @Override
    public void onCreate() {
        doHttpJsonQuery();
    }

    public boolean responseReceived() {
        return this.received;
    }

    public List<MyModel> getResponseAsObject() {
        if (!this.received) return new ArrayList<MyModel>;

        // Many code lines that convert the data into a list.
        // [...]

        return the_list;
    }

    // [...]
    private void doHttpJsonQuery() {

        OkHttpClient client = new OkHttpClient.Builder()
                .build();

        Request request = new Request.Builder()
                .url(url)
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
                call.cancel();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String myResponse = response.body().string();
                //...and some code to hold data as JSONArray
                //[...]
            }
        });
        this.received = true;
    }
}

此服务有效;美好的。然后,从另一个类(其目的是处理将接收到的数据插入本地 Room 数据库的持久性),我尝试执行以下操作(这就是我大吃一惊的地方):

public class DataRepository {
    private MyRoomDatabase db;
    private MyModelDao mModelDao;
    // I'm skipping irrelevant code on purpose
    // [...]

    public DataRepository(Application application) {
        db = MyRoomDatabase.getDatabase(application);
        mModelDao = db.myModelDao();
        // [...]

        //  Here I instance a subclass of ContextWrapper(i named it RemoteDataSource) which
        // responsability will be handling different Services for making HTTP operations
        mRemoteDataSource = new RemoteDataSource(application.getApplicationContext());
        // It holds a reference to MyService. It has some public methods, like this one, to
        // control the referenced Service from outside with some encaspsulation
        mRemoteDataSource.startMyService();

        // Instantiating a private nested class...
        PopulateDbAsync mPopulateDbAsync = new PopulateDbAsync(db);
        mPopulateDbAsync.doInBackground();
    }
    // [...]

    // Here is the failing code
    private class PopulateDbAsync extends AsyncTask<Void, Void, Void> {
        PopulateDbAsync(MyRoomDatabase db) {}

        @Override
        protected Void doInBackground(final Void... params) {
            MyService mService = mRemoteDataSource.getMyService();
            if (mService == null) {
                // This doesn't happen at all right now...
                Log.e("MY_ERROR","DataRepository.PopulateDbAsync --> MyService from RemoteDataSource is NULL!!!!");
            }
            List<MyModel> the_list = mService.getResponseAsObject();
            if (the_list == null) {
                // HERE! I obtain the NullReferenceException here.
                // I am confused about how would I avoid this flaw in my code
                Log.e("MY_ERROR", "DataRepository.PopulateDbAsync --> error: response isn't ready yet.");
            }
            for (MyModel i_model : the_list) {
                Log.d("MY_LOG", "DataRepository.PopulateDbAsync --> Inserting data in local DB...");
                mModelDao.insert(i_model);
            }
            return null;
        }
    }
}

总结:我的问题是我在这一行总是会得到NullReferenceException

for (MyModel i_model : the_list) {

我不熟悉多线程、异步操作和并发执行。两个星期以来,我一直在阅读互联网上来自 Android 官方网站和其他网站的许多不同文档,试图弄清楚......“AsyncTask 不适合执行这种操作”.. . 所以,我应该实现什么基础设施,我一直在想......我应该使用处理程序、线程、信使还是什么?我读得越多,我就越困惑。这就像我有一个Analysis Paralysis 问题...

我在那里找到的大多数示例都提供了关于如何实现多线程和并发执行的过于冗长的代码示例;当我阅读它们并试图弄清楚如何在我的代码中实现这些结构时,我就卡住了;而且,有这么多的课程可供选择,我更加困惑......

由于需要异步执行 HTTP 调用(并且响应时间并不总是相同),我试图弄清楚如何制作为 MyService 抛出 NullReferenceException“等待”的代码在开始执行之前完成它的工作; while 循环将无法工作,因为它会破坏主线程的生命周期。 “知道”服务是否完成了它的任务就像使用布尔方法responseReceived 一样简单。重要的想法是,每次通过 HTTP 获取新数据时,都会用它更新 RoomDatabase,同时 MainActivity 会显示当前的本地数据(如果有的话,或者如果还没有的话,则显示一个空列表)。

所以,当我得到它时,我将了解如何正确重构整个代码结构以开始将更多 Service 子实例添加到我的 RemoteDataSource 类中,我创建该类的想法是让所有服务子代使用 OkHttp 执行 HTTP 通信,将它们封装在一个类中以便更好地组织。

什么是实现我正在寻找的正确方法?有人能提供一些简短的例子来解释我需要这样的代码结构吗?包含 cmets 的空块的示例,例如“在此处准备好时执行的代码”会很棒,所以我可以弄清楚。

这里暴露的问题与几周前让我发布this other question 的同一个项目有关;从那时起,我一直在这里和那里阅读,进行一些试验和错误并在这里和那里纠正一些代码问题;但是,我在这里提出了一个不同的问题;找到这个问题的答案可能也是找出另一个问题的答案的第一步。

对我一直在阅读的文档的 URL 引用

我一直在阅读的一些文档(但不限于):

【问题讨论】:

  • 当我有时间的时候,我会尝试编译我一直在阅读的不同网站以了解如何处理这个问题,并将它们的链接发布在问题更新中作为参考。
  • 据我了解,您真正尝试的是使用异步线程实现同步工作流。这是基本问题。是吗?如果我错了,请纠正我
  • 也许我的代码实现比我想象的更错误;我使用的一些代码块在阅读了它们之后是复制粘贴适应的,所以我尝试实现它们以查看它们是否按预期工作;正如我之前所说,我对 AsyncTasks、多线程和其他东西感到非常困惑。我将查看浏览器的历史记录,以编译我阅读的网站、标记和其他用于保存 URL 引用的方法;当我拥有它时,我会在问题更新中提供它们,为问题提供更多背景信息。

标签: java android multithreading okhttp android-room


【解决方案1】:

问题在于您的应用程序逻辑如下,

如果您使用的是AsyncTask,那显然是与主线程分开的线程。但是通过 HTTP 调用检索数据后同步到数据库是一个具有序列的过程(通过 HTTP 调用并检索 -> 然后持久化到数据库),它不能异步执行。所以当你打电话时,

List<MyModel> the_list = mService.getResponseAsObject();

这个调用发生在一个特定的线程中,而程序流在另一个线程中。 由于这些是异步任务,它们异步工作。这意味着您永远不会知道哪个将首先执行,哪个将是下一个。但按照你的逻辑,

if (the_list == null) {

这部分本质上需要the_list 初始化才能运行。但问题在于,服务线程还没有完成他的工作来执行你的下一个逻辑。所以它明显的断裂。

如果您可以重新设计它以便等待 HTTP 请求完成然后持久保存到数据库,那就更好了。因为假设如果您的 HTTP 请求首先完成,但它仍然返回您 null 或任何不需要的输出。所以在这种情况下,你需要在你的逻辑中处理它。

好的,让我告诉你一个快速的解决方法。 让我们只使用一个线程而不是不同的线程。所以考虑改变以下行

private class PopulateDbAsync extends AsyncTask<Void, Void, Void> {

private class PopulateDbAsync

那么你会得到一个错误

@Override
protected Void doInBackground(final Void... params) {

因为我们不再扩展 AsyncTask 类。

所以改变它如下,删除@Override

public Void doInBackground(final Void... params) {

这应该可以解决此处所述的问题。

【讨论】:

  • 感谢您的回答。正如我在问题中所说,我知道由于线程之间的同步问题,我得到了NullReference 异常。我还说我不熟悉多线程,这就是为什么我对要采取什么措施来“解决”这个问题感到困惑。我提供了一些我一直在阅读的文档的参考,也许这将有助于理解我现在的困惑,或者我是如何来到这个“代码混乱”的
  • 毕竟由于我对多线程不熟悉,所以这里贴出的代码是copy->paste->adapt->createlogs->trial_and_error的结果做是按预期工作。但是我发现的代码示例是如此冗长和复杂,以至于我很难弄清楚我应该如何在我的情况下实现它们。无论如何,我很感谢你的帮助。
  • 那么,您的意思是我应该在等待异步 HTTP 调用完成时以同步方式对数据库填充进行 pefmorm 吗?我不明白。 (在您更新之前发表评论,以备记录)
  • 我通过快速解决方法编辑了我的答案。请检查。
  • 好的。我还不明白,但我会做出改变,看看会发生什么......
【解决方案2】:

我找到了解决方案:creating custom listeners

创建自定义监听器的步骤

1.定义一个接口作为一个事件契约,其方法定义为 作为相关事件数据的事件和参数。

2。在子对象中设置监听器成员变量和设置器,可以为其分配接口的实现。

3. Owner 传入一个监听器,它实现了接口并处理来自子对象的事件。

4.当对象想要向其所有者传达事件时,在定义的侦听器上触发事件


我得到了NullReferenceException,因为MyService 还没有完成它的工作。因此,首先我在 MyService 类中创建监听器结构,如下所示(步骤 1 和 2):

private MyServiceListener listener;

public interface MyServiceListener {
    public void onDataDownloaded();
}

public void setMyServiceListener(MyServiceListener listener) {
    this.listener = listener;
}

并且,在 HTTP 请求的回调中(第 4 步):

@Override
public void onResponse(Call call, Response response) throws IOException {
    final String myResponse = response.body().string();
    //...and some code to hold data as JSONArray
    //[...]

    // XXX Trigger the custom event
    if (listener != null) {
        listener.onDataDownloaded();
    }
}

现在,我可以像这样将触发NullReferenceException 的代码封装在自定义侦听器中(第 3 步):

// Within DataRepository class
mService.setMyServiceListener(new MyService.MyServiceListener) {
    @Override
    public void onDataDownloaded() {
        List<MyModel> the_list = mService.getResponseAsObject();
        if (the_list == null) {
            // HERE! I obtainED the NullReferenceException here.
            Log.e("MY_ERROR", "DataRepository.PopulateDbAsync --> error: response isn't ready yet.");
        }
        for (MyModel i_model : the_list) {
            Log.d("MY_LOG", "DataRepository.PopulateDbAsync --> Inserting data in local DB...");
            mModelDao.insert(i_model);
        }
        return null;
    }
}

实际上,按照类似步骤将此代码示例嵌套到另一个自定义侦听器中所需的真正实现;但这对我有用。

【讨论】:

  • 但是,我总结的东西很糟糕......也许有一天,我会编辑问题来总结要点......
猜你喜欢
  • 1970-01-01
  • 2010-12-26
  • 2020-09-21
  • 1970-01-01
  • 1970-01-01
  • 2013-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多