Search code examples
javaandroidjsonmultithreadingdata-retrieval

On retrieving images from json get "The application may be doing too much work on its main thread" error


I want to retrieve photos in background using AsyncTask. I get photo as string in base64 encoded form. However, I have "The application may be doing too much work on its main thread" error message.

My activity:

public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, ItemClickHandler{

    private RecyclerView recyclerView;
    private RecyclerViewAdapter adapter;
    private LayoutManager layoutManager;

    private ArrayList<Device> devices;

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_action_bar);

        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        recyclerView.setHasFixedSize(true);

        layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        adapter = new RecyclerViewAdapter(devices, this);
        recyclerView.setAdapter(adapter);

        initImages();
    }

    private void initImages() {
        Thread thread = new Thread() {
            @Override
            public void run() {
                for(int i = 0; i < devices.size(); i++){
                    final int pos = i;
                    GetImageJSON getImage = new GetImageJSON(MainActivity.this){
                        @Override
                        protected void onPostExecute(final String result) {
                            Log.d(TAG, result);

                            if(pos <= recyclerView.getLayoutManager().getChildCount()){
                                adapter.updateItem(ImageManager.convertToBitmap(result), pos);
                            }
                        }
                    };
                    getImage.execute(ConnectionConfig.getUserItemImage(devices.get(i).getId()));
                }
            }
        };
        thread.start();
    }
}

GetImageJSON class:

public class GetDataJSON extends AsyncTask<String, Void, String> {

    private static String charset = "UTF-8";

    @Override
    protected String doInBackground(String... args) { 
        String result = parseJSONString(args[0]);
        if(!result.isEmpty()){
            try{
                JSONObject json = new JSONObject(result);
                JSONObject jsonObject = json.getJSONObject(ConnectionConfig.TAG_RESULT);
                String base64String = jsonObject.getString("image");
                Log.d(TAG, base64String);
                Bitmap bitmap = ImageManager.convertToBitmap(base64String);
                bitmap = ImageManager.scaleDownBitmap(bitmap, context);
                Log.d(TAG, "got result: " + result);
                return ImageManager.convertBitMapToString(bitmap);
            }catch (JSONException e){
                e.printStackTrace();
            }
        }

        return result;
    }

    public static String parseJSONString(String... args){
        String result = "";
        InputStream inputStream = null;

        Log.d(TAG, args[0]);
        try {
            URL url = new URL(args[0]);
            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

            conn.setDoOutput(false);
            conn.setRequestMethod("GET");
            conn.setRequestProperty("Accept-Charset", charset);
            conn.setConnectTimeout(15000);
            conn.connect();

            try {
                InputStream in = new BufferedInputStream(conn.getInputStream());
                BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                String line;
                while ((line = reader.readLine()) != null) {
                    result += line;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            conn.disconnect();
        } catch (Exception e) {
            Log.d(TAG, "Exception", e);
        } finally {
            try{
                if(inputStream != null)
                    inputStream.close();
            }catch(Exception e){
                Log.d(TAG, e.getMessage());
            }
        }

        return result;
    }
}

I could not find any solution. Please, suggest any one. How I can optimize the process of retrieving data.


Solution

  • I can't tell for sure without a runnable version of your code, but I would guess that having the line ImageManager.convertToBitmap(result) in onPostExecute() is causing the "too much work on main thread" problem. Anything that happens in onPostExecute() happens on the main thread, so you want to keep that method as light as possible. As SRB suggested, you could avoid this by having doInBackground return the bitmap, instead of a String that needs to be converted back into a bitmap. Note that to change the return type, you'll need to change String to Bitmap in two places:

    public class GetDataJSON extends AsyncTask<String, Void, Bitmap> {
    
        @Override
        protected Bitmap doInBackground(String... args) {
            // TODO return the bitmap
        }
    
        //...the rest of your code
    
    }
    

    On a separate note, it looks like there's some room for improvement in your code. These are things that aren't directly related to your question but that it'd be good to understand.

    1. When you call getImage.execute(), doInBackground method of the GetDataJSON class will be executed. The doInBackground method always runs on background thread (see "The 4 steps" section here), so there's no reason to create a new thread in the initImages() method.

    2. One of benefits of using a recyclerview is that you don't need to load everything when recyclerview appears on the screen. If there are views that are off screen, those views can be created when the user scrolls towards them. By retrieving all the images when the activity is created, you're losing that benefit.

    3. There are image loading libraries like Picasso and Glide that will do background image fetches for you. I don't know what your web API looks like, but if you're able to, using a preexisting library can make it simple to quickly deal with issues like placeholders, caching, resizing, etc.