如何使某个在主线程中运行的类同时等待其他类达到某种特定状态? [英] How can I make some class, running in the Main Thread, await concurrently for other class to reach some specific state?

查看:69
本文介绍了如何使某个在主线程中运行的类同时等待其他类达到某种特定状态?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在理解如何使一些在主线程中声明和实例化的AsyncTask子级等待Service子级实例达到某些特定状态方面,我遇到了很大的困难.

作为代码示例,这里是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官方网站和其他网站上阅读了两周的Internet上的许多不同文档,试图弄清楚……"AsyncTask不好执行这种操作".所以,我一直在想...我应该使用什么处理程序,线程,Messenger或什么?我读得越多,就会越困惑.就像我有一个分析瘫痪问题...

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

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

因此,当我理解它时,我将理解如何正确地重构整个代码结构,以开始在我的RemoteDataSource类中添加更多的Service子实例,该实例的创建是为了使所有Service子对象使用OkHttp执行HTTP通信,将它们封装在一个类中,以实现更好的组织.

实现此目标的正确方法是什么?有人能够提供一些简短的示例来解释我需要类似这样的代码结构吗?空的示例包含注释,如准备就绪时执行的代码"这样的示例将非常有用,因此我可以弄清楚.

这里暴露的问题与同一项目有关,该项目使我几周前发布了另一个问题;从那以后,我一直在这里阅读,进行反复试验,并在此进行一些代码问题的更正.但是,我在这里提出了一个不同的问题.为此,找到答案可能也是迈向另一个问题的答案的第一步.

我正在阅读的文档的URL引用

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

解决方案

问题出在您的应用程序逻辑上,如下所示,

如果使用的是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) {

这应该可以解决此处说明的问题.

I am having serious difficulties to understand how can I make some AsyncTask children, declared and instantiated in the Main Thread, to await for a Service child instance to reach some specific state.

As code examples here is the relevant part for Service; this code does what expected: receives the JSON response and holds it.

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;
    }
}

This Service works; fine. Then, from another class (which purpose will be to handle persistence inserting the received data in a local Room Database), I try to do the following (here's where my mind is blown):

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;
        }
    }
}

Summarizing: my problem is that I will always get NullReferenceException in this line:

for (MyModel i_model : the_list) {

I am not familiar with multithreading, asyncronous operations and concurrent execution. I have been reading, for two weeks, lots of different documents on the Internet both from Android Official Website and from other websites as well, trying to figure it out... "AsyncTask is not good to perform this kind of operations"... so, what infrastructure should I implement, I have been wondering... should I use Handlers, Threads, Messengers, or what? The more I read, the more confused I get. It's like I have an Analysis Paralysis issue...

Most of the examples I find out there provide too verbose code examples on how to implement multithreading and concurrent execution; while I read them and try to figure out how to implement those structures in my code, I just get stuck; also, with so many classes to choose, I get even more confused...

Due to the HTTP call will need to be performed asyncronously (and response time will not always be the same), I am trying to figure out how to make the piece of code that throws the NullReferenceException "wait" for MyService to complete it's job before starting it's execution; while loops will not work due to it would break Main Thread's lifecycle. "Knowing" if the Service completed it's task or not would be as simple as using the boolean method responseReceived. The big idea is, every time new data is obtained through HTTP, updating the RoomDatabase with it, and, meanwhile, MainActivity would be showing the current local data (if any, or an empty list if there's nothing yet).

So, when I get it, I will understand how to refactor the whole code structure properly to start adding more Service child instances into my RemoteDataSource class, which I created with the idea of having all Service childs that will use OkHttp to perform HTTP communications, wrapped together in a single class for better organization.

What would be the proper way to achieve what I am looking for about this? Would someone be able to provide some short example explaining the code structure I will need for something like this? Examples with empty blocks containing comments like "code to execute when ready here" would be great so I can figure it out.

The question exposed here is related with the same project that made me post this other question some weeks ago; I have been reading here and there, performing some trial-and-error and correcting some code issues here-and-there since then; however, I am making a different question here; finding an answer for this would probably be the first step towards figuring out an answer to the other question as well.

URL References to documentation I have been reading

Some of the documentation I have been reading (but not limited to):

解决方案

Well problem is with your application logic as follows,

If you are using AsyncTask that is obviously a separate thread from the main thread. But syncing to your database after retrieving data via HTTP call is a process which has a sequence ( Call through HTTP and retreive -> then persist to database ), it cannot perform asynchronously. So when you call,

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

this call happens in a particular thread and the program flow is in a different thread. Since these are asynchronous tasks, they work asynchronously. which means you will never know which one will execute first and which one is next. But as per your logic,

if (the_list == null) {

this part essentially need the_list to be initialized to run. But the problem is at that point, service thread has not finished his work to perform your next logic. so its obvious breaking.

Better if you can re-design this so that you wait for the HTTP request to complete and then persist to database. Because suppose if your HTTP request complets first but still it returns you null or whatever not-desired output. So in that case you need to handle it in your logic.

OK so let me tell you a quick workaround. Lets use just one thread instead of different threads. So consider changing following line

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

to

private class PopulateDbAsync

then you will get an error with

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

since we no longer extend AsyncTask class.

so change it as follows, by removing @Override

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

This should fix the stated problem here.

这篇关于如何使某个在主线程中运行的类同时等待其他类达到某种特定状态?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆