0

"Cannot access database on the main thread since it may potentially lock the UI for a long period of time" but when i allow room to execute queries on main thread then it doesn't give error

// MyIntentService.java:
public class MyIntentService extends IntentService {

    String URL = "http://192.168.1.102/android/Document%20Sharing/getServerData.php";
    String course;
    AppDatabase appDatabase;

    public MyIntentService() { super("MyIntentService"); }

    @Override
    public void onCreate() {
        Log.i("tagg","onCreate");
        super.onCreate();
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        appDatabase = AppDatabase.getAppDatabase(this);
        course = intent.getStringExtra("course");
        getServerData();
        Log.i("tagg","onHandleIntentBefore");
    }

    private void getServerData() {
        Log.i("tagg","here");
        final RequestQueue requestQueue = Volley.newRequestQueue(this);
        final StringRequest stringRequest = new StringRequest(Request.Method.POST, URL,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        Gson gson = new Gson();
                        IT_Subjects[] itSubjects = gson.fromJson(response, IT_Subjects[].class);
                        appDatabase.itSubjectsDao().insertAll(itSubjects);
                        //Log.i("tagg", response);
                        Log.i("tagg","here");
                        requestQueue.stop();
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        error.printStackTrace();
                        Toast.makeText(MyIntentService.this, error.toString(), Toast.LENGTH_LONG).show();
                        requestQueue.stop();
                    }
                }) {
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                Map<String, String> prams = new HashMap<>();
                prams.put("course", course);

                return prams;
            }
        };

        requestQueue.add(stringRequest);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
        Log.i("tagg","onStartCommand");
        return super.onStartCommand(intent,flags,startId);
    }

    @Override
    public void onDestroy() {
        Log.i("tagg","onDestroy");
        AppDatabase.destroyInstance();
        super.onDestroy();
    }

}

//AppDatabase.java
@Database(entities = IT_Subjects.class, version = 1 )
public abstract class AppDatabase extends RoomDatabase {

    public abstract IT_SubjectsDao itSubjectsDao();

    private static AppDatabase INSTANCE;

    public static AppDatabase getAppDatabase(Context context) {
        if (INSTANCE == null) {
            INSTANCE =
                    Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "IT_Students")
                            .build();
        }
        return INSTANCE;
    }

    public static void destroyInstance() {
        INSTANCE = null;
    }
}
Gunnar Bernstein
  • 6,074
  • 2
  • 45
  • 67
Jayesh
  • 1
  • 3
  • 1
    you have to maintain separate thread even within `intent service` ...even i was having the same issue.. – Santanu Sur Apr 18 '18 at 18:27
  • ok thank you Santanu Sur – Jayesh Apr 19 '18 at 16:15
  • You could create a new `ExecutorService` that maintains a single thread, then do all your DB operations there to prevent deadlock. – Richard Dapice Nov 17 '19 at 19:53
  • @SantanuSur `All requests are handled on a single worker thread`, third paragraph here https://developer.android.com/reference/android/app/IntentService If there is one thing that IntentService is known for, it's that it's running a worker thread. – Eugen Pechanec Nov 17 '19 at 20:28

1 Answers1

0

You enqueue a request - the request is processed asynchronously, so onHandleIntent returns immediately. (Then onDestroy is called and your cached static database instance lost!) Then onResponse is called on the main thread and data written to appDatabase.

requestQueue.add(stringRequest);

When you call add(), Volley runs one cache processing thread and a pool of network dispatch threads. When you add a request to the queue, it is picked up by the cache thread and triaged: if the request can be serviced from cache, the cached response is parsed on the cache thread and the parsed response is delivered on the main thread. If the request cannot be serviced from cache, it is placed on the network queue. The first available network thread takes the request from the queue, performs the HTTP transaction, parses the response on the worker thread, writes the response to cache, and posts the parsed response back to the main thread for delivery.

Source Send a simple request | Android Developers, emphasis mine.

A naïve workaround would be to execute the request synchronously (or enqueue it and await result). This has been explained in this answer (I didn't verify it so take it with a pinch of salt).

RequestFuture<JSONObject> future = RequestFuture.newFuture();

JsonObjectRequest request = new JsonObjectRequest(URL, new 
JSONObject(), future, future);
requestQueue.add(request);

JSONObject response = future.get(); // this will block

A better approach might be to not use IntentService at all. Volley already does what you need - do the network stuff on worker thread, deliver the result to main thread, and even process the raw response on worker thread between those two.

You should create a custom Request class and override parseNetworkResponse as best described here: Implementing a custom request | Android Developers

Here's an example implementation of GsonRequest copied verbatim from the link above. It's not super efficient but should be good to start with.

public class GsonRequest<T> extends Request<T> {
    private final Gson gson = new Gson();
    private final Class<T> clazz;
    private final Map<String, String> headers;
    private final Listener<T> listener;

    /**
     * Make a GET request and return a parsed object from JSON.
     *
     * @param url URL of the request to make
     * @param clazz Relevant class object, for Gson's reflection
     * @param headers Map of request headers
     */
    public GsonRequest(String url, Class<T> clazz, Map<String, String> headers,
            Listener<T> listener, ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        this.clazz = clazz;
        this.headers = headers;
        this.listener = listener;
    }

    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        return headers != null ? headers : super.getHeaders();
    }

    @Override
    protected void deliverResponse(T response) {
        listener.onResponse(response);
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            String json = new String(
                    response.data,
                    HttpHeaderParser.parseCharset(response.headers));
            return Response.success(
                    gson.fromJson(json, clazz),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JsonSyntaxException e) {
            return Response.error(new ParseError(e));
        }
    }
}
Eugen Pechanec
  • 37,669
  • 7
  • 103
  • 124